%{
#include <vslc.h>

#define NODE(type, data, n_children, children...) node_init(malloc(sizeof(node_t)), type, data, n_children,  ##children)
%}

%define api.value.type {node_t}
%token FUNC PRINT RETURN CONTINUE IF THEN ELSE WHILE DO OPENBLOCK CLOSEBLOCK
%token VAR NUMBER IDENTIFIER STRING

%left '|' '&' '^'
%left '+' '-'
%left '*' '/'
%nonassoc UMINUS
%right '~'
//%expect 1

%nonassoc IF THEN
%nonassoc ELSE

/* Tried fixing vscode complaining about the type for the non-terminals, didn't work
%union {
  node_t* node;
}

%type <node> global_list global 
%type <node> statement_list print_list expression_list variable_list argument_list parameter_list declaration_list
%type <node> function statement block 
%type <node> assignment_statement return_statement print_statement null_statement if_statement while_statement
%type <node> relation expression declaration print_item identifier number string
*/ 

%%

program:
    global_list {
      root = NODE(PROGRAM, NULL, 1, $1);
    }
  ;

global_list:
    global {
      $$ = NODE(GLOBAL_LIST, NULL, 1, $1);
    }
  | global_list global {
      $$ = NODE(GLOBAL_LIST, NULL, 2, $1, $2);
    }
  ;

global:
    function {
      $$ = NODE(GLOBAL, NULL, 1, $1);
    }
  | declaration {
      $$ = NODE(GLOBAL, NULL, 1, $1);
    }
  ;

statement_list:
    statement {
      $$ = NODE(STATEMENT_LIST, NULL, 1, $1);
    }
  | statement_list statement {
      $$ = NODE(STATEMENT_LIST, NULL, 2, $1, $2);
    }
  ;

print_list: 
    print_item {
      $$ = NODE(PRINT_LIST, NULL, 1, $1);
    }
  | print_list ',' print_item {
      $$ = NODE(PRINT_LIST, NULL, 2, $1, $3);
    }
  ;

expression_list:
    expression {
      $$ = NODE(EXPRESSION_LIST, NULL, 1, $1);
    }
  | expression_list ',' expression {
      $$ = NODE(EXPRESSION_LIST, NULL, 2, $1, $3);
    }
  ;

variable_list:
    identifier {
      $$ = NODE(VARIABLE_LIST, NULL, 1, $1);
    }
  | variable_list ',' identifier {
      $$ = NODE(VARIABLE_LIST, NULL, 2, $1, $3);
    }
  ;

argument_list:
    expression_list {
      $$ = NODE(ARGUMENT_LIST, NULL, 1, $1);
    }
  | /* epsilon */ {
      $$ = NODE(ARGUMENT_LIST, NULL, 0);
    }
  ;

parameter_list:
    variable_list {
      $$ = NODE(PARAMETER_LIST, NULL, 1, $1);
    }
  | /* epsilon */ {
      $$ = NODE(PARAMETER_LIST, NULL, 0);
    }
  ;

declaration_list:
    declaration {
      $$ = NODE(DECLARATION_LIST, NULL, 1, $1);
    }
  | declaration_list declaration {
      $$ = NODE(DECLARATION_LIST, NULL, 2, $1, $2);
    }
  ;

function:
    FUNC identifier '(' parameter_list ')' statement  {
      $$ = NODE(FUNCTION, NULL, 3, $2, $4, $6);
    }
  ;

statement:
    assignment_statement {
      $$ = NODE(STATEMENT, NULL, 1, $1);
    }
  | return_statement {
      $$ = NODE(STATEMENT, NULL, 1, $1);
    }
  | print_statement {
      $$ = NODE(STATEMENT, NULL, 1, $1);
    }
  | if_statement {
      $$ = NODE(STATEMENT, NULL, 1, $1);
    }
  | while_statement {
      $$ = NODE(STATEMENT, NULL, 1, $1);
    }
  | null_statement {
      $$ = NODE(STATEMENT, NULL, 1, $1);
    }
  | block {
      $$ = NODE(STATEMENT, NULL, 1, $1);
    }
  ;

block:
    OPENBLOCK declaration_list statement_list CLOSEBLOCK {
      $$ = NODE(BLOCK, NULL, 2, $2, $3);
    }
  | OPENBLOCK statement_list CLOSEBLOCK {
      $$ = NODE(BLOCK, NULL, 1, $2);
    }
  ;

