tricu

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

literal-input.js (5692B)


      1 import { literals } from './codecs.js';
      2 // Deliberately a data-only subset, not a second source compiler. No names,
      3 // arbitrary applications, comments, or effects are evaluated in argument fields.
      4 export function parseLiteral(source) {
      5     if (source.length > 16_000)
      6         throw new Error('Argument text limit exceeded');
      7     let at = 0, items = 0;
      8     const space = () => { while (source[at] === ' ' || source[at] === '\n')
      9         at++; };
     10     const fail = () => { throw new Error(`Expected a literal at character ${at + 1}`); };
     11     function atom(depth) {
     12         if (++items > 4096 || depth > 16)
     13             throw new Error('Literal nesting/item limit exceeded');
     14         space();
     15         const start = at, token = source[at++];
     16         if (token === '"') {
     17             let value = '';
     18             while (at < source.length) {
     19                 const char = source[at++];
     20                 if (char === '"') {
     21                     const scalars = Array.from(value);
     22                     if (scalars.length > 4096 || scalars.some(c => { const n = c.codePointAt(0); return n >= 0xd800 && n <= 0xdfff; }))
     23                         throw new Error('Expected bounded Unicode scalar text');
     24                     return { type: 'string', value };
     25                 }
     26                 if (char === '\\') {
     27                     const escapes = { n: '\n', t: '\t', r: '\r', f: '\f', b: '\b', '\\': '\\', '"': '"', "'": "'" };
     28                     const escaped = escapes[source[at++]];
     29                     if (escaped === undefined)
     30                         fail();
     31                     value += escaped;
     32                 }
     33                 else
     34                     value += char;
     35             }
     36             fail();
     37         }
     38         if (token === '[') {
     39             const value = [];
     40             space();
     41             while (source[at] !== ']') {
     42                 if (at >= source.length)
     43                     fail();
     44                 value.push(atom(depth + 1));
     45                 space();
     46             }
     47             at++;
     48             return { type: 'list', value };
     49         }
     50         if (token === '(') {
     51             const value = expression(depth + 1);
     52             space();
     53             if (source[at++] !== ')')
     54                 fail();
     55             return value;
     56         }
     57         if (token === 't')
     58             return { type: 'tree', value: '0' };
     59         if (token && /[0-9]/u.test(token)) {
     60             while (source[at] && /[0-9]/u.test(source[at]))
     61                 at++;
     62             return literals([{ type: 'number', value: source.slice(start, at).replace(/^0+(?=\d)/u, '') }])[0];
     63         }
     64         return fail();
     65     }
     66     function expression(depth) {
     67         space();
     68         const constructor = source[at] === 't';
     69         const first = atom(depth);
     70         if (!constructor)
     71             return first;
     72         const children = [];
     73         space();
     74         while (at < source.length && source[at] !== ')' && source[at] !== ']') {
     75             if (children.length === 2)
     76                 fail();
     77             children.push(atom(depth + 1));
     78             space();
     79         }
     80         // Constructor children can contain encoded scalar/list data without evaluation.
     81         const ternary = (literal) => {
     82             switch (literal.type) {
     83                 case 'tree': return literal.value;
     84                 case 'bool': return literal.value ? '10' : '0';
     85                 case 'number': {
     86                     let n = BigInt(literal.value), result = '';
     87                     while (n) {
     88                         result += n & 1n ? '210' : '20';
     89                         n >>= 1n;
     90                     }
     91                     return result + '0';
     92                 }
     93                 case 'string': return ternary({ type: 'list', value: Array.from(literal.value).map(c => ({ type: 'number', value: String(c.codePointAt(0)) })) });
     94                 case 'bytes': return ternary({ type: 'list', value: literal.value.map(n => ({ type: 'number', value: String(n) })) });
     95                 case 'list': return literal.value.map(v => '2' + ternary(v)).join('') + '0';
     96             }
     97         };
     98         const value = String(children.length) + children.map(ternary).join('');
     99         if (value.length > 8192)
    100             throw new Error('Tree literal limit exceeded');
    101         return { type: 'tree', value };
    102     }
    103     const value = expression(0);
    104     space();
    105     if (at !== source.length)
    106         fail();
    107     return literals([value])[0];
    108 }
    109 export function printLiteral(literal) {
    110     switch (literal.type) {
    111         case 'number': return literal.value;
    112         case 'string': return '"' + literal.value.replace(/[\\"\n\t\r\f\b]/gu, c => ({ '\\': '\\\\', '"': '\\"', '\n': '\\n', '\t': '\\t', '\r': '\\r', '\f': '\\f', '\b': '\\b' })[c]) + '"';
    113         case 'bool': return literal.value ? '(t t)' : 't';
    114         case 'bytes': return '[' + literal.value.join(' ') + ']';
    115         case 'list': return '[' + literal.value.map(printLiteral).join(' ') + ']';
    116         case 'tree': {
    117             let at = 0;
    118             const value = literal.value;
    119             function read(depth) {
    120                 if (depth > 16)
    121                     throw new Error('Use raw JSON for deeply nested trees');
    122                 const tag = value[at++];
    123                 if (tag === '0')
    124                     return 't';
    125                 if (tag === '1')
    126                     return `(t ${read(depth + 1)})`;
    127                 if (tag === '2')
    128                     return `(t ${read(depth + 1)} ${read(depth + 1)})`;
    129                 throw new Error('Invalid ternary tree');
    130             }
    131             const result = read(0);
    132             if (at !== literal.value.length)
    133                 throw new Error('Trailing ternary data');
    134             return result;
    135         }
    136     }
    137 }