diff --git a/src/AstGen.zig b/src/AstGen.zig index 576094c..0d42f8d 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -735,29 +735,103 @@ fn multiIfStmt( const branch = branch_list[0]; const body_node = branch.data.bin.rhs.?; try blockStmt(parent_block, scope, body_node); - return @enumFromInt(0); + return .none; } _ = try ifChain(parent_block, scope, branch_list); - return @enumFromInt(0); + return .none; +} + +fn switchStmt( + parent_block: *GenIr, + scope: *Scope, + stmt_node: *const Ast.Node, +) InnerError!Ir.Inst.Ref { + const astgen = parent_block.astgen; + const gpa = astgen.gpa; + const switch_stmt = stmt_node.data.switch_stmt; + + try validateSwitchProngs(parent_block, stmt_node); + + const cond_inst = try expr(parent_block, scope, switch_stmt.condition_expr); + const switch_br = try parent_block.makeBlockInst(.switch_br); + var case_indexes: std.ArrayListUnmanaged(u32) = .empty; + try case_indexes.ensureUnusedCapacity(gpa, switch_stmt.cases.len); + defer case_indexes.deinit(gpa); + + const switch_cases = switch_stmt.cases[0 .. switch_stmt.cases.len - 1]; + for (switch_cases) |case_stmt| { + // TODO: Maybe make this non-nullable + const case_expr = case_stmt.data.bin.lhs.?; + const operand: Ir.Inst.Ref = switch (case_expr.tag) { + .number_literal => try numberLiteral(parent_block, case_expr), + .true_literal => .bool_true, + .false_literal => .bool_false, + else => return parent_block.fail(.invalid_switch_case, case_stmt), + }; + var case_block = parent_block.makeSubBlock(); + defer case_block.unstack(); + _ = try blockStmt(&case_block, scope, case_stmt.data.bin.rhs.?); + _ = try case_block.addBreak(.@"break", switch_br); + + const body = case_block.instructionsSlice(); + const case_extra_len = @typeInfo(Ir.Inst.SwitchBr.Case).@"struct".fields.len + body.len; + try astgen.extra.ensureUnusedCapacity(gpa, case_extra_len); + + const extra_index = astgen.addExtraAssumeCapacity( + Ir.Inst.SwitchBr.Case{ + .operand = operand, + .body_len = @intCast(body.len), + }, + ); + astgen.appendBlockBody(body); + case_indexes.appendAssumeCapacity(extra_index); + } + + try parent_block.instructions.append(gpa, switch_br); + + const else_branch = switch_stmt.cases[switch_stmt.cases.len - 1]; + var case_block = parent_block.makeSubBlock(); + defer case_block.unstack(); + _ = try blockStmt(&case_block, scope, else_branch.data.bin.rhs.?); + _ = try case_block.addBreak(.@"break", switch_br); + + const else_body = case_block.instructionsSlice(); + const extra_len = + @typeInfo(Ir.Inst.SwitchBr).@"struct".fields.len + case_indexes.items.len + else_body.len; + try astgen.extra.ensureUnusedCapacity(gpa, extra_len); + + astgen.instructions.items[@intFromEnum(switch_br)].data.payload = .{ + .payload_index = astgen.addExtraAssumeCapacity( + Ir.Inst.SwitchBr{ + .operand = cond_inst, + .cases_len = @intCast(switch_cases.len), + .else_body_len = @intCast(else_body.len), + }, + ), + }; + astgen.extra.appendSliceAssumeCapacity(case_indexes.items[0..]); + astgen.appendBlockBody(else_body); + return switch_br.toRef(); } fn contentExpr(block: *GenIr, scope: *Scope, expr_node: *const Ast.Node) InnerError!Ir.Inst.Ref { // FIXME: This is a placeholder until we figure out what this function should be returning. - var last_inst: Ir.Inst.Ref = undefined; // TODO: Make sure that this is not nullable. const node_list = expr_node.data.list.items.?; for (node_list) |child_node| { - last_inst = switch (child_node.tag) { - .string_literal => try stringLiteral(block, child_node), - .inline_logic_expr => try inlineLogicExpr(block, scope, child_node), - .if_stmt => try ifStmt(block, scope, child_node), - .multi_if_stmt => try multiIfStmt(block, scope, child_node), - //.switch_stmt => try switchStmt(block, scope, child_node), + switch (child_node.tag) { + .string_literal => { + const result = try stringLiteral(block, child_node); + _ = try block.addUnaryNode(.content_push, result); + }, + .inline_logic_expr => _ = try inlineLogicExpr(block, scope, child_node), + .if_stmt => _ = try ifStmt(block, scope, child_node), + .multi_if_stmt => _ = try multiIfStmt(block, scope, child_node), + .switch_stmt => _ = try switchStmt(block, scope, child_node), else => unreachable, - }; - last_inst = try block.addUnaryNode(.content_push, last_inst); + } } - return last_inst; + return .none; } fn contentStmt(gen: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref { diff --git a/src/Ir.zig b/src/Ir.zig index 0698098..4c39e0c 100644 --- a/src/Ir.zig +++ b/src/Ir.zig @@ -72,6 +72,7 @@ pub const Inst = struct { block, condbr, @"break", + switch_br, content_push, content_flush, }; @@ -126,6 +127,17 @@ pub const Inst = struct { then_body_len: u32, else_body_len: u32, }; + + pub const SwitchBr = struct { + operand: Ref, + cases_len: u32, + else_body_len: u32, + + pub const Case = struct { + operand: Ref, + body_len: u32, + }; + }; }; pub const Global = struct { @@ -277,6 +289,43 @@ const Render = struct { try io_w.writeAll(")"); } + fn renderSwitchBr(r: *Render, ir: Ir, inst: Inst) Error!void { + const io_w = r.writer; + const switch_node = inst.data.payload; + const switch_extra = ir.extraData(Inst.SwitchBr, switch_node.payload_index); + const cases_slice = ir.bodySlice(switch_extra.end, switch_extra.data.cases_len); + + try io_w.print("{s}(", .{@tagName(inst.tag)}); + try renderInstRef(r, switch_extra.data.operand); + try io_w.print(", \n", .{}); + for (cases_slice) |case_index| { + const case_extra = ir.extraData(Inst.SwitchBr.Case, @intFromEnum(case_index)); + const body_slice = ir.bodySlice(case_extra.end, case_extra.data.body_len); + const old_len = try r.prefix.pushChildPrefix(r.gpa); + defer r.prefix.restore(old_len); + + try r.prefix.writeIndent(io_w); + try renderInstRef(r, case_extra.data.operand); + try io_w.print(" = ", .{}); + try renderBodyInner(r, ir, body_slice); + try io_w.writeAll(",\n"); + } + + const else_body = ir.bodySlice( + switch_extra.end + switch_extra.data.cases_len, + switch_extra.data.else_body_len, + ); + { + const old_len = try r.prefix.pushChildPrefix(r.gpa); + defer r.prefix.restore(old_len); + + try r.prefix.writeIndent(io_w); + try io_w.print("else = ", .{}); + try renderBodyInner(r, ir, else_body); + } + try io_w.writeAll(")"); + } + fn renderKnotDecl(r: *Render, ir: Ir, inst: Inst) Error!void { const io_w = r.writer; const extra = ir.extraData(Inst.Knot, inst.data.payload.payload_index); @@ -340,6 +389,7 @@ const Render = struct { }, .condbr => try r.renderCondbr(ir, inst), .@"break" => try r.renderBreak(ir, inst), + .switch_br => try r.renderSwitchBr(ir, inst), .alloc => try r.renderSimple(inst), .load => try r.renderUnary(inst), .store => try r.renderBinary(inst), @@ -366,7 +416,7 @@ const Render = struct { try io_w.print("{s}(\"{s}\")", .{ @tagName(inst.tag), str_bytes }); }, .content_push => try r.renderUnary(inst), - .content_flush => try r.renderSimple(inst), + .content_flush => try r.renderUnary(inst), } try io_w.writeAll("\n"); } diff --git a/src/Sema.zig b/src/Sema.zig index 6b51dec..4e8d1f0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -174,6 +174,59 @@ fn irBlock(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void { return blockBodyInner(sema, chunk, body); } +fn irSwitchBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void { + const data = sema.ir.instructions[@intFromEnum(inst)].data.payload; + const extra = sema.ir.extraData(Ir.Inst.SwitchBr, data.payload_index); + const cases_slice = sema.ir.bodySlice(extra.end, extra.data.cases_len); + + var case_labels: std.ArrayListUnmanaged(usize) = .empty; + try case_labels.ensureUnusedCapacity(sema.gpa, cases_slice.len + 1); + defer case_labels.deinit(sema.gpa); + + // TODO: Do something with this value? + //const condition = chunk.resolveInst(extra.data.operand); + const exit_label = try chunk.addLabel(); + + const cmp_var = chunk.knot.stack_size; + chunk.knot.stack_size += 1; + _ = try chunk.addConstOp(.store, @intCast(cmp_var)); + + for (cases_slice) |case_index| { + const case_extra = sema.ir.extraData(Ir.Inst.SwitchBr.Case, @intFromEnum(case_index)); + const case_expr = chunk.resolveInst(case_extra.data.operand); + const case_label_index = try chunk.addLabel(); + case_labels.appendAssumeCapacity(case_label_index); + + _ = try chunk.addConstOp(.load, @intCast(cmp_var)); + _ = try chunk.doLoad(case_expr); + _ = try chunk.addByteOp(.cmp_eq); + _ = try chunk.addFixup(.jmp_t, case_label_index); + _ = try chunk.addByteOp(.pop); + } + + const else_label = try chunk.addLabel(); + try chunk.addFixup(.jmp, else_label); + + for (cases_slice, case_labels.items) |case_index, label_index| { + const case_extra = sema.ir.extraData(Ir.Inst.SwitchBr.Case, @intFromEnum(case_index)); + const case_body = sema.ir.bodySlice(case_extra.end, case_extra.data.body_len); + + chunk.setLabel(label_index); + _ = try chunk.addByteOp(.pop); + try blockBodyInner(sema, chunk, case_body); + try chunk.addFixup(.jmp, exit_label); + } + + const else_body = sema.ir.bodySlice( + extra.end + extra.data.cases_len, + extra.data.else_body_len, + ); + + chunk.setLabel(else_label); + try blockBodyInner(sema, chunk, else_body); + chunk.setLabel(exit_label); +} + fn irContentPush(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref { const data = sema.ir.instructions[@intFromEnum(inst)].data.un; const lhs = chunk.resolveInst(data.lhs); @@ -264,6 +317,10 @@ fn blockBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) Inner }, .decl_var => unreachable, // handled in declaration() .decl_knot => unreachable, // handled in declaration() + .switch_br => { + try irSwitchBr(sema, chunk, inst); + continue; + }, .alloc => try irAlloc(sema, chunk, inst), .store => { try irStore(sema, chunk, inst); @@ -534,7 +591,7 @@ pub fn compile(gpa: std.mem.Allocator, ir: *const Ir) !CompiledStory { }; defer sema.deinit(); - try file(&sema, @enumFromInt(0)); + try file(&sema, .file_inst); return .{ .constants = try sema.constants.toOwnedSlice(gpa), .globals = try sema.globals.toOwnedSlice(gpa),