%{
#include <vector>
#include <stdio.h>
#include <stdlib.h>

extern int line_number;
extern char* input_filename;

#define print_line_directive(fp) fprintf(fp, "\n#line %d \"%s\"\n", line_number, input_filename)

extern FILE* fp_bro_init;
extern FILE* fp_func_def;
extern FILE* fp_func_h;
extern FILE* fp_func_init;
extern FILE* fp_netvar_h;
extern FILE* fp_netvar_def;
extern FILE* fp_netvar_init;

int in_c_code = 0;
int definition_type;
const char* bro_prefix;
const char* c_prefix;

enum {
	C_SEGMENT_DEF,
	FUNC_DEF,
	REWRITER_DEF,
	EVENT_DEF,
};

void set_definition_type(int type)
	{
	definition_type = type;
	switch ( type ) {
	case FUNC_DEF:
		bro_prefix = "";
		c_prefix = "bro_";
		break;

	case REWRITER_DEF:
		bro_prefix = "rewrite_";
		c_prefix = "bro_rewrite_";
		break;

	case EVENT_DEF:
		bro_prefix = "";
		break;

	case C_SEGMENT_DEF:
		break;
	}
	}

const char* arg_list_name = "BiF_ARGS";
const char* trace_rewriter_name = "trace_rewriter";

#include "bif_arg.h"

int var_arg; // whether the number of arguments is variable
std::vector<BuiltinFuncArg*> args;

extern const char* decl_name;
extern int yyerror(const char[]);
extern int yylex();

char* concat(char* str1, char* str2)
	{
	int len1 = strlen(str1);
	int len2 = strlen(str2);

	char* s = new char[len1 + len2 +1];

	memcpy(s, str1, len1);
	memcpy(s + len1, str2, len2);

	s[len1+len2] = '\0';

	return s;
	}
%}

%token TOK_LPP TOK_RPP TOK_LPB TOK_RPB TOK_LPPB TOK_RPPB TOK_VAR_ARG
%token TOK_BOOL
%token TOK_FUNCTION TOK_REWRITER TOK_EVENT TOK_CONST
%token TOK_WRITE TOK_PUSH TOK_EOF TOK_TRACE
%token TOK_ARGS TOK_ARG TOK_ARGC
%token TOK_ID TOK_ATTR TOK_CSTR TOK_LF TOK_WS TOK_COMMENT
%token TOK_ATOM TOK_C_TOKEN

%left ',' ':'

%type <str> TOK_C_TOKEN TOK_ID TOK_CSTR TOK_WS TOK_COMMENT TOK_ATTR opt_ws
%type <val> TOK_ATOM TOK_BOOL

%union	{
	char* str;
	int val;
}

%%

definitions:	definitions definition opt_ws
			{ fprintf(fp_func_def, "%s", $3); }
	|	opt_ws
			{
			char auto_gen_comment[1024 + strlen(input_filename)];

			sprintf(auto_gen_comment,
				"This file was automatically generated by bifcl from %s.",
				input_filename);

			fprintf(fp_bro_init, "# %s\n\n", auto_gen_comment);
			fprintf(fp_func_def, "// %s\n\n", auto_gen_comment);
			fprintf(fp_func_h, "// %s\n\n", auto_gen_comment);
			fprintf(fp_func_init, "// %s\n\n", auto_gen_comment);
			fprintf(fp_netvar_def, "// %s\n\n", auto_gen_comment);
			fprintf(fp_netvar_h, "// %s\n\n", auto_gen_comment);
			fprintf(fp_netvar_init, "// %s\n\n", auto_gen_comment);

			fprintf(fp_func_def, "%s", $1);
			}
	;

definition:	event_def
	|	func_def
	|	rewriter_def
	|	c_code_segment
	|	const_def
	;

event_def:	event_prefix opt_ws plain_head end_of_head ';'
	;

func_def:	func_prefix opt_ws typed_head end_of_head body
	;

rewriter_def:	rewriter_prefix opt_ws plain_head end_of_head body
	;

const_def:	const_def_1 const_init opt_attr ';'
			{
			fprintf(fp_bro_init, ";\n");
			fprintf(fp_netvar_h, "extern int %s;\n", decl_name);
			fprintf(fp_netvar_def, "int %s;\n", decl_name);
			fprintf(fp_netvar_init, "\t%s = internal_val(\"%s\")->AsBool();\n",
				decl_name, decl_name);
			}
	;

const_def_1:	TOK_CONST opt_ws TOK_ID opt_ws
			{
			decl_name = $3;
			fprintf(fp_bro_init, "const%s", $2);
			fprintf(fp_bro_init, "%s: bool%s", $3, $4);
			}
	;

