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

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

#include "parser.h"

bool conf_parser_open_next_file(parser_helper_t *ctx);

static void include_files(parser_helper_t *ctx);

%}
%option debug
%option warn

/* use start conditions stack */
%option stack

/* do not declare unneeded functions */
%option noinput noyywrap

/* do not include unistd.h as it might conflict with our scanner states */
%option nounistd
/* due to that disable interactive mode, which requires isatty() */
%option never-interactive

/* don't use global variables */
%option reentrant

/* maintain the line number */
%option yylineno

/* don't generate a default rule */
%option nodefault

/* prefix function/variable declarations */
%option prefix="conf_parser_"
/* don't change the name of the output file otherwise autotools has issues */
%option outfile="lex.yy.c"

/* type of our extra data */
%option extra-type="parser_helper_t*"

/* state used to scan include file patterns */
%x inc
/* state used to scan quoted strings */
%x str

%%

^[\t ]*"version"[^\n]*$	/* eat legacy version declaration */
^[\t ]+					return SPACES;
[\t ]+					/* eat other whitespace */
[\t ]*#[^\n]*			/* eat comments */

\n						return NEWLINE;

"="						return EQ;
^"config setup"			return CONFIG_SETUP;
^"conn"					return CONN;
^"ca"					return CA;

"include"[\t ]+/[^=]	{
	yyextra->string_init(yyextra);
	yy_push_state(inc, yyscanner);
}

"\""					{
	yyextra->string_init(yyextra);
	yy_push_state(str, yyscanner);
}

(@#)?[^\"#= \t\n]+			{
	yylval->s = strdup(yytext);
	return STRING;
}

<inc>{
	/* we allow all characters except # and spaces, they can be escaped */
	<<EOF>>				|
	[#\n\t ]			{
		if (*yytext)
		{
			switch (yytext[0])
			{
				case '\n':
					/* put the newline back to fix the line numbers */
					unput('\n');
					yy_set_bol(0);
					break;
				case '#':
					/* comments are parsed outside of this start condition */
					unput(yytext[0]);
					break;
			}
		}
		include_files(yyextra);
		yy_pop_state(yyscanner);
	}
	"\""				{	/* string include */
		yy_push_state(str, yyscanner);
	}
	\\					{
		yyextra->string_add(yyextra, yytext);
	}
	\\["#} ]			{
		yyextra->string_add(yyextra, yytext+1);
	}
	[^"\\#\n\t ]+ {
		yyextra->string_add(yyextra, yytext);
	}
}

<str>{
	"\""				|
	<<EOF>>				|
	\\					{
		if (!streq(yytext, "\""))
		{
			PARSER_DBG1(yyextra, "unterminated string detected");
			return STRING_ERROR;
		}
		if (yy_top_state(yyscanner) == inc)
		{	/* string include */
			include_files(yyextra);
			yy_pop_state(yyscanner);
			yy_pop_state(yyscanner);
		}
		else
		{
			yy_pop_state(yyscanner);
			yylval->s = yyextra->string_get(yyextra);
			return STRING;
		}
	}
	\\n     yyextra->string_add(yyextra, "\n");
	\\r     yyextra->string_add(yyextra, "\r");
	\\t     yyextra->string_add(yyextra, "\t");
	\\\r?\n /* merge lines that end with EOL characters */
	\\.     yyextra->string_add(yyextra, yytext+1);
	[^\\"]+			{
		yyextra->string_add(yyextra, yytext);
	}
}

<<EOF>>					{
	conf_parser_pop_buffer_state(yyscanner);
	if (!conf_parser_open_next_file(yyextra) && !YY_CURRENT_BUFFER)
	{
		yyterminate();
	}
}

%%

/**
 * Open the next file, if any is queued and readable, otherwise returns FALSE.
 */
bool conf_parser_open_next_file(parser_helper_t *ctx)
{
	FILE *file;

	file = ctx->file_next(ctx);
	if (!file)
	{
		return FALSE;
	}

	conf_parser_set_in(file, ctx->scanner);
	conf_parser_push_buffer_state(
			conf_parser__create_buffer(file, YY_BUF_SIZE,
									   ctx->scanner), ctx->scanner);
	return TRUE;
}

/**
 * Assumes that the file pattern to include is currently stored as string on
 * the helper object.
 */
static void include_files(parser_helper_t *ctx)
{
	char *pattern = ctx->string_get(ctx);

	ctx->file_include(ctx, pattern);
	free(pattern);

	conf_parser_open_next_file(ctx);
}
