%{

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include <posthaste/parser.h>
#include <posthaste/date.h>
#include <posthaste/ast.h>

%}

%locations

%define parse.trace
%define parse.error verbose
%define api.pure full
%define lr.type ielr

%lex-param {void *scanner} {struct parser *parser}
%parse-param {void *scanner} {struct parser* parser}

%union {
	struct ast *node;
	ph_date_t num;
	int64_t snum;
	char *str;
};

%token <str> STRING
%token <num> DATE_LITERAL
%token <snum> INT_LITERAL
%token <str> IDENT;
%token <str> FUNC_IDENT;
%token <str> PROC_IDENT;

%token LPAREN "("
%token RPAREN ")"
%token LSQUARE "["
%token RSQUARE "]"
%token LCURLY "{"
%token RCURLY "}"

%token APOSTROPHE "'"
%token AMPERSAND "&"
%token COMMA ","
%token DOT "."
%token EQ "="
%token LT "<"
%token PLUS "+"
%token MINUS "-"
%token MULT "*"
%token DIV "/"

%token VAR "var"
%token IS "is"
%token UNLESS "unless"
%token OTHERWISE "otherwise"
%token UNTIL "until"
%token DO "do"
%token DONE "done"
%token PROCEDURE "procedure"
%token FUNCTION "function"
%token RETURN "return"
%token PRINT "print"
%token END "end"

%nterm <node> program
%nterm <node> definition
%nterm <node> definitions
%nterm <node> opt_definitions
%nterm <node> variable_definition
%nterm <node> variable_definitions
%nterm <node> opt_variable_definitions
%nterm <node> procedure_definition
%nterm <node> function_definition
%nterm <node> formal_arg
%nterm <node> formals
%nterm <node> lvalue
%nterm <node> rvalue
%nterm <node> print_item
%nterm <node> print_items
%nterm <node> opt_formals
%nterm <node> statement
%nterm <node> statement_list
%nterm <node> opt_arguments
%nterm <node> arguments
%nterm <node> print_statement
%nterm <node> unless_statement
%nterm <node> unless_expression
%nterm <node> until_statement
%nterm <node> assignment
%nterm <node> procedure_call
%nterm <node> function_call
%nterm <node> simple_expr
%nterm <node> atom
%nterm <node> term
%nterm <node> factor
%nterm <node> expression
%nterm <node> ident

/* special case */
%nterm <str> return
%nterm <str> opt_return

%{

/** Modifies the signature of yylex to fit our parser better. */
#define YY_DECL int yylex(YYSTYPE *yylval, YYLTYPE *yylloc, \
	                  void *yyscanner, struct parser *parser)

/**
 * Declare yylex.
 *
 * @param yylval Bison current value.
 * @param yylloc Bison location info.
 * @param yyscanner Flex scanner.
 * @param parser Current parser state.
 * @return \c 0 when succesful, \c 1 otherwise.
 * More info on yylex() can be found in the flex manual.
 */
YY_DECL;

/**
 * Convert bison location info to our own source location info.
 *
 * @param yylloc Bison location info.
 * @return Internal location info.
 */
static struct src_loc src_loc(YYLTYPE yylloc);

/**
 * Print parsing error.
 * Automatically called by bison.
 *
 * @param yylloc Location of error.
 * @param lexer Lexer.
 * @param parser Parser state.
 * @param msg Message to print.
 */
static void yyerror(YYLTYPE *yylloc, void *lexer,
		struct parser *parser, const char *msg);

%}

%start program;
%%

statement_list
	: statement "," statement_list { $$ = $1; $$->n = $3; }
	| statement

definitions
	: definition definitions { $$ = $1; $$->n = $2; }
	| definition

definition
	: function_definition
	| procedure_definition
	| variable_definition

variable_definition
	: "var" IDENT "=" expression {
		$$ = gen_var_def($[IDENT], $[expression], src_loc(@$));
	}

variable_definitions
	: variable_definition variable_definitions { $$ = $1; $$->n = $2; }
	| variable_definition

opt_variable_definitions
	: variable_definitions
	| { $$ = NULL; }

return
	: "return" IDENT { $$ = $2; }

opt_return
	: return
	| { $$ = NULL; }

function_definition
	: "function" FUNC_IDENT "{" opt_formals "}"
	return opt_variable_definitions "is" rvalue "end" "function" {
		struct ast *ret = gen_return($[rvalue], $[rvalue]->loc);
		$$ = gen_func_def($[FUNC_IDENT],
				  $[return],
				  $[opt_formals],
				  $[opt_variable_definitions],
				  ret,
				  src_loc(@$));
	}

procedure_definition
	: "procedure" PROC_IDENT "{" opt_formals "}"
	opt_return opt_variable_definitions "is" statement_list
	"end" "procedure" {
		$$ = gen_proc_def($[PROC_IDENT],
				$[opt_return],
				$[opt_formals],
				$[opt_variable_definitions],
				$[statement_list],
				src_loc(@$));
	}

formals
	: formal_arg "," formals { $$ = $1; $$->n = $3; }
	| formal_arg

