588 lines
11 KiB
C++
588 lines
11 KiB
C++
#include <iostream>
|
|
#include <string_view>
|
|
#include <string>
|
|
#include "tree.hpp"
|
|
#include "utils.hpp"
|
|
#include <assert.h>
|
|
|
|
using std::cerr;
|
|
using std::string_view;
|
|
using std::string;
|
|
using std::vector;
|
|
using std::pair;
|
|
/* using std::endl; */
|
|
|
|
namespace /* internal */
|
|
{
|
|
struct alloced
|
|
{
|
|
char* const data;
|
|
char* const end;
|
|
alloced(U32 len)
|
|
: data(new char[len])
|
|
, end(data + len)
|
|
{}
|
|
alloced(const alloced&) = delete;
|
|
alloced(alloced&& other)
|
|
: data(other.data)
|
|
, end(other.end)
|
|
{
|
|
const_cast<char*&>(other.data) = nullptr;
|
|
}
|
|
~alloced()
|
|
{
|
|
delete[] data;
|
|
}
|
|
};
|
|
|
|
enum TokenType
|
|
{
|
|
ATTRIBUTE,
|
|
STRING,
|
|
/* DATE, */
|
|
NUMBER,
|
|
NEW_LINE,
|
|
SEPARATOR, /* , */
|
|
REPEAT /* - */
|
|
};
|
|
|
|
struct token
|
|
{
|
|
token(const char* b, const char* e);
|
|
explicit token(TokenType t)
|
|
: type(t)
|
|
{
|
|
}
|
|
|
|
U32 size() const
|
|
{
|
|
return end - begin;
|
|
}
|
|
|
|
const char* begin;
|
|
const char* end;
|
|
TokenType type;
|
|
};
|
|
|
|
using TokenIt = vector<token>::const_iterator;
|
|
|
|
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(TokenIt& it, TokenIt end);
|
|
/* void state_person_attribute() */
|
|
|
|
/* result */
|
|
Tree* cur_tree;
|
|
|
|
[[noreturn]]void err_unexpected_end()
|
|
{
|
|
endwin();
|
|
cerr << "Unexpected end of file\n";
|
|
cerr.flush();
|
|
exit(1);
|
|
}
|
|
|
|
[[noreturn]]void err_unexpected_char(char c)
|
|
{
|
|
endwin();
|
|
cerr << "Line " << line << ": Unexpected '" << c << "'\n";
|
|
cerr.flush();
|
|
exit(1);
|
|
}
|
|
|
|
[[noreturn]]void err_expected(const char* what, token instead)
|
|
{
|
|
endwin();
|
|
cerr << "Line " << line << ": Expected " << what << "got";
|
|
cerr.write(instead.begin, instead.size());
|
|
cerr << "instead\n";
|
|
cerr.flush();
|
|
exit(1);
|
|
}
|
|
|
|
|
|
[[noreturn]]void err_unexpected_token(token t)
|
|
{
|
|
endwin();
|
|
cerr << "Line " << line << ": Unexpected token '";
|
|
cerr.write(t.begin, t.size());
|
|
cerr << "'\n";
|
|
cerr.flush();
|
|
exit(1);
|
|
}
|
|
|
|
[[noreturn]]void err_illegal_attribute(token attr)
|
|
{
|
|
endwin();
|
|
cerr << "Line " << line << ": Illegal attribute '";
|
|
cerr.write(attr.begin, attr.size());
|
|
cerr << "'\n";
|
|
cerr.flush();
|
|
exit(1);
|
|
}
|
|
|
|
/* [[noreturn]]void err_thirdParent(const string& extra_name) */
|
|
/* { */
|
|
/* endwin(); */
|
|
/* cerr << "Line " << line << ": two parents are already defined, " */
|
|
/* << extra_name << " is third\n"; */
|
|
/* cerr.flush(); */
|
|
/* exit(1); */
|
|
/* } */
|
|
|
|
[[noreturn]]void err_personUndefined(const string& name)
|
|
{
|
|
endwin();
|
|
cerr << "Line " << line << ": '" << name << "' is undefined.\n"
|
|
"Members need to be defined before they are used as relations\n";
|
|
cerr.flush();
|
|
exit(1);
|
|
}
|
|
|
|
[[noreturn]]void err_indexExcedes(U32 index, U32 max)
|
|
{
|
|
endwin();
|
|
cerr << "Line " << line << ": '" << index << "' is too large.\n"
|
|
<< "At most the index after a name can be the number of people with the "
|
|
"same name already defined - 1, since the count starts from 0.\n"
|
|
"In this case the max is " << max << '\n';
|
|
cerr.flush();
|
|
exit(1);
|
|
}
|
|
|
|
/* void err_name_index(const string& name, U32 i) */
|
|
/* { */
|
|
/* endwin(); */
|
|
/* cerr << "Line " << line << " the number given after the declaration of a person can" */
|
|
/* " only be his place among the same named. " << i << " doesn't satisfy this for '" */
|
|
/* << name << '\'' << endl; */
|
|
/* exit(1); */
|
|
/* } */
|
|
|
|
bool isNum(char c)
|
|
{
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
|
|
TokenType findType(token t)
|
|
{
|
|
TokenType type = NUMBER;
|
|
for(const char* it = t.begin; it < t.end; ++it)
|
|
{
|
|
if(!isNum(*it))
|
|
{
|
|
type = STRING;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(type == STRING)
|
|
{
|
|
if(t.size() == 1)
|
|
{
|
|
switch(t.begin[0])
|
|
{
|
|
case ',':
|
|
type = SEPARATOR;
|
|
break;
|
|
case '-':
|
|
type = REPEAT;
|
|
break;
|
|
case '\n':
|
|
type = NEW_LINE;
|
|
}
|
|
}
|
|
else if(t.size() == 2)
|
|
{
|
|
if(t.begin[1] == ':')
|
|
type = ATTRIBUTE;
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
token::token(const char* b, const char* e)
|
|
: begin(b)
|
|
, end(e)
|
|
{
|
|
type = findType(*this);
|
|
}
|
|
|
|
|
|
U32 calcNumber(token t)
|
|
{
|
|
assert(t.type == NUMBER);
|
|
U32 val = 0;
|
|
for(const char* it = t.begin; it < t.end; ++it)
|
|
val = val*10 + *it-'0';
|
|
|
|
return val;
|
|
}
|
|
|
|
void expect_newline(TokenIt& it)
|
|
{
|
|
if(it->type == NEW_LINE)
|
|
{
|
|
++line;
|
|
++it;
|
|
}
|
|
else
|
|
err_unexpected_token(*it);
|
|
}
|
|
|
|
/* construct name from tokens */
|
|
string parseName(TokenIt& it, TokenIt end)
|
|
{
|
|
if(it->type != STRING)
|
|
err_expected("name", *it);
|
|
|
|
string name;
|
|
for(; it < end && it->type == STRING; ++it)
|
|
{
|
|
name.append(it->begin, it->end);
|
|
name += ' ';
|
|
}
|
|
name.pop_back(); /* remove last ' ' */
|
|
return name;
|
|
}
|
|
|
|
/* find a person's id in the tree based on his name and index
|
|
* among same named */
|
|
person_id findPerson(const string& name, U32 same_name_index)
|
|
{
|
|
vector<person_id> same_named = cur_tree->findId(name);
|
|
|
|
if(same_named.empty())
|
|
err_personUndefined(name);
|
|
else if(same_name_index >= same_named.size())
|
|
err_indexExcedes(same_name_index, same_named.size()-1);
|
|
else
|
|
return same_named[same_name_index];
|
|
}
|
|
|
|
U32 sameNamedIndex(TokenIt& it)
|
|
{
|
|
U32 res = 0;
|
|
if(it->type == NUMBER)
|
|
{
|
|
res = calcNumber(*it);
|
|
++it;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
Sex parseSex(TokenIt& it, TokenIt end)
|
|
{
|
|
Sex res = M;
|
|
if(it->type == SEPARATOR)
|
|
{
|
|
++it;
|
|
if(it == end)
|
|
err_unexpected_end();
|
|
|
|
if(it->type == STRING)
|
|
{
|
|
if(it->size() == 1)
|
|
{
|
|
if(it->begin[0] == 'M')
|
|
res = M;
|
|
else if(it->begin[0] == 'F')
|
|
res = F;
|
|
else
|
|
err_expected("sex", *it);
|
|
++it;
|
|
}
|
|
else
|
|
err_expected("sex", *it);
|
|
|
|
}
|
|
else
|
|
err_unexpected_token(*it);
|
|
}
|
|
|
|
expect_newline(it);
|
|
|
|
return res;
|
|
}
|
|
|
|
I32 processDatePart(const char*& it_date, const char* end_date)
|
|
{
|
|
I32 sign = 1;
|
|
I32 val = 0;
|
|
for(; it_date < end_date; ++it_date)
|
|
{
|
|
switch(*it_date)
|
|
{
|
|
case '-':
|
|
sign = -1;
|
|
break;
|
|
case '0' ... '9':
|
|
val = val*10 + (*it_date - '0');
|
|
break;
|
|
case '.':
|
|
if(val == 0)
|
|
err_unexpected_char('.');
|
|
else
|
|
return sign * val;
|
|
break;
|
|
/* case '?': todo */
|
|
default:
|
|
err_unexpected_char(*it_date);
|
|
}
|
|
}
|
|
return sign * val;
|
|
}
|
|
|
|
EventTime state_date(TokenIt& it) /* todo validate */
|
|
{
|
|
++it; /* skip attribute */
|
|
if(it->type == STRING || it->type == NUMBER)
|
|
{
|
|
EventTime date;
|
|
const char* it_date = it->begin;
|
|
const char* end_date = it->end;
|
|
date.year = processDatePart(it_date, end_date);
|
|
if(*it_date == '.')
|
|
{
|
|
++it_date;
|
|
date.month = processDatePart(it_date, end_date);
|
|
|
|
if(*it_date == '.')
|
|
{
|
|
++it_date;
|
|
date.day = processDatePart(it_date, end_date);
|
|
}
|
|
}
|
|
|
|
++it;
|
|
expect_newline(it);
|
|
return date;
|
|
}
|
|
else
|
|
err_expected("date", *it);
|
|
|
|
}
|
|
|
|
pair<person_id, person_id> processParents(TokenIt& it, TokenIt end)
|
|
{
|
|
++it; /* skip attribute */
|
|
|
|
pair<person_id, person_id> parents(Nobody, Nobody);
|
|
if(it->type == REPEAT)
|
|
{
|
|
parents = prev_parents;
|
|
++it;
|
|
}
|
|
else
|
|
{
|
|
string name = parseName(it, end);
|
|
U32 same_name_index = sameNamedIndex(it);
|
|
parents.first = findPerson(name, same_name_index);
|
|
if(it->type == SEPARATOR)
|
|
{
|
|
++it;
|
|
string name = parseName(it, end);
|
|
U32 same_name_index = sameNamedIndex(it);
|
|
parents.second = findPerson(name, same_name_index);
|
|
}
|
|
prev_parents = parents;
|
|
}
|
|
|
|
expect_newline(it);
|
|
return parents;
|
|
}
|
|
|
|
vector<person_id> parseSpouses(TokenIt& it, TokenIt end)
|
|
{
|
|
++it; /* skip attribute */
|
|
vector<person_id> spouses;
|
|
while(true)
|
|
{
|
|
switch(it->type)
|
|
{
|
|
case STRING:
|
|
{
|
|
string name = parseName(it, end); /* todo unify in 1 function */
|
|
U32 same_name_index = sameNamedIndex(it);
|
|
spouses.push_back(findPerson(name, same_name_index));
|
|
}
|
|
break;
|
|
case SEPARATOR:
|
|
++it;
|
|
break;
|
|
case REPEAT:
|
|
++it;
|
|
spouses.push_back( cur_tree->last() );
|
|
break;
|
|
case NEW_LINE:
|
|
++line;
|
|
++it;
|
|
return spouses;
|
|
default:
|
|
err_unexpected_token(*it);
|
|
}
|
|
}
|
|
}
|
|
|
|
void state_neutral(const vector<token>& tokens)
|
|
{
|
|
auto end = tokens.cend();
|
|
for(TokenIt it = tokens.cbegin(); it < end;)
|
|
{
|
|
switch(it->type)
|
|
{
|
|
case NEW_LINE:
|
|
++line;
|
|
++it;
|
|
break;
|
|
case STRING:
|
|
state_person(it, end);
|
|
break;
|
|
default:
|
|
err_unexpected_token(*it);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void state_person(TokenIt& it, TokenIt end)
|
|
{
|
|
pair<person_id, person_id> parents(Nobody, Nobody);
|
|
vector<person_id> spouses;
|
|
EventTime birth{};
|
|
EventTime death{};
|
|
|
|
string name = parseName(it, end);
|
|
|
|
if(it->type == NUMBER)
|
|
++it;
|
|
|
|
/* U32 same_name_index = sameNamedIndex(it); */
|
|
/* todo */
|
|
/* if(cur_tree->findId(name).size() != same_name_index) */
|
|
/* err_name_index(name, same_name_index); */
|
|
|
|
Sex sex = parseSex(it, end);
|
|
|
|
while(true)
|
|
{
|
|
switch(it->type)
|
|
{
|
|
case ATTRIBUTE:
|
|
switch(it->begin[0])
|
|
{
|
|
case 'b':
|
|
birth = state_date(it); /* todo check for redefinition */
|
|
break;
|
|
case 'd':
|
|
death = state_date(it);
|
|
break;
|
|
case 'p':
|
|
parents = processParents(it, end);
|
|
break;
|
|
case 's':
|
|
spouses = parseSpouses(it, end);
|
|
break;
|
|
default:
|
|
err_illegal_attribute(*it);
|
|
}
|
|
break;
|
|
case NEW_LINE:
|
|
++line;
|
|
++it;
|
|
case STRING:
|
|
/* todo str prevent copy */
|
|
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());
|
|
return;
|
|
default:
|
|
err_unexpected_token(*it);
|
|
}
|
|
}
|
|
}
|
|
|
|
alloced removeComments(MappedFile file) /* todo - drop */
|
|
{
|
|
alloced res(file.len);
|
|
|
|
char* itres = res.data;
|
|
char* end = file.data + file.len;
|
|
for(char* it = file.data; it < end; ++it)
|
|
{
|
|
if(*it == '#')
|
|
{
|
|
while(++it != end && *it != '\n'); /* skip comment */
|
|
*itres++ = '\n'; /* add \n */
|
|
}
|
|
else
|
|
{
|
|
*itres = *it;
|
|
++itres;
|
|
}
|
|
}
|
|
end_data_ = itres;
|
|
return res;
|
|
}
|
|
|
|
vector<token> tokenize(const char* data_begin, const char* data_end)
|
|
{
|
|
vector<token> tokens;
|
|
tokens.reserve(256);
|
|
const char* token_begin = data_begin;
|
|
|
|
for(const char* it = data_begin; it < data_end; ++it)
|
|
{
|
|
switch(*it)
|
|
{
|
|
case ':':
|
|
if(token_begin < it)
|
|
tokens.emplace_back(token_begin, it+1); /* consume the : */
|
|
token_begin = it + 1;
|
|
break;
|
|
case ' ':
|
|
case '\t':
|
|
if(token_begin < it)
|
|
tokens.emplace_back(token_begin, it);
|
|
token_begin = it + 1; /* skip the ws */
|
|
break;
|
|
case ',':
|
|
case '\n':
|
|
if(token_begin < it)
|
|
tokens.emplace_back(token_begin, it);
|
|
tokens.emplace_back(it, it+1);
|
|
token_begin = it+1;
|
|
break;
|
|
/* case '0' ... '9': */
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
tokens.emplace_back(NEW_LINE);
|
|
tokens.emplace_back(NEW_LINE);
|
|
return tokens;
|
|
}
|
|
}
|
|
|
|
|
|
Tree parseTftFile(const char* path)
|
|
{
|
|
fd_t file = openOrDie(path);
|
|
|
|
Tree result(path);
|
|
cur_tree = &result;
|
|
|
|
MappedFile mapedfile = mapfile(file);
|
|
alloced preprocessed = removeComments(mapedfile);
|
|
vector<token> tokens = tokenize(preprocessed.data, end_data_);
|
|
|
|
state_neutral(tokens);
|
|
|
|
return result;
|
|
}
|