%{
/**
 * Parser of LVLIB configuration files 
 * Part 2 - gramatic analysis and TCL dict generations 
 * 
 * @file microcode.y
 * @date 2017
 * @author Lukas Vykydal
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "linkedList.h"
#define MICROCODE_YACC_BRIDGE
#include "microcode.h"
#include <tcl.h>

void config_lvlib_yyerror(char *s);

// Function defined in lexer produced from microcode.l
int config_lvlib_yylex(void);

// Input file pointer for LEX
extern FILE* config_lvlib_yyin;

// Tcl interpreter object
static Tcl_Interp *interp;

static Tcl_Obj* result;

%}

/* Union contains every possible value (variable) passed between rules */
%union{
  char* Text;
  int   Num; 
  double Double;
  struct {
    Tcl_Obj *Name;
    Tcl_Obj *Value;
  } Item;
  Tcl_Obj *Dictionary;
}

/* Top level rule is file - this will be "root" of parsed AST */
%start file

/* Tokens from lexer analysis */
%token TOK_R_BRACKET
%token TOK_L_BRACKET
%token TOK_R_CURLY_BRACKET
%token TOK_L_CURLY_BRACKET
%token TOK_COLLON
%token TOK_SEMICOLLON

%token <Num>    TOK_NUMBER
%token <Double> TOK_DOUBLE
%token <Text>   TOK_STRING


/* Type of tokens defined below */
%type <Item> property;
%type <Item> sectionBody;
%type <Item> sectionHeader;
%type <Item> section;
/* problem is becoaose elelmet_stmt can be from element element. this is ambigous between rules stmts and element_stmt */

%%

file: section { result = $1.Value; };

section: sectionHeader TOK_L_CURLY_BRACKET sectionBody TOK_R_CURLY_BRACKET { $$ = $3; $$.Name = $1.Name; 
                                                                             if ($1.Value != NULL) {
                                                                              Tcl_DictObjPut(interp, $$.Value, Tcl_NewStringObj("#type", -1), $1.Value);
                                                                           }};

sectionHeader: TOK_STRING { $$.Name = Tcl_NewStringObj($1, -1); $$.Value = NULL; }
             | TOK_STRING TOK_L_BRACKET TOK_STRING TOK_R_BRACKET { $$.Name = Tcl_NewStringObj($3, -1);
                                                                   $$.Value = Tcl_NewStringObj($1, -1); };

sectionBody: property { $$.Value = Tcl_NewDictObj(); Tcl_DictObjPut(interp, $$.Value, $1.Name, $1.Value); }
           | section  { $$.Value = Tcl_NewDictObj(); Tcl_DictObjPut(interp, $$.Value, $1.Name, $1.Value); }
           | sectionBody property { Tcl_DictObjPut(interp, $1.Value, $2.Name, $2.Value); $$ = $1; }
           | sectionBody section  { Tcl_DictObjPut(interp, $1.Value, $2.Name, $2.Value); $$ = $1; };

property: TOK_STRING TOK_COLLON TOK_STRING TOK_SEMICOLLON { $$.Name = Tcl_NewStringObj($1, -1); $$.Value = Tcl_NewStringObj($3, -1); }
        | TOK_STRING TOK_COLLON TOK_NUMBER TOK_SEMICOLLON { $$.Name = Tcl_NewStringObj($1, -1); $$.Value = Tcl_NewLongObj($3); }
        | TOK_STRING TOK_COLLON TOK_DOUBLE TOK_SEMICOLLON { $$.Name = Tcl_NewStringObj($1, -1); $$.Value = Tcl_NewDoubleObj($3); }
        | TOK_STRING TOK_SEMICOLLON { $$.Name = Tcl_NewStringObj($1, -1); $$.Value = Tcl_NewIntObj(0);};

%%

/**
 * Print error during parsing. Kill app after printing
 *
 * \param s Error message
 */
void config_lvlib_yyerror(char *s){
  extern int config_lvlib_yylineno;
  extern char *config_lvlib_yytext;

  assert(s != NULL);

  printf("Parse error on line %d near symbol %s \n\t%s\n", config_lvlib_yylineno, config_lvlib_yytext, s);
  exit(1);
}

static int TclConfigLvlib(ClientData clientData, Tcl_Interp *interpr, int argc, Tcl_Obj * CONST *objv){
  if(argc != 2){  
    Tcl_WrongNumArgs(interpr, 1, objv, "usage: readLvlib [filename]");
    return TCL_ERROR;
  }

  OPEN(config_lvlib_yyin, Tcl_GetStringFromObj(objv[1],NULL), "r");
  config_lvlib_yyparse();  
  fclose(config_lvlib_yyin);

  Tcl_SetObjResult(interpr, result);
  return TCL_OK;
}

void Config_lvlib_TclRegister(Tcl_Interp *tcl){
  if(Tcl_CreateObjCommand(tcl, "readLvlib",  TclConfigLvlib, NULL, NULL) == NULL){
    printf("Error creating microcode command\n");
  }
}
