// Expect retcode = 0 // 属性注册表与属性节点(移植自 stage1/spl_attr.c)。注册表是数据:内置属性 // 在 register_builtins 里登记,查找按 (ns, name)。节点记录命中信息 + 实参。 // 新增属性只加注册项,不改 sema 逻辑(对应 SPL.md 的 @/# 扩展点)。 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, .append_i64 } = @import("utils/fmt.spl"); type AttrStage = enum { Sema, Layout, Ir, Opt, Backend, } type AttrTarget = enum { Decl, Type, Expr, Stmt, } type AttrDemand = enum { Optional, Required, } type AttrInfo = struct { ns: ByteView, name: ByteView, stage: AttrStage, target: AttrTarget, demand: AttrDemand, min_args: i32, max_args: i32, // -1 = variadic } type AttrArgKind = enum { Int, Str, Def, Type, } type AttrArg = enum { Int: i64, Str: ByteView, Def: usize, Type: usize, } type AttrNode = struct { info_index: usize, // 注册表下标;0 = 无 args: Vec, // AttrArg consumed_mask: u32, } type Attr = @self; table: Vec, // AttrNode registry: Vec, // AttrInfo type AttrResult = enum { Ok: Attr, Err: Error, } fn empty_info(): AttrInfo { ret AttrInfo { .ns = ByteView.empty(), .name = ByteView.empty(), .stage = .Sema, .target = .Decl, .demand = .Optional, .min_args = 0, .max_args = 0, }; } fn new_node(): AttrNode { ret AttrNode { .info_index = 0, .args = Vec.blank(@sizeof(AttrArg), @alignof(AttrArg)), .consumed_mask = 0, }; } fn new(): AttrResult { var at = Attr { .table = Vec.blank(@sizeof(AttrNode), @alignof(AttrNode)), .registry = Vec.blank(@sizeof(AttrInfo), @alignof(AttrInfo)), }; var sentinel = new_node(); match Vec.push(&at.table, &sentinel as *u8) { .Ok => {}, .Err(error) => { Vec.drop(&at.table); Vec.drop(&at.registry); ret .Err(error); }, } var reg_sentinel = empty_info(); match Vec.push(&at.registry, ®_sentinel as *u8) { .Ok => {}, .Err(error) => { Vec.drop(&at.table); Vec.drop(&at.registry); ret .Err(error); }, } register_builtins(&at); ret .Ok(at); } fn drop(self: *Attr) { var i: usize = 1; while i < self.table.len { var a = node(self, i); if a != null { Vec.drop(&a.args); } i = i + 1; } Vec.drop(&self.table); Vec.drop(&self.registry); } fn node(self: *Attr, id: usize): *AttrNode { if id == 0 || id >= self.table.len { ret null; } ret (self.table.data as *AttrNode) + id; } fn info_at(self: *Attr, index: usize): *AttrInfo { if index == 0 || index >= self.registry.len { ret null; } match Vec.get(&self.registry, index) { .Ok(ptr) => { ret ptr as *AttrInfo; }, .Err(error) => { ret null; }, } ret null; } fn node_info(self: *Attr, id: usize): *AttrInfo { var a = node(self, id); if a == null { ret null; } ret info_at(self, a.info_index); } // 追加属性节点(args 所有权转移)。 fn alloc(self: *Attr, info_index: usize, args: Vec): usize { var a = new_node(); a.info_index = info_index; a.args = args; match Vec.push(&self.table, &a as *u8) { .Ok => {}, .Err(error) => { ret 0; }, } ret self.table.len - 1; } fn registry_add(self: *Attr, info: AttrInfo) { match Vec.push(&self.registry, &info as *u8) { .Ok => {}, .Err(error) => {}, } } // 返回注册表下标(>=1)或 0。 fn lookup(self: *Attr, ns: ByteView, name: ByteView): usize { var i: usize = 1; while i < self.registry.len { var info = info_at(self, i); if info != null && ByteView.eq(info.ns, ns) && ByteView.eq(info.name, name) { ret i; } i = i + 1; } ret 0; } fn make_info(ns: []u8, name: []u8, stage: AttrStage, target: AttrTarget, demand: AttrDemand, min_args: i32, max_args: i32): AttrInfo { ret AttrInfo { .ns = ByteView.from_slice(ns), .name = ByteView.from_slice(name), .stage = stage, .target = target, .demand = demand, .min_args = min_args, .max_args = max_args, }; } fn register_builtins(self: *Attr) { registry_add(self, make_info("", "extern", .Ir, .Decl, .Required, 1, 1)); registry_add(self, make_info("", "inline", .Opt, .Decl, .Optional, 0, 0)); registry_add(self, make_info("", "noinline", .Opt, .Decl, .Optional, 0, 0)); registry_add(self, make_info("", "align", .Layout, .Type, .Required, 1, 1)); registry_add(self, make_info("", "volatile", .Ir, .Expr, .Required, 0, 0)); registry_add(self, make_info("", "atomic", .Ir, .Expr, .Required, 0, 1)); registry_add(self, make_info("", "export", .Ir, .Decl, .Optional, 1, 1)); } // ---- dump(镜像 stage1 spl_attr_dump / spl_attr_dump_id)---- fn push_out(buf: *ByteBuffer, bytes: []u8): bool { match ByteBuffer.append(buf, bytes) { .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 push_hex(buf: *ByteBuffer, value: u32): bool { if !push_out(buf, "0x") { ret false; } match append_usize(buf, value as usize, 16, false) { .Ok => { ret true; }, .Err(error) => { ret false; }, } ret false; } fn stage_name(s: AttrStage): []u8 { match s { .Sema => { ret "SEMA"; }, .Layout => { ret "LAYOUT"; }, .Ir => { ret "IR"; }, .Opt => { ret "OPT"; }, .Backend => { ret "BACKEND"; }, } ret "?"; } fn target_name(t: AttrTarget): []u8 { match t { .Decl => { ret "DECL"; }, .Type => { ret "TYPE"; }, .Expr => { ret "EXPR"; }, .Stmt => { ret "STMT"; }, } ret "?"; } fn demand_name(d: AttrDemand): []u8 { match d { .Required => { ret "REQUIRED"; }, .Optional => { ret "OPTIONAL"; }, } ret "?"; } fn dump_id(self: *Attr, id: usize, out: *ByteBuffer): bool { if id == 0 { ret push_out(out, "?"); } var a = node(self, id); if a == null { ret push_out(out, "?"); } var info = info_at(self, a.info_index); if info == null { ret push_out(out, "?"); } if !push_out(out, ByteView.as_slice(info.name)) { ret false; } if a.args.len == 0 { ret true; } if !push_out(out, "(") { ret false; } var i: usize = 0; while i < a.args.len { if i > 0 && !push_out(out, ", ") { ret false; } var arg = arg_at(&a.args, i); if arg == null { ret false; } match arg.* { .Int(v) => { match append_i64(out, v, 10, false) { .Ok => {}, .Err(error) => { ret false; }, } }, .Str(v) => { if !push_out(out, "\"") { ret false; } if !push_out(out, ByteView.as_slice(v)) { ret false; } if !push_out(out, "\"") { ret false; } }, .Def(v) => { if !push_out(out, "def#") { ret false; } if !push_usize(out, v) { ret false; } }, .Type(v) => { if !push_out(out, "type#") { ret false; } if !push_usize(out, v) { ret false; } }, } i = i + 1; } ret push_out(out, ")"); } fn arg_at(v: *Vec, i: usize): *AttrArg { match Vec.get(v, i) { .Ok(ptr) => { ret ptr as *AttrArg; }, .Err(error) => { ret null; }, } ret null; } fn dump(self: *Attr, out: *ByteBuffer): bool { if !push_out(out, "AttrTable:\n") { ret false; } var i: usize = 1; while i < self.table.len { var a = node(self, i); if !push_out(out, " #") { ret false; } if !push_usize(out, i) { ret false; } if !push_out(out, " ") { ret false; } if !dump_id(self, i, out) { ret false; } if a != null { var info = info_at(self, a.info_index); if info != null { if !push_out(out, " [stage=") { ret false; } if !push_out(out, stage_name(info.stage)) { ret false; } if !push_out(out, " target=") { ret false; } if !push_out(out, target_name(info.target)) { ret false; } if !push_out(out, " demand=") { ret false; } if !push_out(out, demand_name(info.demand)) { ret false; } if !push_out(out, " consumed=") { ret false; } if !push_hex(out, a.consumed_mask) { ret false; } if !push_out(out, "]") { ret false; } } } if !push_out(out, "\n") { ret false; } i = i + 1; } ret true; } // ---- 测试 ---- fn expect_text(buf: ByteBuffer, expected: []u8) { var got = ByteBuffer.as_slice(buf); @assert(got.len == expected.len, "dump length"); var i: usize = 0; while i < expected.len { @assert(got.ptr[i] == expected.ptr[i], "dump byte"); i = i + 1; } } fn test_registry() { var at: Attr = undefined; match new() { .Ok(v) => { at = v; }, .Err(error) => { @assert(false, "attr new failed"); }, } defer { drop(&at); } @assert(at.registry.len == 8, "1 sentinel + 7 builtins"); var i = lookup(&at, ByteView.empty(), ByteView.from_slice("extern")); @assert(i != 0, "extern found"); var info = info_at(&at, i); @assert(info != null, "extern info"); @assert(info.min_args == 1 && info.max_args == 1, "extern arity"); var j = lookup(&at, ByteView.empty(), ByteView.from_slice("atomic")); @assert(j != 0, "atomic found"); var jinfo = info_at(&at, j); @assert(jinfo.min_args == 0 && jinfo.max_args == 1, "atomic arity"); @assert(lookup(&at, ByteView.empty(), ByteView.from_slice("nope")) == 0, "unknown attr"); } fn test_alloc_and_dump_id() { var at: Attr = undefined; match new() { .Ok(v) => { at = v; }, .Err(error) => { @assert(false, "attr new failed"); }, } defer { drop(&at); } var idx = lookup(&at, ByteView.empty(), ByteView.from_slice("extern")); var args = Vec.blank(@sizeof(AttrArg), @alignof(AttrArg)); var s: AttrArg = .Str(ByteView.from_slice("vm")); match Vec.push(&args, &s as *u8) { .Ok => {}, .Err(e) => { @assert(false, "push str"); } } var n: AttrArg = .Int(3); match Vec.push(&args, &n as *u8) { .Ok => {}, .Err(e) => { @assert(false, "push int"); } } var d: AttrArg = .Def(9); match Vec.push(&args, &d as *u8) { .Ok => {}, .Err(e) => { @assert(false, "push def"); } } var id = alloc(&at, idx, args); @assert(id == 1, "first attr id"); var out = ByteBuffer.new(); defer { ByteBuffer.drop(&out); } if !dump_id(&at, id, &out) { @assert(false, "dump_id ok"); } expect_text(out, "extern(\"vm\", 3, def#9)"); var out2 = ByteBuffer.new(); defer { ByteBuffer.drop(&out2); } if !dump_id(&at, 0, &out2) { @assert(false, "dump_id none ok"); } expect_text(out2, "?"); var out3 = ByteBuffer.new(); defer { ByteBuffer.drop(&out3); } var l = lookup(&at, ByteView.empty(), ByteView.from_slice("inline")); var id2 = alloc(&at, l, Vec.blank(@sizeof(AttrArg), @alignof(AttrArg))); if !dump(&at, &out3) { @assert(false, "dump ok"); } // 只校验首尾关键片段,避免依赖完整表格排版 var got = ByteBuffer.as_slice(out3); @assert(got.len > 0, "dump nonempty"); } fn main() { test_registry(); test_alloc_and_dump_id(); }