Files
zig/astgen_test.zig
Motiejus Jakštys 5fb7a1ab9c Add astgen scaffolding with ZIR data structures and first passing test
Introduce zir.h/zir.c with ZIR instruction types (269 tags, 56 extended
opcodes, 8-byte Data union) ported from lib/std/zig/Zir.zig, and
astgen.h/astgen.c implementing the empty-container fast path that produces
correct ZIR for empty source files.

The test infrastructure in astgen_test.zig compares C astGen() output
field-by-field against Zig's std.zig.AstGen.generate() using tag-based
dispatch, avoiding raw byte comparison since Zig's Data union has no
guaranteed in-memory layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 18:37:07 +00:00

123 lines
3.9 KiB
Zig

const std = @import("std");
const Ast = std.zig.Ast;
const Zir = std.zig.Zir;
const AstGen = std.zig.AstGen;
const Allocator = std.mem.Allocator;
const c = @cImport({
@cInclude("astgen.h");
});
test "astgen: empty source" {
const gpa = std.testing.allocator;
const source: [:0]const u8 = "";
// Reference: parse and generate ZIR with Zig.
var tree = try Ast.parse(gpa, source, .zig);
defer tree.deinit(gpa);
var ref_zir = try AstGen.generate(gpa, tree);
defer ref_zir.deinit(gpa);
// Test: parse and generate ZIR with C.
var c_ast = c.astParse(source.ptr, @intCast(source.len));
defer c.astDeinit(&c_ast);
var c_zir = c.astGen(&c_ast);
defer c.zirDeinit(&c_zir);
try expectEqualZir(ref_zir, c_zir);
}
fn expectEqualZir(ref: Zir, got: c.Zir) !void {
// Compare instruction count.
const ref_len: u32 = @intCast(ref.instructions.len);
try std.testing.expectEqual(ref_len, got.inst_len);
// Compare instructions (tag + data) field-by-field.
const ref_tags = ref.instructions.items(.tag);
const ref_datas = ref.instructions.items(.data);
for (0..ref_len) |i| {
const ref_tag: u8 = @intFromEnum(ref_tags[i]);
const got_tag: u8 = @intCast(got.inst_tags[i]);
if (ref_tag != got_tag) {
std.debug.print(
"inst_tags[{d}] mismatch: ref={d} got={d}\n",
.{ i, ref_tag, got_tag },
);
return error.TestExpectedEqual;
}
try expectEqualData(i, ref_tags[i], ref_datas[i], got.inst_datas[i]);
}
// Compare extra data.
const ref_extra_len: u32 = @intCast(ref.extra.len);
try std.testing.expectEqual(ref_extra_len, got.extra_len);
for (0..ref_extra_len) |i| {
if (ref.extra[i] != got.extra[i]) {
std.debug.print(
"extra[{d}] mismatch: ref=0x{x:0>8} got=0x{x:0>8}\n",
.{ i, ref.extra[i], got.extra[i] },
);
return error.TestExpectedEqual;
}
}
// Compare string bytes.
const ref_sb_len: u32 = @intCast(ref.string_bytes.len);
try std.testing.expectEqual(ref_sb_len, got.string_bytes_len);
for (0..ref_sb_len) |i| {
if (ref.string_bytes[i] != got.string_bytes[i]) {
std.debug.print(
"string_bytes[{d}] mismatch: ref=0x{x:0>2} got=0x{x:0>2}\n",
.{ i, ref.string_bytes[i], got.string_bytes[i] },
);
return error.TestExpectedEqual;
}
}
}
/// Compare a single instruction's data, dispatching by tag.
/// Zig's Data union has no guaranteed in-memory layout, so we
/// compare each variant's fields individually.
fn expectEqualData(
idx: usize,
tag: Zir.Inst.Tag,
ref: Zir.Inst.Data,
got: c.ZirInstData,
) !void {
switch (tag) {
.extended => {
const r = ref.extended;
const g = got.extended;
if (@intFromEnum(r.opcode) != g.opcode or
r.small != g.small or
r.operand != g.operand)
{
std.debug.print(
"inst_datas[{d}] (extended) mismatch:\n" ++
" ref: opcode={d} small=0x{x:0>4} operand={d}\n" ++
" got: opcode={d} small=0x{x:0>4} operand={d}\n",
.{
idx,
@intFromEnum(r.opcode),
r.small,
r.operand,
g.opcode,
g.small,
g.operand,
},
);
return error.TestExpectedEqual;
}
},
// Add more tag handlers as AstGen implementation grows.
else => {
std.debug.print(
"inst_datas[{d}]: unhandled tag {d} in comparison\n",
.{ idx, @intFromEnum(tag) },
);
return error.TestUnexpectedResult;
},
}
}