427 lines
12 KiB
Plaintext
427 lines
12 KiB
Plaintext
// 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; }
|
||
match Vec.get(&self.table, id) {
|
||
.Ok(ptr) => { ret ptr as *AttrNode; },
|
||
.Err(error) => { ret null; },
|
||
}
|
||
ret null;
|
||
}
|
||
|
||
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));
|
||
}
|
||
|
||
// ---- 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 == 7, "1 sentinel + 6 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();
|
||
}
|