%{
/* 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/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <grecs.h>
#include <grecs-gram.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
%}

%expect 1
%define api.prefix {grecs_grecs_}
%define api.pure full
%code requires {
#include <signal.h>
#include <grecs/symtab.h>
#include <glob.h>

typedef void *yyscan_t;

struct input_file_ident {
	ino_t i_node;
	dev_t device;
	pid_t pid;
};

struct buffer_ctx;

struct parser_control {
	struct grecs_node *parse_tree;
	struct grecs_locus_point point;
	int error_suppress;
	/* Line accumulator. */
	struct grecs_txtacc *acc;

	int preproc_mode;
	int emit_syncline; /* If not null, emit syncline before next line
			      of output. */
	int emit_comment_warning;
	int beginning_of_line; /* True if output is at the beginning of line */
	/* Here-document support.*/
	char *multiline_delimiter;       /* Here-document delimiter text. */
	size_t multiline_delimiter_len;  /* Its length. */
	int multiline_unescape;          /* Unescape here-document contents */
	/* Source inclusion support. */
	glob_t include_glob;
	size_t include_pos;
	int include_once;
	struct grecs_symtab *incl_sources;
	/* Input context */
	struct buffer_ctx *context_stack;
	struct input_file_ident input_file_ident;
	/* Sigchld handling */
	int sigchld_reset;
	struct sigaction oldsigchld;
};
#define GRECS_GRECS_LTYPE grecs_locus_t
}
%code provides {
int grecs_grecs_lex(GRECS_GRECS_STYPE *lvalp, GRECS_GRECS_LTYPE *llocp,
		    yyscan_t yyscanner);
int grecs_grecs_lex_init_extra(struct parser_control *, yyscan_t *);
int grecs_grecs_lex_destroy (yyscan_t);
void grecs_grecs_error (GRECS_GRECS_LTYPE const *llocp,
			  struct parser_control *pctl, yyscan_t scanner,
			  char const *msg);

struct grecs_node *grecs_grecs_parser(const char *name, int traceflags);
}
%parse-param { struct parser_control *pctl } { void *yyscanner }
%lex-param { yyscan_t yyscanner }
%define parse.error verbose
%locations

%union {
	char *string;
	grecs_value_t svalue, *pvalue;
	struct grecs_list *list;
	struct grecs_node *node;
	grecs_locus_t locus;
	struct { struct grecs_node *head, *tail; } node_list;
}

%token <string> STRING QSTRING MSTRING IDENT
%type <string> string slist
%type <list> slist0
%type <svalue> value
%type <pvalue> vallist tag
%type <list> values list vlist
%type <node> stmt simple block maybe_stmtlist
%type <node_list> stmtlist

%%

input   : maybe_stmtlist
	  {
		  pctl->parse_tree = grecs_node_create(grecs_node_root, &@1);
		  pctl->parse_tree->v.texttab = grecs_text_table();
		  grecs_node_bind(pctl->parse_tree, $1, 1);
	  }
	;

maybe_stmtlist:
	  /* empty */
	  {
		  $$ = NULL;
	  }
	| stmtlist
	  {
		  $$ = $1.head;
	  }
	;

stmtlist: stmt
	  {
		  $$.head = $$.tail = $1;
	  }
	| stmtlist stmt
	  {
		  grecs_node_bind($1.tail, $2, 0);
	  }
	;

stmt    : simple
	| block
	;

simple  : IDENT vallist ';'
	  {
		  $$ = grecs_node_create_points(grecs_node_stmt,
						@1.beg, @2.end);
		  $$->ident = $1;
		  $$->idloc = @1;
		  $$->v.value = $2;
	  }
	| IDENT ';'
	  {
		  $$ = grecs_node_create(grecs_node_stmt, &@1);
		  $$->ident = $1;
		  $$->idloc = @1;
		  $$->v.value = NULL;
	  }
	;

block   : IDENT tag '{' stmtlist '}' opt_sc
	  {
		  $$ = grecs_node_create_points(grecs_node_block,
						@1.beg, @5.end);
		  $$->ident = $1;
		  $$->idloc = @1;
		  $$->v.value = $2;
		  grecs_node_bind($$, $4.head, 1);
	  }
	;