opt_formals
	: formals
	| { $$ = NULL; }

formal_arg
	: IDENT "[" IDENT "]" {
		$$ = gen_formal_def($1, $3, src_loc(@$));
	}

procedure_call
	: PROC_IDENT "(" opt_arguments ")" {
		$$ = gen_proc_call($[PROC_IDENT], $[opt_arguments], src_loc(@$));
	}

arguments
	: expression "," arguments { $$ = $1; $$->n = $3; }
	| expression

opt_arguments
	: arguments
	| { $$ = NULL; }

assignment
	: lvalue "=" rvalue {
		$$ = gen_assign($[lvalue], $[rvalue], src_loc(@$));
	}

ident
	: IDENT {
		$$ = gen_id($[IDENT], src_loc(@$));
	}

lvalue
	: ident
	| ident "." IDENT { $$ = gen_dot($1, $3, src_loc(@$)); }

rvalue
	: expression
	| unless_expression

print_statement
	: "print" print_items { $$ = gen_print($[print_items], src_loc(@$)); }

print_items
	: print_item "&" print_items { $$ = $1; $$->n = $3; }
	| print_item

print_item
	: STRING { $$ = gen_const_string($[STRING], src_loc(@$)); }
	| expression

statement
	: procedure_call
	| assignment
	| print_statement
	| until_statement
	| unless_statement
	| "return" expression { $$ = gen_return($[expression], src_loc(@$)); }

until_statement
	: "do" statement_list "until" expression {
		$$ = gen_until($[statement_list], $[expression], src_loc(@$));
	}

unless_statement
	: "do" statement_list "unless" expression "done" {
		$$ = gen_unless($[statement_list], $[expression], NULL, src_loc(@$));
	}
	| "do" statement_list "unless" expression "otherwise" statement_list "done" {
		$$ = gen_unless($2, $[expression], $6, src_loc(@$));
	}

expression
	: simple_expr
	| expression "=" simple_expr {
		$$ = gen_eq($1, $[simple_expr], src_loc(@$));
	}
	| expression "<" simple_expr {
		$$ = gen_lt($1, $[simple_expr], src_loc(@$));
	}

simple_expr
	: term
	| simple_expr "+" term {
		$$ = gen_add($1, $[term], src_loc(@$));
	}
	| simple_expr "-" term {
		$$ = gen_sub($1, $[term], src_loc(@$));
	}

term
	: factor
	| term "*" factor {
		$$ = gen_mul($1, $[factor], src_loc(@$));
	}
	| term "/" factor {
		$$ = gen_div($1, $[factor], src_loc(@$));
	}

factor
	: "+" atom {
		$$ = gen_pos($[atom], src_loc(@$));
	}
	| "-" atom {
		$$ = gen_neg($[atom], src_loc(@$));
	}
	| atom

atom
	: ident
	| ident "'" IDENT {
		$$ = gen_attr($[ident], $[IDENT], src_loc(@$));
	}
	| INT_LITERAL {
		$$ = gen_const_int($[INT_LITERAL], src_loc(@$));
	}
	| DATE_LITERAL {
		$$ = gen_const_date($[DATE_LITERAL], src_loc(@$));
	}
	| function_call
	| procedure_call
	| "(" expression ")" { $$ = $2; }

function_call
	: FUNC_IDENT "(" opt_arguments ")" {
		$$ = gen_func_call($[FUNC_IDENT], $[opt_arguments], src_loc(@$));
	}

unless_expression
	: "do" expression "unless" expression "otherwise" expression "done" {
		$$ = gen_unless_expr($2, $4, $6, src_loc(@$));
	}

opt_definitions
	: definitions
	| { $$ = NULL; }

program
	: opt_definitions statement_list {
		if ($1) {
			ast_last($1)->n = $2;
			$$ = $1;
		}
		else {
			$$ = $2;
		}

		parser->tree = $$;
	}
	| error {
		/* make sure to catch parse errors */
		parser->failed = true;
	}

%%

#include "gen_lexer.inc"

static struct src_loc src_loc(YYLTYPE yylloc)
{
	struct src_loc loc;
	loc.first_line = yylloc.first_line;
	loc.last_line = yylloc.last_line;
	loc.first_col = yylloc.first_column;
	loc.last_col = yylloc.last_column;
	return loc;
}

static void yyerror(YYLTYPE *yylloc, void *lexer,
		struct parser *parser, const char *msg)
{
	(void)lexer;

	struct src_issue issue;
	issue.loc = src_loc(*yylloc);
	issue.fname = parser->fname;
	issue.buf = parser->buf;

	src_issue(issue, msg);
}

struct parser *create_parser()
{
	return calloc(1, sizeof(struct parser));
}

void destroy_parser(struct parser *p)
{
	yylex_destroy(p->lexer);
	free(p);
}

void parse(struct parser *p, const char *fname, const char *buf)
{
	p->fname = fname;
	p->buf = buf;

	p->comment_nesting = 0;

	p->failed = false;

	yylex_init(&p->lexer);

	yy_scan_string(buf, p->lexer);
	yyparse(p->lexer, p);
}