tricu

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

tree-export.js (4899B)


      1 export const FLAT_EXPORT_LIMIT = 33_554_432;
      2 export const DAG_EXPORT_LIMIT = 134_217_728;
      3 export function treeFormat(value) {
      4     if (value !== 'ternary' && value !== 'tricu' && value !== 'triangle' && value !== 'dag')
      5         throw new Error('Unknown tree export format');
      6     return value;
      7 }
      8 // Only immutable, authenticated bundle records are read. No graph observation,
      9 // application or reduction. Reachability and capped size calculation count DAG
     10 // records, not unfolded occurrences; expanded writers run only after preflight.
     11 export function exportTree(bundle, name, format) {
     12     format = treeFormat(format);
     13     const selected = bundle.exports.find(item => item.name === name);
     14     if (!selected)
     15         throw new Error('Unknown export');
     16     const reachable = new Set(), todo = [selected.root];
     17     while (todo.length) {
     18         const id = todo.pop();
     19         if (reachable.has(id))
     20             continue;
     21         reachable.add(id);
     22         const node = bundle.nodes[id];
     23         if (node.kind === 'stem')
     24             todo.push(node.child);
     25         else if (node.kind === 'fork')
     26             todo.push(node.left, node.right);
     27     }
     28     const ids = [...reachable].sort((a, b) => a - b);
     29     // Bound the number of fragments, not just output bytes. One array entry per
     30     // unfolded node costs far more memory than the resulting text for large trees.
     31     const parts = [], chunk = [];
     32     let chunkLength = 0;
     33     const flush = () => { if (chunk.length)
     34         parts.push(chunk.join('')); chunk.length = 0; chunkLength = 0; };
     35     const write = (text) => {
     36         chunk.push(text);
     37         chunkLength += text.length;
     38         if (chunkLength >= 65_536)
     39             flush();
     40     };
     41     const complete = () => { flush(); return parts.join(''); };
     42     let bytes = 0;
     43     const append = (text) => {
     44         const size = text.length + 2 * (text.split('△').length - 1);
     45         bytes += size;
     46         if (bytes > DAG_EXPORT_LIMIT)
     47             throw new Error('DAG export exceeds 128 MiB');
     48         write(text);
     49     };
     50     if (format === 'dag') {
     51         const keys = new Map();
     52         for (const id of ids) {
     53             const node = bundle.nodes[id], key = `:n${id}`;
     54             if (node.kind === 'leaf')
     55                 keys.set(id, '△');
     56             else {
     57                 const child = keys.get(node.kind === 'stem' ? node.child : node.left);
     58                 if (node.kind === 'stem')
     59                     append(`${key} △ ${child}\n`);
     60                 else {
     61                     const intermediate = `:f${id}`, right = keys.get(node.right);
     62                     append(`${intermediate} △ ${child}\n`);
     63                     append(`${key} ${intermediate} ${right}\n`);
     64                 }
     65                 keys.set(id, key);
     66             }
     67         }
     68         const root = keys.get(selected.root);
     69         // A safe fixed interface name, independent of arbitrary bundle export names.
     70         const ending = `tree ${root}\ntree\n`;
     71         append(ending);
     72         return { text: complete(), bytes, extension: 'dag', storedNodes: ids.length };
     73     }
     74     const sizes = new Map(), triangle = format === 'triangle';
     75     const symbol = triangle ? '△' : 't', symbolBytes = triangle ? 3 : 1;
     76     for (const id of ids) {
     77         const node = bundle.nodes[id];
     78         let size;
     79         if (format === 'ternary')
     80             size = 1 + (node.kind === 'leaf' ? 0 : node.kind === 'stem'
     81                 ? sizes.get(node.child) : sizes.get(node.left) + sizes.get(node.right));
     82         else
     83             size = node.kind === 'leaf' ? symbolBytes : node.kind === 'stem'
     84                 ? symbolBytes + 3 + sizes.get(node.child)
     85                 : symbolBytes + 4 + sizes.get(node.left) + sizes.get(node.right);
     86         sizes.set(id, Math.min(FLAT_EXPORT_LIMIT + 1, size));
     87     }
     88     bytes = sizes.get(selected.root);
     89     if (bytes > FLAT_EXPORT_LIMIT)
     90         throw new Error('Expanded tree exceeds 32 MiB. Choose DAG to preserve sharing.');
     91     const stack = [selected.root];
     92     while (stack.length) {
     93         const item = stack.pop();
     94         if (typeof item === 'string') {
     95             write(item);
     96             continue;
     97         }
     98         const node = bundle.nodes[item];
     99         if (format === 'ternary') {
    100             write(node.kind === 'leaf' ? '0' : node.kind === 'stem' ? '1' : '2');
    101             if (node.kind === 'stem')
    102                 stack.push(node.child);
    103             else if (node.kind === 'fork')
    104                 stack.push(node.right, node.left);
    105         }
    106         else if (node.kind === 'leaf')
    107             write(symbol);
    108         else {
    109             write(`(${symbol} `);
    110             stack.push(')');
    111             if (node.kind === 'stem')
    112                 stack.push(node.child);
    113             else
    114                 stack.push(node.right, ' ', node.left);
    115         }
    116     }
    117     return { text: complete(), bytes, extension: format === 'tricu' ? 'tri' : 'txt', storedNodes: ids.length };
    118 }