253 lines
9.1 KiB
Zig
253 lines
9.1 KiB
Zig
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");
|
|
const io_driver = @import("io_driver.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;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tree inspection (Layer 1 — for custom IO drivers and non-POSIX hosts)
|
|
// All return 1 on success / true, 0 on failure / false.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export fn arb_is_leaf(ctx: *ArbCtx, root: u32) c_int {
|
|
if (root >= ctx.arena.len()) return 0;
|
|
return if (ctx.arena.nodes.items[root] == .leaf) 1 else 0;
|
|
}
|
|
|
|
export fn arb_is_stem(ctx: *ArbCtx, root: u32) c_int {
|
|
if (root >= ctx.arena.len()) return 0;
|
|
return if (ctx.arena.nodes.items[root] == .stem) 1 else 0;
|
|
}
|
|
|
|
export fn arb_is_fork(ctx: *ArbCtx, root: u32) c_int {
|
|
if (root >= ctx.arena.len()) return 0;
|
|
return if (ctx.arena.nodes.items[root] == .fork) 1 else 0;
|
|
}
|
|
|
|
export fn arb_is_app(ctx: *ArbCtx, root: u32) c_int {
|
|
if (root >= ctx.arena.len()) return 0;
|
|
return if (ctx.arena.nodes.items[root] == .app) 1 else 0;
|
|
}
|
|
|
|
export fn arb_get_stem_child(ctx: *ArbCtx, root: u32, out: *u32) c_int {
|
|
if (root >= ctx.arena.len()) return 0;
|
|
const node = ctx.arena.nodes.items[root];
|
|
if (node != .stem) return 0;
|
|
out.* = node.stem.child;
|
|
return 1;
|
|
}
|
|
|
|
export fn arb_get_fork_children(ctx: *ArbCtx, root: u32, out_left: *u32, out_right: *u32) c_int {
|
|
if (root >= ctx.arena.len()) return 0;
|
|
const node = ctx.arena.nodes.items[root];
|
|
if (node != .fork) return 0;
|
|
out_left.* = node.fork.left;
|
|
out_right.* = node.fork.right;
|
|
return 1;
|
|
}
|
|
|
|
export fn arb_get_app_func_arg(ctx: *ArbCtx, root: u32, out_func: *u32, out_arg: *u32) c_int {
|
|
if (root >= ctx.arena.len()) return 0;
|
|
const node = ctx.arena.nodes.items[root];
|
|
if (node != .app) return 0;
|
|
out_func.* = node.app.func;
|
|
out_arg.* = node.app.arg;
|
|
return 1;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// IO driver (Layer 2 — POSIX interaction-tree runtime)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
pub const arb_io_perms_t = extern struct {
|
|
allow_read_all: c_int,
|
|
allow_write_all: c_int,
|
|
};
|
|
|
|
export fn arb_run_io(ctx: *ArbCtx, program: u32, perms: ?*const arb_io_perms_t) u32 {
|
|
const zig_perms = if (perms) |p| io_driver.IOPerms{
|
|
.allow_read_all = p.allow_read_all != 0,
|
|
.allow_write_all = p.allow_write_all != 0,
|
|
} else io_driver.IOPerms{};
|
|
return io_driver.runIO(ctx.gpa, &ctx.arena, program, zig_perms) catch 0;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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;
|
|
}
|