/* grecs - Gray's Extensible Configuration System       -*- c -*- */
%option nounput
%option prefix="grecs_grecs_"
%option reentrant bison-bridge bison-locations
%option extra-type="struct parser_control *"
%top {
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
}
%{
/* grecs - Gray's Extensible Configuration System
   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/>. */

#include <grecs.h>
#include <grecs-gram.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <glob.h>
#include <signal.h>
#include <wordsplit.h>

#define PCTL (yyget_extra (yyscanner))

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

#define YYSTYPE         GRECS_GRECS_STYPE

/*
 * Redefine default error reporting function.
 */
#define YY_FATAL_ERROR(t) grecs_error(NULL, errno, "%s", t)

/*
 * Preprocessing and synclines.
 */
static void echo(yyscan_t yyscanner, char const *text, size_t len);

/* Inform flex that it should use echo. */
#define ECHO echo(yyscanner, yytext, yyleng)
#define PPECHO do { if (PCTL->preproc_mode) ECHO; } while(0)

/*
 * String concatenation support.
 *
 * String concatenation is a legacy feature of Grecs parser that instructs
 * it to concatenate adjacent quoted strings.  It is enabled if the
 * GRECS_OPTION_QUOTED_STRING_CONCAT bit is set in parser options.  This
 * bit causes the lexer to qualify each double-quoted string as QSTRING.
 */
#define qstring()							\
	((grecs_parser_options & GRECS_OPTION_QUOTED_STRING_CONCAT)	\
	 ? QSTRING : STRING)

/*
 * Lexical tie-in variables.
 */
#define grecs_yyerror_suppress PCTL->error_suppress

/*
 * Here-document support.
 */
static int (*char_to_strip)(char);      /* Strips matching characters of each
					   here-document line */

static void multiline_begin(yyscan_t scanner, char *);
static void multiline_add(yyscan_t scanner, char *);
static char *multiline_strip_tabs(char *text);
static int isemptystr(char const *text, int off);

/*
 * Forward declarations.
 */
static int string_or_ident(char const *);
static void qstring_locus_fixup(yyscan_t scanner);
static int parse_include(yyscan_t scanner, char *text);
static int push_source(yyscan_t scanner, const char *name, int once);
static int pop_source(yyscan_t scanner);
static void ctx_abort(yyscan_t scanner);

/*
 * Versions of grecs up to 03044f768f didn't recognize comment starters that
 * followed a non-quoted string constituent, as in inet://127.0.0.1.  That
 * silently encouraged a bad practice of omitting quotes around such strings.
 *
 * To discontinue such practice and point to the source of the problem, a
 * warning is emitted each time a comment starter is encountered immediately
 * following a non-quoted string constituent.
 */
static void comment_warning(yyscan_t scanner);

/*
 * The report_diag should be called only when yytext begins with the following
 * prefix:
 *       ^[ \t]*#[ \t]*.{WLEN}
 * It returns a pointer to yytext past the prefix.
 */
static char *
report_diag(char *text, size_t leng, size_t wlen)
{
	char *p = strchr(text, '#');
	p += strspn(p, " \t") + wlen;
	p += strspn(p, " \t");
	text[leng-1] = 0;
	if (p[0] == '"' && text[leng-2] == '"') {
		char *q, *r;

		text[leng-2] = 0;
		p++;
		q = r = p;
		do {
			if (*r == '\\') {
				int c = wordsplit_c_unquote_char(*++r);
				*q = c ? c : *r;
			} else
				*q = *r++;
		} while (*q++ == 0);
	}
	return p;
}
%}

%x COMMENT ML STR NQSTR

WS [ \t\f][ \t\f]*
ID [a-zA-Z_][a-zA-Z_0-9-]*
P [1-9][0-9]*
NQSC [^ \t\f\n'"(){},;/*]
%%
	 /* C-style comments */
"/*"   {
	    BEGIN(COMMENT);
	    PCTL->beginning_of_line = 0;
	    comment_warning(yyscanner);
 }
<COMMENT>{
[^*\n]*        /* eat anything that's not a '*' */
"*"+[^*/\n]*   /* eat up '*'s not followed by '/'s */
\n          { struct parser_control *pctl = yyget_extra(yyscanner);
	      grecs_locus_point_advance_line(pctl->point);
	      pctl->emit_syncline = 1; }
"*"+"/"     {
	      struct parser_control *pctl = yyget_extra(yyscanner);
	      BEGIN(INITIAL);
	      if (pctl->preproc_mode) {
		      if (yylloc->end.line != yylloc->beg.line) {
			      if (!(yylloc->beg.line == 1 &&
				    yylloc->beg.col == 1))
				      echo(yyscanner, "\n", 1);
			      pctl->beginning_of_line = 1;
		      } else
			      pctl->beginning_of_line = 0;
	      }
 }
<<EOF>>     { grecs_error(yylloc, 0, _("end of file in comment"));
	      return 0;
 }
}

	 /* Line directive */
^[ \t]*#[ \t]*{P}[ \t]+\".*\".*\n {
	 PPECHO;
	 grecs_parse_line_directive_cpp(yytext, yylloc, &PCTL->point);
 }
^[ \t]*#[ \t]*line[ \t].*\n {
	 PPECHO;
	 grecs_parse_line_directive(yytext, yylloc, &PCTL->point);
 }

	 /* Includes */
