%{ 
(* parser.mly takes a sequence of tokens produced by the scanner and assembles
 * them into an abstract syntax tree. Each pattern-action rule takes some
 * pattern in the thus-far-assembled input and createse a "higher" type out
 * of it. *)

open Ast 

(* Splits an id with dots in it into a list of ids *)
let split_dot_id dot_id = 
    Str.split (Str.regexp "[.]") dot_id

%}

%token EOF
%token NEWLINE INDENT DEDENT
%token <int> DEDENT_MULT
%token DO WITH AND
%token NEW COLON CONFIGURE
%token IF ELSE WHILE FOR SEMI BREAK CONTINUE
%token OPENPAREN CLOSEPAREN OPENBRACE CLOSEBRACE
%token <bool> BOOL_LITERAL
%token <string> STRING_LITERAL ID DOT_ID TYPE
%token <int> NUMBER_LITERAL
%token ADD MINUS TIMES DIVIDE LT LTOE GT GTOE EQUAL NOTEQUAL
%token NOT DISJ CONJ
%token PREPEND_TOP PREPEND_BOTTOM APPEND_TOP APPEND_BOTTOM
%token HAS CALLED

/* Lowest Precedence */
%left DISJ CONJ
%left EQUAL NOTEQUAL
%nonassoc LT LTOE GT GTOE
%left ADD MINUS
%left TIMES DIVIDE
%right NOT
/* Highest Precedence */

%start program
%type <Ast.program> program

%%

program:
    | header vdecl_list func_list EOF       { { configs = List.rev (fst $1);
                                                field_decls = List.rev (snd $1);
                                                vars = List.rev $2;
                                                funcs = List.rev $3; } }
    | header func_list EOF                  { { configs = List.rev (fst $1);
                                                field_decls = List.rev (snd $1);
                                                vars = [];
                                                funcs = List.rev $2; } }

header:
    | config_list                           { ($1, []) }
    | config_list field_decl_list           { ($1, $2) }

config_list:
    | /* nothing */                         { [] }
    | config_list config                    { $2 :: $1 }
    | config_list NEWLINE                   { $1 }

config:
    | CONFIGURE ID COLON expr               { { config_id = $2; config_value = $4 } }

field_decl_list:
    | field_decl                            { [$1] }
    | field_decl_list field_decl            { $2 :: $1 }
    | field_decl_list NEWLINE               { $1 }

field_decl:
    | TYPE HAS TYPE CALLED ID               { { parent_type = $1;
                                                field_type = $3;
                                                field_id = $5; } }

vdecl_list:
    | vdecl                                 { [$1] }
    | vdecl_list vdecl                      { $2 :: $1 } 
    | vdecl_list NEWLINE                    { $1 }

vdecl:
    | NEW TYPE ID COLON expr                { VarDecl({ var_decl_id = $3;
                                                        var_decl_type = $2;
                                                        var_decl_value = $5 }) }

func_list:
    | func                                  { [$1] }
    | func_list func                        { $2 :: $1 }
    | func_list NEWLINE                     { $1 }

func:
    | ID COLON block                        { { decl_name = $1;
                                                formals = [];
                                                body = $3; } }
    | ID WITH formal_list COLON block       { { decl_name = $1;
                                                formals = List.rev $3;
                                                body = $5; } }

formal_list:
    | formal                                { [$1] }
    | formal_list AND formal                { $3 :: $1 }

formal:
    | TYPE ID                               { { formal_id = $2; formal_type = $1 } }

block:
    | INDENT stmt_list DEDENT               { List.rev $2 }
    | INDENT stmt_list EOF                  { List.rev $2 }

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

stmt:
    | stmt NEWLINE                          { $1 }
    | update                                { Update($1) }
    | DO ID                                 { Call({ fname = $2; args = [] }) }
    | DO ID WITH arg_list                   { Call({ fname = $2; 
                                                     args = List.rev $4 }) }
    | IF expr COLON block                   { If($2, $4, []) }
    | IF expr COLON block ELSE COLON block  { If($2, $4, $7) }
    | WHILE expr COLON block                { While($2, $4) }
    | FOR update SEMI expr SEMI update 
        COLON block                         { For($2, $4, $6, $8) }
    | BREAK                                 { Break }
    | CONTINUE                              { Continue }
    | OPENBRACE stmt CLOSEBRACE TIMES expr  { TimesLoop($2, $5) }
    | expr PREPEND_TOP expr                 { Prepend($1, $3, Top) }
    | expr PREPEND_BOTTOM expr              { Prepend($1, $3, Bottom) }
    | expr APPEND_TOP expr                  { Append($3, $1, Top) }
    | expr APPEND_BOTTOM expr               { Append($3, $1, Bottom) }

arg_list:
    | expr                                  { [$1] }
    | arg_list AND expr                     { $3 :: $1 }

update:
    | vdecl                                 { $1 }
    | var COLON expr                        { Assign($1, $3) }

expr:
    | NUMBER_LITERAL                        { Number($1) }
    | BOOL_LITERAL                          { Boolean($1) }
    | STRING_LITERAL                        { String($1) }
    | var                                   { Var($1) }
    | expr ADD expr                         { Binop($1, Add, $3) }
    | expr MINUS expr                       { Binop($1, Minus, $3) }
    | expr TIMES expr                       { Binop($1, Multiply, $3) }
    | expr DIVIDE expr                      { Binop($1, Divide, $3) }
    | expr LT expr                          { Binop($1, Lt, $3) }
    | expr LTOE expr                        { Binop($1, Ltoe, $3) }
    | expr GT expr                          { Binop($1, Gt, $3) }
    | expr GTOE expr                        { Binop($1, Gtoe, $3) }
    | expr EQUAL expr                       { Binop($1, Equal, $3) }
    | expr NOTEQUAL expr                    { Binop($1, NotEqual, $3) }
    | expr DISJ expr                        { Binop($1, Disj, $3) }
    | expr CONJ expr                        { Binop($1, Conj, $3) }
    | NOT expr                              { Unop(Not, $2) }
    | OPENPAREN expr CLOSEPAREN             { $2 }
    
var:
    | ID                                    { SimpleId($1) }
    | DOT_ID                                { DotId(split_dot_id $1) }

%%
