%{
#include <stdio.h>
#include <string.h>
#include <err.h>
#include "types.h"
#include "ast.h"
#include "codegen.h"

int yyparse(void);
int yylex(void);

extern int yylineno;
char *yytext;

/* XXX */
extern FILE *binout;
extern FILE *yyin;

void yyerror(const char * str)
{
	fprintf(stderr, "\n%s on line %d before '%s'\n", str, yylineno, yytext);
}

int yywrap()
{
	return 1;
}

int main(int argc, char *argv[])
{
	argv++; /* skip over program name */
	argc--;
	if (argc > 0) {
		char *outfile;
		int len;

		outfile = strdup(argv[0]);
		if (outfile == NULL)
			err(1, "Failed to allocate memory");
		len = strlen(outfile);
		if ((len < 3) ||
		    (outfile[len-1] != 's') ||
		    (outfile[len-2] != 'l') ||
		    (outfile[len-3] != '.'))
			errx(1, "Can only compile .ls files");
		outfile[len-1] = 'c';

		yyin = fopen(argv[0], "r");
		if (yyin == NULL)
			err(1, "%s: Failed to open input file", argv[0]);
		binout = fopen(outfile, "w");
		if (binout == NULL)
			err(1, "%s: Failed to open output file", outfile);

		free(outfile);
	} else {
		yyin = stdin;
		binout = fopen("out.lc", "w");
		if (binout == NULL)
			err(1, "%s: Failed to open output file", "out.lc");
	}

	yyparse();
	fclose(binout);
	fclose(yyin);
	return 0;
}

%}

%token TOKFUNCTION TOKFNDEFINT TOKFNDEFEXT TOKCONSTANT
%token TOKWHILE TOKIF TOKELSE TOKSWITCH TOKCASE TOKDEFAULT
%token TOKBREAK TOKRETURN TOKEQ TOKNE TOKAND TOKOR STRING

%union {
	ast*		Tast;
	ast_list*	Tast_list;
	char *		Tstring;
	long		Tinteger;
	float		Treal;
}

%type <Tast> argument
%type <Tast> constant
%type <Tast> function
%type <Tast> fndefint
%type <Tast> fndefext
%type <Tast> statement
%type <Tast> assignment
%type <Tast> expression
%type <Tast> realconstant
%type <Tast> statement_if
%type <Tast> statement_case
%type <Tast> statement_while
%type <Tast> statement_break
%type <Tast> statement_switch
%type <Tast> statement_return
%type <Tast> statement_default
%type <Tast_list> stmt_list_inner
%type <Tast_list> exp_list_inner
%type <Tast_list> arg_list_inner
%type <Tast_list> fn_list_inner
%type <Tast_list> assign_list
%type <Tast_list> stmt_list
%type <Tast_list> exp_list
%type <Tast_list> arg_list
%type <Tast_list> fn_list
%token <Tstring> IDENTIFIER
%token <Tstring> STRING
%token <Tinteger> NUMBER
%token <Treal> REAL

%left '+'
%left '-'
%left '*'
%left '/'
%left TOKAND
%left TOKOR
%nonassoc '<'
%nonassoc '>'
%nonassoc TOKLE
%nonassoc TOKGE
%nonassoc TOKEQ
%nonassoc TOKNE

%start start

%%

start:
	fn_list
	{
	  codegen(make_list_node($1));
	  output_code();
	}
	;

fn_list:
	fn_list_inner { $$ = reverse_list($1); }
	;

fn_list_inner:
	  fn_list_inner function	{ $$ = make_list($2, $1); }
	| fn_list_inner fndefint	{ $$ = make_list($2, $1); }
	| fn_list_inner fndefext	{ $$ = make_list($2, $1); }
	| fn_list_inner constant	{ $$ = make_list($2, $1); }
	| fn_list_inner realconstant	{ $$ = make_list($2, $1); }
	|				{ $$ = NULL; }
	;

function:
	TOKFUNCTION IDENTIFIER '(' arg_list ')' '{' stmt_list '}'
	{ $$ = make_function($2, $4, $7); }
	;

fndefint:
	TOKFNDEFINT IDENTIFIER NUMBER ';'
	{ $$ = make_fndefint($2, $3); }
	;

constant:
	TOKCONSTANT IDENTIFIER NUMBER ';'
	{ $$ = make_constant($2, $3); }
	;

realconstant:
	TOKCONSTANT IDENTIFIER REAL ';'
	{ $$ = make_realconstant($2, $3); }
	;

fndefext:
	TOKFNDEFEXT IDENTIFIER ';'
	{ $$ = make_fndefext($2); }
	;

