/* grecs - Gray's Extensible Configuration System       -*- c -*- */
%option nounput
%top {
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif	
}
%{
/* grecs - Gray's Extensible Configuration System
   Copyright (C) 2007-2022 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>

/*
 * Locus tracking
 */
struct grecs_locus_point grecs_current_locus_point;   /* Input file location */

#define YY_USER_ACTION do {						\
		if (YYSTATE == INITIAL) {		                \
			yylloc.beg = grecs_current_locus_point;		\
			yylloc.beg.col++;				\
		}							\
  		grecs_current_locus_point.col += yyleng;		\
 		yylloc.end = grecs_current_locus_point;			\
   	} while (0);

/*
 * Redefine default error reporting function.
 */
#define YY_FATAL_ERROR(t) grecs_error(NULL, errno, "%s", t)

/*
 * Preprocessing and synclines.
 */
static int preproc_mode;
static int emit_syncline; /* If not null, emit syncline before next output
			   * line.
			   */
static int beginning_of_line; /* True if output is at the beginning of line */

/*
 * Echo LEN bytes from TEXT to yyout.
 */
static void
echo(char const *text, size_t len)
{
	if (len == 0) return;
	/* Check if we should print a syncline */
	if (beginning_of_line && emit_syncline) {
		fprintf(yyout, "#line %lu \"%s\"\n",
			(unsigned long)yylloc.end.line,
			yylloc.end.file);
		emit_syncline = 0;
	}
	/* Actually echo the text. */
	fwrite(text, len, 1, yyout);
	/* Determine the beginning of line status. */
	beginning_of_line = text[len-1] == '\n';
}
/* Inform flex that it should use echo. */
#define ECHO echo(yytext, yyleng)
#define PPECHO do { if (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.
 */
extern int grecs_yyerror_suppress;

/*
 * Here-document support.
 */
static char *multiline_delimiter;       /* Here-document delimiter text. */
static size_t multiline_delimiter_len;  /* Its length. */
static int multiline_unescape;          /* Unescape here-document contents */
static int (*char_to_strip)(char);      /* Strips matching characters of each
					   here-document line */

static void multiline_begin(char *);
static void multiline_add(char *);
static char *multiline_strip_tabs(char *text);
static int isemptystr(int off);

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

/*
 * 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 int emit_comment_warning;
static void
comment_warning(void)
{
        if (emit_comment_warning) {
	        grecs_warning(&yylloc, 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"), yytext);
	        emit_comment_warning = 0;
        }
}

/*
 * The report_diag should be called only when yytext begins with the following
 * prefix:
 *       ^[ \t]*#[ \t]*.{LEN}
 * It returns a pointer to yytext past the prefix.
 */
static char *
report_diag(size_t len)
{
	char *p = strchr(yytext, '#');
	p += strspn(p, " \t") + len;
	p += strspn(p, " \t");
	yytext[yyleng-1] = 0;
	if (p[0] == '"' && yytext[yyleng-2] == '"') {
		char *q, *r;
		
		yytext[yyleng-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);
	    beginning_of_line = 0;
	    comment_warning();
 }
<COMMENT>{
[^*\n]*        /* eat anything that's not a '*' */
"*"+[^*/\n]*   /* eat up '*'s not followed by '/'s */
\n          { grecs_locus_point_advance_line(grecs_current_locus_point);
	      emit_syncline = 1; }
"*"+"/"     {
	      BEGIN(INITIAL);
	      if (preproc_mode) {
		      if (yylloc.end.line != yylloc.beg.line) {
			      if (!(yylloc.beg.line == 1 && yylloc.beg.col == 1))
				      echo("\n", 1);
			      beginning_of_line = 1;
		      } else
			      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, &grecs_current_locus_point);
 }
^[ \t]*#[ \t]*line[ \t].*\n {
	 PPECHO;
	 grecs_parse_line_directive(yytext, &yylloc, &grecs_current_locus_point);
 }

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

         /* Non-quoted strings and identifiers */
{NQSC}+ {
	 PPECHO;
         grecs_line_begin();
	 grecs_line_add(yytext, yyleng);
	 BEGIN(NQSTR);
 }
[/*]{NQSC}* {
	 PPECHO;
         grecs_line_begin();
	 grecs_line_add(yytext, yyleng);
	 BEGIN(NQSTR);
 }
<NQSTR>{
{NQSC}*/"/"[/*] {
	 PPECHO;
         emit_comment_warning = 1;
         grecs_line_add(yytext, yyleng);
         yylval.string = grecs_line_finish();
	 BEGIN(INITIAL);
         return STRING;
 }
{NQSC}*[/*] {
         PPECHO;
         grecs_line_add(yytext, yyleng);
 }
{NQSC}+ { 
         PPECHO;
         grecs_line_add(yytext, yyleng);
         yylval.string = grecs_line_finish();
	 BEGIN(INITIAL);
         return string_or_ident();
 }
\n {
         yyless(0);
         yylloc.end.col--;
         grecs_current_locus_point.col--;
         yylval.string = grecs_line_finish();
	 BEGIN(INITIAL);
         return string_or_ident();
 }	 
. {
         yyless(0);
         yylloc.end.col--;
         grecs_current_locus_point.col--;
         yylval.string = grecs_line_finish();
	 BEGIN(INITIAL);
         return string_or_ident();
 }	 
}
         /* Quoted strings */
\"[^\\"\n]*\" {
         PPECHO;
         grecs_line_begin();
         grecs_line_add(yytext + 1, yyleng - 2);
         yylval.string = grecs_line_finish();
         qstring_locus_fixup();
         return qstring();
 }
\"[^\\"\n]*\\\n {
         PPECHO;
	 BEGIN(STR);
         grecs_line_begin();
 	 grecs_line_acc_grow_unescape_last(yytext + 1, yyleng - 1, &yylloc);
         grecs_locus_point_advance_line(grecs_current_locus_point);
 }
