feat(zig): native Arboricx bundle parser and C ABI
This commit is contained in:
183
ext/zig/src/c_abi.zig
Normal file
183
ext/zig/src/c_abi.zig
Normal file
@@ -0,0 +1,183 @@
|
||||
const std = @import("std");
|
||||
const tree = @import("tree.zig");
|
||||
const Arena = @import("arena.zig").Arena;
|
||||
const reduce = @import("reduce.zig");
|
||||
const codecs = @import("codecs.zig");
|
||||
const kernel = @import("kernel.zig");
|
||||
const bundle = @import("bundle.zig");
|
||||
|
||||
/// Opaque handle for the C API. Layout is not exposed to C.
|
||||
/// Holds a persistent arena for user-built terms and the kernel.
|
||||
pub const ArbCtx = struct {
|
||||
gpa: std.mem.Allocator,
|
||||
arena: Arena,
|
||||
kernel_root: u32,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Context lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export fn arboricx_init() ?*ArbCtx {
|
||||
const ptr = std.heap.smp_allocator.create(ArbCtx) catch return null;
|
||||
ptr.gpa = std.heap.smp_allocator;
|
||||
ptr.arena = Arena.init(std.heap.smp_allocator);
|
||||
ptr.kernel_root = kernel.loadKernel(&ptr.arena) catch {
|
||||
ptr.arena.deinit();
|
||||
std.heap.smp_allocator.destroy(ptr);
|
||||
return null;
|
||||
};
|
||||
return ptr;
|
||||
}
|
||||
|
||||
export fn arboricx_free(ctx: *ArbCtx) void {
|
||||
ctx.arena.deinit();
|
||||
ctx.gpa.destroy(ctx);
|
||||
}
|
||||
|
||||
export fn arboricx_free_buf(_: *ArbCtx, ptr: [*]u8, len: usize) void {
|
||||
std.heap.smp_allocator.free(ptr[0..len]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tree construction (all write into the persistent arena)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export fn arb_leaf(ctx: *ArbCtx) u32 {
|
||||
return ctx.arena.alloc(.leaf) catch 0;
|
||||
}
|
||||
|
||||
export fn arb_stem(ctx: *ArbCtx, child: u32) u32 {
|
||||
return ctx.arena.alloc(.{ .stem = .{ .child = child } }) catch 0;
|
||||
}
|
||||
|
||||
export fn arb_fork(ctx: *ArbCtx, left: u32, right: u32) u32 {
|
||||
return ctx.arena.alloc(.{ .fork = .{ .left = left, .right = right } }) catch 0;
|
||||
}
|
||||
|
||||
export fn arb_app(ctx: *ArbCtx, func: u32, arg: u32) u32 {
|
||||
return ctx.arena.alloc(.{ .app = .{ .func = func, .arg = arg } }) catch 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Reduction
|
||||
// ---------------------------------------------------------------------------
|
||||
/// Reduces `root` in a *fresh* scratch arena so that garbage from previous
|
||||
/// reductions never accumulates. The kernel and term are deep-copied into
|
||||
/// the scratch arena, reduced there, and the result is copied back into the
|
||||
/// persistent arena.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export fn arb_reduce(ctx: *ArbCtx, root: u32, fuel: u64) u32 {
|
||||
// 1. Fresh scratch arena
|
||||
var scratch = Arena.init(ctx.gpa);
|
||||
defer scratch.deinit();
|
||||
|
||||
// 2. Deep-copy the term (which may reference kernel nodes) into scratch
|
||||
const scratch_root = tree.copyTree(ctx.arena.nodes.items, &scratch, root) catch return 0;
|
||||
|
||||
// 3. Reduce in scratch
|
||||
const scratch_result = reduce.reduce(scratch_root, &scratch, fuel) catch return 0;
|
||||
|
||||
// 4. Copy the result back to the persistent arena
|
||||
return tree.copyTree(scratch.nodes.items, &ctx.arena, scratch_result) catch 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Codec constructors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export fn arb_of_number(ctx: *ArbCtx, n: u64) u32 {
|
||||
return codecs.ofNumber(&ctx.arena, n) catch 0;
|
||||
}
|
||||
|
||||
export fn arb_of_string(ctx: *ArbCtx, s: [*:0]const u8) u32 {
|
||||
const slice = std.mem.sliceTo(s, 0);
|
||||
return codecs.ofString(&ctx.arena, slice) catch 0;
|
||||
}
|
||||
|
||||
export fn arb_of_bytes(ctx: *ArbCtx, bytes: [*]const u8, len: usize) u32 {
|
||||
return codecs.ofBytes(&ctx.arena, bytes[0..len]) catch 0;
|
||||
}
|
||||
|
||||
export fn arb_of_list(ctx: *ArbCtx, items: [*]const u32, len: usize) u32 {
|
||||
return codecs.ofList(&ctx.arena, items[0..len]) catch 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Codec destructors
|
||||
// Return 1 on success, 0 on failure.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export fn arb_to_number(ctx: *ArbCtx, root: u32, out: *u64) c_int {
|
||||
const n = codecs.toNumber(&ctx.arena, root) catch return 0;
|
||||
if (n == null) return 0;
|
||||
out.* = n.?;
|
||||
return 1;
|
||||
}
|
||||
|
||||
export fn arb_to_string(ctx: *ArbCtx, root: u32, out_ptr: **u8, out_len: *usize) c_int {
|
||||
const s = codecs.toString(&ctx.arena, root) catch return 0;
|
||||
if (s == null) return 0;
|
||||
out_ptr.* = @ptrCast(s.?.ptr);
|
||||
out_len.* = s.?.len;
|
||||
return 1;
|
||||
}
|
||||
|
||||
export fn arb_to_bytes(ctx: *ArbCtx, root: u32, out_ptr: **u8, out_len: *usize) c_int {
|
||||
return arb_to_string(ctx, root, out_ptr, out_len);
|
||||
}
|
||||
|
||||
export fn arb_to_bool(ctx: *ArbCtx, root: u32, out: *c_int) c_int {
|
||||
const b = codecs.toBool(&ctx.arena, root) catch return 0;
|
||||
if (b == null) return 0;
|
||||
out.* = if (b.?) 1 else 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Result unwrapping
|
||||
// Return 1 on success, 0 on failure.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export fn arb_unwrap_result(ctx: *ArbCtx, root: u32, out_ok: *c_int, out_value: *u32, out_rest: *u32) c_int {
|
||||
const r = codecs.unwrapResult(&ctx.arena, root) catch return 0;
|
||||
if (r == null) return 0;
|
||||
out_ok.* = if (r.?.ok) 1 else 0;
|
||||
out_value.* = r.?.value;
|
||||
out_rest.* = r.?.rest;
|
||||
return 1;
|
||||
}
|
||||
|
||||
export fn arb_unwrap_host_value(ctx: *ArbCtx, root: u32, out_tag: *u64, out_payload: *u32) c_int {
|
||||
const hv = codecs.unwrapHostValue(&ctx.arena, root) catch return 0;
|
||||
if (hv == null) return 0;
|
||||
out_tag.* = hv.?.tag;
|
||||
out_payload.* = hv.?.payload;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Kernel entrypoints
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export fn arb_kernel_root(ctx: *ArbCtx) u32 {
|
||||
return ctx.kernel_root;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Native bundle loading (fast path — bypasses the Tricu kernel)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Load a named export from an Arboricx bundle directly into the arena.
|
||||
/// Returns the arena index of the exported term, or 0 on error.
|
||||
export fn arb_load_bundle(ctx: *ArbCtx, bytes: [*]const u8, len: usize, name: [*:0]const u8) u32 {
|
||||
const name_slice = std.mem.sliceTo(name, 0);
|
||||
return bundle.loadBundleExport(&ctx.arena, bytes[0..len], name_slice) catch 0;
|
||||
}
|
||||
|
||||
/// Load the default root from an Arboricx bundle directly into the arena.
|
||||
/// Returns the arena index of the root term, or 0 on error.
|
||||
export fn arb_load_bundle_default(ctx: *ArbCtx, bytes: [*]const u8, len: usize) u32 {
|
||||
return bundle.loadBundleDefaultRoot(&ctx.arena, bytes[0..len]) catch 0;
|
||||
}
|
||||
Reference in New Issue
Block a user