^[ \t]*#[ \t]*include(_once)?.*/\n { parse_include(yyscanner, yytext); }
	 /* Error directives */
^[ \t]*#[ \t]*error[ \t].*\n {
	grecs_error(yylloc, 0, "%s", report_diag(yytext, yyleng,
						  sizeof("error")));
 }
^[ \t]*#[ \t]*warning[ \t].*\n {
  grecs_warning(yylloc, 0, "%s", report_diag(yytext, yyleng,
					      sizeof("warning")));
 }
^[ \t]*#[ \t]*abend[ \t].*\n {
  grecs_error(yylloc, 0, "%s", report_diag(yytext, yyleng, sizeof("abend")));
  ctx_abort(yyscanner);
  grecs_yyerror_suppress = 1;
  return 0;
 }
	 /* End-of-line comments */
#.*\n     { grecs_locus_point_advance_line(PCTL->point);
	    PCTL->emit_syncline = 1; }
#.*      /* end-of-file comment */;
"//".*\n  { comment_warning(yyscanner);
	    grecs_locus_point_advance_line(PCTL->point);
	    PCTL->emit_syncline = 1; }
"//".*    { comment_warning(yyscanner); } /* end of file comment */

	 /* Non-quoted strings and identifiers */
{NQSC}+ {
	 PPECHO;
	 grecs_line_begin(&PCTL->acc);
	 grecs_line_add(PCTL->acc, yytext, yyleng);
	 BEGIN(NQSTR);
 }
[/*]{NQSC}* {
	 PPECHO;
	 grecs_line_begin(&PCTL->acc);
	 grecs_line_add(PCTL->acc, yytext, yyleng);
	 BEGIN(NQSTR);
 }
<NQSTR>{
{NQSC}*/"/"[/*] {
	 PPECHO;
	 PCTL->emit_comment_warning = 1;
	 grecs_line_add(PCTL->acc, yytext, yyleng);
	 yylval->string = grecs_line_finish(PCTL->acc);
	 BEGIN(INITIAL);
	 return STRING;
 }
{NQSC}*[/*] {
	 PPECHO;
	 grecs_line_add(PCTL->acc, yytext, yyleng);
 }
{NQSC}+ {
	 PPECHO;
	 grecs_line_add(PCTL->acc, yytext, yyleng);
	 yylval->string = grecs_line_finish(PCTL->acc);
	 BEGIN(INITIAL);
	 return string_or_ident(yylval->string);
 }
\n {
	 yyless(0);
	 yylloc->end.col--;
	 PCTL->point.col--;
	 yylval->string = grecs_line_finish(PCTL->acc);
	 BEGIN(INITIAL);
	 return string_or_ident(yylval->string);
 }
. {
	 yyless(0);
	 yylloc->end.col--;
	 PCTL->point.col--;
	 yylval->string = grecs_line_finish(PCTL->acc);
	 BEGIN(INITIAL);
	 return string_or_ident(yylval->string);
 }
}
	 /* Quoted strings */
\"[^\\"\n]*\" {
	 PPECHO;
	 grecs_line_begin(&PCTL->acc);
	 grecs_line_add(PCTL->acc, yytext + 1, yyleng - 2);
	 yylval->string = grecs_line_finish(PCTL->acc);
	 qstring_locus_fixup(yyscanner);
	 return qstring();
 }
\"[^\\"\n]*\\\n {
	 PPECHO;
	 BEGIN(STR);
	 grecs_line_begin(&PCTL->acc);
	 grecs_line_acc_grow_unescape_last(PCTL->acc,
					   yytext + 1, yyleng - 1, yylloc);
	 grecs_locus_point_advance_line(PCTL->point);
 }
\"[^\\"\n]*\\. {
	 PPECHO;
	 BEGIN(STR);
	 grecs_line_begin(&PCTL->acc);
	 grecs_line_acc_grow_unescape_last(PCTL->acc,
					   yytext + 1, yyleng - 1, yylloc);
 }
<STR>{
[^\\"\n]*\\\n  {
	 PPECHO;
	 grecs_line_acc_grow_unescape_last(PCTL->acc, yytext, yyleng, yylloc);
	 grecs_locus_point_advance_line(PCTL->point);
 }
[^\\"\n]*\\. {
	 PPECHO;
	 grecs_line_acc_grow_unescape_last(PCTL->acc, yytext, yyleng, yylloc);
 }
[^\\"\n]*\"      {
	 PPECHO;
	 BEGIN(INITIAL);
	 if (yyleng > 1)
		 grecs_line_add(PCTL->acc, yytext, yyleng - 1);
	 yylval->string = grecs_line_finish(PCTL->acc);
	 qstring_locus_fixup(yyscanner);
	 return qstring();
 }
<<EOF>> {
	grecs_error(yylloc, 0, _("end of file in quoted string"));
	return 0;
 }
}
	 /* Multiline strings */
