tricu

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

module-identity.js (3760B)


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