%top {
/* Include this before everything else, for various large-file definitions */
#include "config.h"
}

/*
 * We want a reentrant scanner.
 */
%option reentrant

/*
 * We don't use input, so don't generate code for it.
 */
%option noinput

/*
 * We don't use unput, so don't generate code for it.
 */
%option nounput

/*
 * We don't read interactively from the terminal.
 */
%option never-interactive

/*
 * Prefix scanner routines with "df_" rather than "yy", so this scanner
 * can coexist with other scanners.
 */
%option prefix="df_"

/*
 * We're reading from a string, so we don't need yywrap.
 */
%option noyywrap

/*
 * The type for the state we keep for a scanner.
 */
%option extra-type="df_scanner_state_t *"

/*
 * We have to override the memory allocators so that we don't get
 * "unused argument" warnings from the yyscanner argument (which
 * we don't use, as we have a global memory allocator).
 *
 * We provide, as macros, our own versions of the routines generated by Flex,
 * which just call malloc()/realloc()/free() (as the Flex versions do),
 * discarding the extra argument.
 */
%option noyyalloc
%option noyyrealloc
%option noyyfree

%{
/*
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 2001 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include <stdlib.h>
#include <errno.h>

#include "dfilter-int.h"
#include "syntax-tree.h"
#include "grammar.h"
#include "dfunctions.h"

/*
 * Disable diagnostics in the code generated by Flex.
 */
DIAG_OFF_FLEX

#define LVAL		df_lval
#define LVAL_TYPE	stnode_t*
#define LVAL_INIT_VAL	NULL
#define MODNAME		df
#define FLEX_YY_PREFIX	df_

#include <lemonflex-head.inc>

/*#undef YY_NO_UNPUT*/

static int set_lval(int token, gpointer data);
static int set_lval_int(dfwork_t *dfw, int token, char *s);
static int simple(int token);
static gboolean str_to_gint32(dfwork_t *dfw, char *s, gint32* pint);
static void mark_lval_deprecated(const char *s);

/*
 * Sleazy hack to suppress compiler warnings in yy_fatal_error().
 */
#define YY_EXIT_FAILURE ((void)yyscanner, 2)

/*
 * Macros for the allocators, to discard the extra argument.
 */
#define df_alloc(size, yyscanner)		(void *)malloc(size)
#define df_realloc(ptr, size, yyscanner)	(void *)realloc((char *)(ptr), (size))
#define df_free(ptr, yyscanner)			free((char *)ptr)

%}

%x RANGE_INT
%x RANGE_PUNCT
%x DQUOTE
%x SQUOTE

%%

[[:blank:]\n]+		{
	/* Ignore whitespace, unless set elements are being parsed. Perhaps it
	 * should have used commas from the beginning, but now we are stuck with
	 * whitespace as separators. */
	if (yyextra->in_set) {
		return simple(TOKEN_WHITESPACE);
	}
}



"("				return simple(TOKEN_LPAREN);
")"				return simple(TOKEN_RPAREN);
","				return simple(TOKEN_COMMA);

"{"[[:blank:]\n]*	{
	yyextra->in_set = TRUE;
	return simple(TOKEN_LBRACE);
}
[[:blank:]\n]*".."[[:blank:]\n]*	return simple(TOKEN_DOTDOT);
[[:blank:]\n]*"}"	{
	yyextra->in_set = FALSE;
	return simple(TOKEN_RBRACE);
}

