Files
spl/stage2/ast.spl
T

643 lines
24 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Expect retcode = 0
// AST:用 ADT(enum 带载荷)表达节点,节点引用是 arena 序号 usize。
// stage1 的 C 结构只是「手写 union 模拟 ADT」;这里直接用 SPL 的 enum 载荷,
// 不照抄 union 布局,只要求最终 dump / 语义与 stage1 一致。
// 索引 0 为 NONE 哨兵(与 stage1 一致)。
type .{ .Error } = @import("utils/error.spl");
type .{ .Vec } = @import("utils/vec.spl");
type .{ .ByteView } = @import("utils/byte_view.spl");
type .{ .ByteBuffer } = @import("utils/byte_buffer.spl");
type .{ .append_usize } = @import("utils/fmt.spl");
type .{ .Punct } = @import("token.spl");
type BigInt = @import("utils/mp.spl");
type .{ .Span, .span_none, .span_make, .span_is_none } = @import("./diag.spl");
type .{ .Sources, .Source, .set_at } = @import("utils/source.spl");
// 算子不另立 enum:AST 直接存词法 Punct。一元/二元由节点 kind 区分
// (一元:Sub=负 / Not=! / BitNot=~ / And=& / Mul=.*;二元即对应 Punct)。
// ---- 节点体(ADT;variant 名 = stage1 的 kind 名;载荷用内联匿名 struct)----
type Body = enum {
None,
Root: struct { members: usize },
ContainerMembers: struct { external_tags: Vec, declarations: Vec },
ExternalTagItem: struct { is_at: bool, is_external: bool, name: ByteView, args: Vec },
TagItem: struct { is_at: bool, is_external: bool, name: ByteView, args: Vec },
ComptimeDecl: struct { body: usize },
TopLevelBlock: struct { tag_list: Vec, block: usize },
FnDecl: struct {
tag_list: Vec,
is_comptime: bool,
name: ByteView,
params: Vec,
ret_type: usize,
body: usize,
},
VarDecl: struct { attr_list: Vec, pattern: usize, type_expr: usize, init_expr: usize },
ConstDecl: struct { attr_list: Vec, pattern: usize, type_expr: usize, init_expr: usize },
TypeDecl: struct { attr_list: Vec, pattern: usize, type_expr: usize },
MemberDecl: struct {
attr_list: Vec,
name: ByteView,
type_expr: usize,
init_expr: usize,
},
ParamDecl: struct { attr_list: Vec, name: ByteView, type_expr: usize },
StatementRet: struct { expr: usize },
StatementBreak: struct { label: usize, expr: usize },
StatementContinue: struct { label: usize },
StatementDefer: struct { body: usize },
StatementComptime: struct { body: usize },
StatementDecl: struct { decl: usize },
StatementExpr: struct { expr: usize },
AggregateMember: struct {
attr_list: Vec,
name: ByteView,
type_expr: usize,
init_expr: usize,
},
StructInitItem: struct { name: ByteView, expr: usize },
MatchArm: struct { pattern: usize, body: usize },
Label: struct { name: ByteView },
ExprIntLit: struct { value: BigInt }, // 任意宽度(mp),解析后由 Ast 拥有
ExprFloatLit: struct { text: ByteView },
ExprCharLit: struct { value: u8 },
ExprStringLit: struct { data: ByteView },
ExprBoolLit: struct { value: bool },
ExprNullLit,
ExprUndefinedLit,
ExprIdent: struct { name: ByteView },
ExprUnary: struct { op: Punct, operand: usize },
ExprBinary: struct { op: Punct, left: usize, right: usize },
ExprCall: struct { callee: usize, args: Vec },
ExprField: struct { obj: usize, name: ByteView },
ExprIndex: struct { obj: usize, index: usize },
ExprSlice: struct { obj: usize, begin: usize, end: usize },
ExprCast: struct { expr: usize, type_expr: usize },
ExprTuple: struct { items: Vec },
ExprArrayLit: struct {
has_len: bool,
len: usize,
is_underscore_len: bool,
elem_type: usize,
elems: Vec,
},
ExprAggregateLit: struct { name: ByteView, fields: Vec },
ExprComptimeCall: struct { name: ByteView, args: Vec },
ExprBlock: struct { stmts: Vec, tail_expr: usize },
ExprIf: struct { cond: usize, then_block: usize, else_branch: usize },
ExprIfvar: struct { var_decl: usize, then_block: usize, else_branch: usize },
ExprWhile: struct { label: usize, cond: usize, body: usize, else_branch: usize },
ExprLoop: struct { label: usize, body: usize },
ExprFor: struct {
label: usize,
iter_exprs: Vec,
pattern: usize,
body: usize,
else_branch: usize,
},
ExprMatch: struct { matched_expr: usize, arms: Vec },
ExprFnType: struct { is_comptime: bool, params: Vec, ret_type: usize },
TypePath: struct { name: ByteView, generic_args: Vec },
TypePointer: struct { pointee: usize },
TypeArray: struct {
has_len: bool,
len: isize,
is_underscore_len: bool,
elem_type: usize,
},
TypeSlice: struct { elem_type: usize },
TypeFn: struct { is_comptime: bool, params: Vec, ret_type: usize },
TypeParen: struct { types: Vec },
TypeAggregate: struct { kind_name: ByteView, members: Vec },
TypeComptimeCall: struct { name: ByteView, args: Vec },
PatternIdent: struct { name: ByteView },
PatternUnderscore,
PatternDot: struct { name: ByteView, sub_pattern: usize },
PatternTuple: struct { items: Vec },
PatternList: struct { items: Vec },
PatternAggregate: struct { items: Vec },
PatternField: struct { name: ByteView, pattern: usize },
}
fn kind_name(body: Body): []u8 {
match body {
.None => { ret "none"; },
.Root(_) => { ret "root"; },
.ContainerMembers(_) => { ret "container_members"; },
.ExternalTagItem(_) => { ret "external_tag_item"; },
.TagItem(_) => { ret "tag_item"; },
.ComptimeDecl(_) => { ret "comptime_decl"; },
.TopLevelBlock(_) => { ret "top_level_block"; },
.FnDecl(_) => { ret "fn_decl"; },
.VarDecl(_) => { ret "var_decl"; },
.ConstDecl(_) => { ret "const_decl"; },
.TypeDecl(_) => { ret "type_decl"; },
.MemberDecl(_) => { ret "member_decl"; },
.ParamDecl(_) => { ret "param_decl"; },
.StatementRet(_) => { ret "statement_ret"; },
.StatementBreak(_) => { ret "statement_break"; },
.StatementContinue(_) => { ret "statement_continue"; },
.StatementDefer(_) => { ret "statement_defer"; },
.StatementComptime(_) => { ret "statement_comptime"; },
.StatementDecl(_) => { ret "statement_decl"; },
.StatementExpr(_) => { ret "statement_expr"; },
.AggregateMember(_) => { ret "aggregate_member"; },
.StructInitItem(_) => { ret "struct_init_item"; },
.MatchArm(_) => { ret "match_arm"; },
.Label(_) => { ret "label"; },
.ExprIntLit(_) => { ret "int_lit"; },
.ExprFloatLit(_) => { ret "float_lit"; },
.ExprCharLit(_) => { ret "char_lit"; },
.ExprStringLit(_) => { ret "string_lit"; },
.ExprBoolLit(_) => { ret "bool_lit"; },
.ExprNullLit => { ret "null_lit"; },
.ExprUndefinedLit => { ret "undefined_lit"; },
.ExprIdent(_) => { ret "ident"; },
.ExprUnary(_) => { ret "unary"; },
.ExprBinary(_) => { ret "binary"; },
.ExprCall(_) => { ret "call"; },
.ExprField(_) => { ret "field"; },
.ExprIndex(_) => { ret "index"; },
.ExprSlice(_) => { ret "slice"; },
.ExprCast(_) => { ret "cast"; },
.ExprTuple(_) => { ret "tuple"; },
.ExprArrayLit(_) => { ret "array_literal"; },
.ExprAggregateLit(_) => { ret "aggregate_literal"; },
.ExprComptimeCall(_) => { ret "comptime_call"; },
.ExprBlock(_) => { ret "block"; },
.ExprIf(_) => { ret "if_expr"; },
.ExprIfvar(_) => { ret "ifvar_expr"; },
.ExprWhile(_) => { ret "while_expr"; },
.ExprLoop(_) => { ret "loop_expr"; },
.ExprFor(_) => { ret "for_expr"; },
.ExprMatch(_) => { ret "match_expr"; },
.ExprFnType(_) => { ret "fn_type"; },
.TypePath(_) => { ret "type_path"; },
.TypePointer(_) => { ret "type_pointer"; },
.TypeArray(_) => { ret "type_array"; },
.TypeSlice(_) => { ret "type_slice"; },
.TypeFn(_) => { ret "type_fn"; },
.TypeParen(_) => { ret "type_paren"; },
.TypeAggregate(_) => { ret "type_aggregate"; },
.TypeComptimeCall(_) => { ret "type_comptime_call"; },
.PatternIdent(_) => { ret "pattern_ident"; },
.PatternUnderscore => { ret "pattern_underscore"; },
.PatternDot(_) => { ret "pattern_dot"; },
.PatternTuple(_) => { ret "pattern_tuple"; },
.PatternList(_) => { ret "pattern_list"; },
.PatternAggregate(_) => { ret "pattern_aggregate"; },
.PatternField(_) => { ret "pattern_field"; },
}
ret "?";
}
type Node = struct {
span: Span,
resolved_def_id: usize,
resolved_lang_tid: usize,
expected_lang_tid: usize,
attrs: Vec,
body: Body,
}
type Ast = @self;
nodes: Vec, // Node
root: usize,
sources: *Sources, // 借用;可为 null(dump 时不解析行列)
type AstResult = enum {
Ok: Ast,
Err: Error,
}
fn new_node(body: Body, span: Span): Node {
ret Node {
.span = span,
.resolved_def_id = 0,
.resolved_lang_tid = 0,
.expected_lang_tid = 0,
.attrs = Vec.blank(@sizeof(usize), @alignof(usize)),
.body = body,
};
}
fn new(sources: *Sources): AstResult {
var ast = Ast {
.nodes = Vec.blank(@sizeof(Node), @alignof(Node)),
.root = 0,
.sources = sources,
};
var sentinel = new_node(.None, span_none());
match Vec.push(&ast.nodes, &sentinel as *u8) {
.Ok => {},
.Err(error) => {
Vec.drop(&ast.nodes);
ret .Err(error);
},
}
ret .Ok(ast);
}
fn drop(self: *Ast) {
var i: usize = 0;
while i < self.nodes.len {
var n = node(self, i);
if n != null {
Vec.drop(&n.attrs);
match n.body {
.ExprIntLit(x) => { BigInt.drop(&x.value); },
_ => {},
}
}
i = i + 1;
}
Vec.drop(&self.nodes);
self.root = 0;
}
fn node(self: *Ast, ref: usize): *Node {
match Vec.get(&self.nodes, ref) {
.Ok(ptr) => { ret ptr as *Node; },
.Err(error) => { ret null; },
}
ret null;
}
// 追加节点,返回其引用(失败返回 0)。
fn alloc(self: *Ast, body: Body, span: Span): usize {
var n = new_node(body, span);
match Vec.push(&self.nodes, &n as *u8) {
.Ok => {},
.Err(error) => {
Vec.drop(&n.attrs);
ret 0;
},
}
ret self.nodes.len - 1;
}
fn count(self: *Ast): usize {
ret self.nodes.len;
}
fn ref_vec(): Vec {
ret Vec.blank(@sizeof(usize), @alignof(usize));
}
// ---- dump(镜像 stage1 spl_ast_dump)----
//
// AST:
// <indent>`-/|-kind #ref [line:col]
// indent:栈上每层 last 为真输出 " ",否则 "| "。
fn push(buf: *ByteBuffer, bytes: []u8): bool {
match ByteBuffer.append(buf, bytes) {
.Ok => { ret true; },
.Err(error) => { ret false; },
}
ret false;
}
fn push_byte(buf: *ByteBuffer, byte: u8): bool {
match ByteBuffer.append_byte(buf, byte) {
.Ok => { ret true; },
.Err(error) => { ret false; },
}
ret false;
}
fn push_usize(buf: *ByteBuffer, value: usize): bool {
match append_usize(buf, value, 10, false) {
.Ok => { ret true; },
.Err(error) => { ret false; },
}
ret false;
}
fn ref_at(list: Vec, index: usize): usize {
match Vec.get(&list, index) {
.Ok(ptr) => { ret (ptr as *usize).*; },
.Err(error) => { ret 0; },
}
ret 0;
}
fn dump_opt(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool): bool {
if ref == 0 { ret true; }
ret dump_node(ast, buf, stack, ref, last);
}
fn dump_list(ast: *Ast, buf: *ByteBuffer, stack: *Vec, list: Vec): bool {
var i: usize = 0;
while i < list.len {
var last = (i + 1 == list.len);
if !dump_node(ast, buf, stack, ref_at(list, i), last) { ret false; }
i = i + 1;
}
ret true;
}
fn dump_node(ast: *Ast, buf: *ByteBuffer, stack: *Vec, ref: usize, last: bool): bool {
var n = node(ast, ref);
if n == null { ret false; }
var i: usize = 0;
while i < stack.len {
var flag: u8 = 0;
match Vec.get(stack, i) {
.Ok(ptr) => { flag = ptr.*; },
.Err(error) => { ret false; },
}
if !push(buf, if flag != 0 { " " } else { "| " }) { ret false; }
i = i + 1;
}
if !push(buf, if last { "`-" } else { "|-" }) { ret false; }
if !push(buf, kind_name(n.body)) { ret false; }
if !push(buf, " #") { ret false; }
if !push_usize(buf, ref) { ret false; }
if !span_is_none(n.span) && ast.sources != null {
var src = set_at(ast.sources, n.span.file);
if src != null {
var pos = Source.line_col(src.*, n.span.start);
if !push(buf, " [") { ret false; }
if !push_usize(buf, pos.line) { ret false; }
if !push_byte(buf, ':') { ret false; }
if !push_usize(buf, pos.col) { ret false; }
if !push_byte(buf, ']') { ret false; }
}
}
if !push_byte(buf, '\n') { ret false; }
var l: u8 = 0;
if last { l = 1; }
match Vec.push(stack, &l as *u8) {
.Ok => {},
.Err(error) => { ret false; },
}
var ok = true;
match n.body {
.None => {},
.Root(r) => { ok = dump_opt(ast, buf, stack, r.members, true); },
.ContainerMembers(c) => {
ok = dump_list(ast, buf, stack, c.external_tags) &&
dump_list(ast, buf, stack, c.declarations);
},
.ExternalTagItem(t) => { ok = dump_list(ast, buf, stack, t.args); },
.TagItem(t) => { ok = dump_list(ast, buf, stack, t.args); },
.ComptimeDecl(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
.TopLevelBlock(t) => {
ok = dump_list(ast, buf, stack, t.tag_list) &&
dump_opt(ast, buf, stack, t.block, true);
},
.FnDecl(f) => {
ok = dump_list(ast, buf, stack, f.tag_list) &&
dump_list(ast, buf, stack, f.params) &&
dump_opt(ast, buf, stack, f.ret_type, false) &&
dump_opt(ast, buf, stack, f.body, true);
},
.VarDecl(d) => {
ok = dump_list(ast, buf, stack, d.attr_list) &&
dump_opt(ast, buf, stack, d.pattern, false) &&
dump_opt(ast, buf, stack, d.type_expr, false) &&
dump_opt(ast, buf, stack, d.init_expr, true);
},
.ConstDecl(d) => {
ok = dump_list(ast, buf, stack, d.attr_list) &&
dump_opt(ast, buf, stack, d.pattern, false) &&
dump_opt(ast, buf, stack, d.type_expr, false) &&
dump_opt(ast, buf, stack, d.init_expr, true);
},
.TypeDecl(d) => {
ok = dump_list(ast, buf, stack, d.attr_list) &&
dump_opt(ast, buf, stack, d.pattern, false) &&
dump_opt(ast, buf, stack, d.type_expr, true);
},
.MemberDecl(d) => {
ok = dump_list(ast, buf, stack, d.attr_list) &&
dump_opt(ast, buf, stack, d.type_expr, false) &&
dump_opt(ast, buf, stack, d.init_expr, true);
},
.ParamDecl(p) => {
ok = dump_list(ast, buf, stack, p.attr_list) &&
dump_opt(ast, buf, stack, p.type_expr, true);
},
.StatementRet(e) => { ok = dump_opt(ast, buf, stack, e.expr, true); },
.StatementBreak(b) => {
ok = dump_opt(ast, buf, stack, b.label, false) &&
dump_opt(ast, buf, stack, b.expr, true);
},
.StatementContinue(x) => { ok = dump_opt(ast, buf, stack, x.label, true); },
.StatementDefer(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
.StatementComptime(x) => { ok = dump_opt(ast, buf, stack, x.body, true); },
.StatementDecl(d) => { ok = dump_opt(ast, buf, stack, d.decl, true); },
.StatementExpr(e) => { ok = dump_opt(ast, buf, stack, e.expr, true); },
.AggregateMember(d) => {
ok = dump_list(ast, buf, stack, d.attr_list) &&
dump_opt(ast, buf, stack, d.type_expr, false) &&
dump_opt(ast, buf, stack, d.init_expr, true);
},
.StructInitItem(s) => { ok = dump_opt(ast, buf, stack, s.expr, true); },
.MatchArm(m) => {
ok = dump_opt(ast, buf, stack, m.pattern, false) &&
dump_opt(ast, buf, stack, m.body, true);
},
.Label(x) => {},
.ExprIntLit(x) => {},
.ExprFloatLit(x) => {},
.ExprCharLit(x) => {},
.ExprStringLit(x) => {},
.ExprBoolLit(x) => {},
.ExprNullLit => {},
.ExprUndefinedLit => {},
.ExprIdent(x) => {},
.ExprUnary(u) => { ok = dump_opt(ast, buf, stack, u.operand, true); },
.ExprBinary(b) => {
ok = dump_opt(ast, buf, stack, b.left, false) &&
dump_opt(ast, buf, stack, b.right, true);
},
.ExprCall(c) => {
ok = dump_opt(ast, buf, stack, c.callee, false) &&
dump_list(ast, buf, stack, c.args);
},
.ExprField(f) => { ok = dump_opt(ast, buf, stack, f.obj, true); },
.ExprIndex(x) => {
ok = dump_opt(ast, buf, stack, x.obj, false) &&
dump_opt(ast, buf, stack, x.index, true);
},
.ExprSlice(s) => {
ok = dump_opt(ast, buf, stack, s.obj, false) &&
dump_opt(ast, buf, stack, s.begin, false) &&
dump_opt(ast, buf, stack, s.end, true);
},
.ExprCast(c) => {
ok = dump_opt(ast, buf, stack, c.expr, false) &&
dump_opt(ast, buf, stack, c.type_expr, true);
},
.ExprTuple(v) => { ok = dump_list(ast, buf, stack, v.items); },
.ExprArrayLit(a) => {
ok = dump_opt(ast, buf, stack, a.elem_type, false) &&
dump_list(ast, buf, stack, a.elems);
},
.ExprAggregateLit(a) => { ok = dump_list(ast, buf, stack, a.fields); },
.ExprComptimeCall(c) => { ok = dump_list(ast, buf, stack, c.args); },
.ExprBlock(b) => {
ok = dump_list(ast, buf, stack, b.stmts) &&
dump_opt(ast, buf, stack, b.tail_expr, true);
},
.ExprIf(x) => {
ok = dump_opt(ast, buf, stack, x.cond, false) &&
dump_opt(ast, buf, stack, x.then_block, false) &&
dump_opt(ast, buf, stack, x.else_branch, true);
},
.ExprIfvar(x) => {
ok = dump_opt(ast, buf, stack, x.var_decl, false) &&
dump_opt(ast, buf, stack, x.then_block, false) &&
dump_opt(ast, buf, stack, x.else_branch, true);
},
.ExprWhile(w) => {
ok = dump_opt(ast, buf, stack, w.label, false) &&
dump_opt(ast, buf, stack, w.cond, false) &&
dump_opt(ast, buf, stack, w.body, false) &&
dump_opt(ast, buf, stack, w.else_branch, true);
},
.ExprLoop(x) => {
ok = dump_opt(ast, buf, stack, x.label, false) &&
dump_opt(ast, buf, stack, x.body, true);
},
.ExprFor(f) => {
ok = dump_opt(ast, buf, stack, f.label, false) &&
dump_list(ast, buf, stack, f.iter_exprs) &&
dump_opt(ast, buf, stack, f.pattern, false) &&
dump_opt(ast, buf, stack, f.body, false) &&
dump_opt(ast, buf, stack, f.else_branch, true);
},
.ExprMatch(m) => {
ok = dump_opt(ast, buf, stack, m.matched_expr, false) &&
dump_list(ast, buf, stack, m.arms);
},
.ExprFnType(f) => {
ok = dump_list(ast, buf, stack, f.params) &&
dump_opt(ast, buf, stack, f.ret_type, true);
},
.TypePath(p) => {},
.TypePointer(p) => { ok = dump_opt(ast, buf, stack, p.pointee, true); },
.TypeArray(a) => { ok = dump_opt(ast, buf, stack, a.elem_type, true); },
.TypeSlice(s) => { ok = dump_opt(ast, buf, stack, s.elem_type, true); },
.TypeFn(f) => {
ok = dump_list(ast, buf, stack, f.params) &&
dump_opt(ast, buf, stack, f.ret_type, true);
},
.TypeParen(p) => { ok = dump_list(ast, buf, stack, p.types); },
.TypeAggregate(a) => { ok = dump_list(ast, buf, stack, a.members); },
.TypeComptimeCall(c) => { ok = dump_list(ast, buf, stack, c.args); },
.PatternIdent(x) => {},
.PatternUnderscore => {},
.PatternDot(p) => { ok = dump_opt(ast, buf, stack, p.sub_pattern, true); },
.PatternTuple(v) => { ok = dump_list(ast, buf, stack, v.items); },
.PatternList(v) => { ok = dump_list(ast, buf, stack, v.items); },
.PatternAggregate(v) => { ok = dump_list(ast, buf, stack, v.items); },
.PatternField(p) => { ok = dump_opt(ast, buf, stack, p.pattern, true); },
}
stack.len = stack.len - 1;
ret ok;
}
// 把整棵树 dump 到 out(追加,不 clear)。
fn dump(ast: *Ast, out: *ByteBuffer): bool {
if !push(out, "AST:\n") { ret false; }
var stack = Vec.blank(1, 1);
defer { Vec.drop(&stack); }
ret dump_node(ast, out, &stack, ast.root, true);
}
// ---- 测试 ----
fn test_kind_names() {
@assert(ByteView.eq(ByteView.from_slice(kind_name(.None)), ByteView.from_slice("none")),
"none name");
@assert(ByteView.eq(ByteView.from_slice(kind_name(.ExprBinary(.{
.op = .Add,
.left = 0,
.right = 0,
}))),
ByteView.from_slice("binary")),
"binary name");
}
fn test_alloc_and_payload() {
var ast: Ast = undefined;
match new(null) {
.Ok(v) => { ast = v; },
.Err(error) => { @assert(false, "ast new failed"); },
}
defer { drop(&ast); }
@assert(count(&ast) == 1, "sentinel only");
var r = alloc(&ast, .ExprIdent(.{ .name = ByteView.from_slice("main") }), span_make(0, 5, 9));
@assert(r == 1, "first alloc is 1");
var n = node(&ast, r);
n.resolved_def_id = 99;
@assert(n.span.file == 0 && n.span.start == 5 && n.span.end == 9, "span stored");
@assert(n.resolved_def_id == 99, "header field");
match n.body {
.ExprIdent(i) => {
@assert(ByteView.eq(i.name, ByteView.from_slice("main")), "payload name");
},
_ => { @assert(false, "expected ident"); },
}
}
fn expect_bytes(buf: *ByteBuffer, expected: []u8) {
var actual = ByteBuffer.as_slice(buf.*);
@assert(actual.len == expected.len, "dump length");
var i: usize = 0;
while i < expected.len {
@assert(actual.ptr[i] == expected.ptr[i], "dump byte");
i = i + 1;
}
}
fn test_dump() {
var ast: Ast = undefined;
match new(null) {
.Ok(v) => { ast = v; },
.Err(error) => { @assert(false, "ast new failed"); },
}
defer { drop(&ast); }
var id_ref = alloc(&ast, .ExprIdent(.{ .name = ByteView.from_slice("main") }), span_none()); // #1
var decls = ref_vec();
match Vec.push(&decls, &id_ref as *u8) {
.Ok => {},
.Err(error) => { @assert(false, "push decl"); },
}
var cm_ref = alloc(&ast, .ContainerMembers(.{
.external_tags = ref_vec(),
.declarations = decls,
}), span_none()); // #2
var root_ref = alloc(&ast, .Root(.{ .members = cm_ref }), span_none()); // #3
ast.root = root_ref;
var buf = ByteBuffer.new();
defer { ByteBuffer.drop(&buf); }
@assert(dump(&ast, &buf), "dump ok");
// sources=null → 不解析行列;无 span 时也不打印。
expect_bytes(&buf, "AST:\n`-root #3\n `-container_members #2\n `-ident #1\n");
}
fn main() {
test_kind_names();
test_alloc_and_payload();
test_dump();
}