%{


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

#include "mytypes.h"
#include "types.h"
#include "tree_basic.h"
#include "str.h"
#include "dbug.h"
#include "ctinfo.h"
#include "free.h"
#include "globals.h"

static node *parseresult = NULL;
extern int yylex();
static int yyerror( char *errname);
static node *reverseVardecls (node*);

#define YYDEBUG 1

%}

%union {
 nodetype            nodetype;
 char               *id;
 int                 cint;
 float               cflt;
 node               *node;
 type_t              ctype;
}

%token BRACKET_L BRACKET_R BRACE_L BRACE_R PAREN_L PAREN_R COMMA SEMICOLON
%token MINUS PLUS STAR SLASH PERCENT LE LT GE GT EQ NE OR AND NOT
%token TRUEVAL FALSEVAL LET VOID
%token RETURN FOR DO WHILE IF ELSE
%token EXTERN EXPORT COLON QUESMARK

%token <cint> NUM
%token <cflt> FLOAT
%token <id> ID
%token <ctype> TYPE

%type <node> intval floatval boolval constant exprs expr
%type <node> stmts stmt assign varlet program decls decl
%type <node> fundef globaldef funbody params vardecls
%type <node> block call fundec globaldec localfundefs
%type <node> nonzero_params nonzero_exprs arrexpr param
%type <node> nonzero_ids arrexprs

/* Attaches else to closest if (since else has higher precedence, the parser
 * shifts instead of reducing) */
%precedence IFSTMT
%precedence ELSE

%right LET
%right QUESMARK COLON
%left OR
%left AND
%left EQ NE
%left LE LT GE GT
%left PLUS MINUS
%left STAR SLASH PERCENT
%right NOT NEG TYPECAST

/* Expect zero S-R, R-R conflicts, error otherwise */
%expect 0

%start program

%%

program: decls
         {
           parseresult = TBmakeProgram ($1);
         };

decls: decl decls
        {
            $$ = TBmakeDecls ($1, $2);
        }
     | decl
        {
            $$ = TBmakeDecls ($1, NULL);
        }
        ;

decl: fundef
        { $$ = $1; }
    | EXPORT fundef
        {
          FUNDEF_ISEXPORTED ($2) = TRUE;
          $$ = $2;
        }
    | globaldef
        { $$ = $1; }
    | EXPORT globaldef
        {
          GLOBALDEF_ISEXPORTED ($2) = TRUE;
          $$ = $2;
        }
    | fundec
        { $$ = $1; }
    | globaldec
        { $$ = $1; }
    ;

globaldec: EXTERN TYPE ID SEMICOLON
             {
               $$ = TBmakeGlobaldef ($2, $3, NULL, NULL);
               GLOBALDEF_ISEXTERN($$) = TRUE;
             }
         | EXTERN TYPE BRACKET_L nonzero_ids BRACKET_R ID SEMICOLON
            {
              $$ = TBmakeGlobaldef($2 | TY_arr, $6, NULL, $4);
              GLOBALDEF_ISEXTERN($$) = TRUE;
            }
           ;

nonzero_ids: ID COMMA nonzero_ids
            {
                $$ = TBmakeIds($1, $3);
            }
            | ID
            {
                $$ = TBmakeIds($1, NULL);
            };



globaldef: TYPE ID SEMICOLON
           {
             $$ = TBmakeGlobaldef ($1, $2, NULL, NULL);
           }
           | TYPE ID LET expr SEMICOLON
           {
             $$ = TBmakeGlobaldef ($1, $2, $4, NULL);
           }
           | TYPE BRACKET_L nonzero_exprs BRACKET_R ID SEMICOLON
           {
               $$ = TBmakeGlobaldef($1 | TY_arr, $5, NULL, $3);
           }
           | TYPE BRACKET_L nonzero_exprs BRACKET_R ID LET arrexpr SEMICOLON
           {
               $$ = TBmakeGlobaldef($1 | TY_arr, $5, $7, $3);
           }
           ;

fundec: EXTERN TYPE ID PAREN_L params PAREN_R SEMICOLON
          {
            $$ = TBmakeFundef ($2, $3, $5, NULL);
            FUNDEF_ISEXTERN ($$) = TRUE;
          }
      | EXTERN VOID ID PAREN_L params PAREN_R SEMICOLON
          {
            $$ = TBmakeFundef (TY_void, $3, $5, NULL);
            FUNDEF_ISEXTERN ($$) = TRUE;
          }
      ;

