/* $Id$
 *
 * Intermediate code interpreter.
 *
 * Copyright (C) 2008-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

%error-verbose
%defines
%parse-param	{struct fauhdli *instance}
%parse-param	{const char *filename}
%lex-param	{instance}
%lex-param	{filename}

%{
#include "basetypes.h"
#include <assert.h>
#include <stdio.h>
#include "fauhdli_private.h"
#include "list.h"
%}

%token t_ABORT
%token t_ADD
%token t_AOFFSET
%token t_BEGINTRANSFER
%token t_CALL
%token t_CONNECT
%token t_CONTAINER
%token t_DATA
%token t_DIV
%token t_DRIVER
%token t_END
%token t_ENDTRANSFER
%token t_GETPARAM
%token t_GETSIG
%token t_GETSIMTIME
%token t_IMUL
%token t_IS
%token t_JB
%token t_JBE
%token t_JE
%token t_JMP
%token t_JNE
%token t_LOG
%token t_MOV
%token t_PROC
%token t_RESOLVEDBY
%token t_RETURN
%token t_ROFFSET
%token t_SETPARAM
%token t_SIGNAL
%token t_SUB
%token t_SUSPEND
%token t_TYPE
%token t_UPDATE
%token t_VARIABLE
%token t_WAKEAT
%token t_WAKEON
%token t_Reference
%token t_Int
%token t_Float
%token t_Label
%token t_Semicolon
%token t_Comma
%token t_CodeSegment
%token t_StackSegment
%token t_TransferSegment
%token t_TypeSegment
%token t_LeftParen
%token t_RightParen
%token t_LeftBracket
%token t_RightBracket
%token t_ValAssign
%token t_Target
%token t_TypeSpecFloat
%token t_TypeSpecInt
%token t_TypeSpecPointer
%token t_Register
%token t_Registers
%token t_Identifier
%token t_Colon
%token t_Arrow
%token t_PosNumber
%token t_Invalid
%token t_AnnotateEqSym
%token t_AnnotateName
%token t_AnnotateString
%token t_AnnotateInt

%union {
	universal_real		univ_real;
	universal_integer	univ_int;
	char *			text;
	unsigned int		number;
	struct operand *	operand;
	struct opcode *		opcode;
	enum storage_kind	storage_kind;
	enum type_kind		type_spec;
	enum opcode_kind	opcode_kind;
	struct slist *		list;
	struct code_container * container;
	struct data_definition *data_definition;
	struct type_declaration *type_declaration;
	struct type_element *	type_element;
	int			annotate_int;
	struct annotation_spec *annotation_spec;
};

%{
#include <stdlib.h>
#include <string.h>
#include "glue-log.h"

static void 
yyerror(struct fauhdli *instance, const char *filename, const char *msg);

extern int yylex(const struct fauhdli *instance, const char *filename);
extern int yylineno;
%}


/* ---------------- terminals -------------- */
%type <annotate_int>
	t_AnnotateInt
%type <number>
	t_Register
%type <text>
	t_AnnotateName
	t_AnnotateString
	t_Identifier
	t_Label
	t_Reference
	t_Target
%type <type_spec>
	type_spec
%type <univ_int>
	t_Int
	t_PosNumber
%type <univ_real>
	t_Float
/* -------------- non-terminals ------------ */
%type <annotation_spec>
	annotation_spec
	annotation_spec_int
	annotation_spec_string
%type <container>
	container
%type <data_definition>
	data_definition
%type <list>
	annotation_spec_list
	container_list
	data_declaration_list
	list_of_commands
	immediate_operand_list
	opt_annotation
	opt_container_list
	opt_default_val
	opt_stack_segment
	opt_text_segment
	opt_transfer_segment
	opt_type_segment
	stack_segment
	text_segment
	transfer_segment
	type_declaration_list
	type_list
	type_segment
%type <opcode>
	arith_operation
	cmd_abort
	cmd_aoffset
	cmd_begintrans
	cmd_call
	cmd_connect
	cmd_endtrans
	cmd_getparam
	cmd_getsig
	cmd_getsimtime
	cmd_jmp
	cmd_log
	cmd_mov
	cmd_proc
	cmd_return
	cmd_roffset
	cmd_setparam
	cmd_suspend
	cmd_update
	cmd_wakeat
	cmd_wakeon
	command
	command_wo_annotation
	conditional_jump
	label
