/** * lib.js — FFI wrapper around libarboricx.so via koffi. * * Exports low-level C ABI bindings and high-level helpers. */ import { existsSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import koffi from 'koffi'; const __dirname = dirname(fileURLToPath(import.meta.url)); koffi.opaque('arb_ctx_t'); // ── Library discovery ─────────────────────────────────────────────────────── export function findLib() { const env = process.env.ARBORICX_LIB; if (env) { if (existsSync(env)) return env; throw new Error(`ARBORICX_LIB set but file not found: ${env}`); } const candidates = [ resolve(__dirname, 'libarboricx.so'), 'libarboricx.so', './libarboricx.so', '/usr/local/lib/libarboricx.so', '/usr/lib/libarboricx.so', ]; for (const p of candidates) { if (existsSync(p)) return p; } throw new Error('libarboricx.so not found. Set ARBORICX_LIB to its full path.'); } // ── FFI setup ─────────────────────────────────────────────────────────────── let _lib = null; let _libPath = null; function ensureLib() { if (_lib) return _lib; const path = findLib(); _lib = koffi.load(path); _libPath = path; return _lib; } export function loadLib(path) { if (_lib && _libPath === path) return; _lib = koffi.load(path); _libPath = path; } function getLib() { if (_lib) return _lib; return ensureLib(); } // ── Context lifecycle ─────────────────────────────────────────────────────── export function init(libPath) { if (libPath) loadLib(libPath); const lib = getLib(); const ctx = lib.func('arb_ctx_t *arboricx_init(void)')(); if (!ctx) throw new Error('arboricx_init failed'); return ctx; } export function free(ctx) { getLib().func('void arboricx_free(arb_ctx_t *ctx)')(ctx); } // ── Bundle loading ────────────────────────────────────────────────────────── export function loadBundle(ctx, bytes, name) { const result = getLib().func('uint32_t arb_load_bundle(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len, const char *name)')(ctx, bytes, bytes.length, name); if (result === 0) throw new Error(`arb_load_bundle failed for export "${name}"`); return result; } export function loadBundleDefault(ctx, bytes) { const result = getLib().func('uint32_t arb_load_bundle_default(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len)')(ctx, bytes, bytes.length); if (result === 0) throw new Error('arb_load_bundle_default failed'); return result; } // ── Reduction ─────────────────────────────────────────────────────────────── export function reduce(ctx, root, fuel = 1_000_000_000n) { const f = getLib().func('uint32_t arb_reduce(arb_ctx_t *ctx, uint32_t root, uint64_t fuel)'); return f(ctx, root, typeof fuel === 'bigint' ? fuel : BigInt(fuel)); } // ── Tree construction ─────────────────────────────────────────────────────── export function leaf(ctx) { return getLib().func('uint32_t arb_leaf(arb_ctx_t *ctx)')(ctx); } export function stem(ctx, child) { return getLib().func('uint32_t arb_stem(arb_ctx_t *ctx, uint32_t child)')(ctx, child); } export function fork(ctx, left, right) { return getLib().func('uint32_t arb_fork(arb_ctx_t *ctx, uint32_t left, uint32_t right)')(ctx, left, right); } export function app(ctx, func, arg) { return getLib().func('uint32_t arb_app(arb_ctx_t *ctx, uint32_t func, uint32_t arg)')(ctx, func, arg); } // ── Codec constructors ────────────────────────────────────────────────────── export function ofNumber(ctx, n) { const big = typeof n === 'bigint' ? n : BigInt(n); return getLib().func('uint32_t arb_of_number(arb_ctx_t *ctx, uint64_t n)')(ctx, big); } export function ofString(ctx, s) { return getLib().func('uint32_t arb_of_string(arb_ctx_t *ctx, const char *s)')(ctx, s); } export function ofBytes(ctx, bytes) { return getLib().func('uint32_t arb_of_bytes(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len)')(ctx, bytes, bytes.length); } export function ofList(ctx, items) { const arr = new Uint32Array(items); return getLib().func('uint32_t arb_of_list(arb_ctx_t *ctx, _In_ uint32_t *items, size_t len)')(ctx, arr, arr.length); } // ── Codec destructors ─────────────────────────────────────────────────────── export function toNumber(ctx, root) { const out = [0]; const ok = getLib().func('int arb_to_number(arb_ctx_t *ctx, uint32_t root, _Out_ uint64_t *out)')(ctx, root, out); if (!ok) throw new Error('arb_to_number failed'); return typeof out[0] === 'bigint' ? Number(out[0]) : out[0]; } export function toString(ctx, root) { const ptrOut = [null]; const lenOut = [0]; const ok = getLib().func('int arb_to_string(arb_ctx_t *ctx, uint32_t root, _Out_ uint8_t **out_ptr, _Out_ size_t *out_len)')(ctx, root, ptrOut, lenOut); if (!ok) throw new Error('arb_to_string failed'); const bytes = koffi.decode(ptrOut[0], 'uint8_t', lenOut[0]); const str = Buffer.from(bytes).toString('utf-8'); getLib().func('void arboricx_free_buf(arb_ctx_t *ctx, uint8_t *ptr, size_t len)')(ctx, ptrOut[0], lenOut[0]); return str; } export function toBytes(ctx, root) { const ptrOut = [null]; const lenOut = [0]; const ok = getLib().func('int arb_to_bytes(arb_ctx_t *ctx, uint32_t root, _Out_ uint8_t **out_ptr, _Out_ size_t *out_len)')(ctx, root, ptrOut, lenOut); if (!ok) throw new Error('arb_to_bytes failed'); const bytes = Buffer.from(koffi.decode(ptrOut[0], 'uint8_t', lenOut[0])); getLib().func('void arboricx_free_buf(arb_ctx_t *ctx, uint8_t *ptr, size_t len)')(ctx, ptrOut[0], lenOut[0]); return bytes; } export function toBool(ctx, root) { const out = [0]; const ok = getLib().func('int arb_to_bool(arb_ctx_t *ctx, uint32_t root, _Out_ int *out)')(ctx, root, out); if (!ok) throw new Error('arb_to_bool failed'); return out[0] !== 0; } // ── Result unwrapping ─────────────────────────────────────────────────────── export function unwrapResult(ctx, root) { const outOk = [0]; const outValue = [0]; const outRest = [0]; const ok = getLib().func('int arb_unwrap_result(arb_ctx_t *ctx, uint32_t root, _Out_ int *out_ok, _Out_ uint32_t *out_value, _Out_ uint32_t *out_rest)')(ctx, root, outOk, outValue, outRest); if (!ok) throw new Error('arb_unwrap_result failed'); return { ok: outOk[0] !== 0, value: outValue[0], rest: outRest[0] }; } export function unwrapHostValue(ctx, root) { const outTag = [0n]; const outPayload = [0]; const ok = getLib().func('int arb_unwrap_host_value(arb_ctx_t *ctx, uint32_t root, _Out_ uint64_t *out_tag, _Out_ uint32_t *out_payload)')(ctx, root, outTag, outPayload); if (!ok) throw new Error('arb_unwrap_host_value failed'); return { tag: outTag[0], payload: outPayload[0] }; } // ── Kernel ────────────────────────────────────────────────────────────────── export function kernelRoot(ctx) { return getLib().func('uint32_t arb_kernel_root(arb_ctx_t *ctx)')(ctx); } // ── High-level helpers ────────────────────────────────────────────────────── export function decode(ctx, root) { try { return toBool(ctx, root) ? 'true' : 'false'; } catch { try { return toString(ctx, root); } catch { try { return String(toNumber(ctx, root)); } catch { throw new Error('could not decode result'); } } } } export function decodeType(ctx, root) { try { toBool(ctx, root); return 'bool'; } catch {} try { toString(ctx, root); return 'string'; } catch {} try { toNumber(ctx, root); return 'number'; } catch {} return 'unknown (raw tree)'; }