/* * A very simple parser for aux files. * This simply lexes/parses \foo and \foo{arg}... * * This file is part of Beastie * SPDX-FileCopyrightText: 2023 Norman Gray * SPDX-License-Identifier: BSD-2-Clause */ %{ #include #include "beastie.h" #include "core.h" #include "util.h" #include "parse-aux.h" static void auxerror(YYLTYPE* locp, aux_extra_t, s7_pointer*, yyscan_t, const char* msg); static int l_matches_one_of(const char* s, ...); #ifndef WITH_MAIN #define WITH_MAIN 0 #endif %} %locations %define parse.error verbose %define api.pure %lex-param {yyscan_t scanner} %parse-param {aux_extra_t extra_info} %parse-param {s7_pointer* parse_result} %parse-param {yyscan_t scanner} %token CMD %token BRACEDSTRING %token STRING %token WHITESPACE %% input: cmd.sequence { *parse_result = $1; } | whitespace cmd.sequence { *parse_result = $2; } cmd.sequence: cmd.and.args { const char* cmdname = s7_string(s7_car($1)); if (l_matches_one_of(cmdname, "citation", "bibdata", "bibstyle", NULL)) { $$ = scheme_make_list($1, NULL); } else { $$ = GCP(s7_nil(S7)); } } | cmd.sequence cmd.and.args { const char* cmdname = s7_string(s7_car($2)); if (l_matches_one_of(cmdname, "citation", "bibdata", "bibstyle", NULL)) { $$ = GCP(s7_cons(S7, $2, $1)); } else { $$ = $1; } } cmd.and.args: CMD { $$ = scheme_make_list($1, NULL); } | CMD arg.sequence { $$ = GCP(s7_cons(S7, $1, s7_reverse(S7, $2))); } arg.sequence: arg.component { $$ = scheme_make_list($1, NULL); } | arg.component whitespace { $$ = scheme_make_list($1, NULL); } | arg.sequence arg.component { $$ = GCP(s7_cons(S7, $2, $1)); } | arg.sequence arg.component whitespace { $$ = GCP(s7_cons(S7, $2, $1)); } /* in an aux file, the STRING here is going to be something like [1] or "*"; * gobble the following whitespace */ arg.component: BRACEDSTRING | STRING /* It looks like we should be able to include an EMPTY element * in this rule, and simplify a couple of the rules above, * but that seems to produce shift/reduce errors, * for reasons I don't understand. */ whitespace: WHITESPACE | whitespace WHITESPACE %% static int l_matches_one_of(const char* s1, ...) { int result = 0; va_list ap; va_start(ap, s1); for (const char* t = va_arg(ap, const char*); t != NULL; t = va_arg(ap, const char*)) { if (strcmp(s1, t) == 0) { result = 1; break; } } va_end(ap); return result; } static void auxerror(YYLTYPE* locp, aux_extra_t ignored1, s7_pointer* ignored2, yyscan_t scanner, const char* msg) { scheme_eval("print-warning", s7_make_string(S7, "parse-aux: syntax error on line ~a: ~a"), s7_make_integer(S7, auxget_lineno(scanner)), s7_make_string(S7, msg), NULL); } #if WITH_MAIN /* This main program isn't particularly useful, but I retain it as * an example of a calling structure to the parser. */ s7_scheme* S7; extern int auxdebug; static const char* progname = NULL; void Usage(void) { fprintf(stderr, "Usage: %s [-v] [foo.aux]\n", progname); exit(1); } int main(int argc, char** argv) { progname = argv[0]; FILE* input_file = NULL; const char* input_string = NULL; S7 = s7_init(); s7_pointer result; for (argc--, argv++; argc > 0; argc--, argv++) { if (**argv == '-') { switch (*++*argv) { case 'v': auxdebug = 1; break; case 's': input_string = argv[1]; argc--, argv++; break; default: Usage(); } } else { input_file = fopen(*argv, "r"); if (input_file == NULL) { fprintf(stderr, "Can't open file %s to read\n", *argv); exit(1); } } } yyscan_t scanner; if (input_string) { scanner = parse_aux_setup_string(input_string); } else { scanner = parse_aux_setup_file(input_file); } auxparse(&result, scanner); parse_aux_finish(scanner); if (input_file) fclose(input_file); s7w("result: ", result, "\n"); } #endif