"<<"(-" "?)?\\?{ID}[ \t]*#.*\n |
"<<"(-" "?)?\\?{ID}[ \t]*"//".*\n |
"<<"(-" "?)?\\?{ID}[ \t]*\n |
"<<"(-" "?)?\"{ID}\"[ \t]*#.*\n |
"<<"(-" "?)?\"{ID}\"[ \t]*"//".*\n |
"<<"(-" "?)?\"{ID}\"[ \t]*\n {
	 PPECHO;
	 BEGIN(ML);
	 multiline_begin(yyscanner, yytext+2);
 }
<ML>{
	/* Ignore m4 line statements */
^"#line ".*\n {
	grecs_locus_point_advance_line(PCTL->point);
}
.*\n {  struct parser_control *pctl = yyget_extra(yyscanner);
	 char *p = multiline_strip_tabs(yytext);
	 int len = p + pctl->multiline_delimiter_len - yytext;
	 if (!strncmp(p, pctl->multiline_delimiter,
		      pctl->multiline_delimiter_len)
	     && isemptystr(yytext, len)) {
		 /* Update end pos */
		 if (grecs_parser_options & GRECS_OPTION_ADJUST_STRING_LOCATIONS)
			 yylloc->end.col = 1;
		 else
			 yylloc->end.col -= yyleng - len;
		 /*
		  * Adjust current position and put the characters that
		  * follow the delimiter back to input.
		  */
		 pctl->point.col -= yyleng - len;
		 yyless(len);

		 /* Echo the consumed input if preprocessing. */
		 PPECHO;

		 grecs_free(pctl->multiline_delimiter);
		 pctl->multiline_delimiter = NULL;
		 BEGIN(INITIAL);
		 yylval->string = grecs_line_finish(pctl->acc);

		 return MSTRING;
	 } else
		 PPECHO;
	 grecs_locus_point_advance_line(pctl->point);
	 multiline_add(yyscanner, p);
  }
}

{WS}     PPECHO;
	 /* Other tokens */
\n {
	 PPECHO;
	 grecs_locus_point_advance_line(PCTL->point);
 }
[,;{}()] {
	 PPECHO;
	 return yytext[0];
 }
.        { if (isascii(yytext[0]) && isprint(yytext[0]))
		grecs_error(yylloc, 0,
			     _("stray character %c"), yytext[0]);
	   else
		grecs_error(yylloc, 0,
			     _("stray character \\%03o"),
			       (unsigned char) yytext[0]); }

%%
/*
 * Echo LEN bytes from TEXT to yyout.
 */
static void
echo(yyscan_t scanner, char const *text, size_t len)
{
	struct parser_control *pctl = yyget_extra(scanner);
	FILE *out = yyget_out(scanner);
	YYLTYPE *lloc = yyget_lloc(scanner);

	if (len == 0) return;
	/* Check if we should print a syncline */
	if (pctl->beginning_of_line && pctl->emit_syncline) {
		fprintf(out, "#line %lu \"%s\"\n",
			(unsigned long)lloc->end.line,
			lloc->end.file);
		pctl->emit_syncline = 0;
	}
	/* Actually echo the text. */
	fwrite(text, len, 1, out);
	/* Determine the beginning of line status. */
	pctl->beginning_of_line = text[len-1] == '\n';
}

static void
comment_warning(yyscan_t scanner)
{
	struct parser_control *pctl = yyget_extra(scanner);
	if (pctl->emit_comment_warning) {
		grecs_warning(yyget_lloc(scanner), 0,
			      _("\"%2.2s\" starts comment here;"
				" use quotes if you want it to be"
				" a part of string, or insert"
				" a whitespace to suppress this warning"),
			      yyget_text(scanner));
		pctl->emit_comment_warning = 0;
	}
}



/*
 * Source inclusion support.
 */
static int
isglob(const char *s)
{
	return s[strcspn(s, "*?[")] != 0;
}

static int
parse_include(yyscan_t scanner, char *text)
{
	struct parser_control *pctl = yyget_extra(scanner);
	char *p;
	int rc = 1;
	int once;

	p = strchr(text, '#');
	p += strspn(p, " \t") + sizeof("include");
	if (strncmp(p, "_once", 5) == 0) {
		once = 1;
		p += 5;
	} else
		once = 0;
	p += strspn(p, " \t");

	if (!*p) {
		grecs_error(yyget_lloc(scanner), 0,
			    _("invalid include statement"));
		return 1;
	} else {
		size_t len;
		int allow_cwd;

		len = strlen(p);

		if (p[0] == '<' && p[len - 1] == '>') {
			allow_cwd = 0;
			p[len - 1] = 0;
			p++;
		} else if (p[0] == '"' && p[len - 1] == '"') {
			allow_cwd = 1;
			p[len - 1] = 0;
			p++;
		} else {
			grecs_error(yyget_lloc(scanner), 0,
				    _("invalid include statement"));
			return 1;
		}

		if (isglob(p)) {
			switch (glob(p, 0, NULL, &pctl->include_glob)) {
			case 0:
				pctl->include_pos = 0;
				pctl->include_once = once;
				break;
			case GLOB_NOSPACE:
				grecs_alloc_die();
			case GLOB_NOMATCH:
				break;
			default:
				grecs_error(yyget_lloc(scanner), 0,
					    _("read error"));
			}
			p = NULL;
		} else if (p[0] != '/') {
			char *q = p;
			p = grecs_find_include_file(q, allow_cwd);
			if (!p)
				grecs_error(yyget_lloc(scanner), 0,
					    _("%s: No such file or directory"),
					    q);
		}
	}

	if (p)
		rc = push_source(scanner, p, once);
	else if (pctl->include_pos < pctl->include_glob.gl_pathc)
		rc = push_source(scanner,
			      pctl->include_glob.gl_pathv[pctl->include_pos++],
				 once);
	return rc;
}

