tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

codecs.js (8424B)


      1 import { treeResult } from './tree-result.js';
      2 // Decoded program data is not a tiny constructor-preview list.
      3 export const DECODED_LIST_LIMIT = 4096;
      4 export const TEXT_LIMIT = 4096;
      5 export function natural(graph, value) {
      6     if (value < 0n || value.toString(2).length > 4096)
      7         throw new Error('Natural number limit exceeded');
      8     const bits = [];
      9     while (value) {
     10         bits.push((value & 1n) !== 0n);
     11         value >>= 1n;
     12     }
     13     let result = graph.leaf;
     14     for (let i = bits.length - 1; i >= 0; i--)
     15         result = graph.fork(bits[i] ? graph.stem(graph.leaf) : graph.leaf, result);
     16     return result;
     17 }
     18 export function readNatural(graph, handle) {
     19     let current = handle, result = 0n, place = 1n;
     20     for (let bits = 0; bits <= 4096; bits++) {
     21         const { node } = graph.observe(current);
     22         if (node.kind === 'leaf')
     23             return result;
     24         if (node.kind !== 'fork')
     25             throw new Error('Invalid canonical number');
     26         const bit = graph.observe(node.left).node;
     27         if (bit.kind === 'stem' && graph.observe(bit.child).node.kind === 'leaf')
     28             result += place;
     29         else if (bit.kind !== 'leaf')
     30             throw new Error('Invalid number bit');
     31         place <<= 1n;
     32         current = node.right;
     33     }
     34     throw new Error('Natural number decode limit exceeded');
     35 }
     36 export function list(graph, values) {
     37     let result = graph.leaf;
     38     for (let i = values.length - 1; i >= 0; i--)
     39         result = graph.fork(values[i], result);
     40     return result;
     41 }
     42 export function readList(graph, handle, limit = TEXT_LIMIT) {
     43     const values = [];
     44     let current = handle;
     45     for (;;) {
     46         const { node } = graph.observe(current);
     47         if (node.kind === 'leaf')
     48             return values;
     49         if (node.kind !== 'fork')
     50             throw new Error('Invalid canonical list');
     51         if (values.length >= limit)
     52             throw new Error('List decode limit exceeded');
     53         values.push(node.left);
     54         current = node.right;
     55     }
     56 }
     57 export function text(graph, value, limit = TEXT_LIMIT) {
     58     if (value.length > limit * 2)
     59         throw new Error('Text limit exceeded');
     60     const chars = Array.from(value);
     61     if (chars.length > limit)
     62         throw new Error('Text limit exceeded');
     63     return list(graph, chars.map(char => {
     64         const scalar = char.codePointAt(0);
     65         if (scalar >= 0xd800 && scalar <= 0xdfff)
     66             throw new Error('Unpaired Unicode surrogate');
     67         return natural(graph, BigInt(scalar));
     68     }));
     69 }
     70 export function readText(graph, handle, limit = TEXT_LIMIT) {
     71     return readList(graph, handle, limit).map(char => {
     72         const n = readNatural(graph, char);
     73         if (n > 0x10ffffn || (n >= 0xd800n && n <= 0xdfffn))
     74             throw new Error('Invalid Unicode scalar');
     75         return String.fromCodePoint(Number(n));
     76     }).join('');
     77 }
     78 export function line(graph, value) {
     79     if (/[\r\n]/u.test(value))
     80         throw new Error('Input must be one line');
     81     return text(graph, value, 256);
     82 }
     83 // Runtime validation complements TS: every boundary accepts unknown data.
     84 export function literals(input) {
     85     if (!Array.isArray(input) || input.length > 16)
     86         throw new Error('Arguments must be an array of at most 16 literals');
     87     let count = 0;
     88     function validate(value, depth) {
     89         if (++count > 4096 || depth > 16)
     90             throw new Error('Literal nesting/item limit exceeded');
     91         if (!value || typeof value !== 'object' || Array.isArray(value))
     92             throw new Error('Expected typed literal');
     93         const v = value;
     94         switch (v['type']) {
     95             case 'string':
     96             case 'number':
     97             case 'tree': {
     98                 if (typeof v['value'] !== 'string' || v['value'].length > 8192)
     99                     throw new Error('Literal text limit exceeded');
    100                 if (v['type'] === 'number' && (!/^(0|[1-9][0-9]*)$/u.test(v['value']) || v['value'].length > 1024))
    101                     throw new Error('Natural input must be bounded nonnegative decimal digits');
    102                 return { type: v['type'], value: v['value'] };
    103             }
    104             case 'bool':
    105                 if (typeof v['value'] !== 'boolean')
    106                     throw new Error('Boolean literal requires true or false');
    107                 return { type: 'bool', value: v['value'] };
    108             case 'bytes': {
    109                 const bytes = v['value'];
    110                 if (!Array.isArray(bytes) || bytes.length > TEXT_LIMIT || !bytes.every(n => Number.isInteger(n) && n >= 0 && n <= 255))
    111                     throw new Error('Bytes must be a bounded array of integers 0–255');
    112                 return { type: 'bytes', value: bytes };
    113             }
    114             case 'list':
    115                 if (!Array.isArray(v['value']) || v['value'].length > TEXT_LIMIT)
    116                     throw new Error('List literal requires a bounded array of typed literals');
    117                 return { type: 'list', value: v['value'].map(item => validate(item, depth + 1)) };
    118             default: throw new Error('Unsupported literal type');
    119         }
    120     }
    121     return input.map(value => validate(value, 0));
    122 }
    123 export function makeLiteral(graph, value) {
    124     switch (value.type) {
    125         case 'string': return text(graph, value.value);
    126         case 'number': return natural(graph, BigInt(value.value));
    127         case 'bool': return value.value ? graph.stem(graph.leaf) : graph.leaf;
    128         case 'tree': return graph.fromTernary(value.value);
    129         case 'bytes': return list(graph, value.value.map(n => natural(graph, BigInt(n))));
    130         case 'list': return list(graph, value.value.map(item => makeLiteral(graph, item)));
    131     }
    132 }
    133 export function outputType(input) {
    134     if (!['tree', 'string', 'number', 'bool', 'bytes', 'list', 'values', 'tricu', 'triangle', 'dag', 'ternary'].includes(String(input)))
    135         throw new Error('Unsupported output codec');
    136     return input;
    137 }
    138 export function view(graph, handle, type) {
    139     switch (type) {
    140         case 'tricu':
    141         case 'triangle':
    142         case 'dag':
    143         case 'ternary': return treeResult(graph.snapshot(handle), type);
    144         case 'values': return { type, value: readList(graph, handle, DECODED_LIST_LIMIT).map(id => readScalar(graph, id)) };
    145         case 'tree': return { type, value: graph.preview(handle) };
    146         case 'string': return { type, value: readText(graph, handle) };
    147         case 'number': return { type, value: readNatural(graph, handle).toString() };
    148         case 'bool': {
    149             const { node } = graph.observe(handle);
    150             if (node.kind === 'leaf')
    151                 return { type, value: false };
    152             if (node.kind === 'stem' && graph.observe(node.child).node.kind === 'leaf')
    153                 return { type, value: true };
    154             throw new Error('Invalid canonical Boolean');
    155         }
    156         case 'bytes': return { type, value: readList(graph, handle).map(id => {
    157                 const n = readNatural(graph, id);
    158                 if (n > 255n)
    159                     throw new Error('Invalid byte');
    160                 return Number(n);
    161             }) };
    162         case 'list': return { type, value: readList(graph, handle, 32).map(id => graph.preview(id, 8)) };
    163     }
    164 }
    165 // Deliberately conservative display policy shared by Auto and decoded lists.
    166 // Unicode letters/marks/numbers/punctuation/symbols/spaces, text whitespace and
    167 // joining characters are readable; controls/private-use/unassigned scalars aren't.
    168 export function readableText(value) {
    169     return value.length > 0 && /^[\p{L}\p{M}\p{N}\p{P}\p{S}\p{Zs}\t\n\r\u200c\u200d]+$/u.test(value);
    170 }
    171 export function readScalar(graph, handle) {
    172     try {
    173         const value = readText(graph, handle);
    174         if (readableText(value))
    175             return { type: 'string', value };
    176     }
    177     catch { /* Try a natural interpretation of this same tree. */ }
    178     return { type: 'number', value: readNatural(graph, handle).toString() };
    179 }
    180 export function valueText(result) {
    181     if ('file' in result)
    182         return result.file.text;
    183     if (result.type === 'values')
    184         return '[' + result.value.map(item => item.type === 'string' ? JSON.stringify(item.value) : item.value).join(' ') + ']';
    185     if (result.type === 'tree' || result.type === 'list')
    186         throw new Error('Choose a source interpretation for trees');
    187     return typeof result.value === 'string' ? result.value : JSON.stringify(result.value, null, 2);
    188 }