feat: code generation for simple choice statements, testing machinery

This commit is contained in:
Brett Broadhurst 2026-03-16 17:33:31 -06:00
parent fac5a968e3
commit ee26be6254
Failed to generate hash of commit
13 changed files with 304 additions and 70 deletions

View file

@ -758,6 +758,7 @@ fn switchStmt(
try case_indexes.ensureUnusedCapacity(gpa, switch_stmt.cases.len);
defer case_indexes.deinit(gpa);
// TODO: Length checks.
const switch_cases = switch_stmt.cases[0 .. switch_stmt.cases.len - 1];
for (switch_cases) |case_stmt| {
// TODO: Maybe make this non-nullable
@ -855,78 +856,77 @@ fn assignStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void
return gi.fail(.unknown_identifier, identifier_node);
}
fn choiceStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void {
const Choice = struct {
label_index: usize,
start_expression: ?*const Ast.Node,
option_expression: ?*const Ast.Node,
inner_expression: ?*const Ast.Node,
block_stmt: ?*const Ast.Node,
};
fn choiceStarStmt(gi: *GenIr, _: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
return stringLiteral(gi, node);
}
const branch_list = stmt_node.data.list.items orelse unreachable;
assert(branch_list.len != 0);
fn choiceStmt(
parent_block: *GenIr,
scope: *Scope,
stmt_node: *const Ast.Node,
) InnerError!void {
const astgen = parent_block.astgen;
const gpa = astgen.gpa;
const choice_branches = stmt_node.data.list.items.?;
assert(choice_branches.len != 0);
const gpa = gen.astgen.gpa;
var choice_list: std.ArrayListUnmanaged(Choice) = .empty;
defer choice_list.deinit(gpa);
try choice_list.ensureUnusedCapacity(gpa, branch_list.len);
const choice_br = try parent_block.makeBlockInst(.choice_br);
var case_indexes: std.ArrayListUnmanaged(u32) = .empty;
try case_indexes.ensureUnusedCapacity(gpa, choice_branches.len);
defer case_indexes.deinit(gpa);
for (branch_list) |branch_stmt| {
for (choice_branches) |branch_stmt| {
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 orelse unreachable;
const branch_expr_data = branch_expr.data.choice_expr;
const label_index = try gen.makeLabel();
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_data.start_expr) |node| {
try stringLiteral(gen, node);
try gen.emitSimpleInst(.stream_push);
if (branch_expr.start_expr) |node| {
op_1 = try choiceStarStmt(parent_block, scope, node);
}
if (branch_expr_data.option_expr) |node| {
try stringLiteral(gen, node);
try gen.emitSimpleInst(.stream_push);
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);
}
const fixup_offset = try gen.emitJumpInst(.br_push);
_ = try gen.makeFixup(.{
.mode = .absolute,
.label_index = label_index,
.code_offset = fixup_offset,
});
var sub_block = parent_block.makeSubBlock();
defer sub_block.unstack();
if (branch_data.rhs) |branch_body| {
_ = try blockStmt(&sub_block, scope, branch_body);
}
_ = try sub_block.addUnaryNode(.implicit_ret, .none);
choice_list.appendAssumeCapacity(.{
.label_index = label_index,
.start_expression = branch_expr_data.start_expr,
.inner_expression = branch_expr_data.inner_expr,
.option_expression = branch_expr_data.option_expr,
.block_stmt = branch_data.rhs,
});
const body = sub_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.ChoiceBr.Case{
.operand_1 = op_1,
.operand_2 = op_2,
.operand_3 = op_3,
.body_len = @intCast(body.len),
},
);
astgen.appendBlockBody(body);
case_indexes.appendAssumeCapacity(extra_index);
}
try gen.emitSimpleInst(.br_table);
try gen.emitSimpleInst(.br_select_index);
try gen.emitSimpleInst(.br_dispatch);
try parent_block.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);
for (choice_list.items) |choice| {
gen.setLabel(choice.label_index);
if (choice.start_expression) |expr_node| {
try stringLiteral(gen, expr_node);
try gen.emitSimpleInst(.stream_push);
}
if (choice.inner_expression) |expr_node| {
try stringLiteral(gen, expr_node);
try gen.emitSimpleInst(.stream_push);
}
try gen.emitSimpleInst(.stream_flush);
if (choice.block_stmt) |block| {
try blockStmt(gen, scope, block);
} else {
try gen.emitSimpleInst(.exit);
}
}
astgen.instructions.items[@intFromEnum(choice_br)].data.payload = .{
.payload_index = astgen.addExtraAssumeCapacity(
Ir.Inst.ChoiceBr{
.cases_len = @intCast(choice_branches.len),
},
),
};
astgen.extra.appendSliceAssumeCapacity(case_indexes.items[0..]);
}
fn tempDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
@ -982,7 +982,7 @@ fn blockInner(gi: *GenIr, parent_scope: *Scope, stmt_list: []*Ast.Node) !void {
.temp_decl => try tempDecl(gi, &child_scope, inner_node),
.assign_stmt => try assignStmt(gi, &child_scope, inner_node),
.content_stmt => try contentStmt(gi, &child_scope, inner_node),
//.choice_stmt => try choiceStmt(gen, scope, inner_node),
.choice_stmt => try choiceStmt(gi, &child_scope, inner_node),
.expr_stmt => try exprStmt(gi, &child_scope, inner_node),
else => unreachable,
};