%type <opcode_kind>
	arith_opcode
	cond_jump_opcode
%type <operand>
	bool_flag
	container_name
	destination
	immediate_float
	immediate_int
	immediate_operand
	index
	indirect_operand
	operand
	reference_operand
	register_operand
	source
	target
	transfer_element
%type <storage_kind>
	data_kind
%type <text>
	name
	opt_resolution
%type <type_declaration>
	type_declaration
%type <type_element>
	type
	type_name
%type <univ_int>
	opt_array

%%
%start top_container;

top_container:
	container {
		instance->container = $1;
	}
;

opt_container_list:
	/* nothing */ 		{ $$ = NULL; }
	| container_list	{ $$ = $1; }
;

container_list:
	container_list container {
		$$ = $1;
		slist_add($$, $2, instance->callbacks.malloc);
	}
	| container {
		$$ = slist_create(instance->callbacks.malloc);
		slist_add($$, $1, instance->callbacks.malloc);
	}
;

container:
	t_CONTAINER name 
	t_Registers t_PosNumber
	opt_type_segment
	opt_transfer_segment 
	opt_stack_segment 
	opt_container_list
	opt_text_segment
	t_END t_CONTAINER name t_Semicolon {
		$$ = instance->callbacks.malloc(
				sizeof(struct code_container));
		assert($$ != NULL);
		$$->name = $2;
		$$->num_vregs = $4;
		$$->type_definitions = $5;
		$$->transfer_segment = $6;
		$$->stack_segment = $7;
		$$->sub_containers = $8;
		$$->text_segment = $9;
		$$->stack_size = 0;
		$$->transfer_size = 0;
		
		assert($12 != NULL);
		if (strcmp($2, $12) != 0) {
			yyerror(instance, filename, 
				"End Container name doesn't match "
				"container name\n");
			instance->callbacks.free($12);
			YYERROR;
		}
		instance->callbacks.free($12);
	}
;

opt_transfer_segment:
	/* nothing */			{ $$ = NULL; }
	| transfer_segment		{ $$ = $1; }
;

opt_text_segment:
	/* nothing */			{ $$ = NULL; }
	| text_segment			{ $$ = $1; }
;

opt_stack_segment:
	/* nothing */			{ $$ = NULL; }
	| stack_segment			{ $$ = $1; }
;

opt_type_segment:
	/* nothing */			{ $$ = NULL; }
	| type_segment			{ $$ = $1; }
;

transfer_segment:
	t_TransferSegment data_declaration_list { $$ = $2; }
;

text_segment:
	t_CodeSegment list_of_commands	{ $$ = $2; }
;

stack_segment:
	t_StackSegment data_declaration_list	{ $$ = $2; }
;

type_segment:
	t_TypeSegment type_declaration_list 	{ $$ = $2; }
;

list_of_commands:
	list_of_commands command {
		$$ = $1;
		slist_add($$, $2, instance->callbacks.malloc);
	}
	| command {
		$$ = slist_create(instance->callbacks.malloc);
		slist_add($$, $1, instance->callbacks.malloc);
	}
;

data_declaration_list:
	data_declaration_list data_definition {
		$$ = $1;
		slist_add($$, $2, instance->callbacks.malloc);
	}
	| data_definition {
		$$ = slist_create(instance->callbacks.malloc);
		slist_add($$, $1, instance->callbacks.malloc);
	}
;

type_declaration_list:
	type_declaration_list type_declaration {
		$$ = $1;
		slist_add($$, $2, instance->callbacks.malloc);
	}
	| type_declaration {
		$$ = slist_create(instance->callbacks.malloc);
		slist_add($$, $1, instance->callbacks.malloc);
	}
;

/* *************** opcodes ************** */

command:
	command_wo_annotation opt_annotation {
		$$ = $1;
		$$->annotations = $2;
	}

