%{ #include #include #include #include FILE* yyin = stdin; #define YY_INPUT(buf, result, max_size) { \ int yyc = getc(yyin); \ result = (EOF == yyc) ? 0 : (*(buf) = yyc, 1); \ } #define STATCHECK_GT 1 #define STATCHECK_LT 2 struct statcheck_t { char *stat; int value; int rel; }; struct statchange_t { char *stat; int addend; }; struct Choice { int option; char *flavor; statcheck_t *statcheck; char *id; statchange_t *statchange; Choice(char*, statcheck_t *statcheck, statchange_t *statchange); }; 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 = < i:Identifier > Newline BlankLine* { $$ = i; } Identifier = < [A-Z][A-Z0-9_]+ > - { $$.string = strndup(yytext, yyleng); } 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' - < 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]+ > ')' - { $$.num = atoi(yytext); } Freetext = < ([^[<])+ > { $$.string = strndup(yytext, yyleng); } Redirect = ck:StatCheck? '[' i:Identifier ']' - cg:StatChange? Newline { $$.choice = new Choice(i.string, ck.statcheck, cg.statchange); } StatCheck = '<' n:StatName v:StatVal r:StatRel '>' - { $$.statcheck = new statcheck_t{n.string, v.num, r.num}; } StatChange = '(' ('+' | '-') v:StatVal n:StatName ')' { $$.statchange = new statchange_t{n.string, v.num}; } StatName = < [A-Za-z]+ > - { $$.string = strdup(yytext); } StatVal = < [0-9]+ > - { $$.num = atoi(yytext); } StatRel = < ('+'|'-')? > { $$.num = 0x0 } - EndOfFile = !. BlankLine = - Newline - = (' ' | '\t')* Newline = '\r\n' | '\r' | '\n' %% Choice::Choice(char *id, statcheck_t *statcheck, statchange_t *statchange) { this->id = id; this->statcheck = statcheck; this->statchange = 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); } } } std::vector CyoaParse(FILE *file) { yyin = file; yyparse(); yyin = stdin; return pages; } /* Local Variables: */ /* mode: text */ /* End: */