diff --git a/build.zig b/build.zig index 05d7272e..bd546b75 100644 --- a/build.zig +++ b/build.zig @@ -92,7 +92,7 @@ fn get_buzz_prefix(b: *Build) []const u8 { pub fn build(b: *Build) !void { // Check minimum zig version const current_zig = builtin.zig_version; - const min_zig = std.SemanticVersion.parse("0.12.0-dev.790+ad6f8e3a5") catch return; + const min_zig = std.SemanticVersion.parse("0.12.0-dev.878+7abf9b3a8") catch return; if (current_zig.order(min_zig).compare(.lt)) { @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); } @@ -322,7 +322,7 @@ pub fn build(b: *Build) !void { if (build_options.needLibC()) { exe.linkLibC(); } - exe.main_pkg_path = .{ .path = "." }; + exe.main_mod_path = .{ .path = "." }; exe.addOptions("build_options", build_options.step(b)); @@ -345,7 +345,7 @@ pub fn build(b: *Build) !void { if (build_options.needLibC()) { lib.linkLibC(); } - lib.main_pkg_path = .{ .path = "src" }; + lib.main_mod_path = .{ .path = "src" }; lib.addOptions("build_options", build_options.step(b)); @@ -427,7 +427,7 @@ pub fn build(b: *Build) !void { if (build_options.needLibC()) { std_lib.linkLibC(); } - std_lib.main_pkg_path = .{ .path = "src" }; + std_lib.main_mod_path = .{ .path = "src" }; std_lib.linkLibrary(lib); std_lib.addOptions("build_options", build_options.step(b)); diff --git a/src/mirjit.zig b/src/mirjit.zig index 81615e18..2bc04798 100644 --- a/src/mirjit.zig +++ b/src/mirjit.zig @@ -1410,6 +1410,13 @@ fn buildPush(self: *Self, value: m.MIR_op_t) !void { const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); const index = try self.REG("index", m.MIR_T_I64); + // Avoid intertwining the push and its value expression + const value_reg = m.MIR_new_reg_op( + self.ctx, + try self.REG("value", m.MIR_T_I64), + ); + self.MOV(value_reg, value); + self.MOV( m.MIR_new_reg_op(self.ctx, index), m.MIR_new_uint_op(self.ctx, 0), @@ -1457,7 +1464,7 @@ fn buildPush(self: *Self, value: m.MIR_op_t) !void { self.MOV( top, - value, + value_reg, ); // Increment stack top @@ -1468,6 +1475,7 @@ fn buildPush(self: *Self, value: m.MIR_op_t) !void { ); } +// FIXME: we should not need 3 MOV to get to the value? fn buildPop(self: *Self, dest: ?m.MIR_op_t) !void { const ctx_reg = self.state.?.ctx_reg.?; const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); @@ -1522,22 +1530,21 @@ fn buildPop(self: *Self, dest: ?m.MIR_op_t) !void { ); // Store new top in result reg - const top = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - 0, - stack_top_base, - index, - 1, - ); - - self.MOV( - dest orelse m.MIR_new_reg_op( + if (dest) |into| { + const top = m.MIR_new_mem_op( self.ctx, - try self.REG("dismiss", m.MIR_T_I64), - ), - top, - ); + m.MIR_T_P, + 0, + stack_top_base, + index, + 1, + ); + + self.MOV( + into, + top, + ); + } } fn buildPeek(self: *Self, distance: u32, dest: m.MIR_op_t) !void { @@ -1621,14 +1628,24 @@ fn buildGetLocal(self: *Self, slot: usize) !m.MIR_op_t { m.MIR_new_uint_op(self.ctx, slot), ); - return m.MIR_new_mem_op( + // Avoid intertwining the get local and its value expression + const value_reg = m.MIR_new_reg_op( self.ctx, - m.MIR_T_U64, - 0, - base, - index, - @sizeOf(u64), + try self.REG("value", m.MIR_T_I64), ); + self.MOV( + value_reg, + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_U64, + 0, + base, + index, + @sizeOf(u64), + ), + ); + + return value_reg; } fn buildSetLocal(self: *Self, slot: usize, value: m.MIR_op_t) !void { @@ -1636,6 +1653,13 @@ fn buildSetLocal(self: *Self, slot: usize, value: m.MIR_op_t) !void { const index = try self.REG("index", m.MIR_T_I64); const base = try self.REG("base", m.MIR_T_I64); + // Avoid intertwining the set local and its value expression + const value_reg = m.MIR_new_reg_op( + self.ctx, + try self.REG("value", m.MIR_T_I64), + ); + self.MOV(value_reg, value); + self.MOV( m.MIR_new_reg_op(self.ctx, index), m.MIR_new_uint_op(self.ctx, 0), @@ -1667,7 +1691,7 @@ fn buildSetLocal(self: *Self, slot: usize, value: m.MIR_op_t) !void { @sizeOf(u64), ); - self.MOV(local, value); + self.MOV(local, value_reg); } fn buildGetGlobal(self: *Self, slot: usize) !m.MIR_op_t { @@ -2251,7 +2275,6 @@ fn generateNode(self: *Self, node: *n.ParseNode) Error!?m.MIR_op_t { .Unary => try self.generateUnary(n.UnaryNode.cast(node).?), .Pattern => try self.generatePattern(n.PatternNode.cast(node).?), .ForEach => try self.generateForEach(n.ForEachNode.cast(node).?), - .InlineIf => try self.generateInlineIf(n.InlineIfNode.cast(node).?), .TypeExpression => try self.generateTypeExpression(n.TypeExpressionNode.cast(node).?), .TypeOfExpression => try self.generateTypeOfExpression(n.TypeOfExpressionNode.cast(node).?), .AsyncCall, @@ -2928,6 +2951,11 @@ fn generateIf(self: *Self, if_node: *n.IfNode) Error!?m.MIR_op_t { try self.REG("condition", m.MIR_T_I64), ); + const resolved = if (!if_node.is_statement) + try self.REG("resolved", m.MIR_T_I64) + else + null; + // Is it `if (opt -> unwrapped)`? if (if_node.unwrapped_identifier != null) { try self.buildExternApiCall( @@ -2939,6 +2967,7 @@ fn generateIf(self: *Self, if_node: *n.IfNode) Error!?m.MIR_op_t { }, ); + // TODO: replace with condition ^ (MIR_OR, MIR_XOR?) 1 const true_label = m.MIR_new_label(self.ctx); const out_label = m.MIR_new_label(self.ctx); @@ -3022,10 +3051,22 @@ fn generateIf(self: *Self, if_node: *n.IfNode) Error!?m.MIR_op_t { // Push unwrapped value as local of the then block if (if_node.unwrapped_identifier != null or if_node.casted_type != null) { - try self.buildPush(condition_value.?); + try self.buildPush( + if (constant_condition) |constant| + m.MIR_new_uint_op(self.ctx, constant.val) + else + condition_value.?, + ); } - _ = try self.generateNode(if_node.body); + if (if_node.is_statement) { + _ = try self.generateNode(if_node.body); + } else { + self.MOV( + m.MIR_new_reg_op(self.ctx, resolved.?), + (try self.generateNode(if_node.body)).?, + ); + } self.JMP(out_label); } @@ -3034,13 +3075,23 @@ fn generateIf(self: *Self, if_node: *n.IfNode) Error!?m.MIR_op_t { if (if_node.else_branch) |else_branch| { self.append(else_label); - _ = try self.generateNode(else_branch); + if (if_node.is_statement) { + _ = try self.generateNode(else_branch); + } else { + self.MOV( + m.MIR_new_reg_op(self.ctx, resolved.?), + (try self.generateNode(else_branch)).?, + ); + } } } self.append(out_label); - return null; + return if (if_node.is_statement) + null + else + m.MIR_new_reg_op(self.ctx, resolved.?); } fn generateTypeExpression(self: *Self, type_expression_node: *n.TypeExpressionNode) Error!?m.MIR_op_t { @@ -3069,78 +3120,6 @@ fn generateTypeOfExpression(self: *Self, typeof_expression_node: *n.TypeOfExpres return result; } -fn generateInlineIf(self: *Self, inline_if_node: *n.InlineIfNode) Error!?m.MIR_op_t { - const constant_condition = if (inline_if_node.condition.isConstant(inline_if_node.condition)) - inline_if_node.condition.toValue(inline_if_node.condition, self.vm.gc) catch unreachable - else - null; - - // Generate condition - const condition = (try self.generateNode(inline_if_node.condition)).?; - - const resolved = try self.REG("resolved", m.MIR_T_I64); - - const out_label = m.MIR_new_label(self.ctx); - const then_label = m.MIR_new_label(self.ctx); - const else_label = m.MIR_new_label(self.ctx); - - if (constant_condition != null) { - self.JMP( - if (constant_condition.?.boolean()) - then_label - else - else_label, - ); - } else { - const unwrapped_condition = m.MIR_new_reg_op( - self.ctx, - try self.REG("ucond", m.MIR_T_I64), - ); - - try self.unwrap( - .Bool, - condition, - unwrapped_condition, - ); - - self.BEQ( - m.MIR_new_label_op(self.ctx, then_label), - unwrapped_condition, - m.MIR_new_uint_op(self.ctx, 1), - ); - - self.JMP(else_label); - } - - if (constant_condition == null or constant_condition.?.boolean()) { - self.append( - then_label, - ); - - self.MOV( - m.MIR_new_reg_op(self.ctx, resolved), - (try self.generateNode(inline_if_node.body)).?, - ); - - self.JMP(out_label); - } - - if (constant_condition == null or !constant_condition.?.boolean()) { - self.append( - else_label, - ); - - self.MOV( - m.MIR_new_reg_op(self.ctx, resolved), - (try self.generateNode(inline_if_node.else_branch)).?, - ); - } - - self.append(out_label); - - return m.MIR_new_reg_op(self.ctx, resolved); -} - fn generateBinary(self: *Self, binary_node: *n.BinaryNode) Error!?m.MIR_op_t { const left_type_def = binary_node.left.type_def.?.def_type; const right_type_def = binary_node.right.type_def.?.def_type; @@ -4535,6 +4514,10 @@ fn generateTry(self: *Self, try_node: *n.TryNode) Error!?m.MIR_op_t { } fn generateThrow(self: *Self, throw_node: *n.ThrowNode) Error!?m.MIR_op_t { + if (throw_node.unconditional) { + self.state.?.return_emitted = true; + } + try self.buildExternApiCall( .bz_throw, null, diff --git a/src/node.zig b/src/node.zig index fc4871da..f90a1f3c 100644 --- a/src/node.zig +++ b/src/node.zig @@ -88,7 +88,6 @@ pub const ParseNodeType = enum(u8) { Resolve, Yield, If, - InlineIf, Block, Return, For, @@ -5420,14 +5419,14 @@ pub const ThrowNode = struct { } fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); + const codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); + const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); if (node.synchronize(codegen)) { return null; } - var self = Self.cast(node).?; + const self = Self.cast(node).?; if (self.unconditional) { codegen.current.?.return_emitted = true; @@ -5642,11 +5641,11 @@ pub const ContinueNode = struct { } }; -pub const InlineIfNode = struct { +pub const IfNode = struct { const Self = @This(); node: ParseNode = .{ - .node_type = .InlineIf, + .node_type = .If, .toJson = stringify, .toByteCode = generate, .toValue = val, @@ -5655,14 +5654,17 @@ pub const InlineIfNode = struct { }, condition: *ParseNode, + unwrapped_identifier: ?Token, + casted_type: ?*ObjTypeDef, body: *ParseNode, - else_branch: *ParseNode, + else_branch: ?*ParseNode = null, + is_statement: bool, fn constant(nodePtr: *anyopaque) bool { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; + const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); + const self = Self.cast(node).?; - return self.condition.isConstant(self.condition) and self.body.isConstant(self.body) and self.else_branch.isConstant(self.else_branch); + return !self.is_statement and self.condition.isConstant(self.condition) and self.body.isConstant(self.body) and self.else_branch.?.isConstant(self.else_branch.?); } fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { @@ -5670,11 +5672,12 @@ pub const InlineIfNode = struct { if (node.isConstant(node)) { const self = Self.cast(node).?; + assert(!self.is_statement); if ((try self.condition.toValue(self.condition, gc)).boolean()) { return self.body.toValue(self.body, gc); } else { - return self.else_branch.toValue(self.else_branch, gc); + return self.else_branch.?.toValue(self.else_branch.?, gc); } } @@ -5682,175 +5685,50 @@ pub const InlineIfNode = struct { } fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); + const codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); + const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); if (node.synchronize(codegen)) { return null; } - var self = Self.cast(node).?; + const self = Self.cast(node).?; - // Type checking if (self.condition.type_def == null or self.condition.type_def.?.def_type == .Placeholder) { codegen.reporter.reportPlaceholder(self.condition.type_def.?.resolved_type.?.Placeholder); } - if (self.body.type_def == null or self.body.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.body.type_def.?.resolved_type.?.Placeholder); - } - - if (self.else_branch.type_def == null or self.else_branch.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.else_branch.type_def.?.resolved_type.?.Placeholder); - } - - // Both should have same type - if (!node.type_def.?.eql(self.body.type_def.?)) { - codegen.reporter.reportTypeCheck( - .inline_if_body_type, - node.location, - node.type_def.?, - self.body.location, - self.body.type_def.?, - "Inline if body type not matching", - ); - } - - if (!node.type_def.?.eql(self.else_branch.type_def.?)) { - codegen.reporter.reportTypeCheck( - .inline_if_else_type, - node.location, - node.type_def.?, - self.else_branch.location, - self.else_branch.type_def.?, - "Inline if else type not matching", - ); - } - - // If condition is constant only generate appropriate branch - if (self.condition.isConstant(self.condition)) { - const condition = try self.condition.toValue(self.condition, codegen.gc); - - if (condition.boolean()) { - _ = try self.body.toByteCode(self.body, codegen, breaks); - } else { - _ = try self.else_branch.toByteCode(self.else_branch, codegen, breaks); + if (!self.is_statement) { + if (self.body.type_def == null or self.body.type_def.?.def_type == .Placeholder) { + codegen.reporter.reportPlaceholder(self.body.type_def.?.resolved_type.?.Placeholder); } - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - // Generate the if/else - _ = try self.condition.toByteCode(self.condition, codegen, breaks); - - const then_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); - try codegen.emitOpCode(self.node.location, .OP_POP); - - _ = try self.body.toByteCode(self.body, codegen, breaks); - - const else_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP); - - codegen.patchJump(then_jump); - try codegen.emitOpCode(self.node.location, .OP_POP); - - _ = try self.else_branch.toByteCode(self.else_branch, codegen, breaks); - codegen.patchJump(else_jump); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"InlineIf\", \"condition\": "); - - try self.condition.toJson(self.condition, out); - - try out.writeAll(", \"body\": "); - - try self.body.toJson(self.body, out); - - try out.writeAll(", \"else\": "); - try self.else_branch.toJson(self.else_branch, out); - - try out.writeAll(", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeAll("if ("); - try self.condition.render(self.condition, out, depth); - try out.writeAll(") "); - try self.body.render(self.body, out, depth + 1); - try out.writeAll(" else "); - try self.else_branch.render(self.else_branch, out, depth + 1); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .InlineIf) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const IfNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .If, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - condition: *ParseNode, - unwrapped_identifier: ?Token, - casted_type: ?*ObjTypeDef, - body: *ParseNode, - else_branch: ?*ParseNode = null, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } + if (self.else_branch.?.type_def == null or self.else_branch.?.type_def.?.def_type == .Placeholder) { + codegen.reporter.reportPlaceholder(self.else_branch.?.type_def.?.resolved_type.?.Placeholder); + } - var self = Self.cast(node).?; + // Both should have same type + if (!node.type_def.?.eql(self.body.type_def.?)) { + codegen.reporter.reportTypeCheck( + .inline_if_body_type, + node.location, + node.type_def.?, + self.body.location, + self.body.type_def.?, + "Inline if body type not matching", + ); + } - if (self.condition.type_def == null or self.condition.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.condition.type_def.?.resolved_type.?.Placeholder); + if (!node.type_def.?.eql(self.else_branch.?.type_def.?)) { + codegen.reporter.reportTypeCheck( + .inline_if_else_type, + node.location, + node.type_def.?, + self.else_branch.?.location, + self.else_branch.?.type_def.?, + "Inline if else type not matching", + ); + } } if (self.unwrapped_identifier != null) { @@ -5899,14 +5777,14 @@ pub const IfNode = struct { try codegen.emitOpCode(self.condition.location, .OP_IS); } - const then_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); + const else_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); try codegen.emitOpCode(self.node.location, .OP_POP); _ = try self.body.toByteCode(self.body, codegen, breaks); - const else_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP); + const out_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP); - codegen.patchJump(then_jump); + codegen.patchJump(else_jump); if (self.unwrapped_identifier != null or self.casted_type != null) { // Since we did not enter the if block, we did not pop the unwrapped local try codegen.emitOpCode(self.node.location, .OP_POP); @@ -5917,7 +5795,7 @@ pub const IfNode = struct { _ = try else_branch.toByteCode(else_branch, codegen, breaks); } - codegen.patchJump(else_jump); + codegen.patchJump(out_jump); try node.patchOptJumps(codegen); try node.endScope(codegen); diff --git a/src/parser.zig b/src/parser.zig index 60406137..cae67d47 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -83,7 +83,6 @@ const ThrowNode = _node.ThrowNode; const BreakNode = _node.BreakNode; const ContinueNode = _node.ContinueNode; const IfNode = _node.IfNode; -const InlineIfNode = _node.InlineIfNode; const ReturnNode = _node.ReturnNode; const ForNode = _node.ForNode; const ForEachNode = _node.ForEachNode; @@ -2150,7 +2149,7 @@ pub const Parser = struct { return &node.node; } - fn ifStatement(self: *Self, loop_scope: ?LoopScope) anyerror!*ParseNode { + fn @"if"(self: *Self, is_statement: bool, loop_scope: ?LoopScope) anyerror!*ParseNode { const start_location = self.parser.previous_token.?; try self.consume(.LeftParen, "Expected `(` after `if`."); @@ -2160,7 +2159,7 @@ pub const Parser = struct { var unwrapped_identifier: ?Token = null; var casted_type: ?*ObjTypeDef = null; - if (try self.match(.Arrow)) { + if (try self.match(.Arrow)) { // if (opt -> unwrapped) _ = try self.parseVariable( false, try condition.type_def.?.cloneNonOptional(&self.gc.type_registry), @@ -2170,7 +2169,7 @@ pub const Parser = struct { self.markInitialized(); unwrapped_identifier = self.parser.previous_token.?; - } else if (try self.match(.As)) { + } else if (try self.match(.As)) { // if (expr as casted) casted_type = try self.parseTypeDef(null, true); _ = try self.parseVariable( @@ -2184,20 +2183,27 @@ pub const Parser = struct { try self.consume(.RightParen, "Expected `)` after `if` condition."); - try self.consume(.LeftBrace, "Expected `{` after `if` condition."); - var body = try self.block(loop_scope); + const body = if (is_statement) stmt: { + try self.consume(.LeftBrace, "Expected `{` after `if` condition."); + break :stmt try self.block(loop_scope); + } else try self.expression(false); + body.ends_scope = try self.endScope(); var else_branch: ?*ParseNode = null; - if (try self.match(.Else)) { + if (!is_statement or self.check(.Else)) { + try self.consume(.Else, "Expected `else` after inline `if` body."); + if (try self.match(.If)) { - else_branch = try self.ifStatement(loop_scope); - } else { + else_branch = try self.@"if"(is_statement, loop_scope); + } else if (is_statement) { try self.consume(.LeftBrace, "Expected `{` after `else`."); self.beginScope(); else_branch = try self.block(loop_scope); else_branch.?.ends_scope = try self.endScope(); + } else { + else_branch = try self.expression(false); } } @@ -2208,13 +2214,31 @@ pub const Parser = struct { .casted_type = casted_type, .body = body, .else_branch = else_branch, + .is_statement = is_statement, }; node.node.location = start_location; node.node.end_location = self.parser.previous_token.?; + if (!is_statement) { + if (body.type_def == null or body.type_def.?.def_type == .Void) { + node.node.type_def = else_branch.?.type_def; + } else { + node.node.type_def = body.type_def; + } + + const is_optional = node.node.type_def.?.optional or body.type_def.?.optional or else_branch.?.type_def.?.optional or body.type_def.?.def_type == .Void or else_branch.?.type_def.?.def_type == .Void; + if (is_optional and !node.node.type_def.?.optional) { + node.node.type_def = try node.node.type_def.?.cloneOptional(&self.gc.type_registry); + } + } + return &node.node; } + fn ifStatement(self: *Self, loop_scope: ?LoopScope) anyerror!*ParseNode { + return try self.@"if"(true, loop_scope); + } + fn forStatement(self: *Self) !*ParseNode { const start_location = self.parser.previous_token.?; @@ -4077,39 +4101,7 @@ pub const Parser = struct { } fn inlineIf(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - try self.consume(.LeftParen, "Expected `(` after `if`."); - const condition = try self.expression(false); - try self.consume(.RightParen, "Expected `)` after `if` condition."); - - const body = try self.expression(false); - - try self.consume(.Else, "Expected `else` after inline `if` body."); - - const else_branch = try self.expression(false); - - var node = try self.gc.allocator.create(InlineIfNode); - node.* = InlineIfNode{ - .condition = condition, - .body = body, - .else_branch = else_branch, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - if (body.type_def == null or body.type_def.?.def_type == .Void) { - node.node.type_def = else_branch.type_def; - } else { - node.node.type_def = body.type_def; - } - - const is_optional = node.node.type_def.?.optional or body.type_def.?.optional or else_branch.type_def.?.optional or body.type_def.?.def_type == .Void or else_branch.type_def.?.def_type == .Void; - if (is_optional and !node.node.type_def.?.optional) { - node.node.type_def = try node.node.type_def.?.cloneOptional(&self.gc.type_registry); - } - - return &node.node; + return try self.@"if"(false, null); } // FIXME: doesn't need its own function diff --git a/tests/052-inline-if.buzz b/tests/052-inline-if.buzz index 5286862b..d65ebaa1 100644 --- a/tests/052-inline-if.buzz +++ b/tests/052-inline-if.buzz @@ -8,4 +8,31 @@ test "ternary" { value = if ("hello".len() == 2) 0 else 12; assert(value == 12, message: "could use inline if"); +} + +test "multiple branches" { + int value = 12; + + const expr = if (value == 14) + "hello" + else if (value == 12) + "yolo" + else + null; + + assert(expr == "yolo", message: "Could use multiple branches with inline if"); +} + +test "inline if in expression" { + int value = 12; + + assert( + (if (value == 14) + "hello" + else if (value == 12) + "yolo" + else + null) == "yolo", + message: "Could use multiple branches inline if in expression" + ); } \ No newline at end of file