command_wo_annotation:
	cmd_abort		{ $$ = $1; }
	| cmd_aoffset		{ $$ = $1; }
	| cmd_begintrans	{ $$ = $1; }
	| cmd_connect		{ $$ = $1; }
	| cmd_call		{ $$ = $1; }
	| cmd_endtrans		{ $$ = $1; }
	| cmd_getparam		{ $$ = $1; }
	| cmd_getsig		{ $$ = $1; }
	| cmd_getsimtime	{ $$ = $1; }
	| cmd_jmp		{ $$ = $1; }
	| cmd_log		{ $$ = $1; }
	| cmd_mov		{ $$ = $1; }
	| cmd_proc		{ $$ = $1; }
	| cmd_return		{ $$ = $1; }
	| cmd_roffset		{ $$ = $1; }
	| cmd_setparam		{ $$ = $1; }
	| cmd_suspend		{ $$ = $1; }
	| cmd_wakeat		{ $$ = $1; }
	| cmd_wakeon		{ $$ = $1; }
	| cmd_update		{ $$ = $1; }
	| label			{ $$ = $1; }
	| conditional_jump	{ $$ = $1; }
	| arith_operation	{ $$ = $1; }
;

label:	
	t_Label { 
		$$ = fauhdli_alloc_opcode(OPCODE_LABEL, &instance->callbacks);
		$$->label = $1;
		$$->lineno = @1.first_line;
	}
;

cmd_abort:
	t_ABORT {
		$$ = fauhdli_alloc_opcode(OPCODE_ABORT, &instance->callbacks);
		$$->lineno = @1.first_line;
	}
;

cmd_mov:
	t_MOV source t_Comma destination {
		$$ = fauhdli_alloc_opcode(OPCODE_MOV, &instance->callbacks);
		$$->op1 = $2;
		$$->op2 = $4;
		$$->lineno = @1.first_line;
		if ($2->type != $4->type) {
			yyerror(instance, filename,
				"operands must be of same type for MOV.");
			YYERROR;
		}
	}
;

/* TODO 3rd operand is reserved so far */
cmd_getsig:
	t_GETSIG source t_Comma destination {
		/* source is signal, destination the result of the signal */
		$$ = fauhdli_alloc_opcode(OPCODE_GETSIG, &instance->callbacks);
		$$->op1 = $2;
		$$->op2 = $4;
		$$->lineno = @1.first_line;
	}
;

cmd_getsimtime:
	t_GETSIMTIME destination {
		$$ = fauhdli_alloc_opcode(OPCODE_GETSIMTIME,
					&instance->callbacks);
		$$->op1 = $2;
		$$->lineno = @1.first_line;
	}
;

cmd_jmp:
	t_JMP target {
		$$ = fauhdli_alloc_opcode(OPCODE_JMP, &instance->callbacks);
		$$->op1 = $2;
		$$->lineno = @1.first_line;
	}
;

cmd_aoffset:
	t_AOFFSET source t_Comma type_name index t_Comma destination {
		$$ = fauhdli_alloc_opcode(OPCODE_AOFFSET, 
					&instance->callbacks);
		$$->op1 = $2;
		$$->op2 = $5;
		$$->op3 = $7;
		$$->indexed_type = $4;
		$$->lineno = @1.first_line;
	}
;

cmd_roffset:
	t_ROFFSET source t_Comma type_name t_Arrow t_Int t_Comma
	destination {
		$$ = fauhdli_alloc_opcode(OPCODE_ROFFSET, 
					&instance->callbacks);
		$$->op1 = $2;
		$$->op2 = instance->callbacks.malloc(sizeof(struct operand));
		assert($$->op2 != NULL);
		$$->op2->kind = OPERAND_IMMEDIATE;
		$$->op2->type = TYPE_INT;
		$$->op2->bytype.immediate.univ_int = $6;
		$$->op3 = $8;
		$$->indexed_type = $4;
		$$->lineno = @1.first_line;
	}
;

cmd_call:
	t_CALL container_name {
		$$ = fauhdli_alloc_opcode(OPCODE_CALL, &instance->callbacks);
		$$->op1 = $2;
		$$->lineno = @1.first_line;
	}
;

cmd_proc:
	t_PROC container_name {
		$$ = fauhdli_alloc_opcode(OPCODE_PROC, &instance->callbacks);
		$$->op1 = $2;
		$$->lineno = @1.first_line;
	}
