/*
 * Lexer for BibTeX author fields
 *
 * This file is part of Beastie <https://purl.org/nxg/dist/beastie>
 * SPDX-FileCopyrightText: 2023 Norman Gray <https://nxg.me.uk>
 * SPDX-License-Identifier: BSD-2-Clause
 */

%top{
#if __GNUC__
// for fileno
#define _XOPEN_SOURCE 600
#endif
}

%{
#include <ctype.h>
#include <string.h>

#include "beastie.h"
#include "s7.h"
#include "util.h"

#include "parse-authors.h"
#include "parse-authors.tab.h"

#ifndef WITH_MAIN
#define WITH_MAIN 0
#endif
%}

 /* Flex doesn't do Unicode.  There is a alternative lexer RE-flex
  * <https://github.com/Genivia/RE-flex> which does and which might
  * be of interest in the future, but for now, we have to do it by
  * hand.  The following patterns were taken from a Stackoverflow
  * answer <https://stackoverflow.com/a/9617585/375147> by ‘Kaz’,
  *
  * These patters work by spotting UTF-8 byte patterns.
  * They therefore cover only UTF-8.
  * That's mostly OK here, because s7's unicode support is actually
  * just UTF-8 support. and it handles the decoding of the UTF-8
  * strings we lex here, so we don't have to.
  *
  * ASCN and UANYN are ASC and UANY minus newline.
  * UONLY is non-ASCII.
  */
ASC     [\x00-\x7f]
ASCN    [\x00-\t\v-\x7f]
U       [\x80-\xbf]
U2      [\xc2-\xdf]
U3      [\xe0-\xef]
U4      [\xf0-\xf4]

UANY    {ASC}|{U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U}
UANYN   {ASCN}|{U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U}
UONLY   {U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U}

 /* We do currently accept dots in names, so that eg "Jr." is acceptable.
  * Is that right?
  * The Beebe syntax has for abbrev, entry, key, field name syntax,
  *     NAME               [A-Za-z][-A-Za-z0-9:.+/']*
  * so we should, too
  * Here, we allow a name to also include any non-ASCII Unicode character.
  */
CAPNAME	[A-Z]([-A-Za-z0-9:.+/']|{UONLY})*
 /* btxdoc says (p.16):
  *
  *    In general, it’s a von token if the first
  *    letter at brace-level 0 is in lower case.
  *
  * There are a couple of following remarks about special cases
  * concerning 'special characters'.
  */
LCNAME	[a-z]([-A-Za-z0-9:.+/']|{UONLY})*
 /* 'Unicode name' */
UNAME	{UONLY}([-A-Za-z0-9:.+/']|{UONLY})*
OWS	[[:space:]]*
WS	[[:space:]]+

%option noyywrap nounput
%option prefix="authors" reentrant bison-bridge bison-locations
%option extra-type="authors_extra_t"
%%

","{OWS}	return ',';
{WS}		return ' ';

"{" {
    *yylval = scan_to_matching_char(&input, yyscanner, yyget_lineno(yyscanner), "{",
                                    '}', SCAN_INCLUDE_LAST);
    return BRACED_STRING;
}

"\\"([[:alpha:]]+|[^[:space:]])[[:space:]]*	{
    *yylval = s7_make_string(S7, yytext);
    return LCNAME;
}


 /* Match "and" lowercase, only.
    This is a defection from BibTeX, which matches this case insensitively
    (which I think is a bit nuts). */
{WS}"and"{WS} {
    return AND;
}

{OWS}"others"{OWS} return OTHERS;

{CAPNAME} {
    *yylval = s7_make_string(S7, yytext);
    return CAPNAME;
}

{LCNAME} {
    *yylval = s7_make_string(S7, yytext);
    return LCNAME;
}

 /* We parse strings starting with an accented letter as uppercase.
  * This isn't quite right, but (a) fixing it would probably require a
  * lot of work to dig into Unicode uppercase coding, and (b) I'm not
  * aware of 'von' particles which start with an accented letter
  * (which of course means I'm going to learn about one tomorrow).
  * So... this'll do for now.
  */
{UNAME} {
    *yylval = s7_make_string(S7, yytext);
    return CAPNAME;
}


. {
    if (yyextra->location) {
        fprintf(stderr, "Unexpected character '%c' near %s, parsing authorlist: %s\n",
                yytext[0], yyextra->location, yyextra->current_string);
    } else {
        fprintf(stderr, "Unexpected character '%c', parsing authorlist: %s\n",
                yytext[0], yyextra->current_string);
    }
}
%%

yyscan_t parse_authors_setup_string(authors_extra_t extra, const char* s)
{
    yyscan_t scanner;
    yylex_init_extra(extra, &scanner);
    extra->current_string = s;
    extra->location = NULL;     // we hope this will be set after setup and before use

    extra->yyscanbuf = (void*)yy_scan_string(s, scanner);
    yyset_lineno(1, scanner);

    return scanner;
}
void parse_authors_finish(authors_extra_t extra, yyscan_t scanner)
{
    if (extra->yyscanbuf) {
        yy_delete_buffer((YY_BUFFER_STATE)extra->yyscanbuf, scanner);
        extra->yyscanbuf = NULL;
    }
    yylex_destroy(scanner);
}

#if WITH_MAIN

#include <stdio.h>

YYSTYPE one_value;
YYLTYPE locp;
s7_scheme* S7;

static void display_lexemes(yyscan_t scanner)
{
    int l;

    while ((l = authorslex(&one_value, &locp, scanner)) != 0) {
        switch (l) {
          case CAPNAME:
            s7w("capname=", one_value, "\n");
            break;
          case LCNAME:
            s7w("lcname=", one_value, "\n");
            break;
          case AND:
            printf("AND\n");
            break;
          case BRACED_STRING:
            s7w("braced_string=", one_value, "\n");
            break;
          case ',':
            printf(",\n");
            break;
          case ' ':
            printf("space\n");
            break;
          case OTHERS:
            printf("...others\n");
            break;
          default:
            printf("Unexpected lexeme: %d\n", l);
        }
    }
}

int main(int argc, char** argv)
{
    S7 = s7_init();
    if (argc != 2 || argv[1][0] == '-') {
        fprintf(stderr, "Usage: %s \"authorlist-string\"\n", argv[0]);
        exit(1);
    }

    struct authors_extra_s S;
    yyscan_t scanner = parse_authors_setup_string(&S, argv[1]);
    display_lexemes(scanner);
    parse_authors_finish(&S, scanner);
}
#endif