"=="			return simple(TOKEN_TEST_EQ);
"eq"			return simple(TOKEN_TEST_EQ);
"!="			{
	mark_lval_deprecated("!=");
	return simple(TOKEN_TEST_NE);
}
"ne"			{
	mark_lval_deprecated("ne");
	return simple(TOKEN_TEST_NE);
}
">"				return simple(TOKEN_TEST_GT);
"gt"			return simple(TOKEN_TEST_GT);
">="			return simple(TOKEN_TEST_GE);
"ge"			return simple(TOKEN_TEST_GE);
"<"				return simple(TOKEN_TEST_LT);
"lt"			return simple(TOKEN_TEST_LT);
"<="			return simple(TOKEN_TEST_LE);
"le"			return simple(TOKEN_TEST_LE);
"bitwise_and"	return simple(TOKEN_TEST_BITWISE_AND);
"&"				return simple(TOKEN_TEST_BITWISE_AND);
"contains"		return simple(TOKEN_TEST_CONTAINS);
"~"				return simple(TOKEN_TEST_MATCHES);
"matches"		return simple(TOKEN_TEST_MATCHES);
"!"				return simple(TOKEN_TEST_NOT);
"not"			return simple(TOKEN_TEST_NOT);
"&&"			return simple(TOKEN_TEST_AND);
"and"			return simple(TOKEN_TEST_AND);
"||"			return simple(TOKEN_TEST_OR);
"or"			return simple(TOKEN_TEST_OR);
"in"			return simple(TOKEN_TEST_IN);


"["					{
	BEGIN(RANGE_INT);
	return simple(TOKEN_LBRACKET);
}

<RANGE_INT>[+-]?[[:digit:]]+		{
	BEGIN(RANGE_PUNCT);
	return set_lval_int(yyextra->dfw, TOKEN_INTEGER, yytext);
}

<RANGE_INT>[+-]?0x[[:xdigit:]]+		{
	BEGIN(RANGE_PUNCT);
	return set_lval_int(yyextra->dfw, TOKEN_INTEGER, yytext);
}

<RANGE_INT,RANGE_PUNCT>":"		{
	BEGIN(RANGE_INT);
	return simple(TOKEN_COLON);
}

<RANGE_PUNCT>"-"			{
	BEGIN(RANGE_INT);
	return simple(TOKEN_HYPHEN);
}

<RANGE_INT,RANGE_PUNCT>","		{
	BEGIN(RANGE_INT);
	return simple(TOKEN_COMMA);
}

<RANGE_INT,RANGE_PUNCT>"]"		{
	BEGIN(INITIAL);
	return simple(TOKEN_RBRACKET);
}

	/* Error if none of the above while scanning a range (slice) */

<RANGE_PUNCT>[^:\-,\]]+		{
	dfilter_fail(yyextra->dfw, "Invalid string \"%s\" found while scanning slice.", yytext);
	return SCAN_FAILED;
}

	/* XXX It would be nice to be able to match an entire non-integer string,
	 * but beware of Flex's "match the most text" rule.
	 */

<RANGE_INT>.	{
	dfilter_fail(yyextra->dfw, "Invalid character \"%s\" found while scanning slice; expected integer.", yytext);
	return SCAN_FAILED;
}

\042				{
	/* start quote of a quoted string */
	/* The example of how to scan for strings was taken from
	the flex 2.5.4 manual, from the section "Start Conditions".
	See:
	http://www.gnu.org/software/flex/manual/html_node/flex_11.html */

	BEGIN(DQUOTE);
	/* A previous filter that failed to compile due to
	a missing end quote will have left quoted_string set
	to something. Clear it now that we are starting
	a new quoted string. */
	if (yyextra->quoted_string) {
		g_string_free(yyextra->quoted_string, TRUE);
		/* Don't set quoted_string to NULL, as we
		do in other quoted_string-cleanup code, as we're
		about to set it in the next line. */
	}
	yyextra->quoted_string = g_string_new("");
}

<DQUOTE><<EOF>>				{
	/* unterminated string */
	/* The example of how to handle unclosed strings was taken from
	the flex 2.5.4 manual, from the section "End-of-file rules".
	See:
	http://www.gnu.org/software/flex/manual/html_node/flex_13.html */

	dfilter_fail(yyextra->dfw, "The final quote was missing from a quoted string.");
	return SCAN_FAILED;
}

<DQUOTE>\042			{
	/* end quote */
	int token;
	BEGIN(INITIAL);
	token = set_lval(TOKEN_STRING, yyextra->quoted_string->str);
	g_string_free(yyextra->quoted_string, TRUE);
	yyextra->quoted_string = NULL;
	return token;
}