/*
 * Include search path.
 */
static struct grecs_list *grecs_usr_include_path;
static struct grecs_list *grecs_std_include_path;

size_t
grecs_include_path_count(int flag)
{
    size_t count = 0;
    if (flag & GRECS_STD_INCLUDE)
	count += grecs_list_size(grecs_std_include_path);
    if (flag & GRECS_USR_INCLUDE)
	count += grecs_list_size(grecs_usr_include_path);
    return count;
}

static int
foreach_dir(struct grecs_list *list, int flag,
	    int (*fun)(int, const char *, void *), void *data)
{
    int rc = 0;
    struct grecs_list_entry *ep;

    for (ep = list->head; rc == 0 && ep; ep = ep->next)
	rc = fun(flag, ep->data, data);
    return rc;
}

int
grecs_foreach_include_dir(int flag, int (*fun)(int, const char *, void *),
			  void *data)
{
    int rc = 0;

    if (flag & GRECS_STD_INCLUDE)
	rc = foreach_dir(grecs_std_include_path, GRECS_STD_INCLUDE, fun, data);
    if (rc == 0 && (flag & GRECS_USR_INCLUDE))
	rc = foreach_dir(grecs_usr_include_path, GRECS_USR_INCLUDE, fun, data);
    return rc;
}

void
grecs_include_path_clear()
{
	if (grecs_usr_include_path)
		grecs_list_clear(grecs_usr_include_path);
	if (grecs_std_include_path)
		grecs_list_clear(grecs_std_include_path);
}

static void
incl_free(void *data)
{
	grecs_free(data);
}

void
grecs_include_path_setup_v(char **dirs)
{
	if (!grecs_usr_include_path) {
		grecs_usr_include_path = grecs_list_create();
		grecs_usr_include_path->free_entry = incl_free;
	}
	grecs_std_include_path = grecs_list_create();
	grecs_std_include_path->free_entry = incl_free;
	if (dirs) {
		int i;
		for (i = 0; dirs[i]; i++)
			/* FIXME: Element never freed */
			grecs_list_append(grecs_std_include_path,
					  grecs_strdup(dirs[i]));
	}
}

void
grecs_include_path_setup(const char *dir, ...)
{
	const char *p;
	char **argv = NULL;
	size_t argc = 0;
	size_t argi = 0;
	va_list ap;

	va_start(ap, dir);
	p = dir;
	while (1) {
		if (argi == argc) {
			if (argc == 0)
				argc = 16;
			else
				argc += 16;
			argv = grecs_realloc(argv, argc * sizeof(argv[0]));
		}
		argv[argi++] = (char*) p;
		if (!p)
			break;
		p = va_arg(ap, const char*);
	}
	grecs_include_path_setup_v(argv);
	grecs_free(argv);
	va_end(ap);
}

void
grecs_preproc_add_include_dir(char *dir)
{
	if (!grecs_usr_include_path) {
		grecs_usr_include_path = grecs_list_create();
		grecs_usr_include_path->free_entry = incl_free;
	}
	grecs_list_append(grecs_usr_include_path, grecs_strdup(dir));
}

struct file_data {
	const char *name;
	size_t namelen;
	char *buf;
	size_t buflen;
	int found;
};

static int
pp_list_find(struct grecs_list *list, struct file_data *dptr)
{
	struct grecs_list_entry *ep;

	if (!list)
		return 0;
	for (ep = list->head; !dptr->found && ep; ep = ep->next) {
		const char *dir = ep->data;
		size_t size = strlen (dir) + 1 + dptr->namelen + 1;
		if (size > dptr->buflen) {
			dptr->buflen = size;
			dptr->buf = grecs_realloc(dptr->buf, dptr->buflen);
		}
		strcpy(dptr->buf, dir);
		strcat(dptr->buf, "/");
		strcat(dptr->buf, dptr->name);
		dptr->found = access(dptr->buf, F_OK) == 0;
	}
	return dptr->found;
}

char *
grecs_find_include_file(const char *name, int allow_cwd)
{
	static char *cwd = ".";
	struct file_data fd;

	fd.name = name;
	fd.namelen = strlen(name);
	fd.buf = NULL;
	fd.buflen = 0;
	fd.found = 0;

	if (!grecs_usr_include_path)
		grecs_include_path_setup(NULL);
	if (allow_cwd) {
		grecs_list_append(grecs_usr_include_path, cwd);
		pp_list_find(grecs_usr_include_path, &fd);
		grecs_list_remove_tail(grecs_usr_include_path);
	} else
		pp_list_find(grecs_usr_include_path, &fd);

	if (!fd.found) {
		pp_list_find(grecs_std_include_path, &fd);
		if (!fd.found)
			return NULL;
	}
	return fd.buf;
}

/*
 * Input context support.
 */

