| author | |
| committer | |
| log | 21eca6478f80f871f839b20044869cead55582a5 |
| tree | e638b655a8873ec255cc66493ed7cc6778a91c39 |
| parent | fdf6a184615abab3314664b940ea18bc44de4546 |
see #445 files changed, 159 insertions(+), 9 deletions(-)
src/all_types.hpp+13| ... | @@ -24,6 +24,7 @@ struct BlockContext; | ... | @@ -24,6 +24,7 @@ struct BlockContext; |
| 24 | struct TypeTableEntry; | 24 | struct TypeTableEntry; |
| 25 | struct VariableTableEntry; | 25 | struct VariableTableEntry; |
| 26 | struct ErrorTableEntry; | 26 | struct ErrorTableEntry; |
| 27 | struct LabelTableEntry; | ||
| 27 | struct BuiltinFnEntry; | 28 | struct BuiltinFnEntry; |
| 28 | struct TypeStructField; | 29 | struct TypeStructField; |
| 29 | struct CodeGen; | 30 | struct CodeGen; |
| ... | @@ -538,6 +539,7 @@ struct AstNodeLabel { | ... | @@ -538,6 +539,7 @@ struct AstNodeLabel { |
| 538 | 539 | ||
| 539 | // populated by semantic analyzer | 540 | // populated by semantic analyzer |
| 540 | Expr resolved_expr; | 541 | Expr resolved_expr; |
| 542 | LabelTableEntry *label_entry; | ||
| 541 | }; | 543 | }; |
| 542 | 544 | ||
| 543 | struct AstNodeGoto { | 545 | struct AstNodeGoto { |
| ... | @@ -545,6 +547,7 @@ struct AstNodeGoto { | ... | @@ -545,6 +547,7 @@ struct AstNodeGoto { |
| 545 | 547 | ||
| 546 | // populated by semantic analyzer | 548 | // populated by semantic analyzer |
| 547 | Expr resolved_expr; | 549 | Expr resolved_expr; |
| 550 | LabelTableEntry *label_entry; | ||
| 548 | }; | 551 | }; |
| 549 | 552 | ||
| 550 | struct AsmOutput { | 553 | struct AsmOutput { |
| ... | @@ -1009,6 +1012,7 @@ struct FnTableEntry { | ... | @@ -1009,6 +1012,7 @@ struct FnTableEntry { |
| 1009 | ImportTableEntry *import_entry; | 1012 | ImportTableEntry *import_entry; |
| 1010 | // Required to be a pre-order traversal of the AST. (parents must come before children) | 1013 | // Required to be a pre-order traversal of the AST. (parents must come before children) |
| 1011 | ZigList<BlockContext *> all_block_contexts; | 1014 | ZigList<BlockContext *> all_block_contexts; |
| 1015 | ZigList<LabelTableEntry *> all_labels; | ||
| 1012 | Buf symbol_name; | 1016 | Buf symbol_name; |
| 1013 | TypeTableEntry *type_entry; // function type | 1017 | TypeTableEntry *type_entry; // function type |
| 1014 | bool is_inline; | 1018 | bool is_inline; |
| ... | @@ -1020,6 +1024,7 @@ struct FnTableEntry { | ... | @@ -1020,6 +1024,7 @@ struct FnTableEntry { |
| 1020 | ZigList<AstNode *> cast_alloca_list; | 1024 | ZigList<AstNode *> cast_alloca_list; |
| 1021 | ZigList<StructValExprCodeGen *> struct_val_expr_alloca_list; | 1025 | ZigList<StructValExprCodeGen *> struct_val_expr_alloca_list; |
| 1022 | ZigList<VariableTableEntry *> variable_list; | 1026 | ZigList<VariableTableEntry *> variable_list; |
| 1027 | ZigList<AstNode *> goto_list; | ||
| 1023 | }; | 1028 | }; |
| 1024 | 1029 | ||
| 1025 | enum BuiltinFnId { | 1030 | enum BuiltinFnId { |
| ... | @@ -1217,6 +1222,13 @@ struct ErrorTableEntry { | ... | @@ -1217,6 +1222,13 @@ struct ErrorTableEntry { |
| 1217 | AstNode *decl_node; | 1222 | AstNode *decl_node; |
| 1218 | }; | 1223 | }; |
| 1219 | 1224 | ||
| 1225 | struct LabelTableEntry { | ||
| 1226 | AstNode *decl_node; | ||
| 1227 | LLVMBasicBlockRef basic_block; | ||
| 1228 | bool used; | ||
| 1229 | bool entered_from_fallthrough; | ||
| 1230 | }; | ||
| 1231 | |||
| 1220 | struct BlockContext { | 1232 | struct BlockContext { |
| 1221 | // One of: NodeTypeFnDef, NodeTypeBlock, NodeTypeRoot, NodeTypeDefer, NodeTypeVariableDeclaration | 1233 | // One of: NodeTypeFnDef, NodeTypeBlock, NodeTypeRoot, NodeTypeDefer, NodeTypeVariableDeclaration |
| 1222 | AstNode *node; | 1234 | AstNode *node; |
| ... | @@ -1224,6 +1236,7 @@ struct BlockContext { | ... | @@ -1224,6 +1236,7 @@ struct BlockContext { |
| 1224 | // any variables that are introduced by this scope | 1236 | // any variables that are introduced by this scope |
| 1225 | HashMap<Buf *, AstNode *, buf_hash, buf_eql_buf> decl_table; | 1237 | HashMap<Buf *, AstNode *, buf_hash, buf_eql_buf> decl_table; |
| 1226 | HashMap<Buf *, VariableTableEntry *, buf_hash, buf_eql_buf> var_table; | 1238 | HashMap<Buf *, VariableTableEntry *, buf_hash, buf_eql_buf> var_table; |
| 1239 | HashMap<Buf *, LabelTableEntry *, buf_hash, buf_eql_buf> label_table; | ||
| 1227 | 1240 | ||
| 1228 | // if the block is inside a function, this is the function it is in: | 1241 | // if the block is inside a function, this is the function it is in: |
| 1229 | FnTableEntry *fn_entry; | 1242 | FnTableEntry *fn_entry; |
src/analyze.cpp+66-5| ... | @@ -1992,6 +1992,7 @@ BlockContext *new_block_context(AstNode *node, BlockContext *parent) { | ... | @@ -1992,6 +1992,7 @@ BlockContext *new_block_context(AstNode *node, BlockContext *parent) { |
| 1992 | context->parent = parent; | 1992 | context->parent = parent; |
| 1993 | context->decl_table.init(1); | 1993 | context->decl_table.init(1); |
| 1994 | context->var_table.init(1); | 1994 | context->var_table.init(1); |
| 1995 | context->label_table.init(1); | ||
| 1995 | 1996 | ||
| 1996 | if (parent) { | 1997 | if (parent) { |
| 1997 | context->parent_loop_node = parent->parent_loop_node; | 1998 | context->parent_loop_node = parent->parent_loop_node; |
| ... | @@ -2037,6 +2038,18 @@ static VariableTableEntry *find_variable(CodeGen *g, BlockContext *orig_context, | ... | @@ -2037,6 +2038,18 @@ static VariableTableEntry *find_variable(CodeGen *g, BlockContext *orig_context, |
| 2037 | return nullptr; | 2038 | return nullptr; |
| 2038 | } | 2039 | } |
| 2039 | 2040 | ||
| 2041 | static LabelTableEntry *find_label(CodeGen *g, BlockContext *orig_context, Buf *name) { | ||
| 2042 | BlockContext *context = orig_context; | ||
| 2043 | while (context && context->fn_entry) { | ||
| 2044 | auto entry = context->label_table.maybe_get(name); | ||
| 2045 | if (entry) { | ||
| 2046 | return entry->value; | ||
| 2047 | } | ||
| 2048 | context = context->parent; | ||
| 2049 | } | ||
| 2050 | return nullptr; | ||
| 2051 | } | ||
| 2052 | |||
| 2040 | static TypeEnumField *get_enum_field(TypeTableEntry *enum_type, Buf *name) { | 2053 | static TypeEnumField *get_enum_field(TypeTableEntry *enum_type, Buf *name) { |
| 2041 | for (uint32_t i = 0; i < enum_type->data.enumeration.field_count; i += 1) { | 2054 | for (uint32_t i = 0; i < enum_type->data.enumeration.field_count; i += 1) { |
| 2042 | TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[i]; | 2055 | TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[i]; |
| ... | @@ -5326,8 +5339,19 @@ static TypeTableEntry *analyze_block_expr(CodeGen *g, ImportTableEntry *import, | ... | @@ -5326,8 +5339,19 @@ static TypeTableEntry *analyze_block_expr(CodeGen *g, ImportTableEntry *import, |
| 5326 | for (int i = 0; i < node->data.block.statements.length; i += 1) { | 5339 | for (int i = 0; i < node->data.block.statements.length; i += 1) { |
| 5327 | AstNode *child = node->data.block.statements.at(i); | 5340 | AstNode *child = node->data.block.statements.at(i); |
| 5328 | if (child->type == NodeTypeLabel) { | 5341 | if (child->type == NodeTypeLabel) { |
| 5329 | add_node_error(g, child, | 5342 | FnTableEntry *fn_table_entry = child_context->fn_entry; |
| 5330 | buf_sprintf("label and goto not supported yet, see https://github.com/andrewrk/zig/issues/44")); | 5343 | assert(fn_table_entry); |
| 5344 | |||
| 5345 | LabelTableEntry *label = allocate<LabelTableEntry>(1); | ||
| 5346 | label->decl_node = child; | ||
| 5347 | label->entered_from_fallthrough = (return_type->id != TypeTableEntryIdUnreachable); | ||
| 5348 | |||
| 5349 | child->block_context = child_context; | ||
| 5350 | child->data.label.label_entry = label; | ||
| 5351 | fn_table_entry->all_labels.append(label); | ||
| 5352 | |||
| 5353 | child_context->label_table.put(&child->data.label.name, label); | ||
| 5354 | |||
| 5331 | return_type = g->builtin_types.entry_void; | 5355 | return_type = g->builtin_types.entry_void; |
| 5332 | continue; | 5356 | continue; |
| 5333 | } | 5357 | } |
| ... | @@ -5405,13 +5429,35 @@ static TypeTableEntry *analyze_asm_expr(CodeGen *g, ImportTableEntry *import, Bl | ... | @@ -5405,13 +5429,35 @@ static TypeTableEntry *analyze_asm_expr(CodeGen *g, ImportTableEntry *import, Bl |
| 5405 | return return_type; | 5429 | return return_type; |
| 5406 | } | 5430 | } |
| 5407 | 5431 | ||
| 5408 | static TypeTableEntry *analyze_goto(CodeGen *g, ImportTableEntry *import, BlockContext *context, | 5432 | static TypeTableEntry *analyze_goto_pass1(CodeGen *g, ImportTableEntry *import, BlockContext *context, |
| 5409 | TypeTableEntry *expected_type, AstNode *node) | 5433 | TypeTableEntry *expected_type, AstNode *node) |
| 5410 | { | 5434 | { |
| 5411 | add_node_error(g, node, buf_sprintf("goto is broken, see https://github.com/andrewrk/zig/issues/44")); | 5435 | assert(node->type == NodeTypeGoto); |
| 5436 | |||
| 5437 | FnTableEntry *fn_table_entry = context->fn_entry; | ||
| 5438 | assert(fn_table_entry); | ||
| 5439 | |||
| 5440 | fn_table_entry->goto_list.append(node); | ||
| 5441 | |||
| 5412 | return g->builtin_types.entry_unreachable; | 5442 | return g->builtin_types.entry_unreachable; |
| 5413 | } | 5443 | } |
| 5414 | 5444 | ||
| 5445 | static void analyze_goto_pass2(CodeGen *g, ImportTableEntry *import, AstNode *node) { | ||
| 5446 | assert(node->type == NodeTypeGoto); | ||
| 5447 | Buf *label_name = &node->data.goto_expr.name; | ||
| 5448 | BlockContext *context = node->block_context; | ||
| 5449 | assert(context); | ||
| 5450 | LabelTableEntry *label = find_label(g, context, label_name); | ||
| 5451 | |||
| 5452 | if (!label) { | ||
| 5453 | add_node_error(g, node, buf_sprintf("no label in scope named '%s'", buf_ptr(label_name))); | ||
| 5454 | return; | ||
| 5455 | } | ||
| 5456 | |||
| 5457 | label->used = true; | ||
| 5458 | node->data.goto_expr.label_entry = label; | ||
| 5459 | } | ||
| 5460 | |||
| 5415 | static TypeTableEntry *analyze_expression_pointer_only(CodeGen *g, ImportTableEntry *import, | 5461 | static TypeTableEntry *analyze_expression_pointer_only(CodeGen *g, ImportTableEntry *import, |
| 5416 | BlockContext *context, TypeTableEntry *expected_type, AstNode *node, bool pointer_only) | 5462 | BlockContext *context, TypeTableEntry *expected_type, AstNode *node, bool pointer_only) |
| 5417 | { | 5463 | { |
| ... | @@ -5433,7 +5479,7 @@ static TypeTableEntry *analyze_expression_pointer_only(CodeGen *g, ImportTableEn | ... | @@ -5433,7 +5479,7 @@ static TypeTableEntry *analyze_expression_pointer_only(CodeGen *g, ImportTableEn |
| 5433 | return_type = g->builtin_types.entry_void; | 5479 | return_type = g->builtin_types.entry_void; |
| 5434 | break; | 5480 | break; |
| 5435 | case NodeTypeGoto: | 5481 | case NodeTypeGoto: |
| 5436 | analyze_goto(g, import, context, expected_type, node); | 5482 | return_type = analyze_goto_pass1(g, import, context, expected_type, node); |
| 5437 | break; | 5483 | break; |
| 5438 | case NodeTypeBreak: | 5484 | case NodeTypeBreak: |
| 5439 | return_type = analyze_break_expr(g, import, context, expected_type, node); | 5485 | return_type = analyze_break_expr(g, import, context, expected_type, node); |
| ... | @@ -5611,6 +5657,21 @@ static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) { | ... | @@ -5611,6 +5657,21 @@ static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) { |
| 5611 | TypeTableEntry *block_return_type = analyze_expression(g, import, context, expected_type, node->data.fn_def.body); | 5657 | TypeTableEntry *block_return_type = analyze_expression(g, import, context, expected_type, node->data.fn_def.body); |
| 5612 | 5658 | ||
| 5613 | node->data.fn_def.implicit_return_type = block_return_type; | 5659 | node->data.fn_def.implicit_return_type = block_return_type; |
| 5660 | |||
| 5661 | for (int i = 0; i < fn_table_entry->goto_list.length; i += 1) { | ||
| 5662 | AstNode *goto_node = fn_table_entry->goto_list.at(i); | ||
| 5663 | assert(goto_node->type == NodeTypeGoto); | ||
| 5664 | analyze_goto_pass2(g, import, goto_node); | ||
| 5665 | } | ||
| 5666 | |||
| 5667 | for (int i = 0; i < fn_table_entry->all_labels.length; i += 1) { | ||
| 5668 | LabelTableEntry *label = fn_table_entry->all_labels.at(i); | ||
| 5669 | if (!label->used) { | ||
| 5670 | add_node_error(g, label->decl_node, | ||
| 5671 | buf_sprintf("label '%s' defined but not used", | ||
| 5672 | buf_ptr(&label->decl_node->data.label.name))); | ||
| 5673 | } | ||
| 5674 | } | ||
| 5614 | } | 5675 | } |
| 5615 | 5676 | ||
| 5616 | static void add_top_level_decl(CodeGen *g, ImportTableEntry *import, BlockContext *block_context, | 5677 | static void add_top_level_decl(CodeGen *g, ImportTableEntry *import, BlockContext *block_context, |
src/codegen.cpp+36-4| ... | @@ -2719,6 +2719,29 @@ static LLVMValueRef gen_switch_expr(CodeGen *g, AstNode *node) { | ... | @@ -2719,6 +2719,29 @@ static LLVMValueRef gen_switch_expr(CodeGen *g, AstNode *node) { |
| 2719 | } | 2719 | } |
| 2720 | } | 2720 | } |
| 2721 | 2721 | ||
| 2722 | static LLVMValueRef gen_goto(CodeGen *g, AstNode *node) { | ||
| 2723 | assert(node->type == NodeTypeGoto); | ||
| 2724 | |||
| 2725 | add_debug_source_node(g, node); | ||
| 2726 | LLVMBuildBr(g->builder, node->data.goto_expr.label_entry->basic_block); | ||
| 2727 | return nullptr; | ||
| 2728 | } | ||
| 2729 | |||
| 2730 | static LLVMValueRef gen_label(CodeGen *g, AstNode *node) { | ||
| 2731 | assert(node->type == NodeTypeLabel); | ||
| 2732 | |||
| 2733 | LabelTableEntry *label = node->data.label.label_entry; | ||
| 2734 | assert(label); | ||
| 2735 | |||
| 2736 | LLVMBasicBlockRef basic_block = label->basic_block; | ||
| 2737 | if (label->entered_from_fallthrough) { | ||
| 2738 | add_debug_source_node(g, node); | ||
| 2739 | LLVMBuildBr(g->builder, basic_block); | ||
| 2740 | } | ||
| 2741 | LLVMPositionBuilderAtEnd(g->builder, basic_block); | ||
| 2742 | return nullptr; | ||
| 2743 | } | ||
| 2744 | |||
| 2722 | static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { | 2745 | static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { |
| 2723 | Expr *expr = get_resolved_expr(node); | 2746 | Expr *expr = get_resolved_expr(node); |
| 2724 | if (expr->const_val.ok) { | 2747 | if (expr->const_val.ok) { |
| ... | @@ -2766,13 +2789,13 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { | ... | @@ -2766,13 +2789,13 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { |
| 2766 | case NodeTypeBlock: | 2789 | case NodeTypeBlock: |
| 2767 | return gen_block(g, node, nullptr); | 2790 | return gen_block(g, node, nullptr); |
| 2768 | case NodeTypeGoto: | 2791 | case NodeTypeGoto: |
| 2769 | zig_unreachable(); | 2792 | return gen_goto(g, node); |
| 2770 | case NodeTypeBreak: | 2793 | case NodeTypeBreak: |
| 2771 | return gen_break(g, node); | 2794 | return gen_break(g, node); |
| 2772 | case NodeTypeContinue: | 2795 | case NodeTypeContinue: |
| 2773 | return gen_continue(g, node); | 2796 | return gen_continue(g, node); |
| 2774 | case NodeTypeLabel: | 2797 | case NodeTypeLabel: |
| 2775 | zig_unreachable(); | 2798 | return gen_label(g, node); |
| 2776 | case NodeTypeContainerInitExpr: | 2799 | case NodeTypeContainerInitExpr: |
| 2777 | return gen_container_init_expr(g, node); | 2800 | return gen_container_init_expr(g, node); |
| 2778 | case NodeTypeSwitchExpr: | 2801 | case NodeTypeSwitchExpr: |
| ... | @@ -3105,6 +3128,16 @@ static void generate_error_name_table(CodeGen *g) { | ... | @@ -3105,6 +3128,16 @@ static void generate_error_name_table(CodeGen *g) { |
| 3105 | LLVMSetUnnamedAddr(g->err_name_table, true); | 3128 | LLVMSetUnnamedAddr(g->err_name_table, true); |
| 3106 | } | 3129 | } |
| 3107 | 3130 | ||
| 3131 | static void build_label_blocks(CodeGen *g, FnTableEntry *fn) { | ||
| 3132 | LLVMBasicBlockRef entry_block = LLVMAppendBasicBlock(fn->fn_value, "entry"); | ||
| 3133 | for (int i = 0; i < fn->all_labels.length; i += 1) { | ||
| 3134 | LabelTableEntry *label = fn->all_labels.at(i); | ||
| 3135 | Buf *name = &label->decl_node->data.label.name; | ||
| 3136 | label->basic_block = LLVMAppendBasicBlock(fn->fn_value, buf_ptr(name)); | ||
| 3137 | } | ||
| 3138 | LLVMPositionBuilderAtEnd(g->builder, entry_block); | ||
| 3139 | } | ||
| 3140 | |||
| 3108 | static void do_code_gen(CodeGen *g) { | 3141 | static void do_code_gen(CodeGen *g) { |
| 3109 | assert(!g->errors.length); | 3142 | assert(!g->errors.length); |
| 3110 | 3143 | ||
| ... | @@ -3279,8 +3312,7 @@ static void do_code_gen(CodeGen *g) { | ... | @@ -3279,8 +3312,7 @@ static void do_code_gen(CodeGen *g) { |
| 3279 | assert(proto_node->type == NodeTypeFnProto); | 3312 | assert(proto_node->type == NodeTypeFnProto); |
| 3280 | AstNodeFnProto *fn_proto = &proto_node->data.fn_proto; | 3313 | AstNodeFnProto *fn_proto = &proto_node->data.fn_proto; |
| 3281 | 3314 | ||
| 3282 | LLVMBasicBlockRef entry_block = LLVMAppendBasicBlock(fn, "entry"); | 3315 | build_label_blocks(g, fn_table_entry); |
| 3283 | LLVMPositionBuilderAtEnd(g->builder, entry_block); | ||
| 3284 | 3316 | ||
| 3285 | 3317 | ||
| 3286 | // Set up debug info for blocks | 3318 | // Set up debug info for blocks |
test/run_tests.cpp+22| ... | @@ -1788,6 +1788,28 @@ fn test1(a: i32, b: i32) -> i32 { | ... | @@ -1788,6 +1788,28 @@ fn test1(a: i32, b: i32) -> i32 { |
| 1788 | return foo(a)(b); | 1788 | return foo(a)(b); |
| 1789 | } | 1789 | } |
| 1790 | )SOURCE", 1, ".tmp_source.zig:4:16: error: unable to resolve constant expression"); | 1790 | )SOURCE", 1, ".tmp_source.zig:4:16: error: unable to resolve constant expression"); |
| 1791 | |||
| 1792 | add_compile_fail_case("goto jumping into block", R"SOURCE( | ||
| 1793 | fn f() { | ||
| 1794 | { | ||
| 1795 | a_label: | ||
| 1796 | } | ||
| 1797 | goto a_label; | ||
| 1798 | } | ||
| 1799 | )SOURCE", 2, | ||
| 1800 | ".tmp_source.zig:4:1: error: label 'a_label' defined but not used", | ||
| 1801 | ".tmp_source.zig:6:5: error: no label in scope named 'a_label'"); | ||
| 1802 | |||
| 1803 | add_compile_fail_case("goto jumping past a defer", R"SOURCE( | ||
| 1804 | fn f(b: bool) { | ||
| 1805 | if (b) goto label; | ||
| 1806 | defer derp(); | ||
| 1807 | label: | ||
| 1808 | } | ||
| 1809 | fn derp(){} | ||
| 1810 | )SOURCE", 2, | ||
| 1811 | ".tmp_source.zig:3:12: error: no label in scope named 'label'", | ||
| 1812 | ".tmp_source.zig:5:1: error: label 'label' defined but not used"); | ||
| 1791 | } | 1813 | } |
| 1792 | 1814 | ||
| 1793 | ////////////////////////////////////////////////////////////////////////////// | 1815 | ////////////////////////////////////////////////////////////////////////////// |
test/self_hosted.zig+22| ... | @@ -569,3 +569,25 @@ fn error_name_string() { | ... | @@ -569,3 +569,25 @@ fn error_name_string() { |
| 569 | assert(str_eql(@err_name(error.AnError), "AnError")); | 569 | assert(str_eql(@err_name(error.AnError), "AnError")); |
| 570 | assert(str_eql(@err_name(error.ALongerErrorName), "ALongerErrorName")); | 570 | assert(str_eql(@err_name(error.ALongerErrorName), "ALongerErrorName")); |
| 571 | } | 571 | } |
| 572 | |||
| 573 | |||
| 574 | #attribute("test") | ||
| 575 | fn goto_and_labels() { | ||
| 576 | goto_loop(); | ||
| 577 | assert(goto_counter == 10); | ||
| 578 | } | ||
| 579 | fn goto_loop() { | ||
| 580 | var i: i32 = 0; | ||
| 581 | goto cond; | ||
| 582 | loop: | ||
| 583 | i += 1; | ||
| 584 | cond: | ||
| 585 | if (!(i < 10)) goto end; | ||
| 586 | goto_counter += 1; | ||
| 587 | goto loop; | ||
| 588 | end: | ||
| 589 | } | ||
| 590 | var goto_counter: i32 = 0; | ||
| 591 | |||
| 592 | |||
| 593 |