/*
File: PARSER.MLY
Description: Parses input
*/

%{
open Ast
%}

%token SEMI LPAREN RPAREN LBRACE RBRACE COMMA DOT
%token PLUS MINUS TIMES DIVIDE ASSIGN NOT CARET
%token EQ NEQ LT LEQ GT GEQ TRUE FALSE AND OR
%token RETURN IF ELSE FOR WHILE INT BOOL VOID
%token DOUBLE STRING MATRIX ROWSIZE COLSIZE IMAGE RED GREEN BLUE
%token MOD FUNC ARROW LBRACK RBRACK
%token <int> INT_LITERAL
%token <float> DBL_LITERAL
%token <string> STR_LITERAL
%token <string> ID
%token EOF

%nonassoc NOELSE
%nonassoc ELSE
%right ASSIGN
%left COMMA
%left OR
%left AND
%left EQ NEQ
%left LT GT LEQ GEQ
%left PLUS MINUS
%left TIMES DIVIDE MOD
%left CARET
%right NOT NEG
%left LPAREN RPAREN

%start program
%type <Ast.program> program

%%

program:
  decls EOF { $1 }

decls:
   /* nothing */ { [] }
 | decls fdecl { $2 :: $1 }

fdecl:
  FUNC ID LPAREN formals_opt RPAREN ARROW typ LBRACE stmt_list RBRACE
     { { typ = $7;
	 fname = $2;
	 formals = $4;
	 body = List.rev $9 } }

formals_opt:
    /* nothing */ { [] }
  | formal_list   { List.rev $1 }

formal_list:
    typ ID                   { [($1,$2)] }
  | formal_list COMMA typ ID { ($3,$4) :: $1 }

typ:
    INT { Int }
  | BOOL { Bool }
  | VOID { Void }
  | DOUBLE { Double }
  | STRING { String }
  | IMAGE { Image }
  | MATRIX { Matrix }

stmt_list:
    /* nothing */  { [] }
  | stmt_list stmt { $2 :: $1 }

stmt:
    expr SEMI { Expr $1 }
  | RETURN SEMI { Return Noexpr }
  | RETURN expr SEMI { Return $2 }
  | LBRACE stmt_list RBRACE { Block(List.rev $2) }
  | IF LPAREN expr RPAREN stmt %prec NOELSE { If($3, $5, Block([])) }
  | IF LPAREN expr RPAREN stmt ELSE stmt    { If($3, $5, $7) }
  | FOR LPAREN expr_opt SEMI expr SEMI expr_opt RPAREN stmt
     { For($3, $5, $7, $9) }
  | WHILE LPAREN expr RPAREN stmt { While($3, $5) }
  | typ ID SEMI { Local($1, $2, Noassign($1)) }
  | typ ID ASSIGN expr SEMI { Local($1, $2, $4) }

expr_opt:
    /* nothing */ { Noexpr }
  | expr          { $1 }

expr:
    INT_LITERAL      { IntLit($1) }
  | DBL_LITERAL      { DblLit($1) }
  | STR_LITERAL      { StrLit($1) }
  | TRUE             { BoolLit(true) }
  | FALSE            { BoolLit(false) }
  | ID               { Id($1) }
  | matrix_literal   { MatLit($1) }
  | LPAREN expr RPAREN { $2 }
  | expr PLUS   expr { Binop($1, Add,   $3) } /* when writing binop in .ml file, need to overload op */
  | expr MINUS  expr { Binop($1, Sub,   $3) }
  | expr TIMES  expr { Binop($1, Mult,  $3) }
  | expr DIVIDE expr { Binop($1, Div,   $3) }
  | expr EQ     expr { Binop($1, Equal, $3) }
  | expr NEQ    expr { Binop($1, Neq,   $3) }
  | expr MOD    expr { Binop($1, Mod,   $3) }
  | expr LT     expr { Binop($1, Less,  $3) }
  | expr LEQ    expr { Binop($1, Leq,   $3) }
  | expr GT     expr { Binop($1, Greater, $3) }
  | expr GEQ    expr { Binop($1, Geq,   $3) }
  | expr AND    expr { Binop($1, And,   $3) }
  | expr OR     expr { Binop($1, Or,    $3) }
  | MINUS expr %prec NEG { Unop(Neg, $2) }
  | NOT expr         { Unop(Not, $2) }
  | ID ASSIGN expr   { Assign($1, $3) }
  | func_id LPAREN actuals_opt RPAREN { Call($1, $3) }
  | LPAREN ID COMMA ID COMMA ID RPAREN { ImageLit($2, $4, $6) }
  | ID DOT RED  { ImageRedAccess($1) }
  | ID DOT GREEN  { ImageGreenAccess($1) }
  | ID DOT BLUE  { ImageBlueAccess($1) }
  | ID DOT ROWSIZE { MatrixRowSize($1) }
  | ID DOT COLSIZE { MatrixColSize($1) }
  | ID LBRACK expr COMMA expr RBRACK { MatAccess($1, $3, $5) }
  | LPAREN typ RPAREN expr             { Cast ($2, $4) }

func_id:
    ID                  {$1}

actuals_opt:
    /* nothing */ { [] }
  | actuals_list  { List.rev $1 }

actuals_list:
    expr                    { [$1] }
  | actuals_list COMMA expr { $3 :: $1 }

matrix_literal:
   LBRACK RBRACK                { [[]] } /* Empty matrix */
 | LBRACK matrix_body RBRACK    { $2 }

matrix_body:
    matrix_row                   { [$1] }
  | matrix_body SEMI matrix_row  { $3 :: $1 }

matrix_row:
   DBL_LITERAL                                      { [DblLit($1)] }
  | MINUS DBL_LITERAL %prec NEG                     { [Unop(Neg, DblLit($2))] }
  | matrix_row COMMA DBL_LITERAL                    { DblLit($3) :: $1 }
  | matrix_row COMMA MINUS DBL_LITERAL %prec NEG    { Unop(Neg, DblLit($4)) :: $1 }