;

cmd_return:
	t_RETURN {
		$$ = fauhdli_alloc_opcode(OPCODE_RETURN, 
					&instance->callbacks);
		$$->lineno = @1.first_line;
	}
;

cmd_update:
	t_UPDATE source t_Comma operand t_Comma destination {
		$$ = fauhdli_alloc_opcode(OPCODE_UPDATE, 
						&instance->callbacks);
		$$->op1 = $2;
		$$->op2 = $4;
		$$->op3 = $6;
		$$->lineno = @1.first_line;
	}
;

cmd_suspend: 
	t_SUSPEND {
		$$ = fauhdli_alloc_opcode(OPCODE_SUSPEND, 
					&instance->callbacks);
		$$->lineno = @1.first_line;
	}
;

cmd_wakeat:
	t_WAKEAT source {
		$$ = fauhdli_alloc_opcode(OPCODE_WAKEAT, 
					&instance->callbacks);
		$$->op1 = $2;
		$$->lineno = @1.first_line;
	}
;

cmd_wakeon:
	t_WAKEON source {
		$$ = fauhdli_alloc_opcode(OPCODE_WAKEON, 
					&instance->callbacks);
		$$->op1 = $2;
		$$->lineno = @1.first_line;
	}
;

cmd_begintrans:
	t_BEGINTRANSFER container_name t_Comma container_name {
		$$ = fauhdli_alloc_opcode(OPCODE_BEGINTRANS,
					&instance->callbacks);
		$$->op1 = $2;
		$$->lineno = @1.first_line;
		$$->op2 = $4;
	}
;

cmd_endtrans:
	t_ENDTRANSFER container_name t_Comma bool_flag {
		$$ = fauhdli_alloc_opcode(OPCODE_ENDTRANS, 
					&instance->callbacks);
		$$->op1 = $2;
		$$->op2 = $4;
		$$->lineno = @1.first_line;
	}
;

cmd_setparam:
	t_SETPARAM source t_Comma container_name t_Comma transfer_element {
		$$ = fauhdli_alloc_opcode(OPCODE_SETPARAM, 
					&instance->callbacks);
		$$->op1 = $2;
		$$->op2 = $4;
		$$->op3 = $6;
		$$->lineno = @1.first_line;
	}
;

cmd_getparam:
	t_GETPARAM transfer_element t_Comma container_name t_Comma 
	destination {
		$$ = fauhdli_alloc_opcode(OPCODE_GETPARAM, 
					&instance->callbacks);
		$$->op1 = $2;
		$$->op2 = $4;
		$$->op3 = $6;
		$$->lineno = @1.first_line;
	}
;


cmd_connect:
	t_CONNECT source t_Comma destination {
		$$ = fauhdli_alloc_opcode(OPCODE_CONNECT, 
					&instance->callbacks);
		$$->op1 = $2;
		$$->op2 = $4;
		$$->lineno = @1.first_line;
	}
;

cmd_log:
	t_LOG operand t_Comma operand {
		$$ = fauhdli_alloc_opcode(OPCODE_LOG, 
					&instance->callbacks);
		$$->op1 = $2;
		$$->op2 = $4;
		$$->lineno = @1.first_line;
	}
;

cond_jump_opcode:
	t_JB		{ $$ = OPCODE_JB; }
	| t_JBE		{ $$ = OPCODE_JBE; }
	| t_JE		{ $$ = OPCODE_JE; }
	| t_JNE		{ $$ = OPCODE_JNE; }
;

conditional_jump:
	cond_jump_opcode source t_Comma operand t_Comma target {
		$$ = fauhdli_alloc_opcode($1, &instance->callbacks);
		$$->op1 = $2;
		$$->op2 = $4;
		$$->op3 = $6;
		$$->lineno = @1.first_line;

		if ($2->type != $4->type) {
			yyerror(instance, filename,
				"Compare operands must be of same type for "
				"conditional branch.");
			YYERROR;
		}
	}
;

arith_opcode:
	t_ADD		{ $$ = OPCODE_ADD; }
	| t_SUB		{ $$ = OPCODE_SUB; }
	| t_IMUL	{ $$ = OPCODE_IMUL; }
	| t_DIV		{ $$ = OPCODE_DIV; }
