%{
#define YYDEBUG 1
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "ursa.h"

/* prototypes */
nodeType *opr(int oper, int nops, ...);
nodeType *id(char* i);
nodeType *intConst(char* value);
nodeType *boolConst(bool value);
void freeNode(nodeType *p);
int ex(nodeType *p);
int store_procedure(nodeType *p);
int yylex(void);

void yyerror(char *s);
%}

%union {
    char* sValue;               /* integer value written as string */
    char  bValue;               /* boolean value */
    char* sName;                /* symbol table index */
    nodeType *nPtr;             /* node pointer */
};

%token <sValue> INTEGER
%token <bValue> BOOLEAN
%token <sName> BOOLEAN_VARIABLE
%token <sName> INTEGER_VARIABLE
%token <sName> BOOLEAN_ARRAY
%token <sName> INTEGER_ARRAY
%token <sName> PROCEDURE_ID
%token FOR WHILE IF PRINT PRINTB PRINTX MINIMIZE MAXIMIZE ASSERT ASSERTA LIST CLEAR HALT PROCEDURE CALL
%nonassoc IFX
%nonassoc ELSE


%left PLUSEQ MINUSEQ MULTEQ DIVEQ ANDEQ OREQ XOREQ LSHIFTEQ RSHIFTEQ 
%left BITWISEANDEQ BITWISEOREQ BITWISEXOREQ; 
%left LOGICALXOR 
%left LOGICALOR 
%left LOGICALAND  
%left '|' 
%left '^' 
%left '&' 
%left '>' '<' GE LE 
%left EQ NE 
%left LSHIFT RSHIFT        
%left '+' '-' 
%left '*' '/'  
%left '!' 

%nonassoc PLUSPLUS MINUSMINUS UMINUS '~' ITE BOOL2NUM NUM2BOOL SGN

%type <nPtr> stmt expr bool_expr single_stmt comp_stmt stmt_list constraint_list procedure arg arg_list form_arg form_arg_list

%%

program:
        procedure_list main           { exit(0); }
        | main                        { exit(0); }
        ;

procedure_list:
        procedure                     { store_procedure($1); }
        | procedure_list procedure    { store_procedure($2); }
        ;

procedure: 
        PROCEDURE PROCEDURE_ID '(' form_arg_list ')' '{' stmt_list '}' { $$ = opr(PROCEDURE, 3,  id($2), $4, $7); }
        ;

form_arg_list: 
        form_arg                           { $$ = $1; }  
        | form_arg_list ',' form_arg       { $$ = opr(';', 2, $1, $3); }
        ;

form_arg:    
        INTEGER_VARIABLE                   { $$ = id($1); }
        | BOOLEAN_VARIABLE                 { $$ = id($1); }
        ;

arg_list: 
        arg                                { $$ = $1; }  
        | arg_list ',' arg                 { $$ = opr(';', 2, $1, $3); }
        ;

arg:    
        expr                               { $$ = $1; }
        | bool_expr                        { $$ = $1; }
        ;


main:
          main stmt         { ex($2); /* freeNode($2); */ }
        | /* NULL */
        ;


stmt:
          single_stmt ';'                                     { $$ = $1; }
        | comp_stmt                                           { $$ = $1; }


