diff --git a/src/Ast.zig b/src/Ast.zig index 686163b..f1815d4 100644 --- a/src/Ast.zig +++ b/src/Ast.zig @@ -102,11 +102,12 @@ pub const Node = struct { items: []*Node, leading_glue: bool, trailing_glue: bool, + trailing_divert: ?*Node, }, choice_expr: struct { - start_expr: ?*Node, - option_expr: ?*Node, - inner_expr: ?*Node, + start_expr: ?[]*Node, + option_expr: ?[]*Node, + inner_expr: ?[]*Node, }, switch_stmt: struct { condition_expr: ?*Node, @@ -165,6 +166,7 @@ pub const Node = struct { items: []*Node, leading_glue: bool, trailing_glue: bool, + trailing_divert: ?*Node, }, ) !*Node { const node = try Node.create(gpa, tag, span); @@ -173,6 +175,7 @@ pub const Node = struct { .items = options.items, .leading_glue = options.leading_glue, .trailing_glue = options.trailing_glue, + .trailing_divert = options.trailing_divert, }, }; return node; @@ -182,16 +185,16 @@ pub const Node = struct { gpa: std.mem.Allocator, tag: Tag, span: Span, - start_expr: ?*Node, - option_expr: ?*Node, - inner_expr: ?*Node, + start_content: ?[]*Node, + option_only_content: ?[]*Node, + inner_content: ?[]*Node, ) !*Node { const node = try Node.create(gpa, tag, span); node.data = .{ .choice_expr = .{ - .start_expr = start_expr, - .option_expr = option_expr, - .inner_expr = inner_expr, + .start_expr = start_content, + .option_expr = option_only_content, + .inner_expr = inner_content, }, }; return node; diff --git a/src/Ast/Render.zig b/src/Ast/Render.zig index 543845b..5bba3f1 100644 --- a/src/Ast/Render.zig +++ b/src/Ast/Render.zig @@ -356,6 +356,7 @@ fn renderAstWalk( .parameter_list, .block_stmt, .choice_stmt, + .content_stmt, => { const data = node.data.list; for (data.items) |child_node| try children.append(gpa, child_node); @@ -366,9 +367,18 @@ fn renderAstWalk( }, .choice_expr => { const data = node.data.choice_expr; - if (data.start_expr) |lhs| try children.append(gpa, lhs); - if (data.option_expr) |mhs| try children.append(gpa, mhs); - if (data.inner_expr) |rhs| try children.append(gpa, rhs); + if (data.start_expr) |lhs| { + for (lhs) |child| + try children.append(gpa, child); + } + if (data.option_expr) |mhs| { + for (mhs) |child| + try children.append(gpa, child); + } + if (data.inner_expr) |rhs| { + for (rhs) |child| + try children.append(gpa, child); + } }, .add_expr, .subtract_expr, @@ -393,7 +403,6 @@ fn renderAstWalk( .var_decl, .list_decl, .temp_decl, - .content_stmt, .choice_star_stmt, .choice_plus_stmt, .gather_point_stmt, diff --git a/src/AstGen.zig b/src/AstGen.zig index 28c00c7..85d4d38 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -285,11 +285,11 @@ const GenIr = struct { return last_inst.isNoReturn(); } - fn endsWithContent(self: *GenIr) bool { + fn endsWithGlue(self: *GenIr) bool { if (self.isEmpty()) return false; const last_inst_index = self.instructions.items[self.instructions.items.len - 1]; const last_inst = self.astgen.instructions.items[@intFromEnum(last_inst_index)]; - return last_inst.tag == .content_push; + return last_inst.tag == .content_glue; } fn makeSubBlock(self: *GenIr) GenIr { @@ -814,7 +814,7 @@ fn inlineIfStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir. if (data.rhs) |rhs| { // TODO: Revisit this. This isn't quite correct. switch (rhs.tag) { - .content => _ = try content(&then_block, scope, rhs), + .content => _ = try content(&then_block, scope, rhs, false, false), inline else => |tag| @panic("Unexpected node type: " ++ @tagName(tag)), } } @@ -1058,7 +1058,13 @@ fn switchStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.In return switch_br.toRef(); } -fn content(block: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref { +fn content( + block: *GenIr, + scope: *Scope, + node: *const Ast.Node, + is_last: bool, + ignore_divert: bool, +) InnerError!void { const data = node.data.content; if (data.leading_glue) { _ = try block.addUnaryNode(.content_glue, .none); @@ -1081,17 +1087,26 @@ fn content(block: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.In else => unreachable, } } - if (data.trailing_glue) { - _ = try block.addUnaryNode(.content_glue, .none); - } else if (block.endsWithContent()) { + if (is_last) { _ = try block.addUnaryNode(.content_line, .none); + if (data.trailing_glue or data.trailing_divert != null) { + _ = try block.addUnaryNode(.content_glue, .none); + } + if (!ignore_divert) { + if (data.trailing_divert) |trailing| { + _ = try divertExpr(block, scope, trailing); + _ = try block.addUnaryNode(.content_glue, .none); + } + } } - return .none; } -fn contentStmt(block: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void { - const data = node.data.bin; - _ = try content(block, scope, data.lhs.?); +fn contentStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void { + const items = node.data.list.items; + for (items, 0..) |n, i| { + const is_last = i == items.len - 1; + try content(gi, scope, n, is_last, false); + } } fn assignStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void { @@ -1108,19 +1123,13 @@ fn assignStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void return fail(astgen, identifier_node, "unknown identifier", .{}); } -fn choiceStarStmt(gi: *GenIr, _: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref { - return stringLiteral(gi, node); -} - -fn choiceStmt( - parent_block: *GenIr, - scope: *Scope, - stmt_node: *const Ast.Node, -) InnerError!void { - const astgen = parent_block.astgen; +fn choiceStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void { + const astgen = gi.astgen; const gpa = astgen.gpa; - const data = stmt_node.data.list; - const choice_br = try parent_block.makePayloadNode(.choice_br); + const data = node.data.list; + const choice_br = try gi.makePayloadNode(.choice_br); + + var trailing_divert: ?*Ast.Node = null; var case_indexes: std.ArrayListUnmanaged(u32) = .empty; try case_indexes.ensureUnusedCapacity(gpa, data.items.len); defer case_indexes.deinit(gpa); @@ -1129,46 +1138,83 @@ fn choiceStmt( assert(branch_stmt.tag == .choice_star_stmt or branch_stmt.tag == .choice_plus_stmt); const branch_data = branch_stmt.data.bin; const branch_expr = branch_data.lhs.?.data.choice_expr; - var op_1: Ir.Inst.Ref = .none; - var op_2: Ir.Inst.Ref = .none; - var op_3: Ir.Inst.Ref = .none; - if (branch_expr.start_expr) |node| { - op_1 = try choiceStarStmt(parent_block, scope, node); - } - if (branch_expr.option_expr) |node| { - op_2 = try choiceStarStmt(parent_block, scope, node); - } - if (branch_expr.inner_expr) |node| { - op_3 = try choiceStarStmt(parent_block, scope, node); + var block_1 = gi.makeSubBlock(); + defer block_1.unstack(); + + if (branch_expr.start_expr) |lhs| { + for (lhs) |n| { + if (n.data.content.trailing_divert) |trailing| { + trailing_divert = trailing; + } + _ = try content(&block_1, scope, n, false, true); + } } - var sub_block = parent_block.makeSubBlock(); - defer sub_block.unstack(); - if (branch_data.rhs) |branch_body| { - _ = try blockStmt(&sub_block, scope, branch_body); - } - if (!sub_block.endsWithNoReturn()) { - _ = try sub_block.addUnaryNode(.implicit_ret, .none); + var block_2 = block_1.makeSubBlock(); + defer block_2.unstack(); + + if (branch_expr.option_expr) |mhs| { + for (mhs) |n| { + assert(n.data.content.trailing_divert == null); + _ = try content(&block_2, scope, n, false, true); + } } - const body = sub_block.instructionsSlice(); - const case_extra_len = @typeInfo(Ir.Inst.SwitchBr.Case).@"struct".fields.len + body.len; + var block_3 = block_2.makeSubBlock(); + defer block_3.unstack(); + + if (branch_expr.inner_expr) |rhs| { + for (rhs) |n| { + if (n.data.content.trailing_divert) |trailing| { + trailing_divert = trailing; + } + _ = try content(&block_3, scope, n, false, true); + } + } + + var body_block = gi.makeSubBlock(); + defer body_block.unstack(); + + if (trailing_divert) |trailing| { + _ = try divertExpr(&body_block, scope, trailing); + } else if (branch_data.rhs) |branch_body| { + _ = try blockStmt(&body_block, scope, branch_body); + } + if (!body_block.endsWithNoReturn()) { + _ = try body_block.addUnaryNode(.implicit_ret, .none); + } + + const lhs_body = block_1.instructionsSliceUpto(&block_2); + const mhs_body = block_2.instructionsSliceUpto(&block_3); + const rhs_body = block_3.instructionsSliceUpto(&body_block); + const body = body_block.instructionsSlice(); + const case_extra_len = + @typeInfo(Ir.Inst.SwitchBr.Case).@"struct".fields.len + + lhs_body.len + mhs_body.len + rhs_body.len + body.len; + try astgen.extra.ensureUnusedCapacity(gpa, case_extra_len); const extra_index = astgen.addExtraAssumeCapacity( Ir.Inst.ChoiceBr.Case{ - .operand_1 = op_1, - .operand_2 = op_2, - .operand_3 = op_3, + .lhs_len = @intCast(lhs_body.len), + .mhs_len = @intCast(mhs_body.len), + .rhs_len = @intCast(rhs_body.len), .body_len = @intCast(body.len), }, ); + astgen.appendBlockBody(lhs_body); + astgen.appendBlockBody(mhs_body); + astgen.appendBlockBody(rhs_body); astgen.appendBlockBody(body); case_indexes.appendAssumeCapacity(extra_index); } - try parent_block.instructions.append(gpa, choice_br); - const extra_len = @typeInfo(Ir.Inst.ChoiceBr).@"struct".fields.len + case_indexes.items.len; + try gi.instructions.append(gpa, choice_br); + + const extra_len = + @typeInfo(Ir.Inst.ChoiceBr).@"struct".fields.len + + case_indexes.items.len; + try astgen.extra.ensureUnusedCapacity(gpa, extra_len); astgen.instructions.items[@intFromEnum(choice_br)].data.payload = .{ @@ -1177,7 +1223,7 @@ fn choiceStmt( .cases_len = @intCast(data.items.len), }, ), - .src_offset = @intCast(stmt_node.loc.start), + .src_offset = @intCast(node.loc.start), }; astgen.extra.appendSliceAssumeCapacity(case_indexes.items[0..]); } @@ -1631,6 +1677,9 @@ fn knotDecl(gi: *GenIr, parent_scope: *Scope, decl_node: *const Ast.Node) InnerE node_index += 1; } } + if (!child_block.endsWithNoReturn()) { + _ = try child_block.addUnaryNode(.implicit_ret, .none); + } var nested_block = child_block.makeSubBlock(); defer nested_block.unstack(); diff --git a/src/Ir.zig b/src/Ir.zig index a3fe6ef..b248ade 100644 --- a/src/Ir.zig +++ b/src/Ir.zig @@ -314,9 +314,9 @@ pub const Inst = struct { cases_len: u32, pub const Case = struct { - operand_1: Ref, - operand_2: Ref, - operand_3: Ref, + lhs_len: u32, + mhs_len: u32, + rhs_len: u32, body_len: u32, }; }; diff --git a/src/Parse.zig b/src/Parse.zig index 42ab277..0e46110 100644 --- a/src/Parse.zig +++ b/src/Parse.zig @@ -186,7 +186,6 @@ fn eatTokenLooped(p: *Parse, tag: Token.Tag, ignore_whitespace: bool) usize { fn expectToken(p: *Parse, tag: Token.Tag, skip_whitespace: bool) Error!Token { if (skip_whitespace) p.eatToken(.whitespace); if (!p.checkToken(tag)) { - std.debug.print("Expected token '{any}', got '{any}'\n", .{ tag, p.token.tag }); return p.fail(.unexpected_token, p.token); } return p.nextToken(); @@ -809,22 +808,42 @@ fn parseDivertStmt(p: *Parse) Error!*Ast.Node { fn parseChoiceExpr(p: *Parse) Error!?*Ast.Node { const main_token = p.token; - var lhs: ?*Ast.Node = null; - var mhs: ?*Ast.Node = null; - var rhs: ?*Ast.Node = null; - lhs = try parseContent(p, .{ .ignore_brackets = false }); + const lhs = try parseContentList(p, .{ .ignore_brackets = false }); + if (lhs) |nodes| { + const last = nodes[nodes.len - 1]; + if (last.data.content.trailing_divert != null) { + const end_token = try p.expectNewline(); + return .createChoice(p.arena, .choice_expr, .{ + .start = main_token.loc.start, + .end = end_token.loc.start, + }, lhs, null, null); + } + } if (p.checkToken(.left_bracket)) { _ = p.nextToken(); p.eatToken(.whitespace); - mhs = try parseContent(p, .{ .ignore_brackets = false }); + + const mhs = try parseContentList(p, .{ .ignore_brackets = false }); + if (mhs) |nodes| { + const last = nodes[nodes.len - 1]; + if (last.data.content.trailing_divert) |_| { + return p.fail(.unexpected_token, p.token); + } + } + _ = try p.expectToken(.right_bracket, false); - rhs = try parseContent(p, .{}); + const rhs = try parseContentList(p, .{ .ignore_brackets = false }); + + return .createChoice(p.arena, .choice_expr, .{ + .start = main_token.loc.start, + .end = p.token.loc.start, + }, lhs, mhs, rhs); } return .createChoice(p.arena, .choice_expr, .{ .start = main_token.loc.start, .end = p.token.loc.start, - }, lhs, mhs, rhs); + }, lhs, null, null); } fn parseChoiceStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node { @@ -888,15 +907,19 @@ fn parseConditional(p: *Parse, main_token: Token, expr: ?*Ast.Node) Error!?*Ast. }, expr, list); } -fn parseInlineIf(p: *Parse, main_token: Token, lhs: ?*Ast.Node) Error!?*Ast.Node { +fn parseInlineIf(p: *Parse, main_token: Token, lhs: ?*Ast.Node) Error!*Ast.Node { p.eatToken(.whitespace); - const content = try parseContent(p, .{}); + const content_node = switch (try parseContent(p, .{}, false)) { + .node, .split => |n| n, + .none => null, + }; const end_token = try p.expectToken(.right_brace, true); + return .createBinary(p.arena, .inline_if_stmt, .{ .start = main_token.loc.start, .end = end_token.loc.end, - }, lhs, content); + }, lhs, content_node); } fn parseLbraceExpr(p: *Parse) Error!?*Ast.Node { @@ -935,11 +958,31 @@ fn parseLbraceExpr(p: *Parse) Error!?*Ast.Node { const ContentOptions = struct { ignore_brackets: bool = true, - ignore_parens: bool = true, + skip_leading_whitespace: bool = false, + skip_trailing_whitespace: bool = false, }; +const ContentResult = union(enum) { + node: *Ast.Node, + split: *Ast.Node, + none, +}; + +// TODO: This function is getting to be a mess. Refactor if possible. fn parseContentString(p: *Parse, options: ContentOptions) Error!?*Ast.Node { const main_token = p.token; + var end_pos = main_token.loc.start; + + if (options.skip_leading_whitespace) { + while (p.token.tag == .whitespace) _ = p.nextToken(); + switch (p.token.tag) { + .eof, .newline, .glue => return null, + else => {}, + } + } + + const start_pos = p.token.loc.start; + end_pos = start_pos; while (true) { switch (p.token.tag) { @@ -951,78 +994,172 @@ fn parseContentString(p: *Parse, options: ContentOptions) Error!?*Ast.Node { .right_brace, .glue, => break, - .left_bracket, .right_bracket => |tag| if (!options.ignore_brackets) - break - else - p.eatToken(tag), - else => |tag| p.eatToken(tag), + .left_bracket, .right_bracket => { + if (!options.ignore_brackets) break; + end_pos = p.token.loc.end; + _ = p.nextToken(); + }, + .whitespace => { + // TODO: Test for this. + if (options.skip_trailing_whitespace) { + const ws_end = p.token.loc.end; + _ = p.nextToken(); + + if (p.token.tag == .glue) break; + end_pos = ws_end; + } else { + end_pos = p.token.loc.end; + _ = p.nextToken(); + } + }, + else => { + end_pos = p.token.loc.end; + _ = p.nextToken(); + }, } } - if (main_token.loc.start == p.token.loc.start) return null; + if (start_pos == end_pos) + return null; return .createLeaf(p.arena, .string_literal, .{ - .start = main_token.loc.start, - .end = p.token.loc.start, + .start = start_pos, + .end = end_pos, }); } -fn parseContent(p: *Parse, options: ContentOptions) Error!?*Ast.Node { +// TODO: This function is getting to be a mess. Refactor if possible. +fn parseContent( + p: *Parse, + options: ContentOptions, + leading_glue: bool, +) Error!ContentResult { const main_token = p.token; const scratch_top = p.scratch.items.len; - var leading_glue = false; + var trailing_divert: ?*Ast.Node = null; var trailing_glue = false; + var has_internal_leading_glue = false; + var is_split = false; if (p.token.tag == .glue) { - leading_glue = true; + has_internal_leading_glue = true; _ = p.nextToken(); } + if (options.skip_leading_whitespace and !has_internal_leading_glue) { + while (p.token.tag == .whitespace) _ = p.nextToken(); + } + + const effective_leading_glue = leading_glue or has_internal_leading_glue; + loop: while (true) { const node: ?*Ast.Node = switch (p.token.tag) { - .eof, .newline, .left_arrow, .right_brace => break, + .eof, + .newline, + .left_arrow, + .right_brace, + => break, .left_brace => try parseLbraceExpr(p), - .right_arrow => try parseDivertExpr(p), - .glue => blk: { - while (true) { - _ = p.nextToken(); - switch (p.token.tag) { - .whitespace => continue, - .eof, .newline, .right_brace => { - trailing_glue = true; - break :loop; - }, - else => break :blk null, - } + .right_arrow => { + trailing_divert = try parseDivertExpr(p); + break :loop; + }, + .glue => { + _ = p.nextToken(); + switch (p.token.tag) { + .eof, .newline, .right_brace => { + trailing_glue = true; + break :loop; + }, + else => { + is_split = true; + break :loop; + }, } }, else => |tag| blk: { - switch (tag) { - .left_bracket, .right_bracket => if (!options.ignore_brackets) break, - else => {}, + if (tag == .left_bracket or tag == .right_bracket) { + if (!options.ignore_brackets) break; } - break :blk try parseContentString(p, options); + break :blk try parseContentString(p, .{ + .ignore_brackets = options.ignore_brackets, + .skip_leading_whitespace = false, + .skip_trailing_whitespace = true, + }); }, }; if (node) |n| try p.scratch.append(p.gpa, n); } - if (main_token.loc.start == p.token.loc.start) return null; - return .createContent(p.arena, .content, .{ + if (main_token.loc.start == p.token.loc.start) return .none; + + const items = try p.makeNodeSliceFromScratch(scratch_top); + const node = try Ast.Node.createContent(p.arena, .content, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, .{ - .items = try p.makeNodeSliceFromScratch(scratch_top), - .leading_glue = leading_glue, + .items = items, + .leading_glue = effective_leading_glue, .trailing_glue = trailing_glue, + .trailing_divert = trailing_divert, }); + return if (is_split) .{ .split = node } else .{ .node = node }; +} + +fn parseContentList( + p: *Parse, + options: ContentOptions, +) Error!?[]*Ast.Node { + const scratch_top = p.scratch.items.len; + var has_leading_glue = false; + var is_first = true; + + while (true) { + const segment_options: ContentOptions = .{ + .ignore_brackets = options.ignore_brackets, + .skip_leading_whitespace = is_first and options.skip_leading_whitespace, + .skip_trailing_whitespace = true, + }; + switch (try parseContent(p, segment_options, has_leading_glue)) { + .node => |n| { + try p.scratch.append(p.gpa, n); + break; + }, + .split => |n| { + try p.scratch.append(p.gpa, n); + has_leading_glue = true; + }, + .none => break, + } + + is_first = false; + } + + const items = try p.makeNodeSliceFromScratch(scratch_top); + return if (items.len > 0) items else null; } fn parseContentStmt(p: *Parse) Error!*Ast.Node { const main_token = p.token; - const node = try parseContent(p, .{}); + const context = makeStmtContext(p, .block, null); + var has_leading_glue = false; + + while (true) { + switch (try parseContent(p, .{}, has_leading_glue)) { + .node => |n| { + try p.scratch.append(p.gpa, n); + break; + }, + .split => |n| { + try p.scratch.append(p.gpa, n); + has_leading_glue = true; + }, + .none => break, + } + } + const end_token = try p.expectNewline(); - return .createBinary(p.arena, .content_stmt, .{ + return p.makeNodeSlice(&context, .content_stmt, .{ .start = main_token.loc.start, .end = end_token.loc.start, - }, node, null); + }); } fn parseParameterDecl(p: *Parse) Error!*Ast.Node { diff --git a/src/Sema.zig b/src/Sema.zig index 913a3ee..7538313 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -709,28 +709,21 @@ fn irChoiceBr(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!vo for (options_slice) |option_index| { const case_extra = sema.ir.extraData(Ir.Inst.ChoiceBr.Case, @intFromEnum(option_index)); + const lhs_slice = sema.ir.bodySlice( + case_extra.end, + case_extra.data.lhs_len, + ); + const mhs_slice = sema.ir.bodySlice( + case_extra.end + case_extra.data.lhs_len, + case_extra.data.mhs_len, + ); + const case_label = try builder.addLabel(); branch_labels.appendAssumeCapacity(case_label); try builder.addByteOp(.stream_mark); - - switch (case_extra.data.operand_1) { - .none => {}, - else => |content| { - const content_inst = sema.resolveInst(content); - try builder.ensureLoad(content_inst); - try builder.addByteOp(.stream_push); - }, - } - switch (case_extra.data.operand_2) { - .none => {}, - else => |content| { - const content_inst = sema.resolveInst(content); - try builder.ensureLoad(content_inst); - try builder.addByteOp(.stream_push); - }, - } - + _ = try analyzeBodyInner(sema, builder, lhs_slice, false); + _ = try analyzeBodyInner(sema, builder, mhs_slice, false); try builder.addFixupAbsolute(.br_push, case_label); } @@ -740,26 +733,28 @@ fn irChoiceBr(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!vo for (options_slice, branch_labels.items) |option_index, label| { const case_extra = sema.ir.extraData(Ir.Inst.ChoiceBr.Case, @intFromEnum(option_index)); - const body_slice = sema.ir.bodySlice(case_extra.end, case_extra.data.body_len); + const lhs_slice = sema.ir.bodySlice( + case_extra.end, + case_extra.data.lhs_len, + ); + const rhs_slice = sema.ir.bodySlice( + case_extra.end + + case_extra.data.lhs_len + + case_extra.data.mhs_len, + case_extra.data.rhs_len, + ); + const body_slice = sema.ir.bodySlice( + case_extra.end + + case_extra.data.lhs_len + + case_extra.data.mhs_len + + case_extra.data.rhs_len, + case_extra.data.body_len, + ); builder.setLabel(label); - switch (case_extra.data.operand_1) { - .none => {}, - else => |content| { - const content_inst = sema.resolveInst(content); - try builder.ensureLoad(content_inst); - try builder.addByteOp(.stream_push); - }, - } - switch (case_extra.data.operand_3) { - .none => {}, - else => |content| { - const content_inst = sema.resolveInst(content); - try builder.ensureLoad(content_inst); - try builder.addByteOp(.stream_push); - }, - } + _ = try analyzeBodyInner(sema, builder, lhs_slice, false); + _ = try analyzeBodyInner(sema, builder, rhs_slice, false); try builder.addByteOp(.stream_line); _ = try analyzeBodyInner(sema, builder, body_slice, false); } diff --git a/src/Story.zig b/src/Story.zig index cff8700..fa0bcf8 100644 --- a/src/Story.zig +++ b/src/Story.zig @@ -782,6 +782,7 @@ fn resolveOutputStream( gpa: std.mem.Allocator, stream: []const OutputCommand, ) ![]const u8 { + var glue_active = false; var pending_newline = false; var result: std.ArrayListUnmanaged(u8) = .empty; defer result.deinit(gpa); @@ -790,17 +791,20 @@ fn resolveOutputStream( switch (cmd) { .value => |value| { if (pending_newline) { - pending_newline = false; try result.append(gpa, '\n'); + pending_newline = false; } + glue_active = false; const str = try Object.String.fromValue(story, value); try result.appendSlice(gpa, str.toSlice()); }, .line => { - if (result.items.len > 0) - pending_newline = true; + if (!glue_active) pending_newline = true; + }, + .glue => { + pending_newline = false; + glue_active = true; }, - .glue => pending_newline = false, } } return result.toOwnedSlice(gpa); diff --git a/src/print_ir.zig b/src/print_ir.zig index 8e74107..2852aac 100644 --- a/src/print_ir.zig +++ b/src/print_ir.zig @@ -190,18 +190,32 @@ pub const Writer = struct { const options_slice = self.code.bodySlice(extra.end, extra.data.cases_len); for (options_slice) |option_index| { const case_extra = self.code.extraData(Ir.Inst.ChoiceBr.Case, @intFromEnum(option_index)); - const body_slice = self.code.bodySlice(case_extra.end, case_extra.data.body_len); + const lhs_slice = self.code.bodySlice( + case_extra.end, + case_extra.data.lhs_len, + ); + const mhs_slice = self.code.bodySlice( + case_extra.end + case_extra.data.lhs_len, + case_extra.data.mhs_len, + ); + const rhs_slice = self.code.bodySlice( + case_extra.end + case_extra.data.lhs_len + case_extra.data.mhs_len, + case_extra.data.rhs_len, + ); + const body_slice = self.code.bodySlice( + case_extra.end + case_extra.data.lhs_len + case_extra.data.mhs_len + case_extra.data.rhs_len, + case_extra.data.body_len, + ); self.pushIndent(); + try w.writeAll("start="); + try self.writeBodyInner(w, lhs_slice); + try w.writeAll(", choice_only="); + try self.writeBodyInner(w, mhs_slice); + try w.writeAll(", inner="); + try self.writeBodyInner(w, rhs_slice); - try self.writeIndent(w); - try self.writeInstRef(w, case_extra.data.operand_1); - try w.writeAll(", "); - try self.writeInstRef(w, case_extra.data.operand_2); - try w.writeAll(", "); - try self.writeInstRef(w, case_extra.data.operand_3); - - try w.writeAll(" = "); + try w.writeAll(", body="); try self.writeBodyInner(w, body_slice); try w.writeAll(",\n"); self.popIndent(); @@ -258,11 +272,15 @@ pub const Writer = struct { w: *std.Io.Writer, body_list: []const Ir.Inst.Index, ) Error!void { - try w.writeAll("{\n"); - self.pushIndent(); - for (body_list) |inst| try self.writeInst(w, inst); - self.popIndent(); - try self.writeIndent(w); + try w.writeAll("{"); + + if (body_list.len > 0) { + try w.writeAll("\n"); + self.pushIndent(); + for (body_list) |inst| try self.writeInst(w, inst); + self.popIndent(); + try self.writeIndent(w); + } try w.writeAll("}"); }