grammar         = {(fn_decl | import_stmt) (stmt_end | EOF)} ;

import_stmt     = "import" STRING ;

fn_decl         = "let" IDENT '(' [arg {',' arg}] ')' ':' IDENT '=' (expression | block_expr) ;

arg             = IDENT ':' IDENT ;

stmt            = let_decl
                | var_decl
                | fn_decl
                | reassign
                | reassign_by
                | fn_call
                | control_keyword
                | block
                | for_loop
                | if_statement ;

stmt_end        = NEWLINE | ';' ;

let_decl        = "let" IDENT [':' IDENT] '=' expression ;
var_decl        = "var" IDENT [':' IDENT] '=' expression ;

reassign        = IDENT '=' expression ;
reassign_by     = reassign_plus
                | reassign_minus
                | reassign_mul
                | reassign_div
                | reassign_mod ;

reassign_plus   = IDENT '+=' expression ;
reassign_minus  = IDENT '-=' expression ;
reassign_mul    = IDENT '*=' expression ;
reassign_div    = IDENT '/=' expression ;
reassign_mod    = IDENT '%=' expression ;

if_statement    = "if" expression block {'else' 'if' expression block} ['else' block ] ;

for_loop        = "for" [loop_init {',' loop_init}] ';' block ;
loop_init       = let_decl
                | var_decl
                | reassign
                | reassign_by
                | expression ;

control_keyword = "break"
                | "continue"
                | "return" [expression]
                ;

block           = '{' {stmt stmt_end} '}' ;
block_expr      = '{' expression '}' ;

fn_call         = IDENT '(' [expression {',' expression}] ')' ;

expression      = logical_or ;
logical_or      = logical_and {"or" logical_and} ;
logical_and     = logical_not {"and" logical_not} ;
logical_not     = ["not"] (logical_not | comparison) ;
comparison      = additive {("==" | "!=" | "<" | ">" | "<=" | ">=") additive} ;
additive        = multiplicative {('+' | '-') multiplicative} ;
multiplicative  = unary {('*' | '/' | '%') unary} ;
unary           = '-' unary | term ;
term            = NUMBER
                | BOOLEAN
                | fn_call
                | IDENT
                | if_expr
                | cast
                | '(' expression ')' ;

if_expr         = "if" expression block_expr {'else' 'if' expression block_expr} 'else' block_expr ;

cast            = "cast" '(' IDENT ',' expression ')' ;

(* LEXER RULES *)
(* ? ... ?  means regex is inside *)
(* ? skip ? means if you encounter this, skip it *)
IDENT           = (? [a-zA-Z_] ?){? [a-zA-Z_0-9] ?} ;
STRING          = '"' {? . ?} '"' ;
NUMBER          = ? [0-9] ? ['.' {? [0-9] ?}] ;
BOOLEAN         = "true" | 'false' ;

NEWLINE         = ['\r'] '\n' ;
WS              = {'\t'} ? skip ? ;
COMMENT         = '--' {? . ?} NEWLINE ? skip ? ;

(* vim: ft=ebnf
*)