argument:
	IDENTIFIER
	{ $$ = make_variable($1); }
	;

arg_list:
	arg_list_inner { $$ = reverse_list($1); }
	;

arg_list_inner:
	arg_list_inner ',' argument	{ $$ = make_list($3, $1); }
	| argument			{ $$ = make_list($1, NULL); }
	|				{ $$ = NULL; }
	;

stmt_list:
	stmt_list_inner { $$ = reverse_list($1); }
	;

stmt_list_inner:
	stmt_list_inner statement { $$ = make_list($2, $1); }
	| { $$ = NULL; }
	;

statement:
	statement_if
	|
	statement_while
	|
	statement_switch
	|
	statement_case
	|
	statement_default
	|
	statement_break ';'
	|
	assignment ';'
	|
	expression ';'
	{ $$ = make_assignment(NULL, $1); }
	|
	statement_return ';'
	;

assignment:
	assign_list '=' expression
	{ $$ = make_assignment($1, $3); }
	;

assign_list:
	assign_list ',' IDENTIFIER
		{ $$ = make_list(make_variable($3), $1); }
	| IDENTIFIER
		{ $$ = make_list(make_variable($1), NULL); }
	| assign_list ',' IDENTIFIER '[' expression ']'
		{ $$ = make_list(make_array($3, $5), $1); }
	| IDENTIFIER '[' expression ']'
		{ $$ = make_list(make_array($1, $3), NULL); }
	;

expression:
	expression '+' expression { $$ = make_binary_op(op_plus, $1, $3); }
	|
	expression '-' expression { $$ = make_binary_op(op_minus, $1, $3); }
	|
	expression '*' expression { $$ = make_binary_op(op_times, $1, $3); }
	|
	expression '/' expression { $$ = make_binary_op(op_divide, $1, $3); }
	|
	expression '<' expression { $$ = make_binary_op(op_lt, $1, $3); }
	|
	expression '>' expression { $$ = make_binary_op(op_gt, $1, $3); }
	|
	expression TOKLE expression { $$ = make_binary_op(op_le, $1, $3); }
	|
	expression TOKGE expression { $$ = make_binary_op(op_ge, $1, $3); }
	|
	expression TOKEQ expression { $$ = make_binary_op(op_eq, $1, $3); }
	|
	expression TOKNE expression { $$ = make_binary_op(op_ne, $1, $3); }
	|
	expression TOKAND expression { $$ = make_binary_op(op_and, $1, $3); }
	|
	expression TOKOR expression { $$ = make_binary_op(op_or, $1, $3); }
	|
	'-' expression { $$ = make_unary_op(op_minus, $2); }
	|
	'(' expression ')' { $$ = $2; }
	|
	IDENTIFIER '(' exp_list ')'
	{ $$ = make_call($1, $3); }
	|
	IDENTIFIER '[' expression ']'
	{ $$ = make_array($1, $3); }
	|
	IDENTIFIER
	{ $$ = make_variable($1); }
	|
	NUMBER
	{ $$ = make_integer($1); }
	|
	REAL
	{ $$ = make_real($1); }
	|
	STRING
	{ $$ = make_string($1); }
	;

exp_list:
	exp_list_inner { $$ = reverse_list($1); }
	;

exp_list_inner:
	exp_list_inner ',' expression	{ $$ = make_list($3, $1); }
	| expression			{ $$ = make_list($1, NULL); }
	|				{ $$ = NULL; }
	;

statement_if:
	TOKIF '(' expression ')' '{' stmt_list '}'
	{ $$ = make_statement(stmt_if, $3, $6, NULL); }
 	|	
	TOKIF '(' expression ')' '{' stmt_list '}' TOKELSE '{' stmt_list '}'
	{ $$ = make_statement(stmt_if, $3, $6, $10); }
	;

statement_while:
	TOKWHILE '(' expression ')' '{' stmt_list '}'
	{ $$ = make_statement(stmt_while, $3, $6, NULL); }
	;

statement_switch:
	TOKSWITCH '(' expression ')' '{' stmt_list '}'
	{ $$ = make_statement(stmt_switch, $3, $6, NULL); }
	;

statement_case:
	TOKCASE NUMBER ':'
	{ $$ = make_case_statement_number($2); }
	|
	TOKCASE IDENTIFIER ':'
	{ $$ = make_case_statement_variable($2); }
	;

statement_return:
	TOKRETURN
	{ $$ = make_return_statement(); }
	;

statement_break:
	TOKBREAK
	{ $$ = make_break_statement(); }
	;

statement_default:
	TOKDEFAULT ':'
	{ $$ = make_default_statement(); }
	;