struct buffer_ctx {
	struct buffer_ctx *prev;	/* Pointer to previous context */
	struct grecs_locus locus;
	struct grecs_locus_point point;	/* Current input location */
	size_t namelen;		/* Length of the file name */
	struct input_file_ident id;
	FILE *infile;
	YY_BUFFER_STATE state;
};


static void
sigchld_default(struct parser_control *pctl)
{
	if (!pctl->sigchld_reset) {
		struct sigaction act;
		act.sa_flags = 0;
		act.sa_handler = SIG_DFL;
		sigemptyset(&act.sa_mask);
		sigaction(SIGCHLD, &act, &pctl->oldsigchld);
		pctl->sigchld_reset = 1;
	}
}

static void
sigchld_restore(struct parser_control *pctl)
{
	if (pctl->sigchld_reset) {
		sigaction(SIGCHLD, &pctl->oldsigchld, NULL);
		pctl->sigchld_reset = 0;
	}
}

#define STAT_ID_EQ(st,id) ((id).i_node == (st).st_ino       \
			    && (id).device == (st).st_dev)

static struct buffer_ctx *
ctx_lookup(yyscan_t scanner, struct stat *st)
{
	struct parser_control *pctl = yyget_extra(scanner);
	struct buffer_ctx *ctx;

	if (!pctl->context_stack)
		return NULL;

	for (ctx = pctl->context_stack; ctx; ctx = ctx->prev)
		if (STAT_ID_EQ(*st, ctx->id))
			break;
	return ctx;
}

static inline YY_BUFFER_STATE
yyget_current_buffer(yyscan_t yyscanner)
{
    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
    return YY_CURRENT_BUFFER;
}

static void
ctx_abort(yyscan_t scanner)
{
	struct parser_control *pctl = yyget_extra(scanner);

	/* Delete current input buffer */
	yy_delete_buffer(yyget_current_buffer(scanner), scanner);

	/* Clean-up context stack */
	while (pctl->context_stack) {
		struct buffer_ctx *prev = pctl->context_stack->prev;
		if (pctl->context_stack->id.pid > 0)
			kill(pctl->context_stack->id.pid, SIGKILL);
		fclose(pctl->context_stack->infile);
		grecs_free(pctl->context_stack);
		pctl->context_stack = prev;
	}
}

/* Calculate the hash of a struct input_file_ident.  */
static unsigned
incl_hasher(void *data, unsigned long n_buckets)
{
	const struct input_file_ident *id = data;
	return (id->i_node + id->device) % n_buckets;
}

/* Compare two input_file_idents for equality.  */
static int
incl_compare(void const *data1, void const *data2)
{
	const struct input_file_ident *id1 = data1;
	const struct input_file_ident *id2 = data2;
	return !(id1->device == id2->device && id1->i_node == id2->i_node);
}

static int
incl_copy(void *dst, void *src)
{
	memcpy(dst, src, sizeof(struct input_file_ident));
	return 0;
}

static int
source_lookup(yyscan_t scanner, struct stat *st)
{
	struct parser_control *pctl = yyget_extra(scanner);
	struct input_file_ident key;
	int install = 1;

	if (!pctl->incl_sources) {
		pctl->incl_sources = grecs_symtab_create(
			sizeof(struct input_file_ident),
			incl_hasher,
			incl_compare,
			incl_copy,
			NULL,/*FIXME: alloc*/
			NULL);
		if (!pctl->incl_sources)
			grecs_alloc_die();
	}

	key.i_node = st->st_ino;
	key.device = st->st_dev;
	if (!grecs_symtab_lookup_or_install(pctl->incl_sources, &key,
					    &install))
		grecs_alloc_die();
	return !install;
}

static int
push_source(yyscan_t scanner, const char *name, int once)
{
	struct parser_control *pctl = yyget_extra(scanner);
	YYLTYPE *lloc = yyget_lloc(scanner);
	FILE *fp;
	pid_t pid;
	struct buffer_ctx *ctx;
	struct stat st;
	int rc = stat(name, &st);

	if (pctl->context_stack) {
		if (rc) {
			grecs_error(lloc, errno,
				    _("Cannot stat `%s'"), name);
			return 1;
		}

		if (pctl->point.col &&
		    STAT_ID_EQ(st, pctl->input_file_ident)) {
			grecs_error(lloc, 0, _("Recursive inclusion"));
			return 1;
		}

		if ((ctx = ctx_lookup(scanner, &st))) {
			grecs_error(lloc, 0,
				    _("Recursive inclusion"));
			if (ctx->prev)
				grecs_error(&ctx->prev->locus, 0,
					    _("`%s' already included here"),
					    name);
			else
				grecs_error(lloc, 0,
					    _("`%s' already included at"
					      " top level"),
					    name);
			return 1;
		}
	} else if (rc) {
		grecs_error(NULL, errno, _("Cannot stat `%s'"), name);
		return 1;
	}

	if (once && source_lookup(scanner, &st))
		return -1;

	if (grecs_preprocessor) {
		if (access(name,R_OK)) {
			grecs_error(pctl->context_stack ? lloc : NULL,
				    errno,
				    _("Cannot open `%s'"), name);
			return -1;
		}
		if (!pctl->context_stack) {
			/*
			 * Ensure default handling of SIGCHLD.
			 * It will be reset back to its original state
			 * by pop_source, when it has popped off the initial
			 * context.
			 */
			sigchld_default(pctl);
		}
		fflush(yyget_out(scanner));
		fp = grecs_preproc_extrn_start(name, &pid);
	} else {
		fp = fopen(name, "r");
		pid = -1;
	}
	if (!fp) {
		grecs_error(pctl->context_stack ? lloc : NULL,
			    errno,
			    _("Cannot open `%s'"), name);
		if (!pctl->context_stack)
			sigchld_restore(pctl);
		return 1;
	}

	/* Push current context */
	if (yyget_in (scanner)) {
		ctx = grecs_malloc(sizeof(*ctx));
		ctx->locus = *lloc;
		ctx->point = pctl->point;
		ctx->id = pctl->input_file_ident;
		ctx->infile = yyget_in(scanner);
		ctx->state = yyget_current_buffer(scanner);
		ctx->prev = pctl->context_stack;
		pctl->context_stack = ctx;
	}

	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;

	pctl->input_file_ident.i_node = st.st_ino;
	pctl->input_file_ident.device = st.st_dev;
	pctl->input_file_ident.pid = pid;

	pctl->beginning_of_line = 1;

	if (yyget_debug(scanner))
	  fprintf (stderr, "Processing file `%s'\n", name);

	if (pctl->preproc_mode)
		pctl->emit_syncline = 1;

	return 0;
}