assignment_statement:
    identifier ':' '=' expression {
      $$ = NODE(ASSIGNMENT_STATEMENT, NULL, 2, $1, $4);
    }
  | identifier '+' '=' expression {
      $$ = NODE(ADD_STATEMENT, NULL, 2, $1, $4);
    }
  | identifier '-' '=' expression {
      $$ = NODE(SUBTRACT_STATEMENT, NULL, 2, $1, $4);
    }
  | identifier '*' '=' expression {
      $$ = NODE(MULTIPLY_STATEMENT, NULL, 2, $1, $4);
    }
  | identifier '/' '=' expression {
      $$ = NODE(DIVIDE_STATEMENT, NULL, 2, $1, $4);
    }
  ;

return_statement:
    RETURN expression {
      $$ = NODE(RETURN_STATEMENT, NULL, 1, $2);
    }
  ;

print_statement:
    PRINT print_list {
      $$ = NODE(PRINT_STATEMENT, NULL, 1, $2);
    }
  ;

null_statement:
    CONTINUE {
      $$ = NODE(NULL_STATEMENT, NULL, 0);
    }
  ;

if_statement:
    IF relation THEN statement {
      $$ = NODE(IF_STATEMENT, NULL, 2, $2, $4);
    }
  | IF relation THEN statement ELSE statement {
      $$ = NODE(IF_STATEMENT, NULL, 3, $2, $4, $6);
    }
  ;

while_statement:
    WHILE relation DO statement {
      $$ = NODE(WHILE_STATEMENT, NULL, 2, $2, $4);
    }
  ;

relation:
    expression '=' expression {
      $$ = NODE(RELATION, strdup("="), 2, $1, $3);
    }
  | expression '<' expression {
      $$ = NODE(RELATION, strdup("<"), 2, $1, $3);
    }
  | expression '>' expression {
      $$ = NODE(RELATION, strdup(">"), 2, $1, $3);
    }
  ;

expression:
    expression '|' expression {
      $$ = NODE(EXPRESSION, strdup("|"), 2, $1, $3);
    }
  | expression '^' expression {
      $$ = NODE(EXPRESSION, strdup("^"), 2, $1, $3);
    }
  | expression '&' expression {
      $$ = NODE(EXPRESSION, strdup("&"), 2, $1, $3);
    }
  | expression '+' expression {
      $$ = NODE(EXPRESSION, strdup("+"), 2, $1, $3);
    }
  | expression '-' expression {
      $$ = NODE(EXPRESSION, strdup("-"), 2, $1, $3);
    }
  | expression '*' expression {
      $$ = NODE(EXPRESSION, strdup("*"), 2, $1, $3);
    }
  | expression '/' expression {
      $$ = NODE(EXPRESSION, strdup("/"), 2, $1, $3);
    }
  | '-' expression %prec UMINUS {
      $$ = NODE(EXPRESSION, strdup("-"), 1, $2);
    }
  | '~' expression {
      $$ = NODE(EXPRESSION, strdup("~"), 1, $2);
    }
  | '(' expression ')' {
      $$ = NODE(EXPRESSION, /*NULL*/ strdup("group"), 1, $2);
    }
  | number {
      $$ = NODE(EXPRESSION, /*NULL*/ strdup("number"), 1, $1);
    }
  | identifier {
      $$ = NODE(EXPRESSION, /*NULL*/ strdup("identifier"), 1, $1);
    }
  | identifier '(' argument_list ')' {
      $$ = NODE(EXPRESSION, /*NULL*/ strdup("function_call"), 2, $1, $3);
    }
  ;

declaration: 
    VAR variable_list {
      $$ = NODE(DECLARATION, NULL, 1, $2);
    }
  ;

print_item:
    expression {
      $$ = NODE(PRINT_ITEM, NULL, 1, $1);
    }
  | string {
      $$ = NODE(PRINT_ITEM, NULL, 1, $1);
    }
  ;

identifier:
    IDENTIFIER {
      $$ = NODE(IDENTIFIER_DATA, strdup(yytext), 0); // Zero children
    }
  ;

number:
    NUMBER {
      uint64_t* p_number = malloc(sizeof(uint64_t));
      *p_number = strtol(yytext, NULL, 10);
      $$ = NODE(NUMBER_DATA, p_number, 0); // Zero children
    }
  ;

string:
    STRING {
      $$ = NODE(STRING_DATA, strdup(yytext), 0); // Zero children
    }
  ;

%%

int
yyerror ( const char *error )
{
    fprintf ( stderr, "%s on line %d\n", error, yylineno );
    exit ( EXIT_FAILURE );
}
