tricu

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

module-identity.ts (3826B)


      1 // Native indexed import identity (ContentStore.Bundle / Wire.extractBundleRoot).
      2 // Hash stored DAG records in left-before-right physical-edge order.
      3 import { bundleHash, sha256, type Bundle, type BundleHash } from './bundle.js';
      4 export type ModuleHash = string & { readonly __moduleHash: unique symbol };
      5 export function moduleHash(value: unknown): ModuleHash { return bundleHash(value) as string as ModuleHash; }
      6 const utf8 = new TextEncoder();
      7 function join(parts: Uint8Array[]): Uint8Array {
      8   const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
      9   let at = 0; for (const p of parts) { out.set(p, at); at += p.length; } return out;
     10 }
     11 function word(n: number, size = 4): Uint8Array {
     12   const out = new Uint8Array(size), view = new DataView(out.buffer);
     13   if (size === 8) view.setBigUint64(0, BigInt(n));
     14   else if (size === 2) view.setUint16(0, n); else view.setUint32(0, n);
     15   return out;
     16 }
     17 const string = (s: string): Uint8Array => { const b = utf8.encode(s); return join([word(b.length), b]); };
     18 export function indexedObject(bundle: Bundle, root: number): Uint8Array {
     19   const ids = new Map<number, number>(), rows: Uint8Array[] = [], stack: [number, boolean][] = [[root, false]];
     20   while (stack.length) {
     21     const [id, leave] = stack.pop()!;
     22     if (ids.has(id)) continue;
     23     const node = bundle.nodes[id]; if (!node) throw new Error('Missing indexed node');
     24     const children = node.kind === 'leaf' ? [] : node.kind === 'stem' ? [node.child] : [node.left, node.right];
     25     if (children.some(c => c >= id || c < 0)) throw new Error('Invalid indexed edge');
     26     if (!leave) { stack.push([id, true]); for (const c of children.reverse()) stack.push([c, false]); }
     27     else {
     28       const payload = join([new Uint8Array([children.length]), ...children.map(c => word(ids.get(c)!))]);
     29       ids.set(id, rows.length); rows.push(join([word(payload.length), payload]));
     30     }
     31   }
     32   const index = ids.get(root)!;
     33   const manifest = join([
     34     utf8.encode('ARBMNFST'), word(1, 2), word(1, 2),
     35     ...['arboricx.bundle.manifest.v1', 'tree-calculus-executable-object', 'tree-calculus.v1', 'indexed',
     36       'arboricx.indexed.node.v1', 'arboricx.indexed.payload.v1', 'tree-calculus.v1', 'normal-order', 'arboricx.abi.tree.v1'].map(string),
     37     word(0), new Uint8Array([0]), word(1), word(index), string('default'),
     38     word(1), string('root'), word(index), string('term'), string('arboricx.abi.tree.v1'),
     39     word(1), word(5, 2), string('arboricx'), word(0),
     40   ]);
     41   const nodes = join([word(rows.length, 8), ...rows]);
     42   const section = (type: number, at: number, size: number): Uint8Array => join([
     43     word(type), word(1, 2), word(1, 2), word(0, 2), word(0, 2), word(at, 8), word(size, 8), word(0),
     44   ]);
     45   return join([utf8.encode('ARBORICX'), word(1, 2), word(0, 2), word(2), word(0, 8), word(32, 8),
     46     section(1, 96, manifest.length), section(2, 96 + manifest.length, nodes.length), manifest, nodes]);
     47 }
     48 export async function moduleIdentity(bundle: Bundle): Promise<ModuleHash> {
     49   if (!bundle.exports.length || bundle.exports.some(e => !/^[A-Za-z_][A-Za-z0-9_?$%'.-]*$/u.test(e.name))) {
     50     throw new Error('Package requires a nonempty native module interface');
     51   }
     52   const hashes = new Map<number, BundleHash>();
     53   let manifest = 'arboricx.module-manifest.v1\n';
     54   const escape = (s: string): string => s.replace(/%/g, '%25').replace(/\t/g, '%09').replace(/\n/g, '%0A').replace(/\r/g, '%0D');
     55   for (const e of bundle.exports) {
     56     let hash = hashes.get(e.root);
     57     if (!hash) { hash = await sha256(join([utf8.encode('arboricx.indexed-tree.v1\0'), indexedObject(bundle, e.root)])); hashes.set(e.root, hash); }
     58     manifest += `export\t${escape(e.name)}\tarboricx.indexed-tree.v1\t${hash}\t${e.abi}\n`;
     59   }
     60   return moduleHash(await sha256(utf8.encode('arboricx.module-manifest.v1\0' + manifest)));
     61 }