/* MeTA1 configuration lexer for Grecs. -*- c -*- */
%option noinput
%option nounput
%option noyywrap
%option prefix="grecs_meta1_"
%option reentrant bison-bridge bison-locations
%option extra-type="struct parser_control *"
%top {
/* MeTA1 configuration lexer for Grecs.
   Copyright (C) 2007-2025 Sergey Poznyakoff

   Grecs is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 3 of the License, or (at your
   option) any later version.

   Grecs is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License along
   with Grecs. If not, see <http://www.gnu.org/licenses/>. */

/* This file implements a lexical analyzer for MeTA1 main configuration file.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "grecs.h"
#include "meta1-gram.h"
#include <ctype.h>
}

%{
static void meta1_line_add_unescape_hex(struct grecs_txtacc *acc,
					const char *text, size_t len);

#define PCTL (yyget_extra (yyscanner))

#define YY_USER_ACTION do {						\
		if (YYSTATE == 0) {					\
			yylloc->beg = PCTL->point;			\
			yylloc->beg.col++;				\
		}							\
		PCTL->point.col += yyleng;				\
		yylloc->end = PCTL->point;				\
	} while (0);

#define YYSTYPE         GRECS_META1_STYPE
%}

%x COMMENT STR
X [0-9a-fA-F]
%%
	 /* C-style comments */
"/*"                    BEGIN (COMMENT);
<COMMENT>[^*\n]*        /* eat anything that's not a '*' */
<COMMENT>"*"+[^*/\n]*   /* eat up '*'s not followed by '/'s */
<COMMENT>\n             grecs_locus_point_advance_line(PCTL->point);
<COMMENT>"*"+"/"        BEGIN (INITIAL);
	 /* End-of-line comments */
#.*\n     { grecs_locus_point_advance_line(PCTL->point); }
#.*       /* end-of-file comment */;
	 /* Number */
0[xX]{X}+ |
0[0-7]+ |
[1-9][0-9]+             { grecs_line_begin(&PCTL->acc);
			  grecs_line_add(PCTL->acc, yytext, yyleng);
			  yylval->string = grecs_line_finish(PCTL->acc);
			  return META1_STRING; }
	/* Identifiers (unquoted strings) */
[a-zA-Z0-9_\./:\*-]+    { grecs_line_begin(&PCTL->acc);
			  grecs_line_add(PCTL->acc, yytext, yyleng);
			  yylval->string = grecs_line_finish(PCTL->acc);
			  return META1_IDENT; }
	 /* Quoted strings */
\"[^\\"\n]*\"           { grecs_line_begin(&PCTL->acc);
			  grecs_line_add(PCTL->acc, yytext + 1, yyleng - 2);
			  yylval->string = grecs_line_finish(PCTL->acc);
			  return META1_STRING; }
\"[^\\"\n]*\\x{X}{1,2}  { BEGIN(STR);
			  grecs_line_begin(&PCTL->acc);
			  meta1_line_add_unescape_hex(PCTL->acc, yytext + 1,
						      yyleng - 1);
			}
\"[^\\"\n]*\\.          { BEGIN(STR);
			  grecs_line_begin(&PCTL->acc);
			  grecs_line_acc_grow_unescape_last(PCTL->acc,
							    yytext + 1,
							    yyleng - 1,
							    yylloc); }
<STR>[^\\"\n]*\\x{X}{1,2} { meta1_line_add_unescape_hex(PCTL->acc, yytext,
							yyleng); }
<STR>[^\\"\n]*\\.       { grecs_line_acc_grow_unescape_last(PCTL->acc, yytext,
							    yyleng,
							    yylloc); }
<STR>[^\\"\n]*\"        { BEGIN(INITIAL);
			  if (yyleng > 1)
			    grecs_line_add(PCTL->acc, yytext, yyleng - 1);
			  yylval->string = grecs_line_finish(PCTL->acc);
			  return META1_STRING; }
<STR>[^\\"\n]*\n        { BEGIN(INITIAL);
			  grecs_error(yylloc, 0, _("newline in a string"));
			  grecs_line_add(PCTL->acc, yytext, yyleng - 1);
			  yylval->string = grecs_line_finish(PCTL->acc);
			  return META1_STRING; }
	 /* Other tokens */
[ \t\f][ \t\f]*         ;
\n                      { grecs_locus_point_advance_line(PCTL->point); }
[,;{}=]                 return yytext[0];
.        { grecs_error(yylloc, 0,
		       (isascii(yytext[0]) && isprint(yytext[0])) ?
			  _("stray character %c") :
			  _("stray character \\%03o"),
				 (unsigned char) yytext[0]); }
%%

static void
meta1_line_add_unescape_hex(struct grecs_txtacc *acc, const char *text,
			    size_t len)
{
	for (; text[len-1] != 'x' && len > 0; len--)
		;
	grecs_line_acc_grow(acc, text, len - 2);
	grecs_line_acc_grow_char(acc, (char) strtoul (text + len, NULL, 16));
}

yyscan_t
grecs_meta1_new_scanner(struct parser_control *pctl, int debug)
{
	yyscan_t scanner;

	memset(pctl, 0, sizeof(*pctl));
	yylex_init_extra(pctl, &scanner);
	yyset_debug(debug, scanner);
	pctl->acc = grecs_line_acc_create();
	return scanner;
}

void
grecs_meta1_close_scanner(yyscan_t scanner)
{
	struct parser_control *pctl = yyget_extra(scanner);
	grecs_line_acc_free(pctl->acc);
	pctl->acc = NULL;
	yylex_destroy(scanner);
}

int
grecs_meta1_new_source(yyscan_t scanner, const char *name)
{
	struct parser_control *pctl = yyget_extra(scanner);
	FILE *fp;

	fp = fopen(name, "r");
	if (!fp) {
		grecs_error(NULL, errno, _("Cannot open `%s'"), name);
		return 1;
	}

	yyset_in(fp, scanner);
	yy_switch_to_buffer(yy_create_buffer(fp, YY_BUF_SIZE, scanner),
			    scanner);
	pctl->point.file = grecs_install_text(name);
	pctl->point.line = 1;
	pctl->point.col = 0;
	return 0;
}