static int
pop_source(yyscan_t scanner)
{
	struct parser_control *pctl = yyget_extra(scanner);
	FILE *in = yyget_in(scanner);
	struct buffer_ctx *ctx;

	fclose(in);
	if (pctl->input_file_ident.pid > 0) {
		if (grecs_preproc_extrn_shutdown(pctl->input_file_ident.pid)) {
			/* Abort further input */
			ctx_abort(scanner);

			/* Suppress further errors */
			pctl->error_suppress = 1;

			/* Indicate there is no more input */
			return 1;
		}
	}

	if (!pctl->context_stack) {
		if (yyget_debug(scanner))
			fprintf(stderr, "End of input\n");
		/* Reset SIGCHLD to its original configuration */
		sigchld_restore(pctl);
		yyset_in(NULL, scanner);
		return 1;
	}

	/* Restore previous context */
	yyset_in(pctl->context_stack->infile, scanner);
	pctl->point = pctl->context_stack->point;
	pctl->input_file_ident = pctl->context_stack->id;

	yy_delete_buffer(yyget_current_buffer(scanner), scanner);
	yy_switch_to_buffer(pctl->context_stack->state, scanner);

	ctx = pctl->context_stack->prev;
	grecs_free(pctl->context_stack);
	pctl->context_stack = ctx;

	while (pctl->include_pos < pctl->include_glob.gl_pathc) {
		if (push_source(scanner,
			      pctl->include_glob.gl_pathv[pctl->include_pos++],
			      pctl->include_once) == 0)
			return 0;
	}

	if (pctl->include_glob.gl_pathc) {
		globfree(&pctl->include_glob);
		pctl->include_pos = pctl->include_glob.gl_pathc = 0;
	}

	if (yyget_debug(scanner))
		fprintf(stderr, "Resuming file `%s' at line %lu\n",
			pctl->point.file,
			(unsigned long) pctl->point.line);

	if (pctl->preproc_mode) {
		input(scanner);
		grecs_locus_point_advance_line(pctl->point);
		pctl->emit_syncline = 1;
	}

	return 0;
}

/*
 * Internal lexer functions.
 */
int
yywrap(yyscan_t scanner)
{
	return pop_source(scanner);
}

int
grecs_lex_begin(yyscan_t scanner, const char *name, int trace)
{
	struct parser_control *pctl = yyget_extra(scanner);
	yyset_debug(trace, scanner);
	pctl->acc = grecs_line_acc_create();
	return push_source(scanner, name, 1);
}

void
grecs_lex_end(struct parser_control *pctl)
{
	grecs_symtab_clear(pctl->incl_sources);
	free(pctl->multiline_delimiter);
	grecs_line_acc_free(pctl->acc);
}

/*
 * Here-document functions.
 */
static int
isemptystr(char const *text, int off)
{
	for (; text[off] && isspace(text[off]); off++)
		;
	return text[off] == ';' || text[off] == 0;
}

char *
multiline_strip_tabs(char *text)
{
	if (char_to_strip)
		for (; *text && char_to_strip(*text); text++)
			;
	return text;
}

static void
multiline_add(yyscan_t scanner, char *s)
{
	struct parser_control *pctl = yyget_extra(scanner);

	if (pctl->multiline_unescape) {
		for (; *s; s++)	{
			if (*s == '\\') {
				grecs_line_acc_grow_char_unescape(pctl->acc,
								  s[1]);
				++s;
			} else
				grecs_line_acc_grow_char(pctl->acc, *s);
		}
	} else
		grecs_line_add(pctl->acc, s, strlen(s));
}

static int
is_tab(char c)
{
	return c == '\t';
}

static int
is_ws(char c)
{
	return c == '\t' || c == ' ';
}