<DQUOTE>\\[0-7]{1,3} {
	/* octal sequence */
	unsigned long result;
	result = strtoul(yytext + 1, NULL, 8);
	if (result > 0xff) {
		g_string_free(yyextra->quoted_string, TRUE);
		yyextra->quoted_string = NULL;
		dfilter_fail(yyextra->dfw, "%s is larger than 255.", yytext);
		return SCAN_FAILED;
	}
	g_string_append_c(yyextra->quoted_string, (gchar) result);
}

<DQUOTE>\\x[[:xdigit:]]{1,2} {
	/* hex sequence */
	unsigned long result;
	result = strtoul(yytext + 2, NULL, 16);
	g_string_append_c(yyextra->quoted_string, (gchar) result);
}


<DQUOTE>\\.				{
	/* escaped character */
	g_string_append_c(yyextra->quoted_string, yytext[1]);
}

<DQUOTE>[^\\\042]+			{
	/* non-escaped string */
	g_string_append(yyextra->quoted_string, yytext);
}


\047				{
	/* start quote of a quoted character value */
	/* The example of how to scan for strings was taken from
	the Flex manual, from the section "Start Conditions".
	See:
	http://flex.sourceforge.net/manual/Start-Conditions.html#Start-Conditions */

	BEGIN(SQUOTE);
	/* A previous filter that failed to compile due to
	a missing end quote will have left quoted_string set
	to something. Clear it now that we are starting
	a new quoted string. */
	if (yyextra->quoted_string) {
		g_string_free(yyextra->quoted_string, TRUE);
		/* Don't set quoted_string to NULL, as we
		do in other quoted_string-cleanup code, as we're
		about to set it in the next line. */
	}
	yyextra->quoted_string = g_string_new("'");
}

<SQUOTE><<EOF>>				{
	/* unterminated character value */
	/* The example of how to handle unclosed strings was taken from
	the Flex manual, from the section "End-of-file rules".
	See:
	http://flex.sourceforge.net/manual/EOF.html#EOF.html */

	dfilter_fail(yyextra->dfw, "The final quote was missing from a character constant.");
	return SCAN_FAILED;
}

<SQUOTE>\047			{
	/* end quote */
	int token;
	BEGIN(INITIAL);
	g_string_append_c(yyextra->quoted_string, '\'');
	token = set_lval(TOKEN_CHARCONST, yyextra->quoted_string->str);
	g_string_free(yyextra->quoted_string, TRUE);
	yyextra->quoted_string = NULL;
	return token;
}

<SQUOTE>\\.				{
	/* escaped character */
	g_string_append(yyextra->quoted_string, yytext);
}

<SQUOTE>[^\\\047]+			{
	/* non-escaped string */
	g_string_append(yyextra->quoted_string, yytext);
}



[-[:alnum:]_\.:]*\/[[:digit:]]+  {
        /* CIDR */
        return set_lval(TOKEN_UNPARSED, yytext);
}

		([.][-+[:alnum:]_:]+)+[.]{0,2} |
[-+[:alnum:]_:]+([.][-+[:alnum:]_:]+)*[.]{0,2} {
	/* Is it a field name or some other value (float, integer, bytes, ...)? */
	header_field_info *hfinfo;
	df_func_def_t *df_func_def;

	/* Trailing dot is allowed for floats, but make sure that trailing ".."
	 * is interpreted as a token on its own. */
	if (strstr(yytext, "..")) {
		yyless(yyleng-2);
	}

	hfinfo = proto_registrar_get_byname(yytext);
	if (hfinfo) {
		/* Yes, it's a field name */
		return set_lval(TOKEN_FIELD, hfinfo);
	}

	hfinfo = proto_registrar_get_byalias(yytext);
	if (hfinfo) {
		/* Yes, it's an aliased field name */
		/* XXX Do we need to make a copy of yytext? dfilter_compile
		 * will dup this later on. */
		mark_lval_deprecated(yytext);
		return set_lval(TOKEN_FIELD, hfinfo);
	}

	df_func_def = df_func_lookup(yytext);
	if (df_func_def) {
		/* Yes, it's a dfilter function */
		return set_lval(TOKEN_FUNCTION, df_func_def);
	}
	/* No match, so treat it as an unparsed string */
	return set_lval(TOKEN_UNPARSED, yytext);
}

. {
	/* Default */
	return set_lval(TOKEN_UNPARSED, yytext);
}


