%{
/**
 * Microcode - definition parser 
 * Part 2 - gramatic analysis and structure generations 
 * 
 * @file microcode.y
 * @date 2017
 * @author Lukas Vykydal
 *
 * This file contains gramatic rules in BNF format. This rules are used to analyze input token stream and generate
 * structures describing input march algorithm.
 *
 */

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

void microcode_yyerror(char *s);

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

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

/**
 * Structure to save data int - used to pass result of YACC parser to C API
 */
static t_Microcode *current;

/**
 * \defgroup microcode_linkedList "Linked list access methods"
 * @{
 */
FCN_LIST_APPEND(Operation)
FCN_LIST_APPEND(Element)
FCN_LIST_APPEND(Definition)
FCN_LIST_DROP(Operation, {})
FCN_LIST_DROP(Element, {
  if(currentElement->Operations != NULL)
    DropOperation(currentElement->Operations);
})
FCN_LIST_DROP(Definition, {
  free(currentDefinition->Key);
  free(currentDefinition->Value);
})

FCN_LIST_NEW(Operation, (t_OperationType type, int vector), {
  newItem->Type = type;
  C_ERROR((vector > 64), "Invalid identifier of test data in microcode");
  newItem->TestVector = vector;  
})

FCN_LIST_NEW(Element, (t_Direction dir, t_Operation * ops, t_Access access), {
  newItem->Direction = dir;
  newItem->Operations = ops;
  newItem->AddressAccess = access;
})

FCN_LIST_NEW(Definition, (char* key, char* value), {
  newItem->Key = key;
  newItem->Value = value;

  current->Definitions = AppendDefinition(current->Definitions, newItem);
})

/* @} */

%}

/* Union contains every possible value (variable) passed between rules */
%union{
  char* Text;
  int   Num; 
  t_OperationType Type;
  t_Direction     Direction;
  t_Access        Access;
  t_Operation*    Operation;
  t_Element*      Element;
}

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

/* Tokens from lexer analysis */
%token TOK_RBRACKET
%token TOK_LBRACKET

%token TOK_UP
%token TOK_DOWN
%token TOK_SINGLE

%token TOK_ROW_ACCESS
%token TOK_COL_ACCESS

%token <Num>  TOK_READ
%token <Num>  TOK_WRITE
%token <Num>  TOK_READ_CHK
%token <Num>  TOK_WRITE_CHK
%token <Num>  TOK_SMARCH
%token <Num>  TOK_SMARCH_CHK
%token        TOK_NOP

%token <Text> TOK_TEXT
%token <Num>  TOK_NUMBER

%token TOK_COLLON

%token TOK_DEFINE
%token TOK_DELAY

/* Type of tokens defined below */
%type <Operation> op
%type <Operation> ops
%type <Element>   element
%type <Element>   element_stmt
%type <Direction> direction
%type <Element>   stmt
%type <Element>   stmts
%type <Access>    access
/* problem is becoaose elelmet_stmt can be from element element. this is ambigous between rules stmts and element_stmt */

%%
file: stmts { current->MarchElements = $1; };

stmts: stmt { $$ = $1; }
     | stmts stmt { if ($1 == NULL) $$ = $2; else $$ = AppendElement($1, $2); };

stmt: define_stmt { $$ = NULL; }
    | element_stmt { $$ = $1; };

define_stmt: TOK_DEFINE TOK_TEXT TOK_TEXT { CreateDefinition($2, $3); };

element_stmt: element {$$ = $1; };

element: direction TOK_LBRACKET ops TOK_RBRACKET { $$ = CreateElement($1, $3, ACCESS_NORMAL); }
       | direction access TOK_LBRACKET ops TOK_RBRACKET { $$ = CreateElement($1, $4, $2); }
       | TOK_DELAY { $$ = CreateElement(DIR_NONE, CreateOperation(OP_DELAY, 0), ACCESS_NORMAL); };

ops: op { $$ = $1; }
   | ops TOK_COLLON op { $$ = AppendOperation($1,$3); };

op: TOK_READ  { $$ = CreateOperation(OP_READ, $1); }
  | TOK_WRITE { $$ = CreateOperation(OP_WRITE, $1); }
  | TOK_READ_CHK { $$ = CreateOperation(OP_READ_CHK, $1); }
  | TOK_WRITE_CHK { $$ = CreateOperation(OP_WRITE_CHK, $1); }
  | TOK_SMARCH { $$ = CreateOperation(OP_SMARCH, $1); }
  | TOK_SMARCH_CHK { $$ = CreateOperation(OP_SMARCH_CHK, $1); }
  | TOK_NOP    { $$ = CreateOperation(OP_NOP, 0);};

