tricu

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

tree-export.test.mjs (5762B)


      1 import test from 'node:test';
      2 import assert from 'node:assert/strict';
      3 import { readFile } from 'node:fs/promises';
      4 import { COMPILER_BUNDLE_LIMIT, decodeBundle } from '../dist/bundle.js';
      5 import { exportTree, treeFormat, FLAT_EXPORT_LIMIT, DAG_EXPORT_LIMIT } from '../dist/tree-export.js';
      6 import { makeBundle } from './bundle-fixture.mjs';
      7 // Independent structural readers. Application is constructor assembly only:
      8 // a third argument to a fork would constitute evaluation and fails these tests.
      9 const leaf = [];
     10 function apply(fn, arg) { assert.ok(fn.length < 2); return [...fn, arg]; }
     11 function dag(text) {
     12   const env = new Map([['△', leaf]]);
     13   for (const line of text.split('\n')) {
     14     if (!line) continue;
     15     const words = line.split(' '); assert.ok(words.every(Boolean));
     16     const get = name => { assert.ok(env.has(name), name); return env.get(name); };
     17     if (words.length === 1) return get(words[0]);
     18     assert.ok(words.length === 2 || words.length === 3);
     19     env.set(words[0], words.length === 2 ? get(words[1]) : apply(get(words[1]), get(words[2])));
     20   }
     21   assert.fail('Missing final value');
     22 }
     23 function expression(text, symbol) {
     24   const tokens = text.match(/△|t|\(|\)/gu); let at = 0;
     25   function read() {
     26     const token = tokens[at++];
     27     if (token === symbol) return leaf;
     28     assert.equal(token, '('); let result = read();
     29     while (tokens[at] !== ')') result = apply(result, read());
     30     at++; return result;
     31   }
     32   const result = read(); assert.equal(at, tokens.length); return result;
     33 }
     34 function ternary(text) {
     35   let at = 0;
     36   function read() { const n = Number(text[at++]); assert.ok(n >= 0 && n <= 2); return Array.from({length:n}, read); }
     37   const result = read(); assert.equal(at,text.length); return result;
     38 }
     39 const bundle = records => decodeBundle(makeBundle(records,[['main',records.length-1],['alias',records.length-1]]));
     40 test('leaf, stem, fork and identity export exact stored trees in every format', () => {
     41   for (const [records, expected] of [[ [[]], '0'], [[[],[0]],'10'], [[[],[0,0]],'200'], [[[],[0],[1],[2,0]],'21100']]) {
     42     const b = bundle(records), want = ternary(expected), original = JSON.stringify(b);
     43     for (const format of ['ternary','tricu','triangle','dag']) {
     44       const file = exportTree(b,'main',format);
     45       assert.equal(file.bytes, Buffer.byteLength(file.text));
     46       assert.deepEqual(format === 'ternary' ? ternary(file.text) : format === 'dag' ? dag(file.text) : expression(file.text,format === 'tricu' ? 't' : '△'),want);
     47       assert.deepEqual(exportTree(b,'alias',format),file);
     48     }
     49     assert.equal(exportTree(b,'main','ternary').text,expected);
     50     assert.equal(JSON.stringify(b),original);
     51   }
     52   const b=bundle([[],[0],[1],[2,0]]);
     53   assert.equal(exportTree(b,'main','tricu').text,'(t (t (t t)) t)');
     54 });
     55 test('DAG exports only reachable records, handles arbitrary names without embedding them, preserves sharing', () => {
     56   const b=decodeBundle(makeBundle([[],[0],[1],[0,0],[3,3]],[['x\n△ hacked',4],['unused',2]]));
     57   const file=exportTree(b,'x\n△ hacked','dag'), tree=dag(file.text);
     58   assert.equal(file.storedNodes,3); assert.equal(tree[0],tree[1]);
     59   assert.ok(!file.text.includes('hacked'));assert.ok(!file.text.includes(':n1'));
     60   assert.match(file.text,/\ntree :n4\ntree\n$/u);
     61 });
     62 test('expanded size preflight rejects exponential trees before unfolding, DAG remains linear', () => {
     63   const records=[[]];for(let i=1;i<64;i++)records.push([i-1,i-1]);
     64   const b=bundle(records), file=exportTree(b,'main','dag');
     65   assert.ok(file.bytes<4000);assert.equal(file.storedNodes,64);
     66   const tree=dag(file.text);assert.equal(tree[0],tree[1]);
     67   for(const format of ['ternary','tricu','triangle']) assert.throws(()=>exportTree(b,'main',format),/32 MiB.*DAG/);
     68   assert.throws(()=>exportTree(b,'missing','dag'),/Unknown export/);
     69   for(const format of ['json','',null]) assert.throws(()=>treeFormat(format),/format/);
     70 });
     71 test('deep exports use iterative traversal, UTF-8 byte budgets and complete nontruncated boundaries', () => {
     72   assert.equal(FLAT_EXPORT_LIMIT,32*1024*1024);assert.equal(DAG_EXPORT_LIMIT,128*1024*1024);
     73   const records=[[]];for(let i=1;i<20000;i++)records.push([i-1]);
     74   const b=bundle(records);
     75   for(const format of ['ternary','tricu','triangle','dag']) {
     76     const file=exportTree(b,'main',format);assert.equal(file.bytes,Buffer.byteLength(file.text));
     77     assert.equal(file.storedNodes,20000);
     78   }
     79   const doubled=[[]];for(let i=1;i<=24;i++)doubled.push([i-1,i-1]);
     80   const exact=exportTree(bundle(doubled),'main','ternary');assert.equal(exact.bytes,FLAT_EXPORT_LIMIT-1);
     81   assert.equal(exact.text.length,exact.bytes);assert.match(exact.text,/^[012]+$/u);
     82   assert.throws(()=>exportTree(bundle([...doubled,[24]]),'main','triangle'),/32 MiB/);
     83   assert.throws(()=>exportTree(bundle([...doubled,[24,24]]),'main','ternary'),/32 MiB.*DAG/);
     84 });
     85 test('every curated definition and compiler definition has bounded DAG output without evaluating', async () => {
     86   const entries=JSON.parse(await readFile(new URL('../catalogue.json',import.meta.url)));
     87   entries.push(JSON.parse(await readFile(new URL('../compiler.json',import.meta.url))));
     88   for(const hash of new Set(entries.map(e=>e.hash))) {
     89     const b=decodeBundle(await readFile(new URL('../bundles/'+hash+'.arboricx',import.meta.url)),COMPILER_BUNDLE_LIMIT);
     90     // Largest compiler exports plus all demo definitions, not repeated all compiler traversals.
     91     const names=b.exports.length>100 ? ['source_unit.compileStableModuleDefinitions','base.id','base.y'] : b.exports.map(e=>e.name);
     92     for(const name of names) {
     93       const file=exportTree(b,name,'dag');assert.equal(file.bytes,Buffer.byteLength(file.text));
     94       assert.ok(file.storedNodes<=b.nodes.length);dag(file.text);
     95     }
     96   }
     97 });