/* You need Bison (or maybe Yacc) to rebuild the .c file from this grammar.
 * Or you could rewrite it to use Lemon, since the ZDoom source comes with Lemon.
 * Since I have Bison, and the existing code works fine, there's no motivation
 * for me to rewrite it.
 */
 
%{
#include "xlat.h"
#include <malloc.h>
#include <string.h>

// verbose doesn't seem to help all that much
//#define YYERROR_VERBOSE 1

int yyerror (char *s);
int yylex (void);

typedef struct _Symbol
{
	struct _Symbol *Next;
	int Value;
	char Sym[1];
} Symbol;

static bool FindToken (char *tok, int *type);
static void AddSym (char *sym, int val);
static bool FindSym (char *sym, Symbol **val);

static int EnumVal;

struct ListFilter
{
	WORD filter;
	BYTE value;
};

typedef struct _morefilters
{
	struct _morefilters *next;
	struct ListFilter filter;
} MoreFilters;

typedef struct _morelines
{
	struct _morelines *next;
	BoomArg arg;
} MoreLines;

%}

%union {
	int val;
	char sym[80];
	char string[80];
	struct
	{
		BYTE addflags;
		BYTE args[5];
	} args;
	struct ListFilter filter;
	MoreFilters *filter_p;
	struct
	{
		BYTE constant;
		WORD mask;
		MoreFilters *filters;
	} boomarg;
	Symbol *symval;
	BoomArg boomline;
	MoreLines *boomlines;
}

%token <val> NUM
%token <symval> SYMNUM
%token <sym> SYM
%token <string> STRING

%type <val> exp
%type <args> special_args
%type <filter> list_val
%type <filter_p> arg_list
%type <boomarg> boom_args
%type <val> boom_op
%type <val> boom_selector
%type <boomline> boom_line
%type <boomlines> boom_body
%type <val> maybe_argcount

%token DEFINE
%token INCLUDE
%token TAG
%token LINEID
%token SPECIAL
%token FLAGS
%token ARG2
%token ARG3
%token ARG4
%token ARG5
%token OR_EQUAL
%token ENUM
%token PRINT
%token ENDL

%left '|'
%left '^'
%left '&'
%left '-' '+'
%left '*' '/'
%left NEG

%start translation_unit

%%

exp:
		  NUM
		| SYMNUM			{ $$ = $1->Value; }
		| exp '+' exp		{ $$ = $1 + $3; }
		| exp '-' exp		{ $$ = $1 - $3; }
		| exp '*' exp		{ $$ = $1 * $3; }
		| exp '/' exp		{ $$ = $1 / $3; }
		| exp '|' exp		{ $$ = $1 | $3; }
		| exp '&' exp		{ $$ = $1 & $3; }
		| exp '^' exp		{ $$ = $1 ^ $3; }
		| '-' exp	%prec NEG	{ $$ = -$2 }
		| '(' exp ')'		{ $$ = $2; }
;

translation_unit:	/* empty line */
		| translation_unit external_declaration
;

external_declaration:
		  define_statement
		| include_statement
		| print_statement
		| enum_statement
		| linetype_declaration
		| boom_declaration
		| special_declaration
;

print_statement:
		  PRINT '(' print_list ')'
		  {
			  printf ("\n");
		  }
;

print_list:
		  /* EMPTY */
		| print_item
		| print_item ',' print_list
;

print_item:
		  STRING { printf ("%s", $1); }
		| exp { printf ("%d", $1); }
		| ENDL { printf ("\n"); }
;

define_statement:
		DEFINE SYM '(' exp ')' { AddSym ($2, $4); }
;

include_statement:
		INCLUDE STRING { IncludeFile ($2); }
;

enum_statement:
		ENUM '{' { EnumVal = 0; } enum_list '}'
;

enum_list:
		/* empty */
		| single_enum
		| single_enum ',' enum_list
;

single_enum:
		  SYM { AddSym ($1, EnumVal++); }
		| SYM '=' exp { AddSym ($1, EnumVal = $3); }
;

/* special declarations work just like they do for ACS, so
 * specials can be defined just by including zspecial.acs
 */
