making initialization statements on if/while/switch better
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 29 Aug 2021 21:12:38 +0000 (16:12 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 29 Aug 2021 21:12:38 +0000 (16:12 -0500)
bin/onyx
docs/bugs
examples/14_overloaded_procs.onyx
examples/15_polymorphic_procs.onyx
include/onyxastnodes.h
src/onyxchecker.c
src/onyxclone.c
src/onyxparser.c
src/onyxsymres.c
src/onyxwasm.c

index ac91d8d6e126c3fad1e73d89f57ecf2f1f46fe95..8fb63e10d8eda1a518b3256b1b2d5d09b2d20041 100755 (executable)
Binary files a/bin/onyx and b/bin/onyx differ
index e3ead4575f4cf034e8f26fd939f970d647dbc82c..209cc77a8759824057c93c8d5304c189f82e7fbe 100644 (file)
--- 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.
index fd500a50c8e0d6cd2c27f533b5c715af548faa40..91a06fb77013d2d779733a5056037731ae75c8a7 100644 (file)
@@ -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
index ddf56f41c5dd009bcefd9aa428a6f83486b5c8e7..c0e7013d1120783f978965026183633c127c749f 100644 (file)
@@ -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
index 48ac2bcbb7e49fac98ca80a18a5040cdbfb3621d..759251ad0e62132fa1767adae85c8ea19ede159e 100644 (file)
@@ -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;
 
index d581a2dc93aff92a08cbb10d97c06e8ee69626e7..7f3c359d95e8f0529e5005ddb0e007fe05c37a65 100644 (file)
@@ -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);
index 7dfe23da80dcee7c5e47652814a0468db0eff31b..8940a035d6f6ba60f77b349f66d1f684ad88677a 100644 (file)
@@ -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);
index 9fefea065daffd282e91c3c55b9f9fe73af36a33..83e8173fd68d6c5704618f4f21f320ff623e5724 100644 (file)
@@ -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;
index 01d86ee55f72e49a2d00c06ef09aaab8bbdb22df..1ea738389d34fafde3c374de175bb99676cf55cd 100644 (file)
@@ -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;
 }
 
index bec0833b19fb9643d8d39b715869b1cc8500cae0..435a7820dd3e9526f1aa8e1e4cdddffa2c14b9ba 100644 (file)
@@ -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;
 }