diff --git a/doc/langref.md b/doc/langref.md index c81c9ede0e..29bd022c10 100644 --- a/doc/langref.md +++ b/doc/langref.md @@ -45,7 +45,7 @@ TypeExpr = PrefixOpExpression | "var" BlockOrExpression = Block | Expression -Expression = ReturnExpression | AssignmentExpression +Expression = ReturnExpression | BreakExpression | AssignmentExpression AsmExpression = "asm" option("volatile") "(" String option(AsmOutput) ")" @@ -79,12 +79,14 @@ SwitchProng = (list(SwitchItem, ",") | "else") "=>" option("|" option("*") Symbo SwitchItem = Expression | (Expression "..." Expression) -ForExpression(body) = "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body +ForExpression(body) = "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body option("else" BlockExpression(body)) BoolOrExpression = BoolAndExpression "or" BoolOrExpression | BoolAndExpression ReturnExpression = option("%") "return" option(Expression) +BreakExpression = "break" option(Expression) + Defer(body) = option("%") "defer" body IfExpression(body) = "if" "(" Expression ")" body option("else" BlockExpression(body)) @@ -151,7 +153,7 @@ GotoExpression = "goto" Symbol GroupedExpression = "(" Expression ")" -KeywordLiteral = "true" | "false" | "null" | "break" | "continue" | "undefined" | "error" | "this" | "unreachable" +KeywordLiteral = "true" | "false" | "null" | "continue" | "undefined" | "error" | "this" | "unreachable" ContainerDecl = option("extern" | "packed") ("struct" | "enum" | "union") "{" many(ContainerMember) "}" ``` diff --git a/src/all_types.hpp b/src/all_types.hpp index dd8a8e7a67..c17205ee43 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -623,6 +623,7 @@ struct AstNodeForExpr { AstNode *elem_node; // always a symbol AstNode *index_node; // always a symbol, might be null AstNode *body; + AstNode *else_node; // can be null bool elem_is_ptr; bool is_inline; }; @@ -774,6 +775,7 @@ struct AstNodeBoolLiteral { }; struct AstNodeBreakExpr { + AstNode *expr; // may be null }; struct AstNodeContinueExpr { diff --git a/src/ast_render.cpp b/src/ast_render.cpp index 348d825332..e1186da2c4 100644 --- a/src/ast_render.cpp +++ b/src/ast_render.cpp @@ -485,6 +485,15 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { } break; } + case NodeTypeBreak: + { + fprintf(ar->f, "break"); + if (node->data.break_expr.expr) { + fprintf(ar->f, " "); + render_node_grouped(ar, node->data.break_expr.expr); + } + break; + } case NodeTypeDefer: { const char *defer_str = defer_string(node->data.defer.kind); @@ -880,11 +889,10 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { fprintf(ar->f, "| "); } render_node_grouped(ar, node->data.for_expr.body); - break; - } - case NodeTypeBreak: - { - fprintf(ar->f, "break"); + if (node->data.for_expr.else_node) { + fprintf(ar->f, " else"); + render_node_grouped(ar, node->data.for_expr.else_node); + } break; } case NodeTypeContinue: diff --git a/src/ir.cpp b/src/ir.cpp index 3b541b7731..0f0c407308 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -22,6 +22,8 @@ struct LoopStackItem { IrBasicBlock *break_block; IrBasicBlock *continue_block; IrInstruction *is_comptime; + ZigList *incoming_values; + ZigList *incoming_blocks; }; struct IrBuilder { @@ -56,6 +58,18 @@ static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, Scope *sc static TypeTableEntry *ir_analyze_instruction(IrAnalyze *ira, IrInstruction *instruction); static IrInstruction *ir_implicit_cast(IrAnalyze *ira, IrInstruction *value, TypeTableEntry *expected_type); +static LoopStackItem *add_loop_stack_item(IrBuilder *irb, IrBasicBlock *break_block, IrBasicBlock *continue_block, + IrInstruction *is_comptime, ZigList *incoming_blocks, ZigList *incoming_values) +{ + LoopStackItem *loop_stack_item = irb->loop_stack.add_one(); + loop_stack_item->break_block = break_block; + loop_stack_item->continue_block = continue_block; + loop_stack_item->is_comptime = is_comptime; + loop_stack_item->incoming_blocks = incoming_blocks; + loop_stack_item->incoming_values = incoming_values; + return loop_stack_item; +} + ConstExprValue *const_ptr_pointee(CodeGen *g, ConstExprValue *const_val) { assert(const_val->type->id == TypeTableEntryIdPointer); assert(const_val->special == ConstValSpecialStatic); @@ -4693,6 +4707,8 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n return err_val_ptr; IrInstruction *err_val = ir_build_load_ptr(irb, scope, node->data.while_expr.condition, err_val_ptr); IrInstruction *is_err = ir_build_test_err(irb, scope, node->data.while_expr.condition, err_val); + IrBasicBlock *after_cond_block = irb->current_basic_block; + IrInstruction *void_else_result = else_node ? nullptr : ir_mark_gen(ir_build_const_void(irb, scope, node)); if (!instr_is_unreachable(is_err)) { ir_mark_gen(ir_build_cond_br(irb, scope, node->data.while_expr.condition, is_err, else_block, body_block, is_comptime)); @@ -4706,10 +4722,9 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n var_ptr_value : ir_build_load_ptr(irb, payload_scope, symbol_node, var_ptr_value); ir_build_var_decl(irb, payload_scope, symbol_node, payload_var, nullptr, var_value); } - LoopStackItem *loop_stack_item = irb->loop_stack.add_one(); - loop_stack_item->break_block = end_block; - loop_stack_item->continue_block = continue_block; - loop_stack_item->is_comptime = is_comptime; + ZigList incoming_values = {0}; + ZigList incoming_blocks = {0}; + add_loop_stack_item(irb, end_block, continue_block, is_comptime, &incoming_blocks, &incoming_values); IrInstruction *body_result = ir_gen_node(irb, node->data.while_expr.body, payload_scope); if (body_result == irb->codegen->invalid_instruction) return body_result; @@ -4727,6 +4742,7 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n ir_mark_gen(ir_build_br(irb, payload_scope, node, cond_block, is_comptime)); } + IrInstruction *else_result = nullptr; if (else_node) { ir_set_cursor_at_end(irb, else_block); @@ -4738,15 +4754,23 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n IrInstruction *err_var_value = ir_build_unwrap_err_code(irb, err_scope, err_symbol_node, err_val_ptr); ir_build_var_decl(irb, err_scope, symbol_node, err_var, nullptr, err_var_value); - IrInstruction *else_result = ir_gen_node(irb, else_node, err_scope); + else_result = ir_gen_node(irb, else_node, err_scope); if (else_result == irb->codegen->invalid_instruction) return else_result; if (!instr_is_unreachable(else_result)) ir_mark_gen(ir_build_br(irb, scope, node, end_block, is_comptime)); } - + IrBasicBlock *after_else_block = irb->current_basic_block; ir_set_cursor_at_end(irb, end_block); - return ir_build_const_void(irb, scope, node); + if (else_result) { + incoming_blocks.append(after_else_block); + incoming_values.append(else_result); + } else { + incoming_blocks.append(after_cond_block); + incoming_values.append(void_else_result); + } + + return ir_build_phi(irb, scope, node, incoming_blocks.length, incoming_blocks.items, incoming_values.items); } else if (var_symbol != nullptr) { ir_set_cursor_at_end(irb, cond_block); // TODO make it an error to write to payload variable @@ -4759,6 +4783,8 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n return maybe_val_ptr; IrInstruction *maybe_val = ir_build_load_ptr(irb, scope, node->data.while_expr.condition, maybe_val_ptr); IrInstruction *is_non_null = ir_build_test_nonnull(irb, scope, node->data.while_expr.condition, maybe_val); + IrBasicBlock *after_cond_block = irb->current_basic_block; + IrInstruction *void_else_result = else_node ? nullptr : ir_mark_gen(ir_build_const_void(irb, scope, node)); if (!instr_is_unreachable(is_non_null)) { ir_mark_gen(ir_build_cond_br(irb, scope, node->data.while_expr.condition, is_non_null, body_block, else_block, is_comptime)); @@ -4769,10 +4795,9 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n IrInstruction *var_value = node->data.while_expr.var_is_ptr ? var_ptr_value : ir_build_load_ptr(irb, child_scope, symbol_node, var_ptr_value); ir_build_var_decl(irb, child_scope, symbol_node, payload_var, nullptr, var_value); - LoopStackItem *loop_stack_item = irb->loop_stack.add_one(); - loop_stack_item->break_block = end_block; - loop_stack_item->continue_block = continue_block; - loop_stack_item->is_comptime = is_comptime; + ZigList incoming_values = {0}; + ZigList incoming_blocks = {0}; + add_loop_stack_item(irb, end_block, continue_block, is_comptime, &incoming_blocks, &incoming_values); IrInstruction *body_result = ir_gen_node(irb, node->data.while_expr.body, child_scope); if (body_result == irb->codegen->invalid_instruction) return body_result; @@ -4790,18 +4815,27 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n ir_mark_gen(ir_build_br(irb, child_scope, node, cond_block, is_comptime)); } + IrInstruction *else_result = nullptr; if (else_node) { ir_set_cursor_at_end(irb, else_block); - IrInstruction *else_result = ir_gen_node(irb, else_node, scope); + else_result = ir_gen_node(irb, else_node, scope); if (else_result == irb->codegen->invalid_instruction) return else_result; if (!instr_is_unreachable(else_result)) ir_mark_gen(ir_build_br(irb, scope, node, end_block, is_comptime)); } - + IrBasicBlock *after_else_block = irb->current_basic_block; ir_set_cursor_at_end(irb, end_block); - return ir_build_const_void(irb, scope, node); + if (else_result) { + incoming_blocks.append(after_else_block); + incoming_values.append(else_result); + } else { + incoming_blocks.append(after_cond_block); + incoming_values.append(void_else_result); + } + + return ir_build_phi(irb, scope, node, incoming_blocks.length, incoming_blocks.items, incoming_values.items); } else { if (continue_expr_node) { ir_set_cursor_at_end(irb, continue_block); @@ -4816,6 +4850,8 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n IrInstruction *cond_val = ir_gen_node(irb, node->data.while_expr.condition, scope); if (cond_val == irb->codegen->invalid_instruction) return cond_val; + IrBasicBlock *after_cond_block = irb->current_basic_block; + IrInstruction *void_else_result = else_node ? nullptr : ir_mark_gen(ir_build_const_void(irb, scope, node)); if (!instr_is_unreachable(cond_val)) { ir_mark_gen(ir_build_cond_br(irb, scope, node->data.while_expr.condition, cond_val, body_block, else_block, is_comptime)); @@ -4823,10 +4859,9 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n ir_set_cursor_at_end(irb, body_block); - LoopStackItem *loop_stack_item = irb->loop_stack.add_one(); - loop_stack_item->break_block = end_block; - loop_stack_item->continue_block = continue_block; - loop_stack_item->is_comptime = is_comptime; + ZigList incoming_values = {0}; + ZigList incoming_blocks = {0}; + add_loop_stack_item(irb, end_block, continue_block, is_comptime, &incoming_blocks, &incoming_values); IrInstruction *body_result = ir_gen_node(irb, node->data.while_expr.body, scope); if (body_result == irb->codegen->invalid_instruction) return body_result; @@ -4835,19 +4870,27 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n if (!instr_is_unreachable(body_result)) ir_mark_gen(ir_build_br(irb, scope, node, continue_block, is_comptime)); + IrInstruction *else_result = nullptr; if (else_node) { ir_set_cursor_at_end(irb, else_block); - IrInstruction *else_result = ir_gen_node(irb, else_node, scope); + else_result = ir_gen_node(irb, else_node, scope); if (else_result == irb->codegen->invalid_instruction) return else_result; if (!instr_is_unreachable(else_result)) ir_mark_gen(ir_build_br(irb, scope, node, end_block, is_comptime)); } - + IrBasicBlock *after_else_block = irb->current_basic_block; ir_set_cursor_at_end(irb, end_block); + if (else_result) { + incoming_blocks.append(after_else_block); + incoming_values.append(else_result); + } else { + incoming_blocks.append(after_cond_block); + incoming_values.append(void_else_result); + } - return ir_build_const_void(irb, scope, node); + return ir_build_phi(irb, scope, node, incoming_blocks.length, incoming_blocks.items, incoming_values.items); } } @@ -4858,6 +4901,7 @@ static IrInstruction *ir_gen_for_expr(IrBuilder *irb, Scope *parent_scope, AstNo AstNode *elem_node = node->data.for_expr.elem_node; AstNode *index_node = node->data.for_expr.index_node; AstNode *body_node = node->data.for_expr.body; + AstNode *else_node = node->data.for_expr.else_node; if (!elem_node) { add_node_error(irb->codegen, node, buf_sprintf("for loop expression missing element parameter")); @@ -4915,6 +4959,7 @@ static IrInstruction *ir_gen_for_expr(IrBuilder *irb, Scope *parent_scope, AstNo IrBasicBlock *cond_block = ir_build_basic_block(irb, child_scope, "ForCond"); IrBasicBlock *body_block = ir_build_basic_block(irb, child_scope, "ForBody"); IrBasicBlock *end_block = ir_build_basic_block(irb, child_scope, "ForEnd"); + IrBasicBlock *else_block = else_node ? ir_build_basic_block(irb, child_scope, "ForElse") : end_block; IrBasicBlock *continue_block = ir_build_basic_block(irb, child_scope, "ForContinue"); IrInstruction *len_val = ir_build_array_len(irb, child_scope, node, array_val); @@ -4923,7 +4968,9 @@ static IrInstruction *ir_gen_for_expr(IrBuilder *irb, Scope *parent_scope, AstNo ir_set_cursor_at_end(irb, cond_block); IrInstruction *index_val = ir_build_load_ptr(irb, child_scope, node, index_ptr); IrInstruction *cond = ir_build_bin_op(irb, child_scope, node, IrBinOpCmpLessThan, index_val, len_val, false); - ir_mark_gen(ir_build_cond_br(irb, child_scope, node, cond, body_block, end_block, is_comptime)); + IrBasicBlock *after_cond_block = irb->current_basic_block; + IrInstruction *void_else_value = else_node ? nullptr : ir_mark_gen(ir_build_const_void(irb, parent_scope, node)); + ir_mark_gen(ir_build_cond_br(irb, child_scope, node, cond, body_block, else_block, is_comptime)); ir_set_cursor_at_end(irb, body_block); IrInstruction *elem_ptr = ir_build_elem_ptr(irb, child_scope, node, array_val_ptr, index_val, false); @@ -4935,10 +4982,9 @@ static IrInstruction *ir_gen_for_expr(IrBuilder *irb, Scope *parent_scope, AstNo } ir_mark_gen(ir_build_store_ptr(irb, child_scope, node, elem_var_ptr, elem_val)); - LoopStackItem *loop_stack_item = irb->loop_stack.add_one(); - loop_stack_item->break_block = end_block; - loop_stack_item->continue_block = continue_block; - loop_stack_item->is_comptime = is_comptime; + ZigList incoming_values = {0}; + ZigList incoming_blocks = {0}; + add_loop_stack_item(irb, end_block, continue_block, is_comptime, &incoming_blocks, &incoming_values); IrInstruction *body_result = ir_gen_node(irb, body_node, child_scope); irb->loop_stack.pop(); @@ -4950,9 +4996,28 @@ static IrInstruction *ir_gen_for_expr(IrBuilder *irb, Scope *parent_scope, AstNo ir_mark_gen(ir_build_store_ptr(irb, child_scope, node, index_ptr, new_index_val)); ir_build_br(irb, child_scope, node, cond_block, is_comptime); - ir_set_cursor_at_end(irb, end_block); - return ir_build_const_void(irb, child_scope, node); + IrInstruction *else_result = nullptr; + if (else_node) { + ir_set_cursor_at_end(irb, else_block); + else_result = ir_gen_node(irb, else_node, parent_scope); + if (else_result == irb->codegen->invalid_instruction) + return else_result; + if (!instr_is_unreachable(else_result)) + ir_mark_gen(ir_build_br(irb, parent_scope, node, end_block, is_comptime)); + } + IrBasicBlock *after_else_block = irb->current_basic_block; + ir_set_cursor_at_end(irb, end_block); + + if (else_result) { + incoming_blocks.append(after_else_block); + incoming_values.append(else_result); + } else { + incoming_blocks.append(after_cond_block); + incoming_values.append(void_else_value); + } + + return ir_build_phi(irb, parent_scope, node, incoming_blocks.length, incoming_blocks.items, incoming_values.items); } static IrInstruction *ir_gen_this_literal(IrBuilder *irb, Scope *scope, AstNode *node) { @@ -5512,8 +5577,20 @@ static IrInstruction *ir_gen_break(IrBuilder *irb, Scope *scope, AstNode *node) is_comptime = loop_stack_item->is_comptime; } + IrInstruction *result_value; + if (node->data.break_expr.expr) { + result_value = ir_gen_node(irb, node->data.break_expr.expr, scope); + if (result_value == irb->codegen->invalid_instruction) + return irb->codegen->invalid_instruction; + } else { + result_value = ir_build_const_void(irb, scope, node); + } + IrBasicBlock *dest_block = loop_stack_item->break_block; ir_gen_defers_for_block(irb, scope, dest_block->scope, false); + + loop_stack_item->incoming_blocks->append(irb->current_basic_block); + loop_stack_item->incoming_values->append(result_value); return ir_build_br(irb, scope, node, dest_block, is_comptime); } diff --git a/src/parser.cpp b/src/parser.cpp index 753dc6e67c..6fa92ea4fb 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -641,7 +641,7 @@ static AstNode *ast_parse_comptime_expr(ParseContext *pc, size_t *token_index, b /* PrimaryExpression = Number | String | CharLiteral | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | (option("extern") FnProto) | AsmExpression | ("error" "." Symbol) | ContainerDecl -KeywordLiteral = "true" | "false" | "null" | "break" | "continue" | "undefined" | "error" | "this" | "unreachable" +KeywordLiteral = "true" | "false" | "null" | "continue" | "undefined" | "error" | "this" | "unreachable" */ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bool mandatory) { Token *token = &pc->tokens->at(*token_index); @@ -677,10 +677,6 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo AstNode *node = ast_create_node(pc, NodeTypeNullLiteral, token); *token_index += 1; return node; - } else if (token->id == TokenIdKeywordBreak) { - AstNode *node = ast_create_node(pc, NodeTypeBreak, token); - *token_index += 1; - return node; } else if (token->id == TokenIdKeywordContinue) { AstNode *node = ast_create_node(pc, NodeTypeContinue, token); *token_index += 1; @@ -1447,6 +1443,24 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, size_t *token_index) { return node; } +/* +BreakExpression : "break" option(Expression) +*/ +static AstNode *ast_parse_break_expr(ParseContext *pc, size_t *token_index) { + Token *token = &pc->tokens->at(*token_index); + + if (token->id == TokenIdKeywordBreak) { + *token_index += 1; + } else { + return nullptr; + } + + AstNode *node = ast_create_node(pc, NodeTypeBreak, token); + node->data.break_expr.expr = ast_parse_expression(pc, token_index, false); + + return node; +} + /* Defer(body) = option("%") "defer" body */ @@ -1668,7 +1682,7 @@ static AstNode *ast_parse_symbol(ParseContext *pc, size_t *token_index) { } /* -ForExpression(body) = option("inline") "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body +ForExpression(body) = option("inline") "for" "(" Expression ")" option("|" option("*") Symbol option("," Symbol) "|") body option("else" BlockExpression(body)) */ static AstNode *ast_parse_for_expr(ParseContext *pc, size_t *token_index, bool mandatory) { Token *first_token = &pc->tokens->at(*token_index); @@ -1728,6 +1742,13 @@ static AstNode *ast_parse_for_expr(ParseContext *pc, size_t *token_index, bool m node->data.for_expr.body = ast_parse_block_or_expression(pc, token_index, true); + Token *else_tok = &pc->tokens->at(*token_index); + if (else_tok->id == TokenIdKeywordElse) { + *token_index += 1; + + node->data.for_expr.else_node = ast_parse_block_or_expression(pc, token_index, true); + } + return node; } @@ -1981,7 +2002,7 @@ static AstNode *ast_parse_block_or_expression(ParseContext *pc, size_t *token_in } /* -Expression = ReturnExpression | AssignmentExpression +Expression = ReturnExpression | BreakExpression | AssignmentExpression */ static AstNode *ast_parse_expression(ParseContext *pc, size_t *token_index, bool mandatory) { Token *token = &pc->tokens->at(*token_index); @@ -1990,6 +2011,10 @@ static AstNode *ast_parse_expression(ParseContext *pc, size_t *token_index, bool if (return_expr) return return_expr; + AstNode *break_expr = ast_parse_break_expr(pc, token_index); + if (break_expr) + return break_expr; + AstNode *ass_expr = ast_parse_ass_expr(pc, token_index, false); if (ass_expr) return ass_expr; diff --git a/test/cases/while.zig b/test/cases/while.zig index 476c29dd78..33833cecfa 100644 --- a/test/cases/while.zig +++ b/test/cases/while.zig @@ -134,3 +134,64 @@ fn getNumberOrNull() -> ?i32 { }; } +test "while on nullable with else result follow else prong" { + const result = while (returnNull()) |value| { + break value; + } else { + i32(2) + }; + assert(result == 2); +} + +test "while on nullable with else result follow break prong" { + const result = while (returnMaybe(10)) |value| { + break value; + } else { + i32(2) + }; + assert(result == 10); +} + +test "while on error union with else result follow else prong" { + const result = while (returnError()) |value| { + break value; + } else |err| { + i32(2) + }; + assert(result == 2); +} + +test "while on error union with else result follow break prong" { + const result = while (returnSuccess(10)) |value| { + break value; + } else |err| { + i32(2) + }; + assert(result == 10); +} + +test "while on bool with else result follow else prong" { + const result = while (returnFalse()) { + break i32(10); + } else { + i32(2) + }; + assert(result == 2); +} + +test "while on bool with else result follow break prong" { + const result = while (returnTrue()) { + break i32(10); + } else { + i32(2) + }; + assert(result == 10); +} + +fn returnNull() -> ?i32 { null } +fn returnMaybe(x: i32) -> ?i32 { x } +error YouWantedAnError; +fn returnError() -> %i32 { error.YouWantedAnError } +fn returnSuccess(x: i32) -> %i32 { x } +fn returnFalse() -> bool { false } +fn returnTrue() -> bool { true }