fundef: TYPE ID PAREN_L params PAREN_R funbody
      {
        $$ = TBmakeFundef($1, $2, $4, $6);
      }
      | VOID ID PAREN_L params PAREN_R funbody
      {
        $$ = TBmakeFundef(TY_void, $2, $4, $6);
      };

funbody: BRACE_L vardecls localfundefs stmts BRACE_R
    {
      $2 = reverseVardecls ($2);
      $$ = TBmakeFunbody ($2, $3, $4);
    };

localfundefs: fundef localfundefs
    {
      $$ = TBmakeFundefs ($1, $2);
    }
    | %empty
    {
      $$ = NULL;
    };

vardecls: vardecls TYPE ID SEMICOLON
        {
          $$ = TBmakeVardecl ($2, $3, NULL, $1, NULL);
        }
        | vardecls TYPE ID LET expr SEMICOLON
        {
          $$ = TBmakeVardecl ($2, $3, $5, $1, NULL);
        }
        | vardecls TYPE BRACKET_L nonzero_exprs BRACKET_R ID SEMICOLON
        {
            $$ = TBmakeVardecl($2 | TY_arr, $6, NULL, $1, $4);
        }
        | vardecls TYPE BRACKET_L nonzero_exprs BRACKET_R ID LET arrexpr SEMICOLON
        {
            $$ = TBmakeVardecl($2 | TY_arr, $6, $8, $1, $4);
        }
        | %empty
        {
          $$ = NULL;
        }

params: nonzero_params
        {
          $$ = $1;
        }
        | %empty
        {
          $$ = NULL;
        }

nonzero_params: param COMMA nonzero_params
        {
            $$ = $1;
            PARAM_NEXT($1) = $3;
        }
        | param
        {
            $$ = $1;
        };

param: TYPE ID
        {
          $$ = TBmakeParam($1, $2, NULL, NULL);
        }
        | TYPE BRACKET_L nonzero_ids BRACKET_R ID
        {
          $$ = TBmakeParam($1 | TY_arr, $5, NULL, $3);
        };

stmts: stmt stmts
        {
          $$ = TBmakeStmts( $1, $2);
        }
      | %empty
        {
          $$ = NULL;
        }
        ;

block: BRACE_L stmts BRACE_R
        {
          $$ = $2;
        }
      | stmt
        {
          $$ = TBmakeStmts ($1, NULL);
        }
      ;

stmt: assign
       {
         $$ = $1;
       }
       | call SEMICOLON
       {
         $$ = $1;
         CALL_ISSTMT($$) = TRUE;
       }
       | IF PAREN_L expr PAREN_R block %prec IFSTMT
       {
         $$ = TBmakeIf ($3, $5, NULL);
       }
       | IF PAREN_L expr PAREN_R block ELSE block
       {
         $$ = TBmakeIf ($3, $5, $7);
       }
       | WHILE PAREN_L expr PAREN_R block
       {
         $$ = TBmakeWhile ($3, $5);
       }
       | DO block WHILE PAREN_L expr PAREN_R SEMICOLON
       {
         $$ = TBmakeWhile ($5, $2);
         WHILE_ISDO($$) = TRUE;
       }
       | FOR PAREN_L TYPE ID LET expr COMMA expr PAREN_R block
       {
         $$ = TBmakeFor($4, $6, $8, TBmakeInt(1), $10);
       }
       | FOR PAREN_L TYPE ID LET expr COMMA expr COMMA expr PAREN_R block
       {
         $$ = TBmakeFor($4, $6, $8, $10, $12);
       }
       | RETURN expr SEMICOLON
       {
         $$ = TBmakeReturn ($2);
       }
       | RETURN SEMICOLON
       {
         $$ = TBmakeReturn (NULL);
       }
       ;

assign: varlet LET expr SEMICOLON
        {
          $$ = TBmakeAssign( $1, $3);
        }
        ;

call: ID PAREN_L exprs PAREN_R
       {
         $$ = TBmakeCall ($1, $3);
       }
       ;

