#include /* #include */ #include #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 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 processParents(char*& it_data) { pair 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 parseSpouses(char*& it_data) { vector 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 parents(Nobody, Nobody); vector 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; }