From: Brendan Hansen Date: Sun, 29 Aug 2021 21:12:38 +0000 (-0500) Subject: making initialization statements on if/while/switch better X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=fb2c6770d579687552ef2f40df3dc33b9e809de3;p=onyx.git making initialization statements on if/while/switch better --- diff --git a/bin/onyx b/bin/onyx index ac91d8d6..8fb63e10 100755 Binary files a/bin/onyx and b/bin/onyx differ diff --git a/docs/bugs b/docs/bugs index e3ead457..209cc77a 100644 --- a/docs/bugs +++ b/docs/bugs @@ -8,6 +8,13 @@ List of known bugs: println(x); // Doesn't work } +[X] Initialization statements on control statements should be better. For example, this should + be possible: + + if x, y := foo(); x != y { + // ... + } + [X] macros are not allowed at the expression level. This is not necessarily a bug, but does bring many complications to the table about how resolve this. Current solution is to turn expression macros (macros that specify a return value) into a `do` expression. diff --git a/examples/14_overloaded_procs.onyx b/examples/14_overloaded_procs.onyx index fd500a50..91a06fb7 100644 --- a/examples/14_overloaded_procs.onyx +++ b/examples/14_overloaded_procs.onyx @@ -1,5 +1,3 @@ -// CLEANUP: FINISH THIS EXAMPLE WHEN OVERLOADS ARE BETTER - // Overloaded procedures, if you're not familiar with the concept, means that a // given procedure has multiple implementations and which one is used depends on diff --git a/examples/15_polymorphic_procs.onyx b/examples/15_polymorphic_procs.onyx index ddf56f41..c0e7013d 100644 --- a/examples/15_polymorphic_procs.onyx +++ b/examples/15_polymorphic_procs.onyx @@ -1,3 +1,62 @@ +// There going to be examples in this sections that I do not want to be part of the +// code, but I want the syntax highlighting to apply to them so I'm just going to +// `#if false` this section out so the examples compiled. +#if false { + +// If you are unfamiliar, polymorphic procedures allow you to write code that applies +// to all types, instead of to only a particular type. For example, you will probably +// want something like a max function that returns the larger of its two arguments. +// You might start by writing it for just integer types as so: + +max :: (a: i32, b: i32) -> i32 { + return a if a > b else b; +} + +// This will work as advertised, but if you try to call it with something other than +// i32's, you will get a compile time error, even though the function body doesn't do +// any thing that only applies to i32's. The function body just assumes that '>' is +// defined for a and b. + +// To write max in a polymorphic way, you can do it in this way: + +max :: (a: $Type, b: Type) -> Type { + return a if a > b else b; +} + +// In this example, 'Type' is a polymorphic variable, meaning that it is solved for +// whenever 'max' is called. You'll notice that the first 'Type' has a dollar sign in +// front of it. The dollar sign appears exactly once in front the polymorphic variable +// name, as it specifies where the variable should be solved for. In this example, 'Type' +// will be given to be the same type as 'a', and then 'b' and the return type should be +// of the same type. + +// As another example, take 'array.push': + +push :: (arr: ^[..] $T, value: T) { + // ... +} + +// The dollar sign in front of the first T means the argument will provide the type for T, +// then the value is expected to be the same type. If the dollar sign would have been on +// the second T like so, + +push :: (arr: ^[..] T, value: $T) { + // ... +} + +// then the type of value would be used for T. This wouldn't change much in practice, for a +// correct program. However, if your program has an error, then the second example would +// report an error saying that the first argument is of the wrong type, instead of the more +// likely error that the second argument is of the wrong type. + +// One final example is a single compose function: + +compose :: (a: $A, f: (A) -> $B, g: (B) -> $C) -> C { + return g(f(a)); +} + +} + #load "core/std" use package core diff --git a/include/onyxastnodes.h b/include/onyxastnodes.h index 48ac2bcb..759251ad 100644 --- a/include/onyxastnodes.h +++ b/include/onyxastnodes.h @@ -695,8 +695,7 @@ struct AstIfWhile { AstNode_base; Scope *scope; - AstLocal *local; - AstBinaryOp *assignment; + AstNode* initialization; AstTyped *cond; @@ -720,8 +719,7 @@ struct AstSwitch { AstNode_base; Scope *scope; - AstLocal *local; - AstBinaryOp *assignment; + AstNode* initialization; AstTyped *expr; diff --git a/src/onyxchecker.c b/src/onyxchecker.c index d581a2dc..7f3c359d 100644 --- a/src/onyxchecker.c +++ b/src/onyxchecker.c @@ -175,7 +175,7 @@ CheckStatus check_return(AstReturn* retnode) { } CheckStatus check_if(AstIfWhile* ifnode) { - if (ifnode->assignment != NULL) CHECK(statement, (AstNode **) &ifnode->assignment); + if (ifnode->initialization != NULL) CHECK(statement_chain, &ifnode->initialization); if (ifnode->kind == Ast_Kind_Static_If) { if ((ifnode->flags & Ast_Flag_Static_If_Resolved) == 0) { @@ -184,7 +184,7 @@ CheckStatus check_if(AstIfWhile* ifnode) { if (static_if_resolution(ifnode)) { if (ifnode->true_stmt != NULL) CHECK(statement, (AstNode **) &ifnode->true_stmt); - + } else { if (ifnode->false_stmt != NULL) CHECK(statement, (AstNode **) &ifnode->false_stmt); } @@ -204,7 +204,7 @@ CheckStatus check_if(AstIfWhile* ifnode) { } CheckStatus check_while(AstIfWhile* whilenode) { - if (whilenode->assignment != NULL) CHECK(statement, (AstNode **) &whilenode->assignment); + if (whilenode->initialization != NULL) CHECK(statement_chain, &whilenode->initialization); CHECK(expression, &whilenode->cond); @@ -317,7 +317,7 @@ static b32 add_case_to_switch_statement(AstSwitch* switchnode, u64 case_value, A } CheckStatus check_switch(AstSwitch* switchnode) { - if (switchnode->assignment != NULL) CHECK(statement, (AstNode **) &switchnode->assignment); + if (switchnode->initialization != NULL) CHECK(statement_chain, &switchnode->initialization); CHECK(expression, &switchnode->expr); Type* resolved_expr_type = resolve_expression_type(switchnode->expr); diff --git a/src/onyxclone.c b/src/onyxclone.c index 7dfe23da..8940a035 100644 --- a/src/onyxclone.c +++ b/src/onyxclone.c @@ -243,11 +243,7 @@ AstNode* ast_clone(bh_allocator a, void* n) { case Ast_Kind_If: case Ast_Kind_While: - C(AstIfWhile, local); - C(AstIfWhile, assignment); - - if (((AstIfWhile *) nn)->assignment) - ((AstIfWhile *) nn)->assignment->left = (AstTyped *) ((AstIfWhile *) nn)->local; + ((AstIfWhile *) nn)->initialization = ast_clone_list(a, ((AstIfWhile *) node)->initialization); C(AstIfWhile, cond); //fallthrough @@ -261,10 +257,7 @@ AstNode* ast_clone(bh_allocator a, void* n) { AstSwitch* dw = (AstSwitch *) nn; AstSwitch* sw = (AstSwitch *) node; - dw->local = (AstLocal *) ast_clone(a, sw->local); - dw->assignment = (AstBinaryOp *) ast_clone(a, sw->assignment); - if (dw->assignment) - dw->assignment->left = (AstTyped *) sw->local; + ((AstSwitch *) nn)->initialization = ast_clone_list(a, ((AstSwitch *) node)->initialization); dw->expr = (AstTyped *) ast_clone(a, sw->expr); dw->default_case = (AstBlock *) ast_clone(a, sw->default_case); diff --git a/src/onyxparser.c b/src/onyxparser.c index 9fefea06..83e8173f 100644 --- a/src/onyxparser.c +++ b/src/onyxparser.c @@ -954,32 +954,30 @@ static AstIfWhile* parse_if_stmt(OnyxParser* parser) { if_node->token = expect_token(parser, Token_Type_Keyword_If); AstIfWhile* root_if = if_node; + AstTyped* cond; + AstNode* initialization_or_cond=NULL; + b32 had_initialization = 0; + if (parse_possible_symbol_declaration(parser, &initialization_or_cond)) { + had_initialization = 1; - // CLEANUP: All of the control blocks use this same kind of logic, and it - // is underpowered for what I think should be possible. Factor it out and - // make it better? - if (peek_token(1)->type == ':') { - OnyxToken* local_sym = expect_token(parser, Token_Type_Symbol); - if_node->local = make_local(parser->allocator, local_sym, NULL); - - expect_token(parser, ':'); - - AstBinaryOp* assignment = make_node(AstBinaryOp, Ast_Kind_Binary_Op); - assignment->operation = Binary_Op_Assign; - assignment->token = expect_token(parser, '='); - assignment->left = (AstTyped *) if_node->local; - assignment->right = parse_expression(parser, 0); + } else { + // NOTE: Assignment is allowed here because instead of not parsing correctly, + // an error is reported in the typechecking, saying that assignment isn't allowed + // here, which is better than an unexpected token error. + initialization_or_cond = (AstNode *) parse_compound_expression(parser, 1); + } - if_node->assignment = assignment; + if (had_initialization || parser->curr->type == ';') { expect_token(parser, ';'); + cond = parse_expression(parser, 1); + } else { + cond = (AstTyped *) initialization_or_cond; + initialization_or_cond = NULL; } - // NOTE: Assignment is allowed here because instead of not parsing correctly, - // an error is reported in the typechecking, saying that assignment isn't allowed - // here, which is better than an unexpected token error. - AstTyped* cond = parse_expression(parser, 1); AstBlock* true_stmt = parse_block(parser, 1); + if_node->initialization = initialization_or_cond; if_node->cond = cond; if (true_stmt != NULL) if_node->true_stmt = true_stmt; @@ -1015,24 +1013,29 @@ static AstIfWhile* parse_while_stmt(OnyxParser* parser) { AstIfWhile* while_node = make_node(AstIfWhile, Ast_Kind_While); while_node->token = while_token; - // CLEANUP: See above in parse_if_stmt - if (peek_token(1)->type == ':') { - OnyxToken* local_sym = expect_token(parser, Token_Type_Symbol); - while_node->local = make_local(parser->allocator, local_sym, NULL); + AstTyped* cond; + AstNode* initialization_or_cond=NULL; + b32 had_initialization = 0; + if (parse_possible_symbol_declaration(parser, &initialization_or_cond)) { + had_initialization = 1; - expect_token(parser, ':'); - - AstBinaryOp* assignment = make_node(AstBinaryOp, Ast_Kind_Binary_Op); - assignment->operation = Binary_Op_Assign; - assignment->token = expect_token(parser, '='); - assignment->left = (AstTyped *) while_node->local; - assignment->right = parse_expression(parser, 0); + } else { + // NOTE: Assignment is allowed here because instead of not parsing correctly, + // an error is reported in the typechecking, saying that assignment isn't allowed + // here, which is better than an unexpected token error. + initialization_or_cond = (AstNode *) parse_compound_expression(parser, 1); + } - while_node->assignment = assignment; + if (had_initialization || parser->curr->type == ';') { expect_token(parser, ';'); + cond = parse_expression(parser, 1); + } else { + cond = (AstTyped *) initialization_or_cond; + initialization_or_cond = NULL; } - while_node->cond = parse_expression(parser, 1); + while_node->initialization = initialization_or_cond; + while_node->cond = cond; while_node->true_stmt = parse_block(parser, 1); if (consume_token_if_next(parser, Token_Type_Keyword_Else)) { @@ -1068,24 +1071,31 @@ static AstSwitch* parse_switch_stmt(OnyxParser* parser) { bh_arr_new(global_heap_allocator, switch_node->cases, 4); - // CLEANUP: See above in parse_if_stmt - if (peek_token(1)->type == ':') { - OnyxToken* local_sym = expect_token(parser, Token_Type_Symbol); - switch_node->local = make_local(parser->allocator, local_sym, NULL); - - expect_token(parser, ':'); + AstTyped* expr; + AstNode* initialization_or_expr=NULL; + b32 had_initialization = 0; + if (parse_possible_symbol_declaration(parser, &initialization_or_expr)) { + had_initialization = 1; - AstBinaryOp* assignment = make_node(AstBinaryOp, Ast_Kind_Binary_Op); - assignment->operation = Binary_Op_Assign; - assignment->token = expect_token(parser, '='); - assignment->left = (AstTyped *) switch_node->local; - assignment->right = parse_expression(parser, 0); + } else { + // NOTE: Assignment is allowed here because instead of not parsing correctly, + // an error is reported in the typechecking, saying that assignment isn't allowed + // here, which is better than an unexpected token error. + initialization_or_expr = (AstNode *) parse_compound_expression(parser, 1); + } - switch_node->assignment = assignment; + if (had_initialization || parser->curr->type == ';') { expect_token(parser, ';'); + expr = parse_expression(parser, 1); + + } else { + expr = (AstTyped *) initialization_or_expr; + initialization_or_expr = NULL; } - switch_node->expr = parse_expression(parser, 1); + switch_node->initialization = initialization_or_expr; + switch_node->expr = expr; + expect_token(parser, '{'); while (consume_token_if_next(parser, Token_Type_Keyword_Case)) { @@ -1204,7 +1214,7 @@ static i32 parse_possible_symbol_declaration(OnyxParser* parser, AstNode** ret) if (parser->curr->type == ':') { AstBinding* binding = parse_top_level_binding(parser, symbol); if (parser->hit_unexpected_token) return 2; - + ENTITY_SUBMIT(binding); return 2; } @@ -1325,7 +1335,7 @@ static AstNode* parse_statement(OnyxParser* parser) { i32 symbol_res = parse_possible_symbol_declaration(parser, &retval); if (symbol_res == 2) needs_semicolon = 0; if (symbol_res != 0) break; - + // fallthrough } @@ -1431,11 +1441,11 @@ static AstNode* parse_statement(OnyxParser* parser) { if (parser->curr->type != '=') memres->type_node = parse_type(parser); - + if (consume_token_if_next(parser, '=')) memres->initial_value = parse_expression(parser, 1); - - + + ENTITY_SUBMIT(memres); AstBinding* binding = make_node(AstBinding, Ast_Kind_Binding); @@ -1873,7 +1883,7 @@ static AstStructType* parse_struct(OnyxParser* parser) { AstBinding* binding = parse_top_level_binding(parser, binding_name); if (binding) ENTITY_SUBMIT(binding); - + consume_token_if_next(parser, ';'); } else { @@ -2302,7 +2312,7 @@ static AstTyped* parse_global_declaration(OnyxParser* parser) { } global_node->type_node = parse_type(parser); - + ENTITY_SUBMIT(global_node); return (AstTyped *) global_node; diff --git a/src/onyxsymres.c b/src/onyxsymres.c index 01d86ee5..1ea73838 100644 --- a/src/onyxsymres.c +++ b/src/onyxsymres.c @@ -520,19 +520,17 @@ static SymresStatus symres_if(AstIfWhile* ifnode) { if (static_if_resolution(ifnode)) { if (ifnode->true_stmt != NULL) SYMRES(statement, (AstNode **) &ifnode->true_stmt, NULL); - + } else { if (ifnode->false_stmt != NULL) SYMRES(statement, (AstNode **) &ifnode->false_stmt, NULL); } } else { - if (ifnode->assignment != NULL) { + if (ifnode->initialization != NULL) { ifnode->scope = scope_create(context.ast_alloc, curr_scope, ifnode->token->pos); scope_enter(ifnode->scope); - SYMRES(local, &ifnode->local); - - SYMRES(statement, (AstNode **) &ifnode->assignment, NULL); + SYMRES(statement_chain, &ifnode->initialization); } SYMRES(expression, &ifnode->cond); @@ -541,20 +539,18 @@ static SymresStatus symres_if(AstIfWhile* ifnode) { if (ifnode->true_stmt != NULL) SYMRES(statement, (AstNode **) &ifnode->true_stmt, NULL); if (ifnode->false_stmt != NULL) SYMRES(statement, (AstNode **) &ifnode->false_stmt, NULL); - if (ifnode->assignment != NULL) scope_leave(); + if (ifnode->initialization != NULL) scope_leave(); } return Symres_Success; } static SymresStatus symres_while(AstIfWhile* whilenode) { - if (whilenode->assignment != NULL) { + if (whilenode->initialization != NULL) { whilenode->scope = scope_create(context.ast_alloc, curr_scope, whilenode->token->pos); scope_enter(whilenode->scope); - SYMRES(local, &whilenode->local); - - SYMRES(statement, (AstNode **) &whilenode->assignment, NULL); + SYMRES(statement_chain, &whilenode->initialization); } SYMRES(expression, &whilenode->cond); @@ -562,7 +558,7 @@ static SymresStatus symres_while(AstIfWhile* whilenode) { if (whilenode->true_stmt) SYMRES(block, whilenode->true_stmt); if (whilenode->false_stmt) SYMRES(block, whilenode->false_stmt); - if (whilenode->assignment != NULL) scope_leave(); + if (whilenode->initialization != NULL) scope_leave(); return Symres_Success; } @@ -579,13 +575,11 @@ static SymresStatus symres_for(AstFor* fornode) { } static SymresStatus symres_switch(AstSwitch* switchnode) { - if (switchnode->assignment != NULL) { + if (switchnode->initialization != NULL) { switchnode->scope = scope_create(context.ast_alloc, curr_scope, switchnode->token->pos); scope_enter(switchnode->scope); - symbol_introduce(curr_scope, switchnode->local->token, (AstNode *) switchnode->local); - - SYMRES(statement, (AstNode **) &switchnode->assignment, NULL); + SYMRES(statement_chain, &switchnode->initialization); } SYMRES(expression, &switchnode->expr); @@ -593,14 +587,15 @@ static SymresStatus symres_switch(AstSwitch* switchnode) { bh_arr_each(AstSwitchCase, sc, switchnode->cases) { bh_arr_each(AstTyped *, value, sc->values) SYMRES(expression, value); - + SYMRES(block, sc->block); } if (switchnode->default_case) SYMRES(block, switchnode->default_case); - if (switchnode->assignment != NULL) scope_leave(); + if (switchnode->initialization != NULL) scope_leave(); + return Symres_Success; } diff --git a/src/onyxwasm.c b/src/onyxwasm.c index bec0833b..435a7820 100644 --- a/src/onyxwasm.c +++ b/src/onyxwasm.c @@ -693,10 +693,12 @@ EMIT_FUNC(load_instruction, Type* type, u32 offset) { EMIT_FUNC(if, AstIfWhile* if_node) { bh_arr(WasmInstruction) code = *pcode; - if (if_node->assignment != NULL) { - bh_imap_put(&mod->local_map, (u64) if_node->local, local_allocate(mod->local_alloc, (AstTyped *) if_node->local)); + if (if_node->initialization != NULL) { + // bh_imap_put(&mod->local_map, (u64) if_node->local, local_allocate(mod->local_alloc, (AstTyped *) if_node->local)); - emit_assignment(mod, &code, if_node->assignment); + forll (AstNode, stmt, if_node->initialization, next) { + emit_statement(mod, &code, stmt); + } } if (if_node->kind == Ast_Kind_Static_If) { @@ -727,21 +729,16 @@ EMIT_FUNC(if, AstIfWhile* if_node) { emit_leave_structured_block(mod, &code); - if (if_node->assignment != NULL) { - local_free(mod->local_alloc, (AstTyped *) if_node->local); - } - - *pcode = code; } EMIT_FUNC(while, AstIfWhile* while_node) { bh_arr(WasmInstruction) code = *pcode; - if (while_node->assignment != NULL) { - bh_imap_put(&mod->local_map, (u64) while_node->local, local_allocate(mod->local_alloc, (AstTyped *) while_node->local)); - - emit_assignment(mod, &code, while_node->assignment); + if (while_node->initialization != NULL) { + forll (AstNode, stmt, while_node->initialization, next) { + emit_statement(mod, &code, stmt); + } } if (while_node->false_stmt == NULL) { @@ -779,9 +776,6 @@ EMIT_FUNC(while, AstIfWhile* while_node) { emit_leave_structured_block(mod, &code); } - if (while_node->assignment != NULL) - local_free(mod->local_alloc, (AstTyped *) while_node->local); - *pcode = code; } @@ -1108,10 +1102,10 @@ EMIT_FUNC(switch, AstSwitch* switch_node) { bh_imap block_map; bh_imap_init(&block_map, global_heap_allocator, bh_arr_length(switch_node->cases)); - if (switch_node->assignment != NULL) { - bh_imap_put(&mod->local_map, (u64) switch_node->local, local_allocate(mod->local_alloc, (AstTyped *) switch_node->local)); - - emit_assignment(mod, &code, switch_node->assignment); + if (switch_node->initialization != NULL) { + forll (AstNode, stmt, switch_node->initialization, next) { + emit_statement(mod, &code, stmt); + } } emit_enter_structured_block(mod, &code, SBT_Breakable_Block); @@ -1178,9 +1172,6 @@ EMIT_FUNC(switch, AstSwitch* switch_node) { emit_leave_structured_block(mod, &code); - if (switch_node->assignment != NULL) - local_free(mod->local_alloc, (AstTyped *) switch_node->local); - bh_imap_free(&block_map); *pcode = code; }