/*
 * A parser for the 'format string' component of the .bst language
 * format.names$ function.
 *
 * 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 "beastie.h"
#include "util.h"

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

#define INCCHAR do { (yyextra->charno) += yyleng; } while (0)

#ifndef WITH_MAIN
#define WITH_MAIN 0
#endif
%}

%option prefix="fmtstring" reentrant bison-bridge bison-locations
%option extra-type="fmtstring_extra_t"
%option noyywrap

%x BL1

/* the following, plus '{' and '}' account for all possible characters */
FMTLETTER	[fvlj]
TIECHAR		[~]
NONFMTLETTER	[a-eghikm-uwxyz]
OTHERCHAR	[^{}~a-z]

%%

"{"	{
     (yyextra->brace_level)++;
     INCCHAR;
     if ((yyextra->brace_level) == 1) {
         BEGIN(BL1);
     } else {
         BEGIN(INITIAL);
     }
     return '{';
}

"}"	{
    if ((yyextra->brace_level) == 0) {
    scheme_eval("print-warning",
                s7_make_string(S7, "more right braces than left at position ~a in format string ~a (ignored)"),
                s7_make_integer(S7, yyextra->charno),
                s7_make_string(S7, yyextra->current_string),
                NULL);
    } else {
        INCCHAR;
        (yyextra->brace_level)--;
        if ((yyextra->brace_level) == 1) {
            BEGIN(BL1);
        } else {
            BEGIN(INITIAL);
        }
        return '}';
    }
 }

 /* all other characters, not at brace-level-1 */
[^{}]+		{
    INCCHAR;
    *yylval = s7_make_string(S7, yytext);
    return STRING;
}

<BL1>"{" {
    (yyextra->brace_level)++;              // from 1
    INCCHAR;
    BEGIN(INITIAL);
    return '{';
}

<BL1>"}" {
    (yyextra->brace_level)--;              // ...down to zero
    INCCHAR;
    BEGIN(INITIAL);
    return '}';
}

<BL1>{FMTLETTER}	{
    int this_char = yytext[0];
    int peek_char = input(yyscanner);
    int full_name;
    INCCHAR;
    // are we looking at (eg) 'f' or 'ff'?
    if (peek_char == this_char) {
        // the character is doubled
        INCCHAR;
        full_name = 1;
    } else {
        unput(peek_char);
        full_name = 0;
    }
    switch (this_char) {
      case 'f':
        *yylval = s7_make_symbol(S7, (full_name ? "first" : "first/i"));
        break;
      case 'v':
        *yylval = s7_make_symbol(S7, (full_name ? "von" : "von/i"));
        break;
      case 'l':
        *yylval = s7_make_symbol(S7, (full_name ? "last" : "last/i"));
        break;
      case 'j':
        *yylval = s7_make_symbol(S7, (full_name ? "junior" : "junior/i"));
        break;
      default:
        error_exit("Found character %c in fmtstring string (should be impossible)",
                   this_char);
    }
    return FMTSYM;
}

<BL1>{TIECHAR}+	{
    int nties = strlen(yytext);
    if (nties > 2) {
#if WITH_MAIN
        fprintf(stderr, "unexpected multiple ties in <%s>\n", yyextra->current_string);
#else
        scheme_eval("print-warning",
                    s7_make_string(S7, "unexpected multiple ties in format ~s"),
                    s7_make_string(S7, yyextra->current_string),
                    NULL);
#endif
    }
    if (nties == 1) {
        *yylval = s7_make_symbol(S7, "nbsp?");
        return OPT_TIE;
    } else {
        *yylval = s7_make_symbol(S7, "nbsp");
        return REQ_TIE;
    }
}


<BL1>{OTHERCHAR}+	{
    INCCHAR;
    *yylval = s7_make_string(S7, yytext);
    return NONALPHA;
    // return STRING;
}

<BL1>{NONFMTLETTER}+ {
    // Inside brace-level one, the only _letters_ we should see are the
    // format letters in FMTLETTER, so if we see one (or more) such letters,
    // that's an error
    INCCHAR;
#if WITH_MAIN
    fprintf(stderr, "unexpected chararacters '%s' at brace-level 1 at position %d in format string %s (ignored)\n",
            yytext,
            yyextra->charno,
            yyextra->current_string);
#else
    scheme_eval("print-warning",
                s7_make_string(S7, "unexpected chararacters '~a' at brace-level 1 at position ~a in format string ~a (ignored)"),
                s7_make_string(S7, yytext),
                s7_make_integer(S7, yyextra->charno),
                s7_make_string(S7, yyextra->current_string),
                NULL);
#endif
}

 /* fallback: if we get here, we've forgotten something
  * (we can't make the fallback '<*>.+' since that would longest-match others above)
  * This produces a flex warning 'rule cannot be matched', but that's
  * OK, since that's what we're expecting to be the case.
  */
 /*
<*>. {
#if WITH_MAIN
    fprintf(stderr, "(very) unexpected chararacter '%c' at position %d in format string %s (ignored)\n",
            yytext[0],
            yyextra->charno,
            yyextra->current_string);
#else
    scheme_eval("print-warning",
                s7_make_string(S7, "(very) unexpected chararacter ~a at position ~a in format string ~a (ignored)"),
                s7_make_character(S7, yytext[0]),
                s7_make_integer(S7, yyextra->charno),
                s7_make_string(S7, yyextra->current_string),
                NULL);
#endif
}
*/

%%

yyscan_t parse_fmtstring_setup_string(fmtstring_extra_t extra, const char* s)
{
    //fprintf(stderr, "fmtstring: %s\n", s);
    yyscan_t scanner;
    yylex_init_extra(extra, &scanner);
    extra->current_string = s;
    extra->brace_level = 0;
    extra->charno = 0;

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

    return scanner;
}
void parse_fmtstring_finish(fmtstring_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>
#include "util.h"

YYSTYPE one_value;
YYLTYPE locp;
s7_scheme* S7;

static void display_lexemes(yyscan_t scanner)
{
    int l;

    while ((l = fmtstringlex(&one_value, &locp, scanner)) != 0) {
        switch (l) {
          case STRING:
            s7w("string: ", one_value, "\n");
            break;
          case NONALPHA:
            s7w("non-alpha: ", one_value, "\n");
            break;
          case FMTSYM:
            s7w("format specification: ", one_value, "\n");
            break;
          case OPT_TIE:
            printf("optional-tie\n");
            break;
          case REQ_TIE:
            printf("required-tie\n");
            break;
          case '{':
            printf("{\n");
            break;
          case '}':
            printf("}\n");
            break;
          default:
            printf("Unexpected lexeme: %d\n", l);
        }
    }
}

const char* progname;
void Usage(void)
{
    fprintf(stderr, "Usage: %s \"fmtstring\"\n", progname);
    exit(1);
}

int main(int argc, char** argv)
{
    progname = argv[0];

    if (argc != 2 || argv[1][0] == '-') Usage();

    S7 = s7_init();

    struct fmtstring_extra_s S;

    yyscan_t scanner = parse_fmtstring_setup_string(&S, argv[1]);
    display_lexemes(scanner);
    parse_fmtstring_finish(&S, scanner);
}
#endif