void
multiline_begin(yyscan_t scanner, char *p)
{
	struct parser_control *pctl = yyget_extra(scanner);

	if (*p == '-') {
		if (*++p == ' ') {
			char_to_strip = is_ws;
			p++;
		} else
			char_to_strip = is_tab;
	} else
		char_to_strip = NULL;
	if (*p == '\\') {
		p++;
		pctl->multiline_unescape = 0;
		pctl->multiline_delimiter_len = strcspn(p, " \t");
	} else if (*p == '"') {
		char *q;

		p++;
		pctl->multiline_unescape = 0;
		q = strchr(p, '"');
		pctl->multiline_delimiter_len = q - p;
	} else {
		pctl->multiline_delimiter_len = strcspn(p, " \t");
		pctl->multiline_unescape = 1;
	}

	/* Remove trailing newline */
	pctl->multiline_delimiter_len--;
	pctl->multiline_delimiter =
		grecs_malloc(pctl->multiline_delimiter_len + 1);
	memcpy(pctl->multiline_delimiter, p, pctl->multiline_delimiter_len);
	pctl->multiline_delimiter[pctl->multiline_delimiter_len] = 0;
	grecs_line_begin(&pctl->acc);

	/* Update locus */
	grecs_locus_point_advance_line(pctl->point);
	if (grecs_parser_options & GRECS_OPTION_ADJUST_STRING_LOCATIONS) {
		YYLTYPE *lloc = yyget_lloc(scanner);
		lloc->beg = pctl->point;
		lloc->beg.col++;
	}
}

/*
 * Auxiliary lexer functions.
 */
static int
string_or_ident(char const *p)
{
	if (isalpha(*p) || *p == '_') {
		while (*++p) {
			if (!(isalnum(*p) || *p == '_' || *p == '-'))
				return STRING;
		}
		return IDENT;
	}
	return STRING;
}

static void
qstring_locus_fixup(yyscan_t scanner)
{
	YYLTYPE *lloc = yyget_lloc(scanner);
	if (grecs_parser_options & GRECS_OPTION_ADJUST_STRING_LOCATIONS) {
		lloc->beg.col++;
		lloc->end.col--;
	}
}

grecs_value_t *
grecs_value_ptr_from_static(grecs_value_t *input)
{
	grecs_value_t *ptr = grecs_malloc(sizeof(*ptr));
	*ptr = *input;
	return ptr;
}

/*
 * Syncline directive parsers.
 */

static int
assign_locus(struct grecs_locus_point *ploc, char *name, char *line)
{
	char *p;

	if (name) {
		ploc->file = grecs_install_text(name);
	}
	ploc->line = strtoul(line, &p, 10);
	ploc->col = 0;
	return *p != 0;
}

void
grecs_parse_line_directive(char *text, grecs_locus_t *ploc,
			   struct grecs_locus_point *ppoint)
{
	int rc = 1;
	struct wordsplit ws;

	if (wordsplit(text, &ws, WRDSF_DEFFLAGS))
		grecs_error(ploc, 0, _("cannot parse #line line: %s"),
			    wordsplit_strerror(&ws));
	else if (ws.ws_wordc == 2)
		rc = assign_locus(ppoint, NULL, ws.ws_wordv[1]);
	else if (ws.ws_wordc >= 3)
		rc = assign_locus(ppoint, ws.ws_wordv[2], ws.ws_wordv[1]);
	else
		grecs_error(ploc, 0, _("invalid #line statement"));

	if (rc)
		grecs_error(ploc, 0, _("malformed #line statement"));
	wordsplit_free(&ws);
}

void
grecs_parse_line_directive_cpp(char *text, grecs_locus_t *ploc,
			       struct grecs_locus_point *ppoint)
{
	struct wordsplit ws;

	if (wordsplit(text, &ws, WRDSF_DEFFLAGS))
		grecs_error(ploc, 0, _("cannot parse #line line: %s"),
			    wordsplit_strerror(&ws));
	else if (ws.ws_wordc < 3)
		grecs_error(ploc, 0, _("invalid #line statement"));
	else if (assign_locus(ppoint, ws.ws_wordv[2], ws.ws_wordv[1]))
		grecs_error(ploc, 0, _("malformed #line statement"));
	wordsplit_free(&ws);
}

/*
 * External preprocessor support.
 */
const char *grecs_preprocessor = NULL; /* Preprocessor command */
int grecs_log_to_stderr = 1;           /* Log to stderr or to syslog? */
void (*grecs_log_setup_hook) () = NULL;/* Log subsystem setup routine. */

static int
preproc_file(const char *file)
{
	size_t size = 0;
	char *cmd = NULL;
	char *setup_file;
	char *argv[4];
	int rc;

	setup_file = grecs_find_include_file("pp-setup", 0);
	if (setup_file)
		rc = grecs_asprintf(&cmd, &size,
				    "%s \"%s\" \"%s\"",
				    grecs_preprocessor, setup_file, file);
	else
		rc = grecs_asprintf(&cmd, &size,
				    "%s \"%s\"", grecs_preprocessor, file);
	if (rc)
		grecs_alloc_die();
	if ((argv[0] = getenv("SHELL")) == NULL)
		argv[0] = "/bin/sh";
	argv[1] = "-c";
	argv[2] = cmd;
	argv[3] = NULL;
	execvp(argv[0], argv);
	return -1;
}