/* Currently support only boolean values */
const_init:	'=' opt_ws TOK_BOOL opt_ws
			{
			fprintf(fp_bro_init, "=%s%c%s", $2, ($3) ? 'T' : 'F', $4);
			}
	;

opt_attr:	/* nothing */
	|	opt_attr TOK_ATTR opt_ws
			{
			fprintf(fp_bro_init, "%s%s", $2, $3);
			}
	;

func_prefix:	TOK_FUNCTION
			{ set_definition_type(FUNC_DEF); }
	;

rewriter_prefix: TOK_REWRITER
			{ set_definition_type(REWRITER_DEF); }
	;

event_prefix:	TOK_EVENT
			{ set_definition_type(EVENT_DEF); }
	;

end_of_head:	/* nothing */
			{
			fprintf(fp_bro_init, ";\n");
			}
	;

typed_head:	plain_head return_type
			{
			}
	;

plain_head:	head_1 args arg_end opt_ws
			{
			if ( var_arg )
				fprintf(fp_bro_init, "va_args: any");
			else
				{
				if ( definition_type == REWRITER_DEF )
					fprintf(fp_bro_init, "c: connection");

				for ( int i = 0; i < args.size(); ++i )
					{
					if ( i > 0 || definition_type == REWRITER_DEF )
						fprintf(fp_bro_init, ", ");
					args[i]->PrintBro(fp_bro_init);
					}
				}

			fprintf(fp_bro_init, ")");

			fprintf(fp_func_def, "%s", $4);
			}
	;

head_1:		TOK_ID opt_ws arg_begin
			{
			const char* method_type = 0;
			decl_name = $1;

			print_line_directive(fp_func_def);

			if ( definition_type == FUNC_DEF || definition_type == REWRITER_DEF )
				method_type = "function";
			else if ( definition_type == EVENT_DEF )
				method_type = "event";

			if ( method_type )
				fprintf(fp_bro_init,
					"global %s%s: %s%s(",
					bro_prefix, decl_name, method_type, $2);

			if ( definition_type == FUNC_DEF || definition_type == REWRITER_DEF )
				{
				fprintf(fp_func_init,
					"\textern Val* %s%s(val_list*);\n",
					c_prefix, decl_name);

				fprintf(fp_func_init,
					"\t(void) new BuiltinFunc(%s%s, \"%s%s\", 0);\n",
					c_prefix, decl_name, bro_prefix, decl_name);

				fprintf(fp_func_h,
					"extern Val* %s%s(val_list*);\n",
					c_prefix, decl_name);

				fprintf(fp_func_def,
					"Val* %s%s(val_list* %s)",
					c_prefix, decl_name, arg_list_name);
				}
			else if ( definition_type == EVENT_DEF )
				{
				fprintf(fp_netvar_h,
					"extern EventHandlerPtr %s;\n",
					decl_name);

				fprintf(fp_netvar_def,
					"EventHandlerPtr %s;\n",
					decl_name);

				fprintf(fp_netvar_init,
					"\t%s = internal_handler(\"%s\");\n",
					decl_name, decl_name);
				}
			}
	;

arg_begin:	TOK_LPP
			{ args.clear(); var_arg = 0; }
	;

arg_end:	TOK_RPP
	;

args:		args_1
	|	opt_ws
			{ /* empty, to avoid yacc complaint about type clash */ }
	;

args_1:		args_1 ',' opt_ws arg opt_ws
	|	opt_ws arg opt_ws
			{ /* empty */ }
	;

arg:		TOK_ID opt_ws ':' opt_ws TOK_ID
			{ args.push_back(new BuiltinFuncArg($1, $5)); }
	|	TOK_VAR_ARG
			{
			if ( definition_type == EVENT_DEF )
				yyerror("events cannot have variable arguments");
			var_arg = 1;
			}
	;

return_type:	':' opt_ws TOK_ID opt_ws
			{
			BuiltinFuncArg *ret = new BuiltinFuncArg("", $3);
			ret->PrintBro(fp_bro_init);
			delete ret;
			fprintf(fp_func_def, "%s", $4);
			}
	;

body:		body_start c_body body_end
			{
			fprintf(fp_func_def, " // end of %s\n", decl_name);
			print_line_directive(fp_func_def);
			}
	;

c_code_begin:	/* empty */
			{
			in_c_code = 1;
			print_line_directive(fp_func_def);
			}
	;

c_code_end:	/* empty */
			{ in_c_code = 0; }
	;