varlet: ID
        {
          $$ = TBmakeVarlet($1, NULL);
        }
        | ID BRACKET_L nonzero_exprs BRACKET_R
        {
            $$ = TBmakeVarlet($1, $3);
        }
        ;

exprs: nonzero_exprs
        {
            $$ = $1;
        }
      | %empty { $$ = NULL; };
        ;

nonzero_exprs: expr COMMA nonzero_exprs
        {
          $$ = TBmakeExprs( $1, $3);
        }
      | expr
        {
          $$ = TBmakeExprs( $1, NULL);
        };

expr: constant
      {
        $$ = $1;
      }
    | call
      {
        $$ = $1;
      }
    | PAREN_L TYPE PAREN_R expr %prec TYPECAST
      {
        $$ = TBmakeCast($2, $4);
      }
    | PAREN_L expr PAREN_R
      {
        $$ = $2;
      }
    | ID
      {
        $$ = TBmakeVar($1, NULL);
      }
    | expr PERCENT expr
      {
        $$ = TBmakeBinop( BO_mod, $1, $3);
      }
    | expr PLUS expr
      {
        $$ = TBmakeBinop( BO_add, $1, $3);
      }
    | expr MINUS expr
      {
        $$ = TBmakeBinop( BO_sub, $1, $3);
      }
    | expr STAR expr
      {
        $$ = TBmakeBinop( BO_mul, $1, $3);
      }
    | expr SLASH expr
      {
        $$ = TBmakeBinop( BO_div, $1, $3);
      }
    | expr LE expr
      {
        $$ = TBmakeBinop( BO_le, $1, $3);
      }
    | expr LT expr
      {
        $$ = TBmakeBinop( BO_lt, $1, $3);
      }
    | expr GE expr
      {
        $$ = TBmakeBinop( BO_ge, $1, $3);
      }
    | expr GT expr
      {
        $$ = TBmakeBinop( BO_gt, $1, $3);
      }
    | expr NE expr
      {
        $$ = TBmakeBinop( BO_ne, $1, $3);
      }
    | expr EQ expr
      {
        $$ = TBmakeBinop( BO_eq, $1, $3);
      }
    | expr AND expr
      {
        $$ = TBmakeBinop( BO_and, $1, $3);
      }
    | expr OR expr
      {
        $$ = TBmakeBinop( BO_or, $1, $3);
      }
    | MINUS expr %prec NEG
      {
        $$ = TBmakeMonop (MO_neg, $2);
      }
    | NOT expr
      {
        $$ = TBmakeMonop (MO_not, $2);
      }
    | expr QUESMARK expr COLON expr
      {
        $$ = TBmakeCondexpr ($1, $3, $5);
      }
    | ID BRACKET_L nonzero_exprs BRACKET_R
      {
        $$ = TBmakeVar($1, $3);
      }
    ;

arrexprs: arrexpr
    {
        $$ = TBmakeExprs($1, NULL);
    }
    | arrexpr COMMA arrexprs
    {
        $$ = TBmakeExprs($1, $3);
    };


arrexpr: BRACKET_L arrexprs BRACKET_R
        {
          $$ = TBmakeArrexpr($2);
        };
        | expr { $$ = $1; }

constant: floatval
          {
            $$ = $1;
          }
        | intval
          {
            $$ = $1;
          }
        | boolval
          {
            $$ = $1;
          }
        ;

floatval: FLOAT
           {
             $$ = TBmakeFloat( $1);
           }
         ;

intval: NUM
        {
          $$ = TBmakeInt( $1);
        }
      ;

boolval: TRUEVAL
         {
           $$ = TBmakeBool( TRUE);
         }
       | FALSEVAL
         {
           $$ = TBmakeBool( FALSE);
         }
       ;

%%

static int yyerror( char *error)
{
  CTIabort( "line %d, col %d\nError parsing source code: %s\n",
            global.line, global.col, error);

  return( 0);
}

node *YYparseTree( void)
{
  DBUG_ENTER("YYparseTree");

  yyparse();

  DBUG_RETURN( parseresult);
}

static node *
reverseVardecls (node *vardecls)
{
   node *current = vardecls, *next, *prev = NULL;

   while (current)
   {
     next = VARDECL_NEXT (current);
     VARDECL_NEXT (current) = prev;
     prev = current;
     current = next;
   }

   return prev;
}