direction: TOK_UP     { $$ = DIR_UP; }
         | TOK_DOWN   { $$ = DIR_DOWN; }
         | TOK_SINGLE { $$ = DIR_SINGLE; };

access: TOK_COL_ACCESS { $$ = ACCESS_COL; }
      | TOK_ROW_ACCESS { $$ = ACCESS_ROW; };
%%

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

  assert(s != NULL);

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

/**
 * Free whole t_Microcode structure from memory
 *
 * \param mc Microcode to free from memory
 */
void Microcode_Destroy(t_Microcode *mc) {
  assert( mc != NULL);
  DropElement(mc->MarchElements);
  DropDefinition(mc->Definitions);
  free(mc);
}

/**
 * Parse input \a ptr File and produce t_Micrococe structure of March algorithm
 *
 * \param ptr Opened file pointer to process
 * \return Pointer to t_Microcode structure
 */
t_Microcode *Microcode_ReadFile(FILE* ptr) {
  assert(ptr != NULL);

  t_Microcode *mc = malloc(sizeof(t_Microcode));
  C_ERROR((mc==NULL), "Can't allocate memory");
  mc->Definitions = NULL;
  mc->MarchElements = NULL; 
 
  microcode_yyin = ptr;
  current = mc;
  C_ERROR((microcode_yyparse() != 0), "Can't parse input file");
  current = NULL;

  return mc;
}


char* Microcode_Export(t_Microcode *mc) {
  char *string;
  int size = 100;
  int index = 0;

  string = malloc(sizeof(char)*size);
  C_ERROR((string == NULL), "Can't allocate memory");

  LIST_FOREACH(Definition, mc->Definitions, {
    index += sprintf(string + index, "#define %s %s\n", currentDefinition->Key,
                                                        currentDefinition->Value);
    if(size - index < 20){
      size += 100;
      string = realloc(string, size);
      C_ERROR((string == NULL), "Can't expand allocated memory");
    }
  });

  LIST_FOREACH(Element, mc->MarchElements, {
    switch(currentElement->Direction) {
      case DIR_UP:     string[index++] = '^'; break;
      case DIR_DOWN:   string[index++] = 'V'; break;
      case DIR_SINGLE: string[index++] = '.'; break;
      default:         string[index++] = '?'; break;
    }

    switch(currentElement->AddressAccess) {
      case ACCESS_COL:     string[index++] = '|'; break;
      case ACCESS_ROW:     string[index++] = '_'; break;
      case ACCESS_NORMAL:                         break;
      default:             string[index++] = '?'; break;
    }

    string[index++] = '(';
  
    LIST_FOREACH(Operation, currentElement->Operations, {
      switch(currentOperation->Type) {
        case OP_READ:  string[index++] = 'r'; break;
        case OP_WRITE: string[index++] = 'w'; break;
        case OP_SMARCH: 
          string[index++] = '('; 
          string[index++] = 'w'; 
          string[index++] = (currentOperation->TestVector & 1) + '0'; 
          string[index++] = 'r'; 
          string[index++] = ((currentOperation->TestVector & 4) == 4) ? 'x' : (currentOperation->TestVector >> 1 & 1) + '0';
          string[index++] = ')'; 
          string[index++] = '^'; 
          string[index++] = 'n'; 
          break;
        case OP_SMARCH_CHK: 
          string[index++] = '('; 
          string[index++] = 'w'; 
          string[index++] = 'c'; 
          string[index++] = (currentOperation->TestVector & 1) + '0'; 
          string[index++] = 'r'; 
          string[index++] = 'c'; 
          string[index++] = ((currentOperation->TestVector & 4) == 4) ? 'x' : (currentOperation->TestVector >> 1 & 1) + '0';
          string[index++] = ')'; 
          string[index++] = '^'; 
          string[index++] = 'n'; 
          break;
        default:       string[index++] = '?'; break;
      }

      if (currentOperation->Type != OP_SMARCH)
        index += sprintf(string + index, "%2d", currentOperation->TestVector);
      
      currentOperation = currentOperation->Next;
      if(currentOperation != NULL) string[index++] = ',';
      
      if(size - index < 20){
        size += 100;
        string = realloc(string, size);
        C_ERROR((string == NULL), "Can't expand allocated memory");
      }
    });

    string[index++] = ')';
  });
  
  string[index++] = '\0';
  return string;
}
