/* * This file is part of Beastie * * SPDX-FileCopyrightText: 2023 Norman Gray * SPDX-License-Identifier: BSD-2-Clause */ /* A parser for BibTeX authors */ %{ #include "beastie.h" #include "core.h" #include "util.h" #include "parse-authors.h" static void authorserror(YYLTYPE* locp, authors_extra_t, s7_pointer* ignored, yyscan_t scanner, const char* msg); static s7_pointer scheme_false; // Put R(1), etc, at the beginning of each action to do coverage checking #if 1 #define R(id) #else #include void trace_coverage(const char* s) { fprintf(stderr, "@ %s\n", s); } #define R(id) trace_coverage(#id); #endif %} %locations %define parse.error verbose %define api.pure %lex-param {yyscan_t scanner} %parse-param {authors_extra_t extra_info} %parse-param {s7_pointer* parse_result} %parse-param {yyscan_t scanner} %initial-action { scheme_false = s7_f(S7); s7_gc_protect(S7, scheme_false); } %token CAPNAME %token LCNAME %token BRACED_STRING %token AND %token OTHERS %% // Parse the input into a list of authorlist structures, // or such a list followed by the symbol 'others input: list.of.names {R(1.1) *parse_result = GCP(s7_reverse(S7, $1)); } | list.of.names AND OTHERS {R(1.2) *parse_result = GCP(s7_reverse(S7, GCP(s7_cons(S7, s7_make_symbol(S7, "others"), $1)))); } list.of.names: name {R(2.1) $$ = scheme_make_list($1, NULL); } | list.of.names AND name {R(2.2) $$ = GCP(s7_cons(S7, $3, $1)); } // Parse a name into an authorlist structure (as created by authors:make) // where each of the elements can have multiple words. // An {Organisation} will be deemed to be a 'last name'. // // From btxdoc: // // To summarize, BibTEX allows three possible forms for the name: // "First von Last" // "von Last, First" // "von Last, Jr, First" // // The author objects are generated by function authors:make, which // expects its arguments to be in reverse order. name: list.of.capnames {R(5) // First Last s7_pointer cdr = GCP(s7_cdr($1)); $$ = scheme_eval("authors:make", GCP(s7_is_null(S7, cdr) ? s7_f(S7) : cdr), scheme_false, scheme_make_list(s7_car($1), NULL), scheme_false, NULL); } | list.of.capnames list.of.lcnames list.of.anynames {R(6) // First von Last or First von Last1 und Last2 $$ = scheme_eval("authors:make", $1, $2, $3, scheme_false, NULL); } | list.of.capnames list.of.anynames {R(7) // The important thing here is that we match a name where the last // token is a lowercase surname ('lowercase-last') // Eg "Luciano dell'Agnello". // The special case here is not that we're accepting particular // name forms, but that BibTeX will always produce a Last name, // even if it starts with a lowercase letter. // // Here and in the next rule we are matching list.of.anynames so // that we can match all possibilities. We presume that this // list will be all lowercase names, and it's not obvious if we // should do something special if it isn't. if (s7_list_length(S7, $2) == 1) {R(8.1) $$ = scheme_eval("authors:make", $1, scheme_false, $2, scheme_false, NULL); } else {R(8.2) // An unusual variant: this is something like Myles na gCopaleen, // where the last tokens, plural, are lowercase. Interpret // this (as BibTeX does) as 'gCopaleen' being the last name, // and the previous lowercase tokens are the von-particles. $$ = scheme_eval("authors:make", $1, GCP(s7_cdr($2)), GCP(s7_list(S7, 1, s7_car($2))), scheme_false, NULL); } } | list.of.capnames list.of.lcnames list.of.anynames ',' list.of.anynames {R(9) // Another unusual case ('spanish-and'): Ruiz y Picasso, Pablo. // The 'y' is not a von-part here. $$ = scheme_eval("authors:make", $5, scheme_false, GCP(s7_append(S7, $3, s7_append(S7, $2, $1))), scheme_false, NULL); } | list.of.capnames list.of.lcnames list.of.anynames ',' list.of.anynames ',' list.of.anynames {R(10) // A 'spanish-and' variant, which includes a Junior part (which I // don't think actually happens). $$ = scheme_eval("authors:make", $5, scheme_false, GCP(s7_append(S7, $3, s7_append(S7, $2, $1))), $7, NULL); } | list.of.capnames ',' list.of.anynames {R(11) // Last, First $$ = scheme_eval("authors:make", $3, scheme_false, $1, scheme_false, NULL); } | list.of.capnames list.of.anynames ',' list.of.anynames {R(12) // Smith dell'Agnello, First ('lowercase-last') $$ = scheme_eval("authors:make", $4, scheme_false, GCP(s7_append(S7, $2, $1)), scheme_false, NULL); } | list.of.capnames ',' list.of.anynames ',' list.of.anynames {R(13) // Last, Jr, First $$ = scheme_eval("authors:make", $5, scheme_false, $1, $3, NULL); } | list.of.capnames list.of.anynames ',' list.of.anynames ',' list.of.anynames {R(14) // Smith dell'Agnello, First, Jr ('lowercase-last') $$ = scheme_eval("authors:make", $4, scheme_false, GCP(s7_append(S7, $2, $1)), $6, NULL); } | list.of.lcnames list.of.anynames {R(15) // von Last (slightly unusual, but not wrong) $$ = scheme_eval("authors:make", scheme_false, $1, $2, scheme_false, NULL); } | list.of.lcnames {R(16) // bell hooks $$ = scheme_eval("authors:make", GCP(s7_cdr($1)), scheme_false, GCP(s7_list(S7, 1, s7_car($1))), scheme_false, NULL); } | list.of.lcnames list.of.anynames ',' list.of.anynames {R(18) // von Last, First // Note: $2 is list.of.anynames rather than list.of.capnames so that we // will match everything (?), but I don't know what // "von Last du Last2, First" should parse to $$ = scheme_eval("authors:make", $4, $1, $2, scheme_false, NULL); } | list.of.lcnames ',' list.of.anynames { // dell'Agnello, Luciano, so the _surname_ starts with a lowercase letter if (s7_list_length(S7, $1) == 1) {R(17.1) $$ = scheme_eval("authors:make", $3, scheme_false, $1, scheme_false, NULL); } else {R(17.2) // Unusual ('lowercase-last') $$ = scheme_eval("authors:make", $3, s7_cdr($1), GCP(s7_list(S7, 1, s7_car($1))), scheme_false, NULL); } } | list.of.lcnames list.of.anynames ',' list.of.anynames ',' list.of.anynames {R(19) // von Last, Jr, First $$ = scheme_eval("authors:make", $6, $1, $2, $4, NULL); } | list.of.lcnames ',' list.of.anynames ',' list.of.anynames { // Unusual ('lowercase-last') // dell'Agnello, Jr, Luciano or // na gCopaleen, XIV, Myles // (this will botch "dell'Agnello del'Angelus, Jr, Bill", but // that's a made-up case, so I don't think I need to worry about // it now). if (s7_list_length(S7, $1) == 1) {R(20.1) $$ = scheme_eval("authors:make", $5, scheme_false, $1, $3, NULL); } else {R(20.2) $$ = scheme_eval("authors:make", $5, s7_cdr($1), GCP(s7_list(S7, 1, s7_car($1))), $3, NULL); } } /* * ADS formats "Lidia van Driel-Gesztelyi" as "{Driel-Gesztelyi}, Lidia van". * I assert that they're wrong about this (and I've reported it to * them), but it would be nice not to fail in this case, and we should * probably produce the same result as BibTeX, which parses this as * surname "{Driel-Gesztelyi}" and _forenames_ "Lidia van" (as can be * confirmed with some .bst hacking and `format.name$`). HOWEVER, this * turns out to be hard, given the grammar we currently (revision * 77205b9) have. I should note that Beebe's bibclean program * normalises the backwards form into the sensible one. * * This is a separate question from what the analysis _should_ be here, in * language terms rather than BibTeX ones, since the surname here is * a hyphenated combination of a Dutch name with a von-particle, and * a Hungarian name without. */ list.of.capnames: capname {R(21.1) $$ = scheme_make_list($1, NULL); } | list.of.capnames ' ' capname {R(21.2) $$ = GCP(s7_cons(S7, $3, $1)); } | list.of.capnames ' ' {R(21.3) $$ = $1; } list.of.lcnames: lcname {R(22.1) $$ = scheme_make_list($1, NULL); } | list.of.lcnames ' ' lcname {R(22.2) $$ = GCP(s7_cons(S7, $3, $1)); } | list.of.lcnames ' ' {R(22.3) $$ = $1; } list.of.anynames: list.of.capnames | list.of.lcnames | list.of.anynames list.of.capnames {R(23.3) // the string "von der Last1 Last2" will appear here as lists // ("der" "von") and ("Last2" "Last1"), so we have to append // them apparently the wrong way around $$ = GCP(s7_append(S7, $2, $1)); } | list.of.anynames list.of.lcnames {R(23.4) $$ = GCP(s7_append(S7, $2, $1));} /* capname can be "Vall{\'e}e", * which is (CAPNAME BRACED_STRING LCNAME) * * Note that the grammar here is ambiguous, since * lcname capname . CAPNAME * could reduce (lcname capname) to lcname, * or shift the CAPNAME on to the capname. We depend on Bison choosing the latter. * The same is true for BRACED_STRING. */ capname: CAPNAME | BRACED_STRING | capname CAPNAME {R(24.3) $$ = scheme_eval("string-append", $1, $2, NULL); } | capname BRACED_STRING {R(24.4) $$ = scheme_eval("string-append", $1, $2, NULL); } | capname LCNAME {R(24.5) $$ = scheme_eval("string-append", $1, $2, NULL); } /* Similarly, lcname can be "d{\'e}l" or "d{\'e}L" * which are (LCNAME BRACED_STRING LCNAME) and (LCNAME BRACED_STRING CAPNAME) respectively. * We want both of these to turn into 'lcname'. * We can never have a LCNAME followed by another LCNAME, or directly by a CAPNAME, * only by a 'capname' which is a sequence starting with BRACED_STRING. */ lcname: LCNAME | LCNAME capname {R(25.2) $$ = scheme_eval("string-append", $1, $2, NULL); } %% // We could be more sophisticated about this, and include error tokens // in the grammar, but we don't want to do anything more sophisticated // than give up at this point. static void authorserror(YYLTYPE* locp, authors_extra_t extra_info, s7_pointer* ignored, yyscan_t scanner, const char* msg) { scheme_eval("print-warning", s7_make_string(S7, "unable to parse as authorlist: ~s (~a)"), s7_make_string(S7, extra_info->current_string), //get_parse_authorlist_argument(), s7_make_string(S7, msg), NULL); } // It's useful to note that a way of debugging name parsing is // ./beastie -d4 -mauthors -e '(write (parse-author-list "Bloggs, Fred"))'