%%

/*
 * Turn diagnostics back on, so we check the code that we've written.
 */
DIAG_ON_FLEX

static int
simple(int token)
{
	switch (token) {
		case TOKEN_LPAREN:
		case TOKEN_RPAREN:
		case TOKEN_LBRACKET:
		case TOKEN_RBRACKET:
		case TOKEN_LBRACE:
		case TOKEN_RBRACE:
		case TOKEN_COLON:
		case TOKEN_COMMA:
		case TOKEN_DOTDOT:
		case TOKEN_HYPHEN:
		case TOKEN_WHITESPACE:
		case TOKEN_TEST_EQ:
		case TOKEN_TEST_NE:
		case TOKEN_TEST_GT:
		case TOKEN_TEST_GE:
		case TOKEN_TEST_LT:
		case TOKEN_TEST_LE:
		case TOKEN_TEST_BITWISE_AND:
		case TOKEN_TEST_CONTAINS:
		case TOKEN_TEST_MATCHES:
		case TOKEN_TEST_NOT:
		case TOKEN_TEST_AND:
		case TOKEN_TEST_OR:
		case TOKEN_TEST_IN:
			break;
		default:
			g_assert_not_reached();
	}
	return token;
}

static int
set_lval(int token, gpointer data)
{
	sttype_id_t	type_id = STTYPE_UNINITIALIZED;

	switch (token) {
		case TOKEN_STRING:
			type_id = STTYPE_STRING;
			break;
		case TOKEN_CHARCONST:
			type_id = STTYPE_CHARCONST;
			break;
		case TOKEN_FIELD:
			type_id = STTYPE_FIELD;
			break;
		case TOKEN_UNPARSED:
			type_id = STTYPE_UNPARSED;
			break;
		case TOKEN_FUNCTION:
			type_id = STTYPE_FUNCTION;
			break;
		default:
			g_assert_not_reached();
	}
	stnode_init(df_lval, type_id, data);
	return token;
}

static int
set_lval_int(dfwork_t *dfw, int token, char *s)
{
	sttype_id_t	type_id = STTYPE_UNINITIALIZED;
	gint32		val;

	if (!str_to_gint32(dfw, s, &val)) {
		return SCAN_FAILED;
	}

	switch (token) {
		case TOKEN_INTEGER:
			type_id = STTYPE_INTEGER;
			break;
		default:
			g_assert_not_reached();
	}

	stnode_init_int(df_lval, type_id, val);
	return token;
}


static gboolean
str_to_gint32(dfwork_t *dfw, char *s, gint32* pint)
{
	char    *endptr;
	long	integer;

	errno = 0;
	integer = strtol(s, &endptr, 0);

	if (errno == EINVAL || endptr == s || *endptr != '\0') {
		/* This isn't a valid number. */
		dfilter_fail(dfw, "\"%s\" is not a valid number.", s);
		return FALSE;
	}
	if (errno == ERANGE) {
		if (integer == LONG_MAX) {
			dfilter_fail(dfw, "\"%s\" causes an integer overflow.", s);
		}
		else if (integer == LONG_MIN) {
			dfilter_fail(dfw, "\"%s\" causes an integer underflow.", s);
		}
		else {
			/*
			 * XXX - can "strtol()" set errno to ERANGE without
			 * returning LONG_MAX or LONG_MIN?
			 */
			dfilter_fail(dfw, "\"%s\" is not an integer.", s);
		}
		return FALSE;
	}
	if (integer > G_MAXINT32) {
		/*
		 * Fits in a long, but not in a gint32 (a long might be
		 * 64 bits).
		 */
		dfilter_fail(dfw, "\"%s\" causes an integer overflow.", s);
		return FALSE;
	}
	if (integer < G_MININT32) {
		/*
		 * Fits in a long, but not in a gint32 (a long might be
		 * 64 bits).
		 */
		dfilter_fail(dfw, "\"%s\" causes an integer underflow.", s);
		return FALSE;
	}

	*pint = (gint32)integer;
	return TRUE;
}

static void
mark_lval_deprecated(const char *s)
{
	df_lval->deprecated_token = s;
}
