%{

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
/* NOTE: Android yacc build rules transform ssfilter.y into ssfilter.h, and
 * #include "ssfilter.h" gets this file instead of the ssfilter.h in the
 * source tree. This does not work. #include <ssfilter.h> instead. */
#include <ssfilter.h>

typedef struct ssfilter * ssfilter_t;

#define YYSTYPE ssfilter_t

static struct ssfilter * alloc_node(int type, void *pred)
{
	struct ssfilter *n = malloc(sizeof(*n));
	if (n == NULL)
		abort();
	n->type = type;
	n->pred = pred;
	n->post = NULL;
	return n;
}

static char		**yy_argv;
static int		yy_argc;
static FILE		*yy_fp;
static ssfilter_t	*yy_ret;
static int tok_type = -1;

static int yylex(void);

static void yyerror(char *s)
{
	fprintf(stderr, "ss: bison bellows (while parsing filter): \"%s!\"", s);
}

%}

%token HOSTCOND DCOND SCOND DPORT SPORT LEQ GEQ NEQ AUTOBOUND DEVCOND DEVNAME MARKMASK FWMARK
%left '|'
%left '&'
%nonassoc '!'

%%
applet: null expr
        {
                *yy_ret = $2;
                $$ = $2;
        }
        | null
        ;
null:   /* NOTHING */ { $$ = NULL; }
        ;
expr:	DCOND HOSTCOND
        {
		$$ = alloc_node(SSF_DCOND, $2);
        }
        | SCOND HOSTCOND
        {
		$$ = alloc_node(SSF_SCOND, $2);
        }
        | DPORT GEQ HOSTCOND
        {
                $$ = alloc_node(SSF_D_GE, $3);
        }
        | DPORT LEQ HOSTCOND
        {
                $$ = alloc_node(SSF_D_LE, $3);
        }
        | DPORT '>' HOSTCOND
        {
                $$ = alloc_node(SSF_NOT, alloc_node(SSF_D_LE, $3));
        }
        | DPORT '<' HOSTCOND
        {
                $$ = alloc_node(SSF_NOT, alloc_node(SSF_D_GE, $3));
        }
        | DPORT '=' HOSTCOND
        {
		$$ = alloc_node(SSF_DCOND, $3);
        }
        | DPORT NEQ HOSTCOND
        {
		$$ = alloc_node(SSF_NOT, alloc_node(SSF_DCOND, $3));
        }

        | SPORT GEQ HOSTCOND
        {
                $$ = alloc_node(SSF_S_GE, $3);
        }
        | SPORT LEQ HOSTCOND
        {
                $$ = alloc_node(SSF_S_LE, $3);
        }
        | SPORT '>' HOSTCOND
        {
                $$ = alloc_node(SSF_NOT, alloc_node(SSF_S_LE, $3));
        }
        | SPORT '<' HOSTCOND
        {
                $$ = alloc_node(SSF_NOT, alloc_node(SSF_S_GE, $3));
        }
        | SPORT '=' HOSTCOND
        {
		$$ = alloc_node(SSF_SCOND, $3);
        }
        | SPORT NEQ HOSTCOND
        {
		$$ = alloc_node(SSF_NOT, alloc_node(SSF_SCOND, $3));
        }
        | DEVNAME '=' DEVCOND
        {
		$$ = alloc_node(SSF_DEVCOND, $3);
        }
        | DEVNAME NEQ DEVCOND
        {
		$$ = alloc_node(SSF_NOT, alloc_node(SSF_DEVCOND, $3));
        }
        | FWMARK '=' MARKMASK
        {
                $$ = alloc_node(SSF_MARKMASK, $3);
        }
        | FWMARK NEQ MARKMASK
        {
                $$ = alloc_node(SSF_NOT, alloc_node(SSF_MARKMASK, $3));
        }
        | AUTOBOUND
        {
                $$ = alloc_node(SSF_S_AUTO, NULL);
        }
        | expr '|' expr
        {
                $$ = alloc_node(SSF_OR, $1);
	        $$->post = $3;
        }
        | expr expr
        {
                $$ = alloc_node(SSF_AND, $1);
	        $$->post = $2;
        }
        | expr '&' expr

        {
                $$ = alloc_node(SSF_AND, $1);
	        $$->post = $3;
        }
        | '!' expr
        {
                $$ = alloc_node(SSF_NOT, $2);
        }
        | '(' expr ')'
        {
                $$ = $2;
        }
;
%%

static char *get_token_from_line(char **ptr)
{
	char *tok, *cp = *ptr;

	while (*cp == ' ' || *cp == '\t') cp++;

	if (*cp == 0) {
		*ptr = cp;
		return NULL;
	}

	tok = cp;

	while (*cp != 0 && *cp != ' ' && *cp != '\t') {
		/* Backslash escapes everything. */
		if (*cp == '\\') {
			char *tp;
			for (tp = cp; tp != tok; tp--)
				*tp = *(tp-1);
			cp++;
			tok++;
			if (*cp == 0)
				break;
		}
		cp++;
	}
	if (*cp)
		*cp++ = 0;
	*ptr = cp;
	return tok;
}