;

arith_operation:
	arith_opcode source t_Comma operand t_Comma destination {
		$$ = fauhdli_alloc_opcode($1, &instance->callbacks);
		$$->op1 = $2;
		$$->op2 = $4;
		$$->op3 = $6;
		$$->lineno = @1.first_line;

		if (($2->type != $4->type) || ($2->type != $6->type)) {
			yyerror(instance, filename,
				"Operands must be of same type for "
				"arithmetic opcode.");
			YYERROR;
		}
	}
;

/* ************** operands ************** */

container_name:
	reference_operand { $$ = $1; }
;

transfer_element:
	reference_operand { $$ = $1; }
;

target:
	t_Target {
		$$ = instance->callbacks.malloc(sizeof(struct operand));
		assert($$ != NULL);
		$$->kind = OPERAND_TARGET;
		$$->type = TYPE_POINTER;
		$$->bytype.target.name = $1;
		$$->bytype.target.ref = NULL;
	}
;

source:
	operand			{ $$ = $1; }
;

destination:
	operand			{ $$ = $1; }
;

operand:
	immediate_operand	{ $$ = $1; }
	| register_operand	{ $$ = $1; }
	| indirect_operand	{ $$ = $1; }
	| reference_operand	{ $$ = $1; }
;

bool_flag:
	immediate_int 		{ $$ = $1; }
;

immediate_operand:
	immediate_int		{ $$ = $1; }
	| immediate_float	{ $$ = $1; }
;

immediate_int:
	t_Int {
		$$ = instance->callbacks.malloc(sizeof(struct operand));
		assert($$ != NULL);
		$$->kind = OPERAND_IMMEDIATE;
		$$->type = TYPE_INT;
		$$->bytype.immediate.univ_int = $1;
	}
;


immediate_float:
	t_Float {
		$$ = instance->callbacks.malloc(sizeof(struct operand));
		assert($$ != NULL);
		$$->kind = OPERAND_IMMEDIATE;
		$$->type = TYPE_FLOAT;
		$$->bytype.immediate.univ_real = $1;
	}
;


reference_operand:
	t_Reference {
		$$ = instance->callbacks.malloc(sizeof(struct operand));
		assert($$ != NULL);
		$$->kind = OPERAND_REFERENCE;
		$$->type = TYPE_POINTER;
		$$->bytype.data.name = $1;
		$$->bytype.data.ref = NULL;
	}
;

indirect_operand:
	type_spec t_LeftParen register_operand t_RightParen {
		if ($3->type != TYPE_POINTER) {
			yyerror(instance, filename,
				"Indirect addressing but register "
				"is no pointer.");
			YYERROR;
		}

		$$ = $3;
		$3->kind = OPERAND_INDIRECT;
		$$->type = $1;
	}
;

register_operand:
	type_spec t_Register {
		$$ = instance->callbacks.malloc(sizeof(struct operand));
		assert($$ != NULL);
		$$->kind = OPERAND_REGISTER;
		$$->type = $1;
		$$->bytype.reg = $2;
	}
;

type_spec:
	t_TypeSpecInt		{ $$ = TYPE_INT; }
	| t_TypeSpecFloat	{ $$ = TYPE_FLOAT; }
	| t_TypeSpecPointer	{ $$ = TYPE_POINTER; }
;

index:
	t_LeftBracket t_PosNumber t_RightBracket {
		$$ = instance->callbacks.malloc(sizeof(struct operand));
		assert($$ != NULL);
		$$->kind = OPERAND_IMMEDIATE;
		$$->type = TYPE_INT;
		$$->bytype.immediate.univ_int = $2;
	}
	| t_LeftBracket register_operand t_RightBracket {
		$$ = $2;
		assert($2->type == TYPE_INT);
	}
;

/* **************** types *************** */

type_declaration:
	t_TYPE name t_IS type_list t_Semicolon {
		$$ = instance->callbacks.malloc(
				sizeof(struct type_declaration));
		assert($$ != NULL);

		$$->name = $2;
		$$->elements = $4;
		$$->elem_count = -1;
	}
;