special_declaration:
		  SPECIAL special_list ';'
;

special_list:
		  /* empty */
		| special_def
		| special_def ',' special_list
;

special_def:
		  exp ':' SYM '(' maybe_argcount ')' { AddSym ($3, $1); }
		| exp ':' SYMNUM
			{ printf ("%s, line %d: %s is already defined\n", SourceName, SourceLine, $3->Sym); }
		  '(' maybe_argcount ')'
;

maybe_argcount:
		  /* empty */	{ $$ = 0; }
		| exp
		| exp ',' exp
;

linetype_declaration:
		  exp '=' exp ',' exp '(' special_args ')'
		  {
			  Simple[$1].NewSpecial = $5;
			  Simple[$1].Flags = $3 | $7.addflags;
			  Simple[$1].Args[0] = $7.args[0];
			  Simple[$1].Args[1] = $7.args[1];
			  Simple[$1].Args[2] = $7.args[2];
			  Simple[$1].Args[3] = $7.args[3];
			  Simple[$1].Args[4] = $7.args[4];
		  }
		| exp '=' exp ',' SYM '(' special_args ')'
		{
			printf ("%s, line %d: %s is undefined\n", SourceName, SourceLine, $5);
		}
;

boom_declaration:
		'[' exp ']' '(' exp ',' exp ')' '{' boom_body '}'
		{
			if (NumBoomish == MAX_BOOMISH)
			{
				MoreLines *probe = $10;

				while (probe != NULL)
				{
					MoreLines *next = probe->next;
					free (probe);
					probe = next;
				}
				printf ("%s, line %d: Too many BOOM translators\n", SourceName, SourceLine);
			}
			else
			{
				int i;
				MoreLines *probe;

				Boomish[NumBoomish].FirstLinetype = $5;
				Boomish[NumBoomish].LastLinetype = $7;
				Boomish[NumBoomish].NewSpecial = $2;
				
				for (i = 0, probe = $10; probe != NULL; i++)
				{
					MoreLines *next = probe->next;;
					if (i < MAX_BOOMISH_EXEC)
					{
						Boomish[NumBoomish].Args[i] = probe->arg;
					}
					else if (i == MAX_BOOMISH_EXEC)
					{
						printf ("%s, line %d: Too many commands for this BOOM translator\n", SourceName, SourceLine);
					}
					free (probe);
					probe = next;
				}
				if (i < MAX_BOOMISH_EXEC)
				{
					Boomish[NumBoomish].Args[i].bDefined = 0;
				}
				NumBoomish++;
			}
		}
;

boom_body:
		/* empty */
		{
			$$ = NULL;
		}
		| boom_line boom_body
		{
			$$ = malloc (sizeof(MoreLines));
			$$->next = $2;
			$$->arg = $1;
		}
;

boom_line:
		boom_selector boom_op boom_args
		{
			$$.bDefined = 1;
			$$.bOrExisting = ($2 == OR_EQUAL);
			$$.bUseConstant = ($3.filters == NULL);
			$$.ArgNum = $1;
			$$.ConstantValue = $3.constant;
			$$.AndValue = $3.mask;

			if ($3.filters != NULL)
			{
				int i;
				MoreFilters *probe;

				for (i = 0, probe = $3.filters; probe != NULL; i++)
				{
					MoreFilters *next = probe->next;
					if (i < 15)
					{
						$$.ResultFilter[i] = probe->filter.filter;
						$$.ResultValue[i] = probe->filter.value;
					}
					else if (i == 15)
					{
						yyerror ("Lists can only have 15 elements");
					}
					free (probe);
					probe = next;
				}
				$$.ListSize = i > 15 ? 15 : i;
			}
		}
;

boom_selector:
		  FLAGS { $$ = 4; }
		| ARG2  { $$ = 0; }
		| ARG3  { $$ = 1; }
		| ARG4  { $$ = 2; }
		| ARG5  { $$ = 3; }
;

boom_op:
		  '=' { $$ = '='; }
		| OR_EQUAL { $$ = OR_EQUAL; }
;

