diff options
Diffstat (limited to 'src/parser.y')
| -rw-r--r-- | src/parser.y | 612 |
1 files changed, 612 insertions, 0 deletions
diff --git a/src/parser.y b/src/parser.y new file mode 100644 index 0000000..8242c2a --- /dev/null +++ b/src/parser.y @@ -0,0 +1,612 @@ +%{ + +/* TODO: clean up this mess and I guess fix location tracking, it works for the + * parser but each ast node should also get some location data + * I'm trying something over in ast.c, but I'm not sure about it + */ + +#include <stdio.h> +#include <stdlib.h> +#include <cu/parser.h> +#include <cu/ast.h> + +%} + +%locations + +%define parse.trace +%define parse.error verbose +%define api.pure full + +%lex-param {void *scanner} {struct parser *parser} +%parse-param {void *scanner} {struct parser* parser} + +%union { + struct ast_node *node; + long long integer; + double dbl; + char *str; +}; + +%token <integer> INT +%token <dbl> FLOAT +%token <str> STRING +%token <str> ID + +%token SQUOTE "'" +%token TO "=" +%token ELLIPSIS "..." +%token SEMICOLON ";" +%token COLON ":" +%token BANG "!" +/* still not sure if this should be just typeof, would be slower to type I guess */ +%token TYPEOF "typeof" +/* typeof does sort of fit into sizeof, hmmm */ +%token SIZEOF "sizeof" +%token PASTE "##" +%token STAR "*" +%token DIV "/" +%token REM "%" +%token MINUS "-" +%token PLUS "+" +%token POW "^^" +%token XOR "^" +%token AND "&" +%token OR "|" +%token LAND "&&" +%token LOR "||" +%token TILDE "~" +%token LT "<" +%token GT ">" +%token LE "<=" +%token GE ">=" +%token NE "!=" +%token EQ "==" +%token LSHIFT "<<" +%token RSHIFT ">>" +%token PLUSSELF "+=" +%token MINUSSELF "-=" +%token STARSELF "*=" +%token DIVSELF "/=" +%token REMSELF "%=" +%token XORSELF "^=" +%token POWSELF "^^=" +%token ANDSELF "&=" +%token ORSELF "|=" +%token LSHIFTSELF "<<=" +%token RSHIFTSELF ">>=" +%token AT "@" +%token COMMA "," +%token PUB "pub" +%token STRUCT "struct" +%token UNION "union" +%token TYPEDEF "type" +%token IMPORT "import" +%token DEFER "defer" +%token GOTO "goto" +%token EMBED "embed" +%token IF "if" +%token ELSE "else" +%token BREAK "break" +%token CONTINUE "continue" +%token SWITCH "switch" +%token CASE "case" +%token FOR "for" +%token WHILE "while" +%token DO "do" +%token MUT "mut" +%token RETURN "return" +%token CONST "const" +%token EXTERN "extern" +%token ENUM "enum" +%token DEFINE "define" +%token LPAREN "(" +%token RPAREN ")" +%token LBRACE "{" +%token RBRACE "}" +%token LBRACKET "[" +%token RBRACKET "]" +%token AS "as" +%token DOT "." +%token FATARROW "=>" + +/* precedence */ +%left "," "typeof" "sizeof" +%right "=" "+=" "-=" "*=" "/=" "%=" "<<=" ">>=" "&=" "^=" "|=" "^^=" +%left "^^" +%left "||" +%left "&&" +%left "==" "!=" +%left "<" ">" "<=" ">=" +%left "|" +%left "^" +%left "&" +%left "<<" ">>" +%left "+" "-" +%left "*" "/" "%" +%left "as" +%right "'" "!" "~" +%left "." "=>" "(" ")" + +/* why doesn't bison allow <*> for %nterm? would be so much easier */ +%nterm <node> import binary_op unary_op arg arg_list decl_list call expr +%nterm <node> while do_while statement statement_list body macro_list macro +%nterm <node> expr_list init_expr expr_if if for case case_list switch const +%nterm <node> func_sign variadic_sign impl impl_list type var_decl var +%nterm <node> var_init proc lambda template_elem template_list type_list +%nterm <node> type_alias type_template enum_val enum_list enum top unit id +%nterm <node> embed param_decl union union_list union_elem struct struct_list +%nterm <node> top_if const_if const_for defer goto + +%destructor {} FLOAT INT STRING ID +%destructor { destroy_ast_tree($$); } <*> + +%start input; +%% +id: ID { + $$ = gen_id(strdup($1)); + $$->loc = to_src_loc(&yylloc); + } + ; + +var: var_decl { $$ = $1; } + | var_init { $$ = $1; } + ; + +embed: "embed" "(" STRING ")" { $$ = gen_embed(clone_string($3)); } + ; + +import: "import" STRING { $$ = gen_import(clone_string($2)); } + ; + +binary_op: expr "=" expr { $$ = gen_binop(AST_ASSIGN, $1, $3); } + | expr "+" expr { $$ = gen_binop(AST_ADD, $1, $3); } + | expr "-" expr { $$ = gen_binop(AST_SUB, $1, $3); } + | expr "*" expr { $$ = gen_binop(AST_MUL, $1, $3); } + | expr "/" expr { $$ = gen_binop(AST_DIV, $1, $3); } + | expr "%" expr { $$ = gen_binop(AST_REM, $1, $3); } + | expr "^" expr { $$ = gen_binop(AST_XOR, $1, $3); } + | expr "^^" expr { $$ = gen_binop(AST_POW, $1, $3); } + | expr "&" expr { $$ = gen_binop(AST_AND, $1, $3); } + | expr "&&" expr { $$ = gen_binop(AST_LAND, $1, $3); } + | expr "|" expr { $$ = gen_binop(AST_OR, $1, $3); } + | expr "||" expr { $$ = gen_binop(AST_LOR, $1, $3); } + | expr "<<" expr { $$ = gen_binop(AST_LSHIFT, $1, $3); } + | expr ">>" expr { $$ = gen_binop(AST_RSHIFT, $1, $3); } + | expr "+=" expr { $$ = gen_binop(AST_ASSIGN_ADD, $1, $3); } + | expr "-=" expr { $$ = gen_binop(AST_ASSIGN_SUB, $1, $3); } + | expr "*=" expr { $$ = gen_binop(AST_ASSIGN_MUL, $1, $3); } + | expr "/=" expr { $$ = gen_binop(AST_ASSIGN_DIV, $1, $3); } + | expr "%=" expr { $$ = gen_binop(AST_ASSIGN_REM, $1, $3); } + | expr "&=" expr { $$ = gen_binop(AST_ASSIGN_AND, $1, $3); } + | expr "|=" expr { $$ = gen_binop(AST_ASSIGN_OR, $1, $3); } + | expr "^=" expr { $$ = gen_binop(AST_ASSIGN_XOR, $1, $3); } + | expr "<<=" expr { + $$ = gen_binop(AST_ASSIGN_LSHIFT, $1, $3); + } + | expr ">>=" expr { + $$ = gen_binop(AST_ASSIGN_RSHIFT, $1, $3); + } + | expr "<" expr { $$ = gen_binop(AST_LT, $1, $3); } + | expr ">" expr { $$ = gen_binop(AST_GT, $1, $3); } + | expr "<=" expr { $$ = gen_binop(AST_LE, $1, $3); } + | expr ">=" expr { $$ = gen_binop(AST_GE, $1, $3); } + | expr "!=" expr { $$ = gen_binop(AST_NE, $1, $3); } + | expr "==" expr { $$ = gen_binop(AST_EQ, $1, $3); } + ; + +unary_op: "-" expr { $$ = gen_unop(AST_NEG, $2); } + | "!" expr { $$ = gen_unop(AST_LNOT, $2); } + | "&" expr { $$ = gen_unop(AST_REF, $2); } + | "'" expr { $$ = gen_unop(AST_DEREF, $2); } + | "~" expr { $$ = gen_unop(AST_NOT, $2); } + ; + +arg: "&" var_decl { $$ = gen_unop(SQUOTE, $2); } + | var_decl { $$ = $1; } + | init_expr { $$ = $1; } + ; + +arg_list: arg "," arg_list { $$ = $1; $1->next = $3; } + | arg { $$ = $1; } + ; + +param_decl: type { $$ = gen_var(NULL, $1, NULL); } + +decl_list: var_decl "," decl_list { $$ = $1; $1->next = $3; } + | param_decl "," decl_list { $$ = $1; $1->next = $3; } + | var_decl { $$ = $1; } + | param_decl { $$ = $1; } + ; + +/* call also works for arrays in this case */ +call: id "(" arg_list ")" { $$ = gen_call($1, $3); } + | id "(" ")" { $$ = gen_call($1, NULL); } + ; + +defer: "defer" "(" expr ")" { $$ = gen_defer($3); } + ; + +/* TODO: concatenate multiple strings together */ +expr: id { $$ = $1; } + | expr "." id { $$ = gen_dot($1, $3); } + | id "##" id { $$ = gen_paste($1, $3); } + | "..." id { $$ = $2; } + | "@" { $$ = gen_last(); } + | INT { $$ = gen_int($1); $$->loc = to_src_loc(&yylloc); } + | FLOAT { $$ = gen_float($1); $$->loc = to_src_loc(&yylloc); } + | STRING { + $$ = gen_string(clone_string($1)); + $$->loc = to_src_loc(&yylloc); + } + | "(" expr ")" { $$ = $2; } + | call { $$ = $1; } + | defer { $$ = $1; } + | binary_op { $$ = $1; } + | unary_op { $$ = $1; } + | "(" var_init ")" { $$ = $2; } + | "sizeof" expr { $$ = gen_sizeof($2); } + | expr "as" type { $$ = gen_cast($1, $3); } + | "as" type { $$ = gen_as($2); } + | embed { $$ = $1; } + | lambda { $$ = $1; } + ; + +while: "while" expr body { $$ = gen_while($2, $3); } + ; + +do_while: "do" body "while" expr ";" { + $$ = gen_while($2, $4); + ast_set_flags($$, AST_FLAG_DELAYED); + } + ; + +goto: "goto" id { $$ = gen_goto(gen_label($2)); } + +statement: expr ";" { $$ = $1; } + | "return" expr ";" { $$ = gen_return($2); } + | "return" ";" { $$ = gen_return(NULL); } + | "break" ";" { $$ = gen_ctrl(AST_CTRL_BREAK, to_src_loc(&yylloc)); } + | "continue" ";" { $$ = gen_ctrl(AST_CTRL_CONTINUE, + to_src_loc(&yylloc)); } + | import ";" { $$ = $1; } + | var ";" { $$ = $1; } + | if { $$ = $1; } + | goto ";"{ $$ = $1; } + | id ":" { $$ = gen_label($1); } + | for { $$ = $1; } + | const { $$ = $1; } + | while { $$ = $1; } + | do_while { $$ = $1; } + | switch { $$ = $1; } + | macro { $$ = $1; } + | struct { $$ = $1; } + | type_alias ";" { $$ = $1; } + | type_template { $$ = $1; } + | enum { $$ = $1; } + | body { $$ = $1; } + | ";" { $$ = gen_empty(); } + | error { + /* TODO: figure out how to destroy any and all possible ast nodes we + * may have generated up until the error */ + $$ = gen_empty(); + parser->failed = true; + /* If we're failing to parse a statement in a block, continue by trying to + * parse the next statement in the block */ + if (next_interesting_feature(&yylval, &yylloc, scanner, parser)) { + destroy_ast_node($$); + YYABORT; + } + yyclearin; + yyerrok; + } + ; + +statement_list: statement statement_list { $$ = $1; $1->next = $2; } + | statement { $$ = $1; } + ; + +body: "{" statement_list "}" { $$ = gen_block($2); } + | "{" "}" { $$ = gen_block(gen_empty()); } + ; + +macro_list: id "," macro_list { $$ = $1; $$->next = $3; } + | id { $$ = $1; } + ; + +macro: "define" id "(" macro_list ")" body { + $$ = gen_macro($2, $4, $6); + ast_set_flags($6, AST_FLAG_UNHYGIENIC); + } + | "define" id "(" macro_list "..." id ")" body { + /* TODO: the location data of the variadic ID is way off */ + ast_append($4, $6); + $$ = gen_macro($2, $4, $8); + ast_set_flags($$, AST_FLAG_VARIADIC); + ast_set_flags($8, AST_FLAG_UNHYGIENIC); + } + | "define" id "(" ")" body { + $$ = gen_macro($2, NULL, $5); + ast_set_flags($5, AST_FLAG_UNHYGIENIC); + } + ; + +expr_list: expr "," expr_list { $$ = $1; $1->next = $3; } + | expr { $$ = $1; } + ; + +init_expr: expr { $$ = $1; } + | var_init { $$ = $1; } + | "&" var_init { $$ = gen_unop(AND, $2); } + | expr_if { $$ = $1; } + ; + +expr_if: "if" init_expr body "else" body { $$ = gen_if($2, $3, $5); } + | "if" init_expr body "else" expr_if { $$ = gen_if($2, $3, $5); } + ; + +if: "if" init_expr body { $$ = gen_if($2, $3, NULL); } + | "if" init_expr body "else" body { $$ = gen_if($2, $3, $5); } + | "if" init_expr body "else" if { $$ = gen_if($2, $3, $5); } + ; + +for: "for" arg ";" expr ";" expr body { $$ = gen_for($2, $4, $6, $7); + } + ; + +case: "case" expr ":" statement_list { $$ = gen_case($2, $4); } + ; + +case_list: case case_list { $$ = $1; $1->next = $2; } + | case { $$ = $1; } + ; + +switch: "switch" expr "{" case_list "}" { $$ = gen_switch($2, $4); } + ; + +const_for: "for" id ":" expr_list body { + /* TODO: should id be a separate rule? */ + $$ = gen_for($2, NULL, $4, $5); + ast_set_flags($5, AST_FLAG_UNHYGIENIC); + } + ; + +const_if: "if" expr body { + $$ = gen_if($2, $3, NULL); + ast_set_flags($3, AST_FLAG_UNHYGIENIC); + } + | "if" expr body "else" body { + $$ = gen_if($2, $3, $5); + ast_set_flags($3, AST_FLAG_UNHYGIENIC); + ast_set_flags($5, AST_FLAG_UNHYGIENIC); + } + | "if" expr body "else" const_if { + $$ = gen_if($2, $3, $5); + ast_set_flags($3, AST_FLAG_UNHYGIENIC); + } + ; + +/* maybe I should add a separate gen_iter_for or something and just support + * iterator for? */ +const: "const" const_if { $$ = $2; ast_set_flags($$, AST_FLAG_CONST); + } + | "const" const_for { $$ = $2; ast_set_flags($$, AST_FLAG_CONST); + } + ; + +func_sign: "(" decl_list "=>" type ")" { + $$ = gen_type(AST_TYPE_SIGN, NULL, $2, $4); + } + | "(" decl_list ")" { $$ = gen_type(AST_TYPE_SIGN, NULL, $2, NULL); + } + | "(" decl_list "=>" ")" { $$ = gen_type(AST_TYPE_SIGN, NULL, $2, NULL); + } + | "(" "=>" type ")" { $$ = gen_type(AST_TYPE_SIGN, NULL, NULL, $3); + } + | "(" ")" { $$ = gen_type(AST_TYPE_SIGN, NULL, NULL, NULL); + } + ; + +variadic_sign: func_sign { $$ = $1; } + | "(" decl_list "..." id "=>" type ")" { + ast_append($2, $4); + $$ = gen_type(AST_TYPE_SIGN, NULL, $2, $6); + ast_set_flags($$, AST_FLAG_VARIADIC); + } + ; + +impl: id "{" type_list "}" { $$ = gen_type(AST_TYPE_IMPL, $1, $3, NULL); + } + | id { $$ = gen_type(AST_TYPE_IMPL, $1, NULL, NULL); } + ; + +impl_list: impl "," impl_list { $$ = $1; $1->next = $3;} + | impl { $$ = $1; } + ; + +type: id { $$ = gen_type(AST_TYPE_ID, $1, NULL, NULL); } + | "'" func_sign { + $$ = gen_type(AST_TYPE_POINTER, NULL, NULL, NULL); + $$->next = $2; + } + | id "(" impl_list ")" { + $$ = gen_type(AST_TYPE_STRUCT, $1, $3, NULL); + } + | "'" type { + $$ = gen_type(AST_TYPE_POINTER, NULL, NULL, NULL); + $$->_type.next = $2; + } + | "[" "]" type { + $$ = gen_type(AST_TYPE_ARR, NULL, NULL, NULL); + $$->_type.next = $3; + } + | "[" expr "]" type { + $$ = gen_type(AST_TYPE_ARR, NULL, $2, NULL); + $$->_type.next = $4; + } + | "typeof" expr { + $$ = gen_type(AST_TYPE_TYPEOF, NULL, $2, NULL); + } + ; + +var_decl: id "mut" type { + $$ = gen_var($1, $3, NULL); + ast_set_flags($$, AST_FLAG_MUTABLE); + } + | id "const" type { $$ = gen_var($1, $3, NULL); } + | id type { $$ = gen_var($1, $2, NULL); } + ; + +var_init: var_decl "=" init_expr { $$ = $1; $1->_var.init = $3; } + | id "mut" "=" init_expr { + $$ = gen_var($1, NULL, $4); + ast_set_flags($$, AST_FLAG_UNTYPED | AST_FLAG_MUTABLE); + } + | id "const" "=" init_expr { + $$ = gen_var($1, NULL, $4); + ast_set_flags($$, AST_FLAG_UNTYPED); + } + ; + +proc: id variadic_sign body { + $$ = gen_proc($1, $2, $3); + ast_set_flags($$, $2->flags); + } + | "extern" id func_sign { + $$ = gen_proc($2, $3, NULL); + ast_set_flags($$, AST_FLAG_EXTERN); + } + ; + +lambda: "[" macro_list "]" func_sign body { $$ = gen_lambda($2, $4, $5); + } + ; + +union_elem: union { $$ = $1; } + | var_decl { $$ = $1; } + ; + +union_list: union_elem ";" union_list { $$ = $1; $1->next = $3; } + | union_elem ";" { $$ = $1; } + ; + +union: "union" id "{" union_list "}" { + $$ = gen_type(AST_TYPE_UNION, $2, $4, NULL); + } + | "union" "{" union_list "}" { + $$ = gen_type(AST_TYPE_UNION, NULL, $3, NULL); } + ; + +/* alias I guess, might try and name things better */ +struct_list: union_list { $$ = $1; } + ; + +struct: "struct" id "{" struct_list "}" { + $$ = gen_struct($2, NULL, $4); + } + | "struct" id "(" type_list ")" "{" struct_list "}" { + $$ = gen_struct($2, $4, $7); + } + ; + +template_elem: impl ";" { $$ = $1; } + | id func_sign ";" { $$ = gen_proc($1, $2, NULL); } + | var_decl ";" { $$ = $1; } + | union { $$ = $1; } + ; + +template_list: template_elem template_list { $$ = $1; $1->next = $2; } + | template_elem { $$ = $1; } + ; + +type_list: type "," type_list { $$ = $1; $1->next = $3; } + | type { $$ = $1; } + ; + +type_alias: "type" id type { $$ = gen_alias($2, $3); } + ; + +/* we'll parse the arg list later in the AST and check that each node is of some + * specific type */ +type_template: "type" id "{" template_list "}" { + $$ = gen_template($2, $4); + } + | "type" id "{" "}" { + /* should match anything, but doesn't implement anything */ + $$ = gen_template($2, NULL); + } + ; + +enum_val: id { $$ = gen_val($1, gen_int(0)); } + | id "=" expr { $$ = gen_val($1, $3); } + ; + +enum_list: enum_val "," enum_list { $$ = $1; $1->next = $3; } + | enum_val "," { $$ = $1; } + | enum_val { $$ = $1; } + ; + +enum: "enum" id ":" type "{" enum_list "}" { $$ = gen_enum($2, $4, $6); } + | "enum" id "{" enum_list "}" { + $$ = gen_enum($2, NULL, $4); + ast_set_flags($$, AST_FLAG_UNTYPED); + } + ; + +top_if: "if" expr "{" unit "}" { + $$ = gen_if($2, $4, NULL); + ast_set_flags($$, AST_FLAG_UNHYGIENIC); + } + | "if" expr "{" unit "}" "else" "{" unit "}" { + $$ = gen_if($2, $4, $8); + ast_set_flags($$, AST_FLAG_UNHYGIENIC); + } + | "if" expr "{" unit "}" "else" top_if { + $$ = gen_if($2, $4, $7); + ast_set_flags($$, AST_FLAG_UNHYGIENIC); + } + ; + +/* slightly silly to allow stray semicolons at a top level, but seems to help + * with recovering from certain syntax errors */ +top: ";" { $$ = gen_empty(); } + | enum { $$ = $1; } + | proc { $$ = $1; } + | struct { $$ = $1; } + | macro { $$ = $1; } + | "const" top_if { $$ = $2; ast_set_flags($$, AST_FLAG_CONST); } + | import ";" { $$ = $1; } + | type_alias ";" { $$ = $1; } + | type_template { $$ = $1; } + | "pub" enum { $$ = $2; ast_set_flags($2, AST_FLAG_PUBLIC); } + | "pub" struct { $$ = $2; ast_set_flags($2, AST_FLAG_PUBLIC); } + | "pub" proc { $$ = $2; ast_set_flags($2, AST_FLAG_PUBLIC); } + | "pub" macro { $$ = $2; ast_set_flags($2, AST_FLAG_PUBLIC); } + | "pub" import ";" { $$ = $2; ast_set_flags($2, AST_FLAG_PUBLIC); } + | "pub" type_alias ";" { $$ = $2; ast_set_flags($2, AST_FLAG_PUBLIC); } + | "pub" type_template { $$ = $2; ast_set_flags($2, AST_FLAG_PUBLIC); } + | var ";" { $$ = $1; } + | "pub" var ";" { $$ = $2; ast_set_flags($2, AST_FLAG_PUBLIC); } + | error { + $$ = gen_empty(); + parser->failed = true; + /* ignore any content inside a top level thing and just move onto + * the next one */ + if (next_interesting_feature(&yylval, &yylloc, scanner, parser)) { + /* remove previously allocated node if we're out of input */ + destroy_ast_node($$); + YYABORT; + } + yyclearin; + yyerrok; + } + ; + +unit: top { $$ = $1; } + | top unit { $$ = $1; $1->next = $2; } + ; + +input: unit { parser->tree = $1; } + | /* empty */ + ; +%% |
