%{
/*
 * Copyright (C) 2013-2014 Tobias Brunner
 *
 * This program 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 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program 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.
 */

#define _GNU_SOURCE /* for asprintf() */
#include <stdio.h>

#include <utils/parser_helper.h>
#include <settings/settings_types.h>
#include <parser/conf_parser.h>

#include "parser.h"

#define YYDEBUG 1

/**
 * Defined by the lexer
 */
int conf_parser_lex(YYSTYPE *lvalp, void *scanner);
int conf_parser_lex_init_extra(parser_helper_t *extra, void *scanner);
int conf_parser_lex_destroy(void *scanner);
int conf_parser_set_in(FILE *in, void *scanner);
void conf_parser_set_debug(int debug, void *scanner);
char *conf_parser_get_text(void *scanner);
int conf_parser_get_leng(void *scanner);
int conf_parser_get_lineno(void *scanner);
/* Custom functions in lexer */
bool conf_parser_open_next_file(parser_helper_t *ctx);

/**
 * Forward declaration
 */
static void conf_parser_error(parser_helper_t *ctx, const char *s);

/**
 * Make sure to call lexer with the proper context
 */
#undef yylex
static int yylex(CONF_PARSER_STYPE *yylval, parser_helper_t *ctx)
{
	return conf_parser_lex(yylval, ctx->scanner);
}

%}
%debug

/* generate verbose error messages */
%define parse.error verbose
/* generate a reentrant parser */
%define api.pure
/* prefix function/variable declarations */
%define api.prefix {conf_parser_}
/* make sure flex uses the right definition */
%code provides
{
	#define YY_DECL \
		int conf_parser_lex(CONF_PARSER_STYPE *yylval, void *yyscanner)
	YY_DECL;
}

/* interact properly with the reentrant lexer */
%lex-param {parser_helper_t *ctx}
%parse-param {parser_helper_t *ctx}

/* types for terminal symbols... */
%union {
	char *s;
	conf_parser_section_t t;
}
%token <s> STRING
%token EQ SPACES NEWLINE CONFIG_SETUP CONN CA STRING_ERROR

/* ...and other symbols */
%type <t> section_type
%type <s> section_name value

/* make the equal sign left associative */
%left EQ

/* properly destroy STRING tokens, which are strdup()ed, on errors */
%destructor { free($$); } STRING section_name value

/* there are two shift/reduce conflicts because we allow empty lines (and lines
 * with spaces) within settings and anywhere else (i.e. in the beginning) */
//%expect 2

%%

/**
 * ipsec.conf grammar rules
 */
statements:
	/* empty */
	| statements NEWLINE
	| statements statement
	;

statement:
	section
	| SPACES setting
	;

section:
	section_type section_name
	{
		if ($1 != CONF_PARSER_CONFIG_SETUP && (!$2 || !strlen($2)))
		{
			PARSER_DBG1(ctx, "section name missing");
			free($2);
			YYERROR;
		}
		conf_parser_t *parser = (conf_parser_t*)ctx->context;
		parser->add_section(parser, $1, $2);
	}
	;

section_type:
	CONFIG_SETUP
	{
		$$ = CONF_PARSER_CONFIG_SETUP;
	}
	|
	CONN
	{
		$$ = CONF_PARSER_CONN;
	}
	|
	CA
	{
		$$ = CONF_PARSER_CA;
	}
	;

section_name:
	/* empty */
	{
		$$ = NULL;
	}
	| STRING
	{
		$$ = $1;
	}
	;

setting:
	/* empty */
	|
	STRING EQ value
	{
		if (!strlen($1))
		{
			PARSER_DBG1(ctx, "setting name can't be empty");
			free($1);
			free($3);
			YYERROR;
		}
		conf_parser_t *parser = (conf_parser_t*)ctx->context;
		parser->add_setting(parser, $1, $value);
	}
	|
	STRING EQ
	{
		if (!strlen($1))
		{
			PARSER_DBG1(ctx, "setting name can't be empty");
			free($1);
			YYERROR;
		}
		conf_parser_t *parser = (conf_parser_t*)ctx->context;
		parser->add_setting(parser, $1, NULL);
	}
	|
	STRING
	{
		PARSER_DBG1(ctx, "missing value for setting '%s'", $1);
		free($1);
		YYERROR;
	}
	;

value:
	STRING
	| value STRING
	{	/* just put a single space between them, use strings for more */
		if (asprintf(&$$, "%s %s", $1, $2) < 0)
		{
			free($1);
			free($2);
			YYERROR;
		}
		free($1);
		free($2);
		/* suppress compiler warning */
		(void)yynerrs;
	}
	;

%%

/**
 * Referenced by the generated parser
 */
static void conf_parser_error(parser_helper_t *ctx, const char *s)
{
	PARSER_DBG1(ctx, "%s", s);
}

/**
 * Parse the given file
 */
bool conf_parser_parse_file(conf_parser_t *this, char *name)
{
	parser_helper_t *helper;
	bool success = FALSE;

	helper = parser_helper_create(this);
	helper->get_lineno = conf_parser_get_lineno;
	if (conf_parser_lex_init_extra(helper, &helper->scanner) != 0)
	{
		helper->destroy(helper);
		return FALSE;
	}
	helper->file_include(helper, name);
	if (!conf_parser_open_next_file(helper))
	{
		DBG1(DBG_CFG, "failed to open config file '%s'", name);
	}
	else
	{
		if (getenv("DEBUG_CONF_PARSER"))
		{
			yydebug = 1;
			conf_parser_set_debug(1, helper->scanner);
		}
		success = yyparse(helper) == 0;
		if (!success)
		{
			DBG1(DBG_CFG, "invalid config file '%s'", name);
		}
	}
	conf_parser_lex_destroy(helper->scanner);
	helper->destroy(helper);
	return success;
}