type_list:
	type_list t_Comma type {
		$$ = $1;
		slist_add($$, $3, instance->callbacks.malloc);
	}
	| type {
		$$ = slist_create(instance->callbacks.malloc);
		slist_add($$, $1, instance->callbacks.malloc);
	}
;

data_definition:
	t_DATA opt_resolution data_kind name type t_Semicolon opt_annotation {
		$$ = instance->callbacks.malloc(
				sizeof(struct data_definition));
		assert($$ != NULL);

		$$->resolver.name = $2;
		$$->resolver.container = NULL;
		$$->name = $4;
		$$->type = $5;
		$$->storage = $3;
		$$->offset = 0;
		$$->storage_size = 0;
		$$->annotations = $7;
	}
;

data_kind:
	t_SIGNAL	{ $$ = STORAGE_SIGNAL; }
	| t_VARIABLE	{ $$ = STORAGE_VARIABLE; }
	| t_DRIVER	{ $$ = STORAGE_DRIVER; }
;

opt_resolution:
	/* nothing */		{ $$ = NULL; }
	| t_RESOLVEDBY name	{ $$ = $2; }
;

type:
	name opt_array opt_default_val opt_annotation {
		$$ = instance->callbacks.malloc(sizeof(struct type_element));
		assert($$ != NULL);
		$$->name = $1;
		$$->elements = $2;
		$$->initial_list = $3;
		$$->elem_count = -1;
		$$->offset = 0;
		$$->annotations = $4;
	}
;

type_name:
	name { 
		$$ = instance->callbacks.malloc(sizeof(struct type_element));
		assert($$ != NULL);
		$$->name = $1;
		$$->elements = 1;
		$$->initial_list = NULL;
		$$->elem_count = -1;
		$$->offset = 0;
		$$->annotations = NULL;
	}
;

opt_array:
	/* nothing */	{ 
		$$ = 1; 
	}
	| t_LeftBracket t_PosNumber t_RightBracket {
		$$ = $2;
	}
;

opt_default_val:
	/* nothing */ { 
		$$ = slist_create(instance->callbacks.malloc);
	}
	| t_ValAssign immediate_operand_list {
		$$ = $2;
	}
;

immediate_operand_list:
	immediate_operand_list immediate_operand {
		$$ = $1;
		slist_add($$, $2, instance->callbacks.malloc);
	}
	| immediate_operand {
		$$ = slist_create(instance->callbacks.malloc);
		slist_add($$, $1, instance->callbacks.malloc);
	}

;

name:
	t_Identifier	{ $$ = $1; }
;

/* ****************** annotations ***************** */

opt_annotation:
	/* nothing */		{ $$ = NULL; }
	| annotation_spec_list	{ $$ = $1; }
;

annotation_spec_list:
	annotation_spec	{
		$$ = slist_create(instance->callbacks.malloc);
		slist_add($$, $1, instance->callbacks.malloc);
	}
	| annotation_spec_list annotation_spec {
		$$ = $1;
		slist_add($$, $2, instance->callbacks.malloc);
	}
;

annotation_spec:
	annotation_spec_int		{ $$ = $1; }
	| annotation_spec_string	{ $$ = $1; }
;

annotation_spec_int:
	t_AnnotateName t_AnnotateEqSym t_AnnotateInt {
		$$ = instance->callbacks.malloc(
				sizeof(struct annotation_spec));
		assert($$ != NULL);

		$$->name = $1;
		$$->int_value = $3;
		$$->string_value = NULL;
	}
;

annotation_spec_string:
	t_AnnotateName t_AnnotateEqSym t_AnnotateString {
		$$ = instance->callbacks.malloc(
				sizeof(struct annotation_spec));
		assert($$ != NULL);

		$$->name = $1;
		$$->string_value = $3;
		$$->int_value = -1;
	}
;


%%

#include "glue-log.h"

static void
yyerror(struct fauhdli *instance, const char *filename, const char *msg)
{
	/* FIXME line number */
	assert(instance->callbacks.log != NULL);
	instance->callbacks.log(FAUHDLI_LOG_ERROR, "fauhdli", "parser", 
		"%s:%d: Parse error: %s\n", filename, yylineno, msg);
}
