tricu

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

module-bundle.ts (4338B)


      1 // Explicit publication boundary: snapshots observe finite constructor trees.
      2 // Canonical indexing preserves DAG sharing.
      3 import { BUNDLE_LIMIT, decodeBundle, type Bundle } from './bundle.js';
      4 import { snapshotBundle } from './tree-result.js';
      5 import type { GraphPreview } from './graph.js';
      6 const encoder = new TextEncoder();
      7 const join = (parts: Uint8Array[]): Uint8Array => {
      8   const length = parts.reduce((n, p) => n + p.length, 0);
      9   if (length > BUNDLE_LIMIT) throw new Error('Module bundle exceeds 16 MiB');
     10   const out = new Uint8Array(length); let at = 0;
     11   for (const part of parts) { out.set(part, at); at += part.length; } return out;
     12 };
     13 function word(n: number, size = 4): Uint8Array {
     14   const bytes = new Uint8Array(size), view = new DataView(bytes.buffer);
     15   if (size === 8) view.setBigUint64(0, BigInt(n));
     16   else if (size === 2) view.setUint16(0, n); else view.setUint32(0, n);
     17   return bytes;
     18 }
     19 const string = (s: string): Uint8Array => { const bytes = encoder.encode(s); return join([word(bytes.length), bytes]); };
     20 export function encodeModule(bundle: Bundle): Uint8Array {
     21   if (!bundle.exports.length || bundle.exports.length > 4096) throw new Error('Invalid module export count');
     22   const mapped = new Map<number, number>(), interned = new Map<string, number>(), records: number[][] = [];
     23   for (const entry of bundle.exports) {
     24     if (!/^[A-Za-z_][A-Za-z0-9_?$%'.-]*$/u.test(entry.name) || encoder.encode(entry.name).length > 512) throw new Error('Invalid module export name');
     25     const stack: [number, boolean][] = [[entry.root, false]];
     26     while (stack.length) {
     27       const [id, leave] = stack.pop()!; if (mapped.has(id)) continue;
     28       const node = bundle.nodes[id]; if (!node) throw new Error('Missing module node');
     29       const children = node.kind === 'leaf' ? [] : node.kind === 'stem' ? [node.child] : [node.left, node.right];
     30       if (children.some(c => c < 0 || c >= id)) throw new Error('Invalid module edge');
     31       if (!leave) { stack.push([id, true]); for (const c of [...children].reverse()) stack.push([c, false]); }
     32       else {
     33         const row = children.map(c => mapped.get(c)!), key = row.length + ':' + row.join(',');
     34         let index = interned.get(key);
     35         if (index === undefined) { index = records.length; interned.set(key, index); records.push(row); }
     36         if (records.length > 100_000) throw new Error('Module node limit exceeded');
     37         mapped.set(id, index);
     38       }
     39     }
     40   }
     41   const manifest = join([encoder.encode('ARBMNFST'), word(1, 2), word(1, 2),
     42     ...['arboricx.bundle.manifest.v1', 'tree-calculus-executable-object', 'tree-calculus.v1', 'indexed',
     43       'arboricx.indexed.node.v1', 'arboricx.indexed.payload.v1', 'tree-calculus.v1', 'normal-order', 'arboricx.abi.tree.v1'].map(string),
     44     word(0), new Uint8Array([0]), word(bundle.exports.length),
     45     ...bundle.exports.flatMap((e, i) => [word(mapped.get(e.root)!), string(i ? 'root' : 'default')]),
     46     word(bundle.exports.length), ...bundle.exports.flatMap(e => [string(e.name), word(mapped.get(e.root)!), string('term'), string('arboricx.abi.tree.v1')]),
     47     word(0), word(0)]);
     48   const nodes = join([word(records.length, 8), ...records.map(row => {
     49     const payload = join([new Uint8Array([row.length]), ...row.map(n => word(n))]); return join([word(payload.length), payload]);
     50   })]);
     51   const section = (type: number, at: number, size: number) => join([word(type), word(1, 2), word(1, 2), word(0), word(at, 8), word(size, 8), word(0)]);
     52   const bytes = join([encoder.encode('ARBORICX'), word(1, 2), word(0, 2), word(2), word(0, 8), word(32, 8),
     53     section(1, 96, manifest.length), section(2, 96 + manifest.length, nodes.length), manifest, nodes]);
     54   decodeBundle(bytes); // Independent reader enforces the actual portable profile.
     55   return bytes;
     56 }
     57 export function moduleSnapshot(names: string[], snapshot: GraphPreview): Uint8Array {
     58   const bundle = snapshotBundle(snapshot); let spine = bundle.roots[0]!;
     59   bundle.exports = names.map(name => {
     60     const cell = bundle.nodes[spine]; if (cell?.kind !== 'fork') throw new Error('Invalid module snapshot spine');
     61     spine = cell.right; return { name, root: cell.left, kind: 'term', abi: 'arboricx.abi.tree.v1' };
     62   });
     63   if (bundle.nodes[spine]?.kind !== 'leaf') throw new Error('Invalid module snapshot tail');
     64   return encodeModule(bundle);
     65 }