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; }