body_start:	TOK_LPB c_code_begin
			{
			int implicit_arg = 0;
			int argc = args.size();

			fprintf(fp_func_def, "{");
			if ( definition_type == REWRITER_DEF )
				{
				implicit_arg = 1;
				++argc;
				}

			if ( argc > 0 || ! var_arg )
				fprintf(fp_func_def, "\n");

			if ( ! var_arg )
				{
				fprintf(fp_func_def, "\tif ( %s->length() != %d )\n", arg_list_name, argc);
				fprintf(fp_func_def, "\t\t{\n");
				fprintf(fp_func_def,
					"\t\trun_time(\"%s() takes exactly %d argument(s)\");\n",
					decl_name, argc);
				fprintf(fp_func_def, "\t\treturn 0;\n");
				fprintf(fp_func_def, "\t\t}\n");
				}
			else if ( argc > 0 )
				{
				fprintf(fp_func_def, "\tif ( %s->length() < %d )\n", arg_list_name, argc);
				fprintf(fp_func_def, "\t\t{\n");
				fprintf(fp_func_def,
					"\t\trun_time(\"%s() takes at least %d argument(s)\");\n",
					decl_name, argc);
				fprintf(fp_func_def, "\t\treturn 0;\n");
				fprintf(fp_func_def, "\t\t}\n");
				}

			if ( definition_type == REWRITER_DEF )
				{
				fprintf(fp_func_def,
					"\tTCP_Rewriter* %s = get_trace_rewriter((*%s)[0]);\n",
					trace_rewriter_name,
					arg_list_name);
				fprintf(fp_func_def, "\tif ( ! trace_rewriter )\n");
				fprintf(fp_func_def, "\t\t{\n");
				fprintf(fp_func_def, "\t\tbuiltin_run_time(\"trace rewriter not initialized\");\n");
				fprintf(fp_func_def, "\t\treturn 0;\n");
				fprintf(fp_func_def, "\t\t}\n");
				}
			for ( int i = 0; i < args.size(); ++i )
				args[i]->PrintCDef(fp_func_def, i + implicit_arg);
			print_line_directive(fp_func_def);
			}
	;

body_end:	TOK_RPB c_code_end
			{
			if ( definition_type == REWRITER_DEF )
				fprintf(fp_func_def, "\n\treturn 0;\n");
			fprintf(fp_func_def, "}");
			}
	;

c_code_segment: TOK_LPPB c_code_begin c_body c_code_end TOK_RPPB
	;

c_body:		opt_ws
			{ fprintf(fp_func_def, "%s", $1); }
	|	c_body c_atom opt_ws
			{ fprintf(fp_func_def, "%s", $3); }
	;

c_atom:		TOK_ID
			{ fprintf(fp_func_def, "%s", $1); }
	|	TOK_C_TOKEN
			{ fprintf(fp_func_def, "%s", $1); }
	|	TOK_ARG
			{ fprintf(fp_func_def, "(*%s)", arg_list_name); }
	|	TOK_ARGS
			{ fprintf(fp_func_def, "%s", arg_list_name); }
	|	TOK_ARGC
			{ fprintf(fp_func_def, "%s->length()", arg_list_name); }
	|	TOK_TRACE
			{ fprintf(fp_func_def, "%s", trace_rewriter_name); }
	|	TOK_WRITE
			{ fprintf(fp_func_def, "%s->WriteData", trace_rewriter_name); }
	|	TOK_PUSH
			{ fprintf(fp_func_def, "%s->Push", trace_rewriter_name); }
	|	TOK_EOF
			{ fprintf(fp_func_def, "%s->EndOfData", trace_rewriter_name); }
	|	TOK_CSTR
			{ fprintf(fp_func_def, "%s", $1); }
	|	TOK_ATOM
			{ fprintf(fp_func_def, "%c", $1); }
	;

opt_ws:		opt_ws TOK_WS
			{ $$ = concat($1, $2); }
	|	opt_ws TOK_LF
			{ $$ = concat($1, "\n"); }
	|	opt_ws TOK_COMMENT
			{
			if ( in_c_code )
				$$ = concat($1, $2);
			else
				$$ = $1;
			}
	|	/* empty */
			{ $$ = ""; }
	;

%%

extern char* yytext;
extern char* input_filename;
extern int line_number;
const char* decl_name;

int yyerror(const char msg[])
	{
	char* msgbuf = new char[strlen(msg) + strlen(yytext) + 64];

	if ( yytext[0] == '\n' )
		sprintf(msgbuf, "%s, on previous line", msg);

	else if ( yytext[0] == '\0' )
		sprintf(msgbuf, "%s, at end of file", msg);

	else
		sprintf(msgbuf, "%s, at or near \"%s\"", msg, yytext);

	/*
	extern int column;
	sprintf(msgbuf, "%*s\n%*s\n", column, "^", column, msg);
	*/

	if ( input_filename )
		fprintf(stderr, "%s:%d: ", input_filename, line_number);
	else
		fprintf(stderr, "line %d: ", line_number);
	fprintf(stderr, "%s\n", msgbuf);

	abort();

	exit(1);
	return 0;
	}
