%{ #include #include #include #include #define STATCHECK_GT 1 #define STATCHECK_LT 2 struct statcheck_t { char *stat; int value; int rel; }; struct statchange_t { char *stat; int addend; }; enum ChoiceFlags : int { StatCheck = 0x1, StatChange = 0x2, }; struct Choice { int flags; int option; char *flavor; statcheck_t statcheck; char *id; statchange_t statchange; Choice(char*, statcheck_t*, statchange_t*); }; enum class FooterType { End, Goto, Choices }; struct Footer { FooterType type; union { std::vector *choices; char *link; }; Footer(); ~Footer(); }; struct Page { char *id; char *body; Footer footer; Page(); ~Page(); }; std::vector pages; Page *emit_ending(); Page *emit_goto(char*); Page *emit_choices(std::vector*); void append_body(Page *page, char *str); union value { Page *page; char *string; Choice *choice; std::vector *choices; int num; statcheck_t *statcheck; statchange_t *statchange; }; #define YYSTYPE union value %} Story = BlankLine* (p:Page { pages.push_back(p.page); })+ EndOfFile; Page = h:Header b:Body { $$.page = b.page; $$.page->id = h.string} BlankLine*; Header = < Identifier > { $$.string = strndup(yytext, yyleng); } Newline BlankLine*; Identifier = [A-Z][A-Z0-9_]+; Body = Footer | t:TextLine b:Body { $$ = b; append_body(b.page, t.string);}; TextLine = < (!Newline .)* Newline > { $$.string = strndup(yytext, yyleng); }; Footer = Ending { $$.page = emit_ending(); } | g:Goto { $$.page = emit_goto(g.string); } | c:ChoiceList { $$.page = emit_choices(c.choices); } ; Goto = 'GOTO' Spacing < i:Identifier > Newline { $$.string = strndup(yytext, yyleng); } ; Ending = 'THE END' Newline; ChoiceList = c:Choice l:ChoiceList { $$ = l; $$.choices->push_back(c.choice); } | c:Choice { $$.choices = new std::vector(); $$.choices->push_back(c.choice); } Choice = b:Bullet f:Freetext r:Redirect { $$ = r; $$.choice->option = b.num; $$.choice->flavor = f.string; }; Bullet = < [0-9]+ > ')' Spacing { $$.num = atoi(yytext); }; Freetext = < (!Redirect .)+ > { $$.string = strndup(yytext, yyleng); }; Redirect = ck:StatCheck? '[' i:Identifier ']' Spacing cg:StatChange? Newline { $$.choice = new Choice(i.string, ck.statcheck, cg.statchange); } StatCheck = '<' StatName Spacing [0-9]+ ('+' | '-')? '>' Spacing; StatChange = '(' ('+' | '-') [0-9]+ Spacing StatName ')'; StatName = [A-Za-z]+; EndOfFile = !.; BlankLine = Spacing Newline; Spacing = (' ' | '\t')*; Newline = '\r\n' | '\r' | '\n'; %% Choice::Choice(char *id, statcheck_t *ck, statchange_t *cg) { this->id = id; if (ck) { statcheck = *ck; flags |= StatCheck; } if (cg) { statchange = *cg; flags |= StatChange; } } Footer::Footer() { } Footer::~Footer() { } Page::Page() { id = NULL; body = NULL; } Page::~Page() { if (id) { free(id); } if (body) { free(body); } } Page *emit_goto(char *id) { Page *mypage = new Page; mypage->footer.type = FooterType::Goto; mypage->footer.link = id; return mypage; } Page *emit_ending() { Page *mypage = new Page; mypage->footer.type = FooterType::End; return mypage; } Page *emit_choices(std::vector *choices) { Page *mypage = new Page; mypage->footer.type = FooterType::Choices; mypage->footer.choices = choices; return mypage; } void append_body(Page *page, char *str) { if (page->body) { int size = strlen(page->body) + strlen(str) + 1; char *newstring = (char*) calloc(size, sizeof(char)); strcpy(newstring, str); strcat(newstring, page->body); free(page->body); page->body = newstring; } else { page->body = str; } } void print_page(Page *page) { printf("HEADER: %s\nBODY: %s\n", page->id, page->body); switch (page->footer.type) { case FooterType::End: printf("THE END\n"); break; case FooterType::Goto: printf("GOTO %s\n", page->footer.link); break; case FooterType::Choices: printf("CHOICES:\n"); for (Choice *c : *page->footer.choices) { printf("%d) %s\n", c->option, c->flavor); } } } int main() { if (!yyparse()) { printf("Parsing Error!\n"); return 1; } else { for (Page* page : pages) { print_page(page); } } return 0; } /* Local Variables: */ /* mode: text */ /* End: */