tag     : /* empty */
	  {
		  $$ = NULL;
	  }
	| vallist
	;

vallist : vlist
	  {
		  size_t n;

		  if ((n = grecs_list_size($1)) == 1) {
			  $$ = grecs_list_index($1, 0);
		  } else {
			  size_t i;
			  struct grecs_list_entry *ep;

			  $$ = grecs_malloc(sizeof($$[0]));
			  $$->type = GRECS_TYPE_ARRAY;
			  $$->locus = @1;
			  $$->v.arg.c = n;
			  $$->v.arg.v = grecs_calloc(n,
						     sizeof($$->v.arg.v[0]));
			  for (i = 0, ep = $1->head; ep; i++, ep = ep->next)
				  $$->v.arg.v[i] = ep->data;
		  }
		  $1->free_entry = NULL;
		  grecs_list_free($1);
	  }
	;

vlist   : value
	  {
		  $$ = grecs_value_list_create();
		  grecs_list_append($$, grecs_value_ptr_from_static(&$1));
	  }
	| vlist value
	  {
		  grecs_list_append($1, grecs_value_ptr_from_static(&$2));
	  }
	;

value   : string
	  {
		  $$.type = GRECS_TYPE_STRING;
		  $$.locus = @1;
		  $$.v.string = $1;
	  }
	| list
	  {
		  $$.type = GRECS_TYPE_LIST;
		  $$.locus = @1;
		  $$.v.list = $1;
	  }
	| MSTRING
	  {
		  $$.type = GRECS_TYPE_STRING;
		  $$.locus = @1;
		  $$.v.string = $1;
	  }
	;

string  : STRING
	| IDENT
	| slist
	;

slist   : slist0
	  {
		  struct grecs_list_entry *ep;

		  grecs_line_begin(&pctl->acc);
		  for (ep = $1->head; ep; ep = ep->next) {
			  grecs_line_add(pctl->acc, ep->data, strlen(ep->data));
			  free(ep->data);
			  ep->data = NULL;
		  }
		  $$ = grecs_line_finish(pctl->acc);
		  grecs_list_free($1);
	  }
	;

slist0  : QSTRING
	  {
		  $$ = grecs_list_create();
		  grecs_list_append($$, $1);
	  }
	| slist0 QSTRING
	  {
		  grecs_list_append($1, $2);
		  $$ = $1;
	  }
	;

list    : '(' ')'
	  {
		  $$ = NULL;
	  }
	| '(' values ')'
	  {
		  $$ = $2;
	  }
	| '(' values ',' ')'
	  {
		  $$ = $2;
	  }
	;

values  : value
	  {
		  $$ = grecs_value_list_create();
		  grecs_list_append($$, grecs_value_ptr_from_static(&$1));
	  }
	| values ',' value
	  {
		  grecs_list_append($1, grecs_value_ptr_from_static(&$3));
		  $$ = $1;
	  }
	;

opt_sc  : /* empty */
	| ';'
	;

%%

void
grecs_grecs_error (GRECS_GRECS_LTYPE const *llocp,
		   struct parser_control *pctl, yyscan_t scanner,
		   char const *msg)
{
	if (pctl->error_suppress)
		return;
	grecs_error(llocp, 0, "%s", msg);
}

struct grecs_node *
grecs_grecs_parser(const char *name, int traceflags)
{
	int rc;
	void *scanner;
	struct parser_control ctl = {
		.parse_tree = NULL,
	};

	grecs_grecs_lex_init_extra (&ctl, &scanner);
	if (grecs_lex_begin(scanner, name, traceflags & GRECS_TRACE_LEX)) {
		grecs_grecs_lex_destroy(scanner);
		return NULL;
	}
	yydebug = traceflags & GRECS_TRACE_GRAM;
	rc = yyparse(&ctl, scanner);
	if (grecs_error_count)
		rc = 1;
	grecs_grecs_lex_destroy(scanner);
	grecs_lex_end(&ctl);
	if (rc) {
		grecs_tree_free(ctl.parse_tree);
		ctl.parse_tree = NULL;
	}
	return ctl.parse_tree;
}