boom_args:
		  exp
		  {
			  $$.constant = $1;
			  $$.filters = NULL;
		  }
		| exp '[' arg_list ']'
		{
			$$.mask = $1;
			$$.filters = $3;
		}
;

arg_list:
		  list_val
		  {
			  $$ = malloc (sizeof (MoreFilters));
			  $$->next = NULL;
			  $$->filter = $1;
		  }
		| list_val ',' arg_list
		{
			$$ = malloc (sizeof (MoreFilters));
			$$->next = $3;
			$$->filter = $1;
		}
;

list_val:
		  exp ':' exp	{ $$.filter = $1; $$.value = $3; }
;

special_args:
		/* EMPTY LINE */
		{
			$$.addflags = 0;
			memset ($$.args, 0, 5);
		}
		| TAG
		{
			$$.addflags = SIMPLE_HASTAGAT1;
			memset ($$.args, 0, 5);
		}
		| TAG ',' exp
		{
			$$.addflags = SIMPLE_HASTAGAT1;
			$$.args[0] = 0;
			$$.args[1] = $3;
			$$.args[2] = 0;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| TAG ',' exp ',' exp
		{
			$$.addflags = SIMPLE_HASTAGAT1;
			$$.args[0] = 0;
			$$.args[1] = $3;
			$$.args[2] = $5;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| TAG ',' exp ',' exp ',' exp
		{
			$$.addflags = SIMPLE_HASTAGAT1;
			$$.args[0] = 0;
			$$.args[1] = $3;
			$$.args[2] = $5;
			$$.args[3] = $7;
			$$.args[4] = 0;
		}
		| TAG ',' exp ',' exp ',' exp ',' exp
		{
			$$.addflags = SIMPLE_HASTAGAT1;
			$$.args[0] = 0;
			$$.args[1] = $3;
			$$.args[2] = $5;
			$$.args[3] = $7;
			$$.args[4] = $9;
		}

		| TAG ',' TAG
		{
			$$.addflags = SIMPLE_HAS2TAGS;
			$$.args[0] = $$.args[1] = 0;
			$$.args[2] = 0;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| TAG ',' TAG ',' exp
		{
			$$.addflags = SIMPLE_HAS2TAGS;
			$$.args[0] = $$.args[1] = 0;
			$$.args[2] = $5;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| TAG ',' TAG ',' exp ',' exp
		{
			$$.addflags = SIMPLE_HAS2TAGS;
			$$.args[0] = $$.args[1] = 0;
			$$.args[2] = $5;
			$$.args[3] = $7;
			$$.args[4] = 0;
		}
		| TAG ',' TAG ',' exp ',' exp ',' exp
		{
			$$.addflags = SIMPLE_HAS2TAGS;
			$$.args[0] = $$.args[1] = 0;
			$$.args[2] = $5;
			$$.args[3] = $7;
			$$.args[4] = $9;
		}

		| LINEID
		{
			$$.addflags = SIMPLE_HASLINEID;
			memset ($$.args, 0, 5);
		}
		| LINEID ',' exp
		{
			$$.addflags = SIMPLE_HASLINEID;
			$$.args[0] = 0;
			$$.args[1] = $3;
			$$.args[2] = 0;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| LINEID ',' exp ',' exp
		{
			$$.addflags = SIMPLE_HASLINEID;
			$$.args[0] = 0;
			$$.args[1] = $3;
			$$.args[2] = $5;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| LINEID ',' exp ',' exp ',' exp
		{
			$$.addflags = SIMPLE_HASLINEID;
			$$.args[0] = 0;
			$$.args[1] = $3;
			$$.args[2] = $5;
			$$.args[3] = $7;
			$$.args[4] = 0;
		}
		| LINEID ',' exp ',' exp ',' exp ',' exp
		{
			$$.addflags = SIMPLE_HASLINEID;
			$$.args[0] = 0;
			$$.args[1] = $3;
			$$.args[2] = $5;
			$$.args[3] = $7;
			$$.args[4] = $9;
		}

		| exp
		{
			$$.addflags = 0;
			$$.args[0] = $1;
			$$.args[1] = 0;
			$$.args[2] = 0;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| exp ',' exp
		{
			$$.addflags = 0;
			$$.args[0] = $1;
			$$.args[1] = $3;
			$$.args[2] = 0;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| exp ',' exp ',' exp
		{
			$$.addflags = 0;
			$$.args[0] = $1;
			$$.args[1] = $3;
			$$.args[2] = $5;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| exp ',' exp ',' exp ',' exp
		{
			$$.addflags = 0;
			$$.args[0] = $1;
			$$.args[1] = $3;
			$$.args[2] = $5;
			$$.args[3] = $7;
			$$.args[4] = 0;
		}
		| exp ',' exp ',' exp ',' exp ',' exp
		{
			$$.addflags = 0;
			$$.args[0] = $1;
			$$.args[1] = $3;
			$$.args[2] = $5;
			$$.args[3] = $7;
			$$.args[4] = $9;
		}

		| exp ',' TAG
		{
			$$.addflags = SIMPLE_HASTAGAT2;
			$$.args[0] = $1;
			$$.args[1] = 0;
			$$.args[2] = 0;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| exp ',' TAG ',' exp
		{
			$$.addflags = SIMPLE_HASTAGAT2;
			$$.args[0] = $1;
			$$.args[1] = 0;
			$$.args[2] = $5;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| exp ',' TAG ',' exp ',' exp
		{
			$$.addflags = SIMPLE_HASTAGAT2;
			$$.args[0] = $1;
			$$.args[1] = 0;
			$$.args[2] = $5;
			$$.args[3] = $7;
			$$.args[4] = 0;
		}
		| exp ',' TAG ',' exp ',' exp ',' exp
		{
			$$.addflags = SIMPLE_HASTAGAT2;
			$$.args[0] = $1;
			$$.args[1] = 0;
			$$.args[2] = $5;
			$$.args[3] = $7;
			$$.args[4] = $9;
		}

		| exp ',' exp ',' TAG
		{
			$$.addflags = SIMPLE_HASTAGAT3;
			$$.args[0] = $1;
			$$.args[1] = $3;
			$$.args[2] = 0;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| exp ',' exp ',' TAG ',' exp
		{
			$$.addflags = SIMPLE_HASTAGAT3;
			$$.args[0] = $1;
			$$.args[1] = $3;
			$$.args[2] = 0;
			$$.args[3] = $7;
			$$.args[4] = 0;
		}
		| exp ',' exp ',' TAG ',' exp ',' exp
		{
			$$.addflags = SIMPLE_HASTAGAT3;
			$$.args[0] = $1;
			$$.args[1] = $3;
			$$.args[2] = 0;
			$$.args[3] = $7;
			$$.args[4] = $9;
		}

		| exp ',' exp ',' exp ',' TAG
		{
			$$.addflags = SIMPLE_HASTAGAT4;
			$$.args[0] = $1;
			$$.args[1] = $3;
			$$.args[2] = $5;
			$$.args[3] = 0;
			$$.args[4] = 0;
		}
		| exp ',' exp ',' exp ',' TAG ',' exp
		{
			$$.addflags = SIMPLE_HASTAGAT4;
			$$.args[0] = $1;
			$$.args[1] = $3;
			$$.args[2] = $5;
			$$.args[3] = 0;
			$$.args[4] = $9;
		}

		| exp ',' exp ',' exp ',' exp ',' TAG
		{
			$$.addflags = SIMPLE_HASTAGAT5;
			$$.args[0] = $1;
			$$.args[1] = $3;
			$$.args[2] = $5;
			$$.args[3] = $7;
			$$.args[4] = 0;
		}
;

%%

#include <ctype.h>
#include <stdio.h>

int yylex (void)
{
	char token[80];
	int toksize;
	int c;

loop:
	while (Source == NULL)
	{
		if (!EndFile ())
			return 0;
	}
	while (isspace (c = fgetc (Source)) && c != EOF)
	{
		if (c == '\n')
			SourceLine++;
	}

	if (c == EOF)
	{
		if (EndFile ())
			goto loop;
		return 0;
	}
	if (isdigit (c))
	{
		int buildup = c - '0';
		if (c == '0')
		{
			c = fgetc (Source);
			if (c == 'x' || c == 'X')
			{
				for (;;)
				{
					c = fgetc (Source);
					if (isdigit (c))
					{
						buildup = (buildup<<4) + c - '0';
					}
					else if (c >= 'a' && c <= 'f')
					{
						buildup = (buildup<<4) + c - 'a' + 10;
					}
					else if (c >= 'A' && c <= 'F')
					{
						buildup = (buildup<<4) + c - 'A' + 10;
					}
					else
					{
						ungetc (c, Source);
						yylval.val = buildup;
						return NUM;
					}
				}
			}
			else
			{
				ungetc (c, Source);
			}
		}
		while (isdigit (c = fgetc (Source)))
		{
			buildup = buildup*10 + c - '0';
		}
		ungetc (c, Source);
		yylval.val = buildup;
		return NUM;
	}
	if (isalpha (c))
	{
		int buildup = 0;
		
		token[0] = c;
		toksize = 1;
		while (toksize < 79 && (isalnum (c = fgetc (Source)) || c == '_'))
		{
			token[toksize++] = c;
		}
		token[toksize] = 0;
		if (toksize == 79 && isalnum (c))
		{
			while (isalnum (c = fgetc (Source)))
				;
		}
		ungetc (c, Source);
		if (FindToken (token, &buildup))
		{
			return buildup;
		}
		if (FindSym (token, &yylval.symval))
		{
			return SYMNUM;
		}
		strcpy (yylval.sym, token);
		return SYM;
	}
	if (c == '/')
	{
		c = fgetc (Source);
		if (c == '*')
		{
			for (;;)
			{
				while ((c = fgetc (Source)) != '*' && c != EOF)
				{
					if (c == '\n')
						SourceLine++;
				}
				if (c == EOF)
					return 0;
				if ((c = fgetc (Source)) == '/')
					goto loop;
				if (c == EOF)
					return 0;
				ungetc (c, Source);
			}
		}
		else if (c == '/')
		{
			while ((c = fgetc (Source)) != '\n' && c != EOF)
				;
			if (c == '\n')
				SourceLine++;
			else if (c == EOF)
				return 0;
			goto loop;
		}
		else
		{
			ungetc (c, Source);
			return '/';
		}
	}
	if (c == '"')
	{
		int tokensize = 0;
		while ((c = fgetc (Source)) != '"' && c != EOF)
		{
			yylval.string[tokensize++] = c;
		}
		yylval.string[tokensize] = 0;
		return STRING;
	}
	if (c == '|')
	{
		c = fgetc (Source);
		if (c == '=')
			return OR_EQUAL;
		ungetc (c, Source);
		return '|';
	}
	return c;
}

static Symbol *FirstSym;

static void AddSym (char *sym, int val)
{
	Symbol *syme = malloc (strlen (sym) + sizeof(Symbol));
	syme->Next = FirstSym;
	syme->Value = val;
	strcpy (syme->Sym, sym);
	FirstSym = syme;
}

static bool FindSym (char *sym, Symbol **val)
{
	Symbol *syme = FirstSym;

	while (syme != NULL)
	{
		if (strcmp (syme->Sym, sym) == 0)
		{
			*val = syme;
			return 1;
		}
		syme = syme->Next;
	}
	return 0;
}

static bool FindToken (char *tok, int *type)
{
	static const char tokens[][8] =
	{
		"endl", "print", "include", "special", "define", "enum",
		"arg5", "arg4", "arg3", "arg2", "flags", "lineid", "tag"
		
	};
	static const short types[] =
	{
		ENDL, PRINT, INCLUDE, SPECIAL, DEFINE, ENUM,
		ARG5, ARG4, ARG3, ARG2, FLAGS, LINEID, TAG
	};
	int i;

	for (i = sizeof(tokens)/sizeof(tokens[0])-1; i >= 0; i--)
	{
		if (strcmp (tok, tokens[i]) == 0)
		{
			*type = types[i];
			return 1;
		}
	}
	return 0;
}

int yyerror (char *s)
{
	if (SourceName != NULL)
		printf ("%s, line %d: %s\n", SourceName, SourceLine, s);
	else
		printf ("%s\n", s);
	return 0;
}
