tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

lib.js (8846B)


      1 /**
      2  * lib.js — FFI wrapper around libarboricx.so via koffi.
      3  *
      4  * Exports low-level C ABI bindings and high-level helpers.
      5  */
      6 
      7 import { existsSync } from 'node:fs';
      8 import { dirname, join, resolve } from 'node:path';
      9 import { fileURLToPath } from 'node:url';
     10 import koffi from 'koffi';
     11 
     12 const __dirname = dirname(fileURLToPath(import.meta.url));
     13 
     14 koffi.opaque('arb_ctx_t');
     15 
     16 // ── Library discovery ───────────────────────────────────────────────────────
     17 
     18 export function findLib() {
     19   const env = process.env.ARBORICX_LIB;
     20   if (env) {
     21     if (existsSync(env)) return env;
     22     throw new Error(`ARBORICX_LIB set but file not found: ${env}`);
     23   }
     24 
     25   const candidates = [
     26     resolve(__dirname, 'libarboricx.so'),
     27     'libarboricx.so',
     28     './libarboricx.so',
     29     '/usr/local/lib/libarboricx.so',
     30     '/usr/lib/libarboricx.so',
     31   ];
     32 
     33   for (const p of candidates) {
     34     if (existsSync(p)) return p;
     35   }
     36 
     37   throw new Error('libarboricx.so not found. Set ARBORICX_LIB to its full path.');
     38 }
     39 
     40 // ── FFI setup ───────────────────────────────────────────────────────────────
     41 
     42 let _lib = null;
     43 let _libPath = null;
     44 
     45 function ensureLib() {
     46   if (_lib) return _lib;
     47   const path = findLib();
     48   _lib = koffi.load(path);
     49   _libPath = path;
     50   return _lib;
     51 }
     52 
     53 export function loadLib(path) {
     54   if (_lib && _libPath === path) return;
     55   _lib = koffi.load(path);
     56   _libPath = path;
     57 }
     58 
     59 function getLib() {
     60   if (_lib) return _lib;
     61   return ensureLib();
     62 }
     63 
     64 // ── Context lifecycle ───────────────────────────────────────────────────────
     65 
     66 export function init(libPath) {
     67   if (libPath) loadLib(libPath);
     68   const lib = getLib();
     69   const ctx = lib.func('arb_ctx_t *arboricx_init(void)')();
     70   if (!ctx) throw new Error('arboricx_init failed');
     71   return ctx;
     72 }
     73 
     74 export function free(ctx) {
     75   getLib().func('void arboricx_free(arb_ctx_t *ctx)')(ctx);
     76 }
     77 
     78 // ── Bundle loading ──────────────────────────────────────────────────────────
     79 
     80 export function loadBundle(ctx, bytes, name) {
     81   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);
     82   if (result === 0) throw new Error(`arb_load_bundle failed for export "${name}"`);
     83   return result;
     84 }
     85 
     86 export function loadBundleDefault(ctx, bytes) {
     87   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);
     88   if (result === 0) throw new Error('arb_load_bundle_default failed');
     89   return result;
     90 }
     91 
     92 // ── Reduction ───────────────────────────────────────────────────────────────
     93 
     94 export function reduce(ctx, root, fuel = 1_000_000_000n) {
     95   const f = getLib().func('uint32_t arb_reduce(arb_ctx_t *ctx, uint32_t root, uint64_t fuel)');
     96   return f(ctx, root, typeof fuel === 'bigint' ? fuel : BigInt(fuel));
     97 }
     98 
     99 // ── Tree construction ───────────────────────────────────────────────────────
    100 
    101 export function leaf(ctx) {
    102   return getLib().func('uint32_t arb_leaf(arb_ctx_t *ctx)')(ctx);
    103 }
    104 
    105 export function stem(ctx, child) {
    106   return getLib().func('uint32_t arb_stem(arb_ctx_t *ctx, uint32_t child)')(ctx, child);
    107 }
    108 
    109 export function fork(ctx, left, right) {
    110   return getLib().func('uint32_t arb_fork(arb_ctx_t *ctx, uint32_t left, uint32_t right)')(ctx, left, right);
    111 }
    112 
    113 export function app(ctx, func, arg) {
    114   return getLib().func('uint32_t arb_app(arb_ctx_t *ctx, uint32_t func, uint32_t arg)')(ctx, func, arg);
    115 }
    116 
    117 // ── Codec constructors ──────────────────────────────────────────────────────
    118 
    119 export function ofNumber(ctx, n) {
    120   const big = typeof n === 'bigint' ? n : BigInt(n);
    121   return getLib().func('uint32_t arb_of_number(arb_ctx_t *ctx, uint64_t n)')(ctx, big);
    122 }
    123 
    124 export function ofString(ctx, s) {
    125   return getLib().func('uint32_t arb_of_string(arb_ctx_t *ctx, const char *s)')(ctx, s);
    126 }
    127 
    128 export function ofBytes(ctx, bytes) {
    129   return getLib().func('uint32_t arb_of_bytes(arb_ctx_t *ctx, _In_ uint8_t *bytes, size_t len)')(ctx, bytes, bytes.length);
    130 }
    131 
    132 export function ofList(ctx, items) {
    133   const arr = new Uint32Array(items);
    134   return getLib().func('uint32_t arb_of_list(arb_ctx_t *ctx, _In_ uint32_t *items, size_t len)')(ctx, arr, arr.length);
    135 }
    136 
    137 // ── Codec destructors ───────────────────────────────────────────────────────
    138 
    139 export function toNumber(ctx, root) {
    140   const out = [0];
    141   const ok = getLib().func('int arb_to_number(arb_ctx_t *ctx, uint32_t root, _Out_ uint64_t *out)')(ctx, root, out);
    142   if (!ok) throw new Error('arb_to_number failed');
    143   return typeof out[0] === 'bigint' ? Number(out[0]) : out[0];
    144 }
    145 
    146 export function toString(ctx, root) {
    147   const ptrOut = [null];
    148   const lenOut = [0];
    149   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);
    150   if (!ok) throw new Error('arb_to_string failed');
    151 
    152   const bytes = koffi.decode(ptrOut[0], 'uint8_t', lenOut[0]);
    153   const str = Buffer.from(bytes).toString('utf-8');
    154   getLib().func('void arboricx_free_buf(arb_ctx_t *ctx, uint8_t *ptr, size_t len)')(ctx, ptrOut[0], lenOut[0]);
    155   return str;
    156 }
    157 
    158 export function toBytes(ctx, root) {
    159   const ptrOut = [null];
    160   const lenOut = [0];
    161   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);
    162   if (!ok) throw new Error('arb_to_bytes failed');
    163 
    164   const bytes = Buffer.from(koffi.decode(ptrOut[0], 'uint8_t', lenOut[0]));
    165   getLib().func('void arboricx_free_buf(arb_ctx_t *ctx, uint8_t *ptr, size_t len)')(ctx, ptrOut[0], lenOut[0]);
    166   return bytes;
    167 }
    168 
    169 export function toBool(ctx, root) {
    170   const out = [0];
    171   const ok = getLib().func('int arb_to_bool(arb_ctx_t *ctx, uint32_t root, _Out_ int *out)')(ctx, root, out);
    172   if (!ok) throw new Error('arb_to_bool failed');
    173   return out[0] !== 0;
    174 }
    175 
    176 // ── Result unwrapping ───────────────────────────────────────────────────────
    177 
    178 export function unwrapResult(ctx, root) {
    179   const outOk = [0];
    180   const outValue = [0];
    181   const outRest = [0];
    182   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);
    183   if (!ok) throw new Error('arb_unwrap_result failed');
    184   return { ok: outOk[0] !== 0, value: outValue[0], rest: outRest[0] };
    185 }
    186 
    187 export function unwrapHostValue(ctx, root) {
    188   const outTag = [0n];
    189   const outPayload = [0];
    190   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);
    191   if (!ok) throw new Error('arb_unwrap_host_value failed');
    192   return { tag: outTag[0], payload: outPayload[0] };
    193 }
    194 
    195 // ── Kernel ──────────────────────────────────────────────────────────────────
    196 
    197 export function kernelRoot(ctx) {
    198   return getLib().func('uint32_t arb_kernel_root(arb_ctx_t *ctx)')(ctx);
    199 }
    200 
    201 // ── High-level helpers ──────────────────────────────────────────────────────
    202 
    203 export function decode(ctx, root) {
    204   try {
    205     return toBool(ctx, root) ? 'true' : 'false';
    206   } catch {
    207     try {
    208       return toString(ctx, root);
    209     } catch {
    210       try {
    211         return String(toNumber(ctx, root));
    212       } catch {
    213         throw new Error('could not decode result');
    214       }
    215     }
    216   }
    217 }
    218 
    219 export function decodeType(ctx, root) {
    220   try { toBool(ctx, root); return 'bool'; } catch {}
    221   try { toString(ctx, root); return 'string'; } catch {}
    222   try { toNumber(ctx, root); return 'number'; } catch {}
    223   return 'unknown (raw tree)';
    224 }