%code top {
    #include "TestLexer.h"
}
%code requires {
    #include "ast/expression.h"
    #include "ast/statement.h"
    #include "ast/type.h"
}

%require "3.2" // suppress generating stack.hh
%language "c++"
%define api.parser.class { TestParser }
%header "yyTestParser.h"

%param {TestLexer* scanner}
%parse-param {Statement** result}
%code {
    #define yylex(L, S) ((S)->yylex(L))

    #define vt100GREEN "\x1b[32m"
    #define DEBUG(X) std::cout << vt100GREEN << " " << #X << std::endl
}


%union {
    std::string* str;
    int num;
    std::vector<Statement*>* statements;
    Statement* statement;
    Type* type;
    LValue* lvalue;
    RValue* rvalue;
    ArithmeticExpression* arithexp;
    BooleanExpression* boolexp;
    RelationExpression* relexp;
}
%token<num> NUMBER
%token<str> STRING
%token<str> IDENTIFIER

%nterm S
%nterm<statements> statements
%nterm<statement> statement
%nterm<rvalue> rvalue
%nterm<lvalue> lvalue
%nterm<boolexp> boolexp
%nterm<relexp> relexp
%nterm<arithexp> arithexp
%nterm<type> type


%token COMMENT INT IF ELSE WHILE INPUT OUTPUT
%token OPENPAREN CLOSEPAREN OPENBRACE CLOSEBRACE COMMA SEMICOLON
%token ASSIGN PLUS MINUS MULT DIV MOD
%token LESS LESSEQUAL EQUAL UNEQUAL GREATEREQUAL GREATER
%token NOT OR AND


%left ASSIGN
%left OR
%left AND
%left EQUAL UNEQUAL
%left LESS LESSEQUAL GREATER GREATEREQUAL
%left PLUS MINUS
%left MULT DIV MOD
%right NOT

%start S


%%

S : statements YYEOF
    {
        DEBUG(S : statements YYEOF);
        *result = new Block($statements);
    }
;

statements
    : %empty
    {
        DEBUG( statements: %empty );
        $$ = new std::vector<Statement*>();
    }
    | statements[s] statement
    {
        DEBUG( statements: statements statement);
        $s->push_back($statement);
        $$ = $s;
    }
;

statement
    : OPENBRACE statements CLOSEBRACE
    {
        DEBUG( statement: OPENBRACE statements CLOSEBRACE );
        $$ = new Block($statements);
    }
    | type IDENTIFIER ASSIGN rvalue SEMICOLON
    {
        DEBUG( statement: type lvalue ASSIGN rvalue SEMICOLON );
        $$ = new VarDeclaration($type, $IDENTIFIER, $rvalue);
    }
    | lvalue ASSIGN rvalue SEMICOLON
    {
        DEBUG( statement: lvalue ASSIGN rvalue SEMICOLON );
        $$ = new Assignment($lvalue, $rvalue);
    }
    | OUTPUT rvalue SEMICOLON
    {
        DEBUG( statement: OUTPUT rvalue SEMICOLON );
        $$ = new Output($rvalue);
    }
    | INPUT lvalue SEMICOLON
    {
        DEBUG( statement: INPUT lvalue SEMICOLON );
        $$ = new Input($lvalue);
    }
    | IF OPENPAREN boolexp[cond] CLOSEPAREN statement[t] ELSE statement[e]
    {
        DEBUG( statement: IF OPENPAREN boolexp CLOSEPAREN statement ELSE statement );
        $$ = new IfThenElse($cond, $t, $e);
    }
    | WHILE OPENPAREN boolexp[cond] CLOSEPAREN statement[body]
    {
        DEBUG( statement: WHILE OPENPAREN boolexp CLOSEPAREN statement );
        $$ = new WhileLoop($cond, $body);
    }
;

type
    : INT
    {
        DEBUG( type: INT );
        $$ = new TypeInt32();
    }
;