FILE *
grecs_preproc_extrn_start(const char *file_name, pid_t *ppid)
{
	int pout[2];
	pid_t pid;
	int i;
	FILE *fp = NULL;
	int rc;

	if (pipe(pout)) {
		grecs_error(NULL, errno, "pipe");
		return NULL;
	}
	switch (pid = fork()) {
		/* The child branch.  */
	case 0:
		if (pout[1] != 1) {
			if (dup2(pout[1], 1) == -1) {
				grecs_error(NULL, errno, "dup2");
				exit(127);
			}
		}

		if (!grecs_log_to_stderr) {
			int p[2];
			char *buf = NULL;
			size_t size = 0;
			FILE *fp;

			signal(SIGCHLD, SIG_DFL);
			if (pipe(p)) {
				grecs_error(NULL, errno, "pipe");
				exit(127);
			}
			switch (pid = fork()) {
				/* Grandchild */
			case 0:
				if (p[1] != 2 && dup2(p[1], 2) == -1) {
					grecs_error(NULL, errno, "dup2");
					exit(127);
				}
				close(p[0]);

				rc = preproc_file(file_name);
				break;

			case -1:
				/*  Fork failed */
				if (grecs_log_setup_hook)
					grecs_log_setup_hook();
				grecs_error(NULL, errno, _("Cannot run `%s'"),
					    grecs_preprocessor);
				exit(127);

			default:
				/* Sub-master */
				close (p[1]);
				fp = fdopen(p[0], "r");
				if (grecs_log_setup_hook)
					grecs_log_setup_hook();
				while (grecs_getline(&buf, &size, fp) > 0) {
					size_t n = strlen(buf);
					if (buf[n-1] == '\n')
						buf[n-1] = 0;
					grecs_error(NULL, 0, "%s", buf);
				}
				wait(&i);
				if (WIFEXITED(i))
					rc = WEXITSTATUS(i);
				else
					rc = 127;
			}
		} else {
			rc = preproc_file(file_name);
		}
		if (rc == -1)
			exit(127);
		exit(rc);

	case -1:
		/*  Fork failed */
		grecs_error(NULL, errno, _("Cannot run `%s'"),
			    grecs_preprocessor);
		break;

	default:
		close(pout[1]);
		fp = fdopen(pout[0], "r");
		break;
	}
	*ppid = pid;
	return fp;
}

int
grecs_preproc_extrn_shutdown(pid_t pid)
{
	int status;

	if (waitpid(pid, &status, 0) != pid) {
		grecs_error(NULL, errno, "waitpid(%d)", pid);
	} else if (WIFEXITED(status)) {
		if (WEXITSTATUS(status)) {
			grecs_error(NULL, 0, _("%s exited with status %d"),
				    grecs_preprocessor, WEXITSTATUS(status));
			return -1;
		}
	} else if (WIFSIGNALED(status)) {
		grecs_error(NULL, 0, _("%s terminated on signal %d"),
			    grecs_preprocessor, WTERMSIG(status));
		return -1;
	} else {
		grecs_error(NULL, 0, _("%s terminated abnormally (%d)"),
			    grecs_preprocessor, status);
		return -1;
	}
	return 0;
}

int
grecs_preprocess(char const *name, int trace)
{
	void *scanner;
	struct parser_control ctl = { NULL };
	int t;
	YYSTYPE lval;
	YYLTYPE lloc;

	memset(&lval, 0, sizeof (lval));
	memset(&lloc, 0, sizeof (lloc));

	grecs_grecs_lex_init_extra(&ctl, &scanner);
	yyget_extra(scanner)->preproc_mode = 1;
	if (grecs_lex_begin(scanner, name, trace)) {
		grecs_grecs_lex_destroy(scanner);
		return -1;
	}

	while ((t = yylex(&lval, &lloc, scanner)) != 0) {
		if (trace) {
			char *p;

			YY_LOCATION_PRINT(stderr, lloc);
			fprintf(stderr, ": %d: ", t);
			switch (t) {
			case STRING:
			case QSTRING:
			case MSTRING:
				fputc('"', stderr);
				for (p = lval.string; *p; p++) {
					if (*p == '\\' || *p == '"')
						fputc('\\', stderr);
					fputc(*p, stderr);
				}
				fputc('"', stderr);
				break;

			case IDENT:
				fprintf(stderr, "%s", lval.string);
				break;

			default:
				fputc(t, stderr);
			}
			fputc('\n', stderr);
		}
	}
	grecs_grecs_lex_destroy(scanner);
	grecs_lex_end(&ctl);
	return 0;
}

/*
 * Additional function.
 *
 * Not used by the lexer, but retained for backward compatibility.
 */
ssize_t
grecs_getline(char **pbuf, size_t *psize, FILE *fp)
{
	char *buf = *pbuf;
	size_t size = *psize;
	ssize_t off = 0;

	if (!buf) {
		size = 1;
		buf = grecs_malloc(size);
	}

	do {
		if (off == size - 1) {
			size_t nsize = 2 * size;
			if (nsize < size)
				grecs_alloc_die();
			buf = grecs_realloc(buf, nsize);
			size = nsize;
		}
		if (!fgets(buf + off, size - off, fp)) {
			if (off == 0)
				off = -1;
			break;
		}
		off += strlen(buf + off);
	} while (buf[off - 1] != '\n');

	*pbuf = buf;
	*psize = size;
	return off;
}