\"[^\\"\n]*\\. {
         PPECHO;
         BEGIN(STR);
         grecs_line_begin();
	 grecs_line_acc_grow_unescape_last(yytext + 1, yyleng - 1, &yylloc);
 }
<STR>{
[^\\"\n]*\\\n  {
         PPECHO;
	 grecs_line_acc_grow_unescape_last(yytext, yyleng, &yylloc);
         grecs_locus_point_advance_line(grecs_current_locus_point);
 }
[^\\"\n]*\\. {
         PPECHO;
         grecs_line_acc_grow_unescape_last(yytext, yyleng, &yylloc);
 }
[^\\"\n]*\"      {
         PPECHO;
	 BEGIN(INITIAL);
         if (yyleng > 1) 
	         grecs_line_add(yytext, yyleng - 1); 
         yylval.string = grecs_line_finish();
         qstring_locus_fixup();    
	 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(yytext+2);
 }
<ML>{
        /* Ignore m4 line statements */
^"#line ".*\n {
	grecs_locus_point_advance_line(grecs_current_locus_point);
}
.*\n {   char *p = multiline_strip_tabs(yytext);
         int len = p + multiline_delimiter_len - yytext;
         if (!strncmp(p, multiline_delimiter, multiline_delimiter_len)
             && isemptystr(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.
		  */
                 grecs_current_locus_point.col -= yyleng - len;		 
  		 yyless(len);

		 /* Echo the consumed input if preprocessing. */
		 PPECHO;
		 
	         grecs_free(multiline_delimiter);
	         multiline_delimiter = NULL;
	         BEGIN(INITIAL);
	         yylval.string = grecs_line_finish();

	         return MSTRING;
         } else
		 PPECHO;		  
         grecs_locus_point_advance_line(grecs_current_locus_point);
         multiline_add(p);
  }
}

{WS}     PPECHO;
         /* Other tokens */
\n {
         PPECHO;
	 grecs_locus_point_advance_line(grecs_current_locus_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]); }

%%
/*
 * Source inclusion support.
 */
static glob_t include_glob;
static size_t include_pos;
static int include_once;

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

static int
parse_include(char *text)
{
	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(&yylloc, 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(&yylloc, 0, _("invalid include statement"));
			return 1;
		}
		
		if (isglob(p)) {
			switch (glob(p, 0, NULL, &include_glob)) {
			case 0:
				include_pos = 0;
				include_once = once;
				break;
			case GLOB_NOSPACE:
				grecs_alloc_die();
			case GLOB_NOMATCH:
				break;
			default:
				grecs_error(&yylloc, 0,  _("read error"));
			}
			p = NULL;
		} else if (p[0] != '/') {
			char *q = p;
			p = grecs_find_include_file(q, allow_cwd);
			if (!p)
				grecs_error(&yylloc, 0,
					    _("%s: No such file or directory"),
					    q);
		}
 	}
  
	if (p)
		rc = push_source(p, once);
	else if (include_pos < include_glob.gl_pathc)
		rc = push_source(include_glob.gl_pathv[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 input_file_ident {
	ino_t i_node;
	dev_t device;
	pid_t pid;
};

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 struct buffer_ctx *context_stack;
static struct input_file_ident input_file_ident;
static int sigchld_reset;
static struct sigaction oldsigchld;

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

static void
sigchld_restore(void)
{
	if (sigchld_reset) {
		sigaction(SIGCHLD, &oldsigchld, NULL);
		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(struct stat *st)
{
	struct buffer_ctx *ctx;

	if (!context_stack)
		return NULL;

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

static void
ctx_abort(void)
{
	/* Delete current input buffer */
	yy_delete_buffer(YY_CURRENT_BUFFER);
			
	/* Clean-up context stack */
	while (context_stack) {
		struct buffer_ctx *prev = context_stack->prev;
		if (context_stack->id.pid > 0)
			kill(context_stack->id.pid, SIGKILL);
		fclose(context_stack->infile);
		grecs_free(context_stack);
		context_stack = prev;
	}
}

static struct grecs_symtab *incl_sources;

/* 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(struct stat *st)
{
	struct input_file_ident key;
	int install = 1;
  
	if (!incl_sources) {
		incl_sources = grecs_symtab_create(
			sizeof(struct input_file_ident), 
			incl_hasher,
			incl_compare,
			incl_copy,
			NULL,/*FIXME: alloc*/
			NULL);
		if (!incl_sources)
			grecs_alloc_die();
	}
	
	key.i_node = st->st_ino;
	key.device = st->st_dev;
	if (!grecs_symtab_lookup_or_install(incl_sources, &key, &install))
		grecs_alloc_die();
	return !install;
}

static int
push_source(const char *name, int once)
{
	FILE *fp;
	pid_t pid;
	struct buffer_ctx *ctx;
	struct stat st;
	int rc = stat(name, &st);

	if (context_stack) {
		if (rc) {
			grecs_error(&yylloc, errno,
				    _("Cannot stat `%s'"), name);
			return 1;
		}

		if (grecs_current_locus_point.col &&
		    STAT_ID_EQ(st, input_file_ident)) {
			grecs_error(&yylloc, 0, _("Recursive inclusion"));
			return 1;
		}
		
		if ((ctx = ctx_lookup(&st))) {
			grecs_error(&yylloc, 0, _("Recursive inclusion"));
			if (ctx->prev)
				grecs_error(&ctx->prev->locus, 0,
					    _("`%s' already included here"),
					    name);
			else
				grecs_error(&yylloc, 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(&st))
		return -1;

	if (grecs_preprocessor) {
		if (access(name,R_OK)) {
			grecs_error(context_stack ? &yylloc : NULL, errno,
				    _("Cannot open `%s'"), name);
			return -1;
		}
		if (!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();
		}
		fp = grecs_preproc_extrn_start(name, &pid);
	} else {
		fp = fopen(name, "r");
		pid = -1;
	}
	if (!fp) {
		grecs_error(context_stack ? &yylloc : NULL, errno,
			    _("Cannot open `%s'"), name);
		if (!context_stack)
			sigchld_restore();
		return 1;
	}

	/* Push current context */
	if (yyin) {
		ctx = grecs_malloc(sizeof(*ctx));
		ctx->locus = yylloc;
		ctx->point = grecs_current_locus_point;
		ctx->id = input_file_ident;
		ctx->infile = yyin;
		ctx->state = YY_CURRENT_BUFFER;
		ctx->prev = context_stack;
		context_stack = ctx;
	}
	
	yyin = fp;
	yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));

	grecs_current_locus_point.file = grecs_install_text(name);
	grecs_current_locus_point.line = 1;
	grecs_current_locus_point.col = 0;
	
	input_file_ident.i_node = st.st_ino;
	input_file_ident.device = st.st_dev;
	input_file_ident.pid = pid;

	beginning_of_line = 1;
	
	if (yy_flex_debug)
	  fprintf (stderr, "Processing file `%s'\n", name);

	if (preproc_mode)
		emit_syncline = 1;
	
	return 0;
}

static int
pop_source(void)
{
	struct buffer_ctx *ctx;

	fclose(yyin);
	if (input_file_ident.pid > 0) {
		if (grecs_preproc_extrn_shutdown(input_file_ident.pid)) {
			/* Abort further input */
			ctx_abort();

			/* Suppress further errors */
			grecs_yyerror_suppress = 1;

			/* Indicate there is no more input */
			return 1;
		}
	}
	
	if (!context_stack) {
		if (yy_flex_debug)
			fprintf(stderr, "End of input\n");
		/* Reset SIGCHLD to its original configuration */
		sigchld_restore();
		yyin = NULL;
		return 1;
	}
	
	/* Restore previous context */
	yyin = context_stack->infile;
	grecs_current_locus_point = context_stack->point;
	input_file_ident = context_stack->id;
	
	yy_delete_buffer(YY_CURRENT_BUFFER);
	yy_switch_to_buffer(context_stack->state);
	
	ctx = context_stack->prev;
	grecs_free(context_stack);
	context_stack = ctx;

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

	if (include_glob.gl_pathc) {
		globfree(&include_glob);
		include_pos = include_glob.gl_pathc = 0;
	}
	
	if (yy_flex_debug)
		fprintf(stderr, "Resuming file `%s' at line %lu\n",
			grecs_current_locus_point.file,
			(unsigned long) grecs_current_locus_point.line);

	if (preproc_mode) {
		input();
		grecs_locus_point_advance_line(grecs_current_locus_point);
		emit_syncline = 1;
	}
	
	return 0;
}

/*
 * Internal lexer functions.
 */
int
yywrap()
{
	return pop_source();
}

int
grecs_lex_begin(const char *name, int trace)
{
	yy_flex_debug = trace;
	grecs_line_acc_create();
	return push_source(name, 1);
}

void
grecs_lex_end(int err)
{
	grecs_symtab_clear(incl_sources);
	grecs_line_acc_free();
}

/*
 * Here-document functions.
 */
static int
isemptystr(int off)
{
	for (; yytext[off] && isspace(yytext[off]); off++)
		;
	return yytext[off] == ';' || yytext[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(char *s)
{
	if (multiline_unescape) {
		for (; *s; s++)	{
			if (*s == '\\') {
				grecs_line_acc_grow_char_unescape(s[1]);
				++s;
			} else
				grecs_line_acc_grow_char(*s);
		}
	} else
		grecs_line_add(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(char *p)
{
	if (*p == '-') {
		if (*++p == ' ') {
			char_to_strip = is_ws;
			p++;
		} else
			char_to_strip = is_tab;
	} else
		char_to_strip = NULL;
	if (*p == '\\') {
		p++;
		multiline_unescape = 0;
		multiline_delimiter_len = strcspn(p, " \t");
	} else if (*p == '"') {
		char *q;
 
		p++;
		multiline_unescape = 0;
		q = strchr(p, '"');
		multiline_delimiter_len = q - p;
	} else {
		multiline_delimiter_len = strcspn(p, " \t");
		multiline_unescape = 1;
	}

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

	/* Update locus */
	grecs_locus_point_advance_line(grecs_current_locus_point);
	if (grecs_parser_options & GRECS_OPTION_ADJUST_STRING_LOCATIONS) {
		yylloc.beg = grecs_current_locus_point;
		yylloc.beg.col++;
	}
}

/*
 * Auxiliary lexer functions.
 */
static int
string_or_ident(void)
{
	char *p = yylval.string;
	if (isalpha(*p) || *p == '_') {
		while (*++p) {
			if (!(isalnum(*p) || *p == '_' || *p == '-'))
				return STRING;
		}
		return IDENT;
	}
	return STRING;
}

static void
qstring_locus_fixup(void)
{
	if (grecs_parser_options & GRECS_OPTION_ADJUST_STRING_LOCATIONS) {
		yylloc.beg.col++;
		yylloc.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;

	fflush(yyout);
	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)
{
	int rc;
	
	yy_flex_debug = trace;
	yyout = stdout;
	grecs_line_acc_create();
	preproc_mode = 1;
	rc = push_source(name, 1);
	if (rc == 0) {
		int t;
		while ((t = yylex()) != 0) {
			if (trace) {
				char *p;

				YY_LOCATION_PRINT(stderr, yylloc);
				fprintf(stderr, ": %d: ", t);
				switch (t) {
				case STRING:
				case QSTRING:
				case MSTRING:
					fputc('"', stderr);
					for (p = yylval.string; *p; p++) {
						if (*p == '\\' || *p == '"')
							fputc('\\', stderr);
						fputc(*p, stderr);
					}
					fputc('"', stderr);
					break;
					
				case IDENT:
					fprintf(stderr, "%s", yylval.string);
					break;
					
				default:
					fputc(t, stderr);
				}
				fputc('\n', stderr);
			}
		}
		grecs_lex_end(0);
	}
	return rc;
}

/*
 * 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;
}
					

	