single_stmt:
          ';'                                                 { $$ = opr(';', 2, NULL, NULL); }
        | CALL PROCEDURE_ID '(' arg_list ')'                  { $$ = opr(CALL, 2,  id($2), $4); }
        | LIST                                                { $$ = opr(LIST, 0); }
        | CLEAR                                               { $$ = opr(CLEAR, 0); }
        | HALT                                                { $$ = opr(HALT, 0); }
        | PRINT expr                                          { $$ = opr(PRINT, 1, $2); }
        | PRINTB expr                                         { $$ = opr(PRINTB, 1, $2); }
        | PRINTX expr                                         { $$ = opr(PRINTX, 1, $2); }
        | PRINT bool_expr                                     { $$ = opr(PRINT, 1, $2); }
        | MINIMIZE '(' INTEGER_VARIABLE ',' expr ',' expr ')' { $$ = opr(MINIMIZE, 3, id($3), $5, $7); }
        | MAXIMIZE '(' INTEGER_VARIABLE ',' expr ',' expr ')' { $$ = opr(MAXIMIZE, 3, id($3), $5, $7); }
        | ASSERT '(' constraint_list ')'                      { $$ = opr(ASSERT, 1, $3); }
        | ASSERTA '(' constraint_list ')'                     { $$ = opr(ASSERTA, 1, $3); }

        | INTEGER_VARIABLE '=' expr                           { $$ = opr('=', 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' '=' expr              { $$ = opr('=', 3, id($1), $3, $6); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' '=' expr { $$ = opr('=', 4, id($1), $3, $6, $9); }

        | INTEGER_VARIABLE PLUSPLUS                           { $$ = opr(PLUSPLUS, 1, id($1)); }
        | INTEGER_VARIABLE '[' expr ']' PLUSPLUS              { $$ = opr(PLUSPLUS, 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' PLUSPLUS { $$ = opr(PLUSPLUS, 3, id($1), $3, $6); }

        | INTEGER_VARIABLE MINUSMINUS                             { $$ = opr(MINUSMINUS, 1, id($1)); }
        | INTEGER_VARIABLE '[' expr ']' MINUSMINUS                { $$ = opr(MINUSMINUS, 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' MINUSMINUS   { $$ = opr(MINUSMINUS, 3, id($1), $3, $6); }

        | INTEGER_VARIABLE PLUSEQ expr                            { $$ = opr(PLUSEQ, 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' PLUSEQ expr               { $$ = opr(PLUSEQ, 3, id($1), $3, $6); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' PLUSEQ expr  { $$ = opr(PLUSEQ, 4, id($1), $3, $6, $9); }

        | INTEGER_VARIABLE MINUSEQ expr                           { $$ = opr(MINUSEQ, 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' MINUSEQ expr              { $$ = opr(MINUSEQ, 3, id($1), $3, $6); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' MINUSEQ expr { $$ = opr(MINUSEQ, 4, id($1), $3, $6, $9); }

        | INTEGER_VARIABLE MULTEQ expr                            { $$ = opr(MULTEQ, 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' MULTEQ expr               { $$ = opr(MULTEQ, 3, id($1), $3, $6); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' MULTEQ expr  { $$ = opr(MULTEQ, 4, id($1), $3, $6, $9); }

        | INTEGER_VARIABLE DIVEQ expr                             { $$ = opr(DIVEQ, 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' DIVEQ expr                { $$ = opr(DIVEQ, 3, id($1), $3, $6); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' DIVEQ expr   { $$ = opr(DIVEQ, 4, id($1), $3, $6, $9); }

        | INTEGER_VARIABLE BITWISEANDEQ expr                             { $$ = opr(BITWISEANDEQ, 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' BITWISEANDEQ expr                { $$ = opr(BITWISEANDEQ, 3, id($1), $3, $6); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' BITWISEANDEQ expr   { $$ = opr(BITWISEANDEQ, 4, id($1), $3, $6, $9); }

        | INTEGER_VARIABLE BITWISEOREQ expr                              { $$ = opr(BITWISEOREQ, 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' BITWISEOREQ expr                 { $$ = opr(BITWISEOREQ, 3, id($1), $3, $6); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' BITWISEOREQ expr    { $$ = opr(BITWISEOREQ, 4, id($1), $3, $6, $9); }

        | INTEGER_VARIABLE BITWISEXOREQ expr                             { $$ = opr(BITWISEXOREQ, 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' BITWISEXOREQ expr                { $$ = opr(BITWISEXOREQ, 3, id($1), $3, $6); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' BITWISEXOREQ expr   { $$ = opr(BITWISEXOREQ, 4, id($1), $3, $6, $9); }

        | INTEGER_VARIABLE LSHIFTEQ expr                             { $$ = opr(LSHIFTEQ, 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' LSHIFTEQ expr                { $$ = opr(LSHIFTEQ, 3, id($1), $3, $6); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' LSHIFTEQ expr   { $$ = opr(LSHIFTEQ, 4, id($1), $3, $6, $9); }

        | INTEGER_VARIABLE RSHIFTEQ expr                             { $$ = opr(RSHIFTEQ, 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' RSHIFTEQ expr                { $$ = opr(RSHIFTEQ, 3, id($1), $3, $6); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' RSHIFTEQ expr   { $$ = opr(RSHIFTEQ, 4, id($1), $3, $6, $9); }

        | BOOLEAN_VARIABLE '=' bool_expr                             { $$ = opr('=', 2, id($1), $3); }
        | BOOLEAN_VARIABLE '[' expr ']' '=' bool_expr                { $$ = opr('=', 3, id($1), $3, $6); }
        | BOOLEAN_VARIABLE '[' expr ']' '[' expr ']' '=' bool_expr   { $$ = opr('=', 4, id($1), $3, $6, $9); }

        | BOOLEAN_VARIABLE ANDEQ bool_expr                           { $$ = opr(ANDEQ, 2, id($1), $3); }
        | BOOLEAN_VARIABLE '[' expr ']' ANDEQ bool_expr              { $$ = opr(ANDEQ, 3, id($1), $3, $6); }
        | BOOLEAN_VARIABLE '[' expr ']' '[' expr ']' ANDEQ bool_expr { $$ = opr(ANDEQ, 4, id($1), $3, $6, $9); }

        | BOOLEAN_VARIABLE OREQ bool_expr                            { $$ = opr(OREQ, 2, id($1), $3); }
        | BOOLEAN_VARIABLE '[' expr ']' OREQ bool_expr               { $$ = opr(OREQ, 3, id($1), $3, $6); }
        | BOOLEAN_VARIABLE '[' expr ']' '[' expr ']' OREQ bool_expr  { $$ = opr(OREQ, 4, id($1), $3, $6, $9); }

        | BOOLEAN_VARIABLE XOREQ bool_expr                           { $$ = opr(XOREQ, 2, id($1), $3); }
        | BOOLEAN_VARIABLE '[' expr ']' XOREQ bool_expr              { $$ = opr(XOREQ, 3, id($1), $3, $6); }
        | BOOLEAN_VARIABLE '[' expr ']' '[' expr ']' XOREQ bool_expr { $$ = opr(XOREQ, 4, id($1), $3, $6, $9); }
        ;

comp_stmt:
          WHILE '(' bool_expr ')' stmt                        { $$ = opr(WHILE, 2, $3, $5); }
        | FOR '(' single_stmt ';' bool_expr ';' single_stmt ')' stmt  { $$ = opr(FOR, 4, $3, $5, $7, $9); }
        | IF '(' bool_expr ')' stmt %prec IFX                 { $$ = opr(IF, 2, $3, $5); }
        | IF '(' bool_expr ')' stmt ELSE stmt                 { $$ = opr(IF, 3, $3, $5, $7); }
        | '{' stmt_list '}'                                   { $$ = $2; }


stmt_list:
          stmt                                                { $$ = $1; }
        | stmt_list stmt                                      { $$ = opr(';', 2, $1, $2); }
        ;


constraint_list: 
          bool_expr                      { $$ = $1; }
        | constraint_list ';' bool_expr  { $$ = opr(';', 2, $1, $3); }
        ;

expr:
          INTEGER                                    { $$ = intConst($1); }
        | INTEGER_VARIABLE                           { $$ = id($1); }
        | INTEGER_VARIABLE '[' expr ']'              { $$ = opr('@', 2, id($1), $3); }
        | INTEGER_VARIABLE '[' expr ']' '[' expr ']' { $$ = opr('@', 3, id($1), $3, $6); }
        | '-' expr %prec UMINUS { $$ = opr(UMINUS, 1, $2); }
        | expr LSHIFT expr                           { $$ = opr(LSHIFT, 2, $1, $3); }
        | expr RSHIFT expr                           { $$ = opr(RSHIFT, 2, $1, $3); }
        | expr '+' expr                              { $$ = opr('+', 2, $1, $3); }
        | expr '-' expr                              { $$ = opr('-', 2, $1, $3); }
        | expr '*' expr                              { $$ = opr('*', 2, $1, $3); }
        | expr '/' expr                              { $$ = opr('/', 2, $1, $3); }
        | expr '&' expr                              { $$ = opr('&', 2, $1, $3); }
        | expr '|' expr                              { $$ = opr('|', 2, $1, $3); }
        | expr '^' expr                              { $$ = opr('^', 2, $1, $3); }
        | ITE '(' bool_expr ',' expr ',' expr ')'    { $$ = opr(ITE, 3, $3, $5, $7); }
        | BOOL2NUM '(' bool_expr ')'                 { $$ = opr(BOOL2NUM, 1, $3); }
        | SGN '(' expr ')'                           { $$ = opr(SGN, 1, $3); }
        | '~' expr                                   { $$ = opr('~', 1, $2); }
        | '(' expr ')'                               { $$ = $2; }
        ;

bool_expr:
          BOOLEAN                                    { $$ = boolConst($1); }
        | BOOLEAN_VARIABLE                           { $$ = id($1); }
        | BOOLEAN_VARIABLE '[' expr ']'              { $$ = opr('@', 2, id($1), $3); }
        | BOOLEAN_VARIABLE '[' expr ']' '[' expr ']' { $$ = opr('@', 3, id($1), $3, $6); }
	      | bool_expr LOGICALAND bool_expr           { $$ = opr(LOGICALAND, 2, $1, $3); }
        | bool_expr LOGICALOR bool_expr              { $$ = opr(LOGICALOR, 2, $1, $3); }
        | bool_expr LOGICALXOR bool_expr             { $$ = opr(LOGICALXOR, 2, $1, $3); }
        | '!' bool_expr                              { $$ = opr('!', 1, $2); }
        | expr '<' expr                              { $$ = opr('<', 2, $1, $3); }
        | expr '>' expr                              { $$ = opr('>', 2, $1, $3); }
        | expr GE expr                               { $$ = opr(GE, 2, $1, $3); }
        | expr LE expr                               { $$ = opr(LE, 2, $1, $3); }
        | expr NE expr                               { $$ = opr(NE, 2, $1, $3); }
        | expr EQ expr                               { $$ = opr(EQ, 2, $1, $3); }
        | ITE '(' bool_expr ',' bool_expr ',' bool_expr ')'  { $$ = opr(ITE, 3, $3, $5, $7); }
        | NUM2BOOL '(' expr ')'                      { $$ = opr(NUM2BOOL, 1, $3); }
        | '(' bool_expr ')'                          { $$ = $2; }
        ;


%%

#define SIZEOF_NODETYPE ((char *)&p->intConst - (char *)p)


nodeType *intConst(char* value) {
    nodeType *p;
    size_t nodeSize;

    /* allocate node */
    nodeSize = SIZEOF_NODETYPE + sizeof(intConstNodeType);
    if ((p = (nodeType*)malloc(nodeSize)) == NULL)
        yyerror((char*)"out of memory");

    /* copy information */
    p->type = typeIntConst;
    p->intConst.value = value;

    return p;
}


nodeType *boolConst(bool value) {
    nodeType *p;
    size_t nodeSize;

    /* allocate node */
    nodeSize = SIZEOF_NODETYPE + sizeof(boolConstNodeType);
    if ((p = (nodeType*)malloc(nodeSize)) == NULL)
        yyerror((char*)"out of memory");

    /* copy information */
    p->type = typeBoolConst;
    p->boolConst.value = value;

    return p;
}



nodeType *id(char *i) {
    nodeType *p;
    size_t nodeSize;

    /* allocate node */
    nodeSize = SIZEOF_NODETYPE + sizeof(idNodeType);
    if ((p = (nodeType*)malloc(nodeSize)) == NULL)
        yyerror((char*)"out of memory");

    /* copy information */
    p->type = typeId;
    p->id.i = i;

    return p;
}

nodeType *opr(int oper, int nops, ...) {
    va_list ap;
    nodeType *p;
    size_t nodeSize;
    int i;

    /* allocate node */
    nodeSize = SIZEOF_NODETYPE + sizeof(oprNodeType) +
        (nops - 1) * sizeof(nodeType*);
    if ((p = (nodeType*)malloc(nodeSize)) == NULL)
        yyerror((char*)"out of memory");

    /* copy information */
    p->type = typeOpr;
    p->opr.oper = oper;
    p->opr.nops = nops;
    va_start(ap, nops);
    for (i = 0; i < nops; i++)
        p->opr.op[i] = va_arg(ap, nodeType*);
    va_end(ap);
    return p;
}

void freeNode(nodeType *p) {
    int i;

    if (!p) return;
    if (p->type == typeOpr) {
        for (i = 0; i < p->opr.nops; i++)
            freeNode(p->opr.op[i]);
    }
    free (p);
}

void yyerror(char *s) {
    fprintf(stdout, "%s\n", s);
}



