| author | |
| committer | |
| log | 3c3be10a605085d8855e54b8c6a769d6ad1e00d9 |
| tree | f3c31aa240dcd35937a9e5d99a266b393db5800a |
| parent | dfb48a2c6bc034dab8d7c17db8d67089fc68043d |
7 files changed, 226 insertions(+), 20 deletions(-)
example/expressions/expressions.zig+16| ... | ... | @@ -1,3 +1,5 @@ |
| 1 | export executable "expressions"; | |
| 2 | ||
| 1 | 3 | #link("c") |
| 2 | 4 | extern { |
| 3 | 5 | fn puts(s: *const u8) -> i32; |
| ... | ... | @@ -28,6 +30,8 @@ export fn _start() -> unreachable { |
| 28 | 30 | |
| 29 | 31 | void_fun(1, void, 2); |
| 30 | 32 | |
| 33 | test_mutable_vars(); | |
| 34 | ||
| 31 | 35 | other_exit(); |
| 32 | 36 | } |
| 33 | 37 | |
| ... | ... | @@ -38,3 +42,15 @@ fn void_fun(a : i32, b : void, c : i32) -> void { |
| 38 | 42 | let w : void = z; // void |
| 39 | 43 | if (x + y == 4) { return w; } |
| 40 | 44 | } |
| 45 | ||
| 46 | fn test_mutable_vars() { | |
| 47 | let mut i = 0; | |
| 48 | loop_start: | |
| 49 | if i == 3 { | |
| 50 | goto done; | |
| 51 | } | |
| 52 | puts("loop"); | |
| 53 | i = i + 1; | |
| 54 | goto loop_start; | |
| 55 | done: | |
| 56 | } |
src/analyze.cpp+46-3| ... | ... | @@ -362,6 +362,13 @@ static BlockContext *new_block_context(AstNode *node, BlockContext *parent) { |
| 362 | 362 | else |
| 363 | 363 | context->root = context; |
| 364 | 364 | context->variable_table.init(8); |
| 365 | ||
| 366 | AstNode *fn_def_node = context->root->node; | |
| 367 | assert(fn_def_node->type == NodeTypeFnDef); | |
| 368 | assert(fn_def_node->codegen_node); | |
| 369 | FnDefNode *fn_def_info = &fn_def_node->codegen_node->data.fn_def_node; | |
| 370 | fn_def_info->all_block_contexts.append(context); | |
| 371 | ||
| 365 | 372 | return context; |
| 366 | 373 | } |
| 367 | 374 | |
| ... | ... | @@ -388,8 +395,13 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, |
| 388 | 395 | return_type = g->builtin_types.entry_void; |
| 389 | 396 | for (int i = 0; i < node->data.block.statements.length; i += 1) { |
| 390 | 397 | AstNode *child = node->data.block.statements.at(i); |
| 391 | if (child->type == NodeTypeLabel) | |
| 398 | if (child->type == NodeTypeLabel) { | |
| 399 | LabelTableEntry *label_entry = child->codegen_node->data.label_entry; | |
| 400 | assert(label_entry); | |
| 401 | label_entry->entered_from_fallthrough = (return_type != g->builtin_types.entry_unreachable); | |
| 402 | return_type = g->builtin_types.entry_void; | |
| 392 | 403 | continue; |
| 404 | } | |
| 393 | 405 | if (return_type == g->builtin_types.entry_unreachable) { |
| 394 | 406 | if (child->type == NodeTypeVoid) { |
| 395 | 407 | // {unreachable;void;void} is allowed. |
| ... | ... | @@ -457,6 +469,8 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, |
| 457 | 469 | LocalVariableTableEntry *variable_entry = allocate<LocalVariableTableEntry>(1); |
| 458 | 470 | buf_init_from_buf(&variable_entry->name, &variable_declaration->symbol); |
| 459 | 471 | variable_entry->type = type; |
| 472 | variable_entry->is_const = variable_declaration->is_const; | |
| 473 | variable_entry->decl_node = node; | |
| 460 | 474 | context->variable_table.put(&variable_entry->name, variable_entry); |
| 461 | 475 | } |
| 462 | 476 | return_type = g->builtin_types.entry_void; |
| ... | ... | @@ -482,6 +496,32 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, |
| 482 | 496 | case NodeTypeBinOpExpr: |
| 483 | 497 | { |
| 484 | 498 | switch (node->data.bin_op_expr.bin_op) { |
| 499 | case BinOpTypeAssign: | |
| 500 | { | |
| 501 | AstNode *lhs_node = node->data.bin_op_expr.op1; | |
| 502 | if (lhs_node->type == NodeTypeSymbol) { | |
| 503 | Buf *name = &lhs_node->data.symbol; | |
| 504 | LocalVariableTableEntry *var = find_local_variable(context, name); | |
| 505 | if (var) { | |
| 506 | if (var->is_const) { | |
| 507 | add_node_error(g, lhs_node, | |
| 508 | buf_sprintf("cannot assign to constant variable")); | |
| 509 | } else { | |
| 510 | analyze_expression(g, import, context, var->type, | |
| 511 | node->data.bin_op_expr.op2); | |
| 512 | } | |
| 513 | } else { | |
| 514 | add_node_error(g, lhs_node, | |
| 515 | buf_sprintf("use of undeclared identifier '%s'", buf_ptr(name))); | |
| 516 | } | |
| 517 | ||
| 518 | } else { | |
| 519 | add_node_error(g, lhs_node, | |
| 520 | buf_sprintf("expected a bare identifier")); | |
| 521 | } | |
| 522 | return_type = g->builtin_types.entry_void; | |
| 523 | break; | |
| 524 | } | |
| 485 | 525 | case BinOpTypeBoolOr: |
| 486 | 526 | case BinOpTypeBoolAnd: |
| 487 | 527 | analyze_expression(g, import, context, g->builtin_types.entry_bool, |
| ... | ... | @@ -721,7 +761,10 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import, |
| 721 | 761 | AstNode *fn_proto_node = node->data.fn_def.fn_proto; |
| 722 | 762 | assert(fn_proto_node->type == NodeTypeFnProto); |
| 723 | 763 | |
| 764 | assert(!node->codegen_node); | |
| 765 | node->codegen_node = allocate<CodeGenNode>(1); | |
| 724 | 766 | BlockContext *context = new_block_context(node, nullptr); |
| 767 | node->codegen_node->data.fn_def_node.block_context = context; | |
| 725 | 768 | |
| 726 | 769 | AstNodeFnProto *fn_proto = &fn_proto_node->data.fn_proto; |
| 727 | 770 | for (int i = 0; i < fn_proto->params.length; i += 1) { |
| ... | ... | @@ -736,6 +779,8 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import, |
| 736 | 779 | LocalVariableTableEntry *variable_entry = allocate<LocalVariableTableEntry>(1); |
| 737 | 780 | buf_init_from_buf(&variable_entry->name, &param_decl->name); |
| 738 | 781 | variable_entry->type = type; |
| 782 | variable_entry->is_const = true; | |
| 783 | variable_entry->decl_node = param_decl_node; | |
| 739 | 784 | |
| 740 | 785 | LocalVariableTableEntry *existing_entry = find_local_variable(context, &variable_entry->name); |
| 741 | 786 | if (!existing_entry) { |
| ... | ... | @@ -756,9 +801,7 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import, |
| 756 | 801 | TypeTableEntry *expected_type = fn_proto->return_type->codegen_node->data.type_node.entry; |
| 757 | 802 | TypeTableEntry *block_return_type = analyze_expression(g, import, context, expected_type, node->data.fn_def.body); |
| 758 | 803 | |
| 759 | node->codegen_node = allocate<CodeGenNode>(1); | |
| 760 | 804 | node->codegen_node->data.fn_def_node.implicit_return_type = block_return_type; |
| 761 | node->codegen_node->data.fn_def_node.block_context = context; | |
| 762 | 805 | |
| 763 | 806 | { |
| 764 | 807 | FnTableEntry *fn_table_entry = fn_proto_node->codegen_node->data.fn_proto_node.fn_table_entry; |
src/codegen.cpp+68-8| ... | ... | @@ -102,6 +102,7 @@ static int count_non_void_params(CodeGen *g, ZigList<AstNode *> *params) { |
| 102 | 102 | } |
| 103 | 103 | |
| 104 | 104 | static void add_debug_source_node(CodeGen *g, AstNode *node) { |
| 105 | // TODO g->block_scopes.last() is not always correct and should probably integrate with BlockContext | |
| 105 | 106 | LLVMZigSetCurrentDebugLocation(g->builder, node->line + 1, node->column + 1, g->block_scopes.last()); |
| 106 | 107 | } |
| 107 | 108 | |
| ... | ... | @@ -210,6 +211,8 @@ static LLVMValueRef gen_arithmetic_bin_op_expr(CodeGen *g, AstNode *node) { |
| 210 | 211 | LLVMValueRef val2 = gen_expr(g, node->data.bin_op_expr.op2); |
| 211 | 212 | |
| 212 | 213 | switch (node->data.bin_op_expr.bin_op) { |
| 214 | case BinOpTypeAssign: | |
| 215 | zig_panic("TODO assignment"); | |
| 213 | 216 | case BinOpTypeBinOr: |
| 214 | 217 | add_debug_source_node(g, node); |
| 215 | 218 | return LLVMBuildOr(g->builder, val1, val2, ""); |
| ... | ... | @@ -358,8 +361,28 @@ static LLVMValueRef gen_bool_or_expr(CodeGen *g, AstNode *expr_node) { |
| 358 | 361 | return phi; |
| 359 | 362 | } |
| 360 | 363 | |
| 364 | static LLVMValueRef gen_assign_expr(CodeGen *g, AstNode *node) { | |
| 365 | assert(node->type == NodeTypeBinOpExpr); | |
| 366 | ||
| 367 | AstNode *symbol_node = node->data.bin_op_expr.op1; | |
| 368 | assert(symbol_node->type == NodeTypeSymbol); | |
| 369 | ||
| 370 | LocalVariableTableEntry *var = find_local_variable(node->codegen_node->expr_node.block_context, | |
| 371 | &symbol_node->data.symbol); | |
| 372 | ||
| 373 | // semantic checking ensures no variables are constant | |
| 374 | assert(!var->is_const); | |
| 375 | ||
| 376 | LLVMValueRef value = gen_expr(g, node->data.bin_op_expr.op2); | |
| 377 | ||
| 378 | add_debug_source_node(g, node); | |
| 379 | return LLVMBuildStore(g->builder, value, var->value_ref); | |
| 380 | } | |
| 381 | ||
| 361 | 382 | static LLVMValueRef gen_bin_op_expr(CodeGen *g, AstNode *node) { |
| 362 | 383 | switch (node->data.bin_op_expr.bin_op) { |
| 384 | case BinOpTypeAssign: | |
| 385 | return gen_assign_expr(g, node); | |
| 363 | 386 | case BinOpTypeInvalid: |
| 364 | 387 | zig_unreachable(); |
| 365 | 388 | case BinOpTypeBoolOr: |
| ... | ... | @@ -498,9 +521,20 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { |
| 498 | 521 | case NodeTypeVariableDeclaration: |
| 499 | 522 | { |
| 500 | 523 | LocalVariableTableEntry *variable = find_local_variable(node->codegen_node->expr_node.block_context, &node->data.variable_declaration.symbol); |
| 501 | assert(node->data.variable_declaration.expr); | |
| 502 | variable->value_ref = gen_expr(g, node->data.variable_declaration.expr); | |
| 503 | return nullptr; | |
| 524 | if (variable->is_const) { | |
| 525 | assert(node->data.variable_declaration.expr); | |
| 526 | variable->value_ref = gen_expr(g, node->data.variable_declaration.expr); | |
| 527 | return nullptr; | |
| 528 | } else { | |
| 529 | if (node->data.variable_declaration.expr) { | |
| 530 | LLVMValueRef value = gen_expr(g, node->data.variable_declaration.expr); | |
| 531 | ||
| 532 | add_debug_source_node(g, node); | |
| 533 | return LLVMBuildStore(g->builder, value, variable->value_ref); | |
| 534 | } else { | |
| 535 | ||
| 536 | } | |
| 537 | } | |
| 504 | 538 | } |
| 505 | 539 | case NodeTypeCastExpr: |
| 506 | 540 | return gen_cast_expr(g, node); |
| ... | ... | @@ -542,7 +576,11 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { |
| 542 | 576 | case NodeTypeSymbol: |
| 543 | 577 | { |
| 544 | 578 | LocalVariableTableEntry *variable = find_local_variable(node->codegen_node->expr_node.block_context, &node->data.symbol); |
| 545 | return variable->value_ref; | |
| 579 | if (variable->is_const) { | |
| 580 | return variable->value_ref; | |
| 581 | } else { | |
| 582 | return LLVMBuildLoad(g->builder, variable->value_ref, ""); | |
| 583 | } | |
| 546 | 584 | } |
| 547 | 585 | case NodeTypeBlock: |
| 548 | 586 | return gen_block(g, node, nullptr); |
| ... | ... | @@ -551,11 +589,15 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { |
| 551 | 589 | return LLVMBuildBr(g->builder, node->codegen_node->data.label_entry->basic_block); |
| 552 | 590 | case NodeTypeLabel: |
| 553 | 591 | { |
| 554 | LLVMBasicBlockRef basic_block = node->codegen_node->data.label_entry->basic_block; | |
| 555 | add_debug_source_node(g, node); | |
| 556 | LLVMValueRef result = LLVMBuildBr(g->builder, basic_block); | |
| 592 | LabelTableEntry *label_entry = node->codegen_node->data.label_entry; | |
| 593 | assert(label_entry); | |
| 594 | LLVMBasicBlockRef basic_block = label_entry->basic_block; | |
| 595 | if (label_entry->entered_from_fallthrough) { | |
| 596 | add_debug_source_node(g, node); | |
| 597 | LLVMBuildBr(g->builder, basic_block); | |
| 598 | } | |
| 557 | 599 | LLVMPositionBuilderAtEnd(g->builder, basic_block); |
| 558 | return result; | |
| 600 | return nullptr; | |
| 559 | 601 | } |
| 560 | 602 | case NodeTypeRoot: |
| 561 | 603 | case NodeTypeRootExportDecl: |
| ... | ... | @@ -696,6 +738,24 @@ static void do_code_gen(CodeGen *g) { |
| 696 | 738 | |
| 697 | 739 | build_label_blocks(g, fn_def_node->data.fn_def.body); |
| 698 | 740 | |
| 741 | // allocate all local variables | |
| 742 | for (int i = 0; i < codegen_fn_def->all_block_contexts.length; i += 1) { | |
| 743 | BlockContext *block_context = codegen_fn_def->all_block_contexts.at(i); | |
| 744 | ||
| 745 | auto it = block_context->variable_table.entry_iterator(); | |
| 746 | for (;;) { | |
| 747 | auto *entry = it.next(); | |
| 748 | if (!entry) | |
| 749 | break; | |
| 750 | ||
| 751 | LocalVariableTableEntry *var = entry->value; | |
| 752 | if (!var->is_const) { | |
| 753 | add_debug_source_node(g, var->decl_node); | |
| 754 | var->value_ref = LLVMBuildAlloca(g->builder, var->type->type_ref, buf_ptr(&var->name)); | |
| 755 | } | |
| 756 | } | |
| 757 | } | |
| 758 | ||
| 699 | 759 | TypeTableEntry *implicit_return_type = codegen_fn_def->implicit_return_type; |
| 700 | 760 | gen_block(g, fn_def_node->data.fn_def.body, implicit_return_type); |
| 701 | 761 |
src/parser.cpp+44-7| ... | ... | @@ -33,6 +33,7 @@ static const char *bin_op_str(BinOpType bin_op) { |
| 33 | 33 | case BinOpTypeMult: return "*"; |
| 34 | 34 | case BinOpTypeDiv: return "/"; |
| 35 | 35 | case BinOpTypeMod: return "%"; |
| 36 | case BinOpTypeAssign: return "="; | |
| 36 | 37 | } |
| 37 | 38 | zig_unreachable(); |
| 38 | 39 | } |
| ... | ... | @@ -1096,7 +1097,7 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool m |
| 1096 | 1097 | } |
| 1097 | 1098 | |
| 1098 | 1099 | /* |
| 1099 | VariableDeclaration : token(Let) token(Symbole) (token(Eq) Expression | token(Colon) Type option(token(Eq) Expression)) | |
| 1100 | VariableDeclaration : token(Let) option(token(Mut)) token(Symbol) (token(Eq) Expression | token(Colon) Type option(token(Eq) Expression)) | |
| 1100 | 1101 | */ |
| 1101 | 1102 | static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, int *token_index, bool mandatory) { |
| 1102 | 1103 | Token *let_tok = &pc->tokens->at(*token_index); |
| ... | ... | @@ -1104,9 +1105,21 @@ static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, int *token |
| 1104 | 1105 | *token_index += 1; |
| 1105 | 1106 | AstNode *node = ast_create_node(pc, NodeTypeVariableDeclaration, let_tok); |
| 1106 | 1107 | |
| 1107 | Token *name_token = &pc->tokens->at(*token_index); | |
| 1108 | Token *name_token; | |
| 1109 | Token *token = &pc->tokens->at(*token_index); | |
| 1110 | if (token->id == TokenIdKeywordMut) { | |
| 1111 | node->data.variable_declaration.is_const = false; | |
| 1112 | *token_index += 1; | |
| 1113 | name_token = &pc->tokens->at(*token_index); | |
| 1114 | ast_expect_token(pc, name_token, TokenIdSymbol); | |
| 1115 | } else if (token->id == TokenIdSymbol) { | |
| 1116 | node->data.variable_declaration.is_const = true; | |
| 1117 | name_token = token; | |
| 1118 | } else { | |
| 1119 | ast_invalid_token_error(pc, token); | |
| 1120 | } | |
| 1121 | ||
| 1108 | 1122 | *token_index += 1; |
| 1109 | ast_expect_token(pc, name_token, TokenIdSymbol); | |
| 1110 | 1123 | ast_buf_from_token(pc, name_token, &node->data.variable_declaration.symbol); |
| 1111 | 1124 | |
| 1112 | 1125 | Token *eq_or_colon = &pc->tokens->at(*token_index); |
| ... | ... | @@ -1178,7 +1191,30 @@ static AstNode *ast_parse_block_expr(ParseContext *pc, int *token_index, bool ma |
| 1178 | 1191 | } |
| 1179 | 1192 | |
| 1180 | 1193 | /* |
| 1181 | NonBlockExpression : ReturnExpression | VariableDeclaration | BoolOrExpression | |
| 1194 | AssignmentExpression : BoolOrExpression token(Equal) BoolOrExpression | BoolOrExpression | |
| 1195 | */ | |
| 1196 | static AstNode *ast_parse_ass_expr(ParseContext *pc, int *token_index, bool mandatory) { | |
| 1197 | AstNode *lhs = ast_parse_bool_or_expr(pc, token_index, mandatory); | |
| 1198 | if (!lhs) | |
| 1199 | return lhs; | |
| 1200 | ||
| 1201 | Token *token = &pc->tokens->at(*token_index); | |
| 1202 | if (token->id != TokenIdEq) | |
| 1203 | return lhs; | |
| 1204 | *token_index += 1; | |
| 1205 | ||
| 1206 | AstNode *rhs = ast_parse_bool_or_expr(pc, token_index, true); | |
| 1207 | ||
| 1208 | AstNode *node = ast_create_node(pc, NodeTypeBinOpExpr, token); | |
| 1209 | node->data.bin_op_expr.op1 = lhs; | |
| 1210 | node->data.bin_op_expr.bin_op = BinOpTypeAssign; | |
| 1211 | node->data.bin_op_expr.op2 = rhs; | |
| 1212 | ||
| 1213 | return node; | |
| 1214 | } | |
| 1215 | ||
| 1216 | /* | |
| 1217 | NonBlockExpression : ReturnExpression | VariableDeclaration | AssignmentExpression | |
| 1182 | 1218 | */ |
| 1183 | 1219 | static AstNode *ast_parse_non_block_expr(ParseContext *pc, int *token_index, bool mandatory) { |
| 1184 | 1220 | Token *token = &pc->tokens->at(*token_index); |
| ... | ... | @@ -1191,9 +1227,10 @@ static AstNode *ast_parse_non_block_expr(ParseContext *pc, int *token_index, boo |
| 1191 | 1227 | if (variable_declaration_expr) |
| 1192 | 1228 | return variable_declaration_expr; |
| 1193 | 1229 | |
| 1194 | AstNode *bool_or_expr = ast_parse_bool_or_expr(pc, token_index, false); | |
| 1195 | if (bool_or_expr) | |
| 1196 | return bool_or_expr; | |
| 1230 | ||
| 1231 | AstNode *ass_expr = ast_parse_ass_expr(pc, token_index, false); | |
| 1232 | if (ass_expr) | |
| 1233 | return ass_expr; | |
| 1197 | 1234 | |
| 1198 | 1235 | if (mandatory) |
| 1199 | 1236 | ast_invalid_token_error(pc, token); |
src/parser.hpp+2-1| ... | ... | @@ -101,6 +101,7 @@ struct AstNodeReturnExpr { |
| 101 | 101 | |
| 102 | 102 | struct AstNodeVariableDeclaration { |
| 103 | 103 | Buf symbol; |
| 104 | bool is_const; | |
| 104 | 105 | // one or both of type and expr will be non null |
| 105 | 106 | AstNode *type; |
| 106 | 107 | AstNode *expr; |
| ... | ... | @@ -108,7 +109,7 @@ struct AstNodeVariableDeclaration { |
| 108 | 109 | |
| 109 | 110 | enum BinOpType { |
| 110 | 111 | BinOpTypeInvalid, |
| 111 | // TODO: include assignment? | |
| 112 | BinOpTypeAssign, | |
| 112 | 113 | BinOpTypeBoolOr, |
| 113 | 114 | BinOpTypeBoolAnd, |
| 114 | 115 | BinOpTypeCmpEq, |
src/semantic_info.hpp+9| ... | ... | @@ -42,6 +42,7 @@ struct LabelTableEntry { |
| 42 | 42 | AstNode *label_node; |
| 43 | 43 | LLVMBasicBlockRef basic_block; |
| 44 | 44 | bool used; |
| 45 | bool entered_from_fallthrough; | |
| 45 | 46 | }; |
| 46 | 47 | |
| 47 | 48 | struct FnTableEntry { |
| ... | ... | @@ -116,6 +117,8 @@ struct LocalVariableTableEntry { |
| 116 | 117 | Buf name; |
| 117 | 118 | TypeTableEntry *type; |
| 118 | 119 | LLVMValueRef value_ref; |
| 120 | bool is_const; | |
| 121 | AstNode *decl_node; | |
| 119 | 122 | }; |
| 120 | 123 | |
| 121 | 124 | struct BlockContext { |
| ... | ... | @@ -137,6 +140,7 @@ struct FnDefNode { |
| 137 | 140 | TypeTableEntry *implicit_return_type; |
| 138 | 141 | BlockContext *block_context; |
| 139 | 142 | bool skip; |
| 143 | ZigList<BlockContext *> all_block_contexts; | |
| 140 | 144 | }; |
| 141 | 145 | |
| 142 | 146 | struct ExprNode { |
| ... | ... | @@ -146,12 +150,17 @@ struct ExprNode { |
| 146 | 150 | BlockContext *block_context; |
| 147 | 151 | }; |
| 148 | 152 | |
| 153 | struct AssignNode { | |
| 154 | LocalVariableTableEntry *var_entry; | |
| 155 | }; | |
| 156 | ||
| 149 | 157 | struct CodeGenNode { |
| 150 | 158 | union { |
| 151 | 159 | TypeNode type_node; // for NodeTypeType |
| 152 | 160 | FnDefNode fn_def_node; // for NodeTypeFnDef |
| 153 | 161 | FnProtoNode fn_proto_node; // for NodeTypeFnProto |
| 154 | 162 | LabelTableEntry *label_entry; // for NodeTypeGoto and NodeTypeLabel |
| 163 | AssignNode assign_node; // for NodeTypeBinOpExpr where op is BinOpTypeAssign | |
| 155 | 164 | } data; |
| 156 | 165 | ExprNode expr_node; // for all the expression nodes |
| 157 | 166 | }; |
test/run_tests.cpp+41-1| ... | ... | @@ -331,6 +331,27 @@ fn void_fun(a : i32, b : void, c : i32) { |
| 331 | 331 | return vv; |
| 332 | 332 | } |
| 333 | 333 | )SOURCE", "OK\n"); |
| 334 | ||
| 335 | add_simple_case("void parameters", R"SOURCE( | |
| 336 | #link("c") | |
| 337 | extern { | |
| 338 | fn puts(s: *const u8) -> i32; | |
| 339 | fn exit(code: i32) -> unreachable; | |
| 340 | } | |
| 341 | ||
| 342 | export fn _start() -> unreachable { | |
| 343 | let mut i = 0; | |
| 344 | loop_start: | |
| 345 | if i == 3 { | |
| 346 | goto done; | |
| 347 | } | |
| 348 | puts("loop"); | |
| 349 | i = i + 1; | |
| 350 | goto loop_start; | |
| 351 | done: | |
| 352 | exit(0); | |
| 353 | } | |
| 354 | )SOURCE", "loop\nloop\nloop\n"); | |
| 334 | 355 | } |
| 335 | 356 | |
| 336 | 357 | static void add_compile_failure_test_cases(void) { |
| ... | ... | @@ -467,10 +488,29 @@ export fn f(a : void) {} |
| 467 | 488 | )SOURCE", 1, ".tmp_source.zig:2:17: error: parameter of type 'void' not allowed on exported functions"); |
| 468 | 489 | |
| 469 | 490 | add_compile_fail_case("unused label", R"SOURCE( |
| 470 | export fn f() { | |
| 491 | fn f() { | |
| 471 | 492 | a_label: |
| 472 | 493 | } |
| 473 | 494 | )SOURCE", 1, ".tmp_source.zig:3:1: error: label 'a_label' defined but not used"); |
| 495 | ||
| 496 | add_compile_fail_case("expected bare identifier", R"SOURCE( | |
| 497 | fn f() { | |
| 498 | 3 = 3; | |
| 499 | } | |
| 500 | )SOURCE", 1, ".tmp_source.zig:3:5: error: expected a bare identifier"); | |
| 501 | ||
| 502 | add_compile_fail_case("assign to constant variable", R"SOURCE( | |
| 503 | fn f() { | |
| 504 | let a = 3; | |
| 505 | a = 4; | |
| 506 | } | |
| 507 | )SOURCE", 1, ".tmp_source.zig:4:5: error: cannot assign to constant variable"); | |
| 508 | ||
| 509 | add_compile_fail_case("use of undeclared identifier", R"SOURCE( | |
| 510 | fn f() { | |
| 511 | b = 3; | |
| 512 | } | |
| 513 | )SOURCE", 1, ".tmp_source.zig:3:5: error: use of undeclared identifier 'b'"); | |
| 474 | 514 | } |
| 475 | 515 | |
| 476 | 516 | static void print_compiler_invocation(TestCase *test_case, Buf *zig_stderr) { |