rvalue
    : arithexp
    {
        DEBUG( rvalue: arithexp );
        $$ = $arithexp;
    }
;

lvalue
    : IDENTIFIER
    {
        DEBUG( lvalue: IDENTIFIER );
        $$ = new LValueIdentifier($IDENTIFIER);
    }
;

boolexp
    : OPENPAREN boolexp[exp] CLOSEPAREN
    {
        DEBUG( boolexp: OPENPAREN boolexp CLOSEPAREN );
        $$ = $exp;
    }
    | relexp
    {
        DEBUG( boolexp: relexp );
        $$ = $relexp;
    }
    | NOT boolexp[exp]
    {
        DEBUG( boolexp: NOT boolexp );
        $$ = new BooleanExpressionNot($exp);
    }
    | boolexp[l] AND boolexp[r]
    {
        DEBUG( boolexp: boolexp AND boolexp );
        $$ = new BooleanExpressionAnd($l, $r);
    }
    | boolexp[l] OR boolexp[r]
    {
        DEBUG( boolexp: boolexp OR boolexp );
        $$ = new BooleanExpressionOr($l, $r);
    }
;

relexp
    : arithexp[l] LESS arithexp[r]
    {
        DEBUG( relexp: arithexp LESS arithexp );
        $$ = new RelationExpressionLess($l, $r);
    }
    | arithexp[l] LESSEQUAL arithexp[r]
    {
        DEBUG( relexp: arithexp LESSEQUAL arithexp );
        $$ = new RelationExpressionLessEqual($l, $r);
    }
    | arithexp[l] EQUAL arithexp[r]
    {
        DEBUG( relexp: arithexp EQUAL arithexp );
        $$ = new RelationExpressionEqual($l, $r);
    }
    | arithexp[l] UNEQUAL arithexp[r]
    {
        DEBUG( relexp: arithexp UNEQUAL arithexp );
        $$ = new RelationExpressionUnequal($l, $r);
    }
    | arithexp[l] GREATEREQUAL arithexp[r]
    {
        DEBUG( relexp: arithexp GREATEREQUAL arithexp );
        $$ = new RelationExpressionGreaterEqual($l, $r);
    }
    | arithexp[l] GREATER arithexp[r]
    {
        DEBUG( relexp: arithexp GREATER arithexp );
        $$ = new RelationExpressionGreater($l, $r);
    }
;

arithexp
    : OPENPAREN arithexp[exp] CLOSEPAREN
    {
        DEBUG( arithexp: OPENPAREN arithexp CLOSEPAREN );
        $$ = $exp;
    }
    | NUMBER
    {
        DEBUG( arithexp: NUMBER );
        $$ = new ArithmeticExpressionConstant($NUMBER);
    }
    | lvalue
    {
        DEBUG( arithexp: lvalue );
        $$ = $lvalue;
    }
    | arithexp[l] PLUS arithexp[r]
    {
        DEBUG( arithexp: arithexp PLUS arithexp );
        $$ = new ArithmeticExpressionPlus($l, $r);
    }
    | arithexp[l] MINUS arithexp[r]
    {
        DEBUG( arithexp: arithexp MINUS arithexp );
        $$ = new ArithmeticExpressionMinus($l, $r);
    }
    | arithexp[l] MULT arithexp[r]
    {
        DEBUG( arithexp: arithexp MULT arithexp );
        $$ = new ArithmeticExpressionMultiply($l, $r);
    }
    | arithexp[l] DIV arithexp[r]
    {
        DEBUG( arithexp: arithexp DIV arithexp );
        $$ = new ArithmeticExpressionDivide($l, $r);
    }
    | arithexp[l] MOD arithexp[r]
    {
        DEBUG( arithexp: arithexp MOD arithexp );
        $$ = new ArithmeticExpressionModulus($l, $r);
    }
;

%%

void yy::TestParser::error(const std::string& msg)
{
    std::cerr << msg << std::endl;
}