int yylex(void)
{
	static char argbuf[1024];
	static char *tokptr = argbuf;
	static int argc;
	char *curtok;

	do {
		while (*tokptr == 0) {
			tokptr = NULL;
			if (argc < yy_argc) {
				tokptr = yy_argv[argc];
				argc++;
			} else if (yy_fp) {
				while (tokptr == NULL) {
					if (fgets(argbuf, sizeof(argbuf)-1, yy_fp) == NULL)
						return 0;
					argbuf[sizeof(argbuf)-1] = 0;
					if (strlen(argbuf) == sizeof(argbuf) - 1) {
						fprintf(stderr, "Too long line in filter");
						exit(-1);
					}
					if (argbuf[strlen(argbuf)-1] == '\n')
						argbuf[strlen(argbuf)-1] = 0;
					if (argbuf[0] == '#' || argbuf[0] == '0')
						continue;
					tokptr = argbuf;
				}
			} else {
				return 0;
			}
		}
	} while ((curtok = get_token_from_line(&tokptr)) == NULL);

	if (strcmp(curtok, "!") == 0 ||
	    strcmp(curtok, "not") == 0)
		return '!';
	if (strcmp(curtok, "&") == 0 ||
	    strcmp(curtok, "&&") == 0 ||
	    strcmp(curtok, "and") == 0)
		return '&';
	if (strcmp(curtok, "|") == 0 ||
	    strcmp(curtok, "||") == 0 ||
	    strcmp(curtok, "or") == 0)
		return '|';
	if (strcmp(curtok, "(") == 0)
		return '(';
	if (strcmp(curtok, ")") == 0)
		return ')';
	if (strcmp(curtok, "dst") == 0) {
		tok_type = DCOND;
		return DCOND;
	}
	if (strcmp(curtok, "src") == 0) {
                tok_type = SCOND;
		return SCOND;
        }
	if (strcmp(curtok, "dport") == 0) {
		tok_type = DPORT;
		return DPORT;
	}
	if (strcmp(curtok, "sport") == 0) {
		tok_type = SPORT;
		return SPORT;
	}
	if (strcmp(curtok, "dev") == 0) {
		tok_type = DEVNAME;
		return DEVNAME;
	}
	if (strcmp(curtok, "fwmark") == 0) {
		tok_type = FWMARK;
		return FWMARK;
	}
	if (strcmp(curtok, ">=") == 0 ||
	    strcmp(curtok, "ge") == 0 ||
	    strcmp(curtok, "geq") == 0)
		return GEQ;
	if (strcmp(curtok, "<=") == 0 ||
	    strcmp(curtok, "le") == 0 ||
	    strcmp(curtok, "leq") == 0)
		return LEQ;
	if (strcmp(curtok, "!=") == 0 ||
	    strcmp(curtok, "ne") == 0 ||
	    strcmp(curtok, "neq") == 0)
		return NEQ;
	if (strcmp(curtok, "=") == 0 ||
	    strcmp(curtok, "==") == 0 ||
	    strcmp(curtok, "eq") == 0)
		return '=';
	if (strcmp(curtok, ">") == 0 ||
	    strcmp(curtok, "gt") == 0)
		return '>';
	if (strcmp(curtok, "<") == 0 ||
	    strcmp(curtok, "lt") == 0)
		return '<';
	if (strcmp(curtok, "autobound") == 0) {
		tok_type = AUTOBOUND;
		return AUTOBOUND;
	}
	if (tok_type == DEVNAME) {
		yylval = (void*)parse_devcond(curtok);
		if (yylval == NULL) {
			fprintf(stderr, "Cannot parse device.\n");
			exit(1);
		}
		return DEVCOND;
	}
	if (tok_type == FWMARK) {
		yylval = (void*)parse_markmask(curtok);
		if (yylval == NULL) {
			fprintf(stderr, "Cannot parse mark %s.\n", curtok);
			exit(1);
		}
		return MARKMASK;
	}
	yylval = (void*)parse_hostcond(curtok, tok_type == SPORT || tok_type == DPORT);
	if (yylval == NULL) {
		fprintf(stderr, "Cannot parse dst/src address.\n");
		exit(1);
	}
	return HOSTCOND;
}

int ssfilter_parse(struct ssfilter **f, int argc, char **argv, FILE *fp)
{
	yy_argc = argc;
	yy_argv = argv;
	yy_fp   = fp;
	yy_ret  = f;

	if (yyparse()) {
		fprintf(stderr, " Sorry.\n");
		return -1;
	}
	return 0;
}
