387 lines
7.2 KiB
C++
387 lines
7.2 KiB
C++
#include <iostream>
|
|
/* #include <string_view> */
|
|
#include <string>
|
|
#include "tree.hpp"
|
|
#include "utils.hpp"
|
|
|
|
/* macro rather then function to avoid warnings */
|
|
#define UNEXPECTED_END() endwin(); cerr << "Unexpected end of file" << std::endl; exit(1)
|
|
|
|
using std::cerr;
|
|
/* using std::string_view; */
|
|
using std::string;
|
|
using std::vector;
|
|
using std::pair;
|
|
|
|
namespace /* internal */
|
|
{
|
|
struct alloced
|
|
{
|
|
char* const data;
|
|
char* const end;
|
|
alloced(U32 len)
|
|
: data(new char[len])
|
|
, end(data + len)
|
|
{
|
|
}
|
|
~alloced()
|
|
{
|
|
delete[] data;
|
|
}
|
|
};
|
|
|
|
char* end_data; /* past the end pointer */
|
|
unsigned line = 1;
|
|
pair<person_id, person_id> prev_parents(Nobody, Nobody);
|
|
|
|
/* state */
|
|
void state_neutral(char* it_data);
|
|
void state_person(char*& it_data);
|
|
/* void state_person_attribute() */
|
|
|
|
/* result */
|
|
Tree* cur_tree;
|
|
|
|
bool isNum(char c)
|
|
{
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
|
|
void unexpected_char(char c)
|
|
{
|
|
endwin();
|
|
cerr << "Line " << line << ": Unexpected '" << c << std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
/* void unexpected_end() */
|
|
/* { */
|
|
/* endwin(); */
|
|
/* cerr << "Unexpected end of file" << std::endl; */
|
|
/* exit(1); */
|
|
/* } */
|
|
|
|
void proceedToNameEnd(char*& it_data)
|
|
{
|
|
while(it_data < end_data &&
|
|
*it_data != ',' &&
|
|
*it_data != '\n' &&
|
|
!isNum(*it_data))
|
|
{
|
|
if(*it_data == ':' /* && *it_data == '-' */)
|
|
unexpected_char(*it_data);
|
|
++it_data;
|
|
}
|
|
|
|
}
|
|
|
|
U32 sameNamedIndex(char*& it_data)
|
|
{
|
|
U32 res = 0;
|
|
if(isNum(*it_data))
|
|
{
|
|
res = *it_data - '0'; /* todo many digits */
|
|
++it_data;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
Sex findSex(char*& it_data)
|
|
{
|
|
Sex res = M;
|
|
if(*it_data == ',')
|
|
{
|
|
++it_data;
|
|
if(*it_data == 'M')
|
|
res = M;
|
|
else if(*it_data == 'F')
|
|
res = F;
|
|
else
|
|
unexpected_char(*it_data);
|
|
|
|
++it_data;
|
|
if(*it_data != '\n')
|
|
unexpected_char(*it_data);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
I32 processDatePart(char*& it_data)
|
|
{
|
|
I32 sign = 1;
|
|
I32 val = 0;
|
|
for(; it_data < end_data; ++it_data)
|
|
{
|
|
switch(*it_data)
|
|
{
|
|
case '-':
|
|
sign = -1;
|
|
break;
|
|
case '0' ... '9':
|
|
val = val*10 + (*it_data - '0');
|
|
break;
|
|
case '.':
|
|
if(val == 0)
|
|
unexpected_char('.');
|
|
else
|
|
return sign * val;
|
|
break;
|
|
case '\n':
|
|
++line;
|
|
return sign * val;
|
|
/* case '?': todo */
|
|
default:
|
|
unexpected_char(*it_data);
|
|
}
|
|
}
|
|
UNEXPECTED_END();
|
|
}
|
|
|
|
EventTime processDate(char*& it_data) /* todo validate */
|
|
{
|
|
EventTime date;
|
|
date.year = processDatePart(it_data);
|
|
if(*it_data == '.')
|
|
{
|
|
++it_data;
|
|
date.month = processDatePart(it_data);
|
|
|
|
if(*it_data == '.')
|
|
{
|
|
++it_data;
|
|
date.day = processDatePart(it_data);
|
|
}
|
|
}
|
|
return date;
|
|
}
|
|
|
|
pair<person_id, person_id> processParents(char*& it_data)
|
|
{
|
|
pair<person_id, person_id> res(Nobody, Nobody);
|
|
U32 same_name_index = 0;
|
|
string name; /* todo dont use string */
|
|
for(; it_data < end_data; ++it_data)
|
|
{
|
|
switch(*it_data)
|
|
{
|
|
case ',':
|
|
if(res.first == Nobody)
|
|
res.first = cur_tree->findId(name.data())[same_name_index]; /* todo validate index */
|
|
else if(res.second == Nobody)
|
|
res.second = cur_tree->findId(name.data())[same_name_index];
|
|
else
|
|
/* todo */TFT_EXIT(1);
|
|
same_name_index = 0;
|
|
name.clear();
|
|
break;
|
|
case '-':
|
|
if(name.empty() && res.first == Nobody)
|
|
return prev_parents;
|
|
else
|
|
unexpected_char('-');
|
|
case '\n':
|
|
++line;
|
|
if(res.first == Nobody)
|
|
res.first = cur_tree->findId(name.data())[same_name_index];
|
|
else
|
|
res.second = cur_tree->findId(name.data())[same_name_index];
|
|
return prev_parents = res;
|
|
case '_':
|
|
name.push_back(' ');
|
|
break;
|
|
case '0' ... '9':
|
|
same_name_index = *it_data - '0';
|
|
break;
|
|
default:
|
|
name.push_back(*it_data);
|
|
}
|
|
}
|
|
UNEXPECTED_END();
|
|
}
|
|
|
|
vector<person_id> parseSpouses(char*& it_data)
|
|
{
|
|
vector<person_id> res;
|
|
U32 same_name_index = 0;
|
|
string name;
|
|
for(; it_data < end_data; ++it_data)
|
|
{
|
|
switch(*it_data)
|
|
{
|
|
case '-':
|
|
if(name.empty())
|
|
res.push_back( cur_tree->last() );
|
|
else
|
|
unexpected_char('-');
|
|
break;
|
|
case ',':
|
|
if(!name.empty())
|
|
{
|
|
res.push_back( cur_tree->findId(name.data())[same_name_index] );
|
|
name.clear();
|
|
same_name_index = 0;
|
|
}
|
|
else
|
|
unexpected_char(',');
|
|
break;
|
|
case '\n':
|
|
++line;
|
|
if(!name.empty())
|
|
res.push_back( cur_tree->findId(name.data())[same_name_index] );
|
|
return res;
|
|
case '_':
|
|
name.push_back(' ');
|
|
break;
|
|
case '0' ... '9':
|
|
same_name_index = *it_data - '0';
|
|
break;
|
|
default:
|
|
name.push_back(*it_data);
|
|
}
|
|
}
|
|
if(!name.empty())
|
|
res.push_back( cur_tree->findId(name.data())[same_name_index] );
|
|
return res;
|
|
}
|
|
|
|
void state_neutral(char* it_data)
|
|
{
|
|
for(; it_data < end_data; ++it_data)
|
|
{
|
|
switch(*it_data)
|
|
{
|
|
default:
|
|
state_person(it_data);
|
|
break;
|
|
case '\n':
|
|
++line;
|
|
break;
|
|
case ',':
|
|
case '-':
|
|
case 0 ... 9:
|
|
case ':':
|
|
unexpected_char(*it_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void state_person(char*& it_data)
|
|
{
|
|
char* name_start = it_data;
|
|
pair<person_id, person_id> parents(Nobody, Nobody);
|
|
vector<person_id> spouses;
|
|
EventTime birth{};
|
|
EventTime death{};
|
|
|
|
proceedToNameEnd(it_data); /* todo merge funcs */
|
|
string name(name_start, it_data);
|
|
|
|
U32 same_name_index = sameNamedIndex(it_data);
|
|
/* string_view name(name_start, it_data); */
|
|
for(char& c: name)
|
|
{
|
|
if(c == '_')
|
|
c = ' ';
|
|
}
|
|
|
|
|
|
|
|
Sex sex = findSex(it_data);
|
|
|
|
for(; it_data < end_data; ++it_data)
|
|
{
|
|
switch(*it_data)
|
|
{
|
|
case '\n':
|
|
++line;
|
|
break;
|
|
default:
|
|
if(it_data[1] == ':') /* todo */
|
|
{
|
|
switch(*it_data)
|
|
{
|
|
case 'b':
|
|
it_data += 2;
|
|
birth = processDate(it_data);
|
|
break;
|
|
case 'd':
|
|
it_data += 2;
|
|
death = processDate(it_data);
|
|
break;
|
|
case 'p':
|
|
it_data += 2;
|
|
parents = processParents(it_data);
|
|
break;
|
|
case 's':
|
|
it_data += 2;
|
|
spouses = parseSpouses(it_data);
|
|
break;
|
|
default:
|
|
unexpected_char(it_data[-1]);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
cur_tree->addPerson(name.c_str(), sex, parents.first, parents.second, birth, death);
|
|
for(person_id spouse: spouses)
|
|
cur_tree->addRelation(spouse, Spouse, cur_tree->last());
|
|
--it_data; /* rewind 1 */
|
|
return;
|
|
}
|
|
break;
|
|
/* case 0...9: */
|
|
/* case ':': */
|
|
/* unexpected_char(*it_data); */
|
|
/* default: */
|
|
/* break; */
|
|
}
|
|
}
|
|
cur_tree->addPerson(name.c_str(), sex, parents.first, parents.second, birth, death);
|
|
for(person_id spouse: spouses)
|
|
cur_tree->addRelation(spouse, Spouse, cur_tree->last());
|
|
}
|
|
|
|
alloced preprocess(MappedFile file) /* remove whitespace and comments */
|
|
{
|
|
alloced res(file.len);
|
|
|
|
char* itres = res.data;
|
|
char* end = file.data + file.len;
|
|
for(char* it = file.data; it < end; ++it)
|
|
{
|
|
switch(*it)
|
|
{
|
|
case '#':
|
|
while(++it != end && *it != '\n'); /* skip comment */
|
|
*itres++ = '\n'; /* add \n */
|
|
break;
|
|
case ' ':
|
|
case '\t':
|
|
case '\r':
|
|
break;
|
|
default:
|
|
*itres = *it;
|
|
++itres;
|
|
}
|
|
}
|
|
end_data = itres;
|
|
return res;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
Tree parseTftFile(fd_t file)
|
|
{
|
|
Tree result("t");
|
|
cur_tree = &result;
|
|
|
|
MappedFile mapedfile = mapfile(file);
|
|
alloced preprocessed = preprocess(mapedfile);
|
|
|
|
state_neutral(preprocessed.data);
|
|
|
|
return result;
|
|
}
|