tricu

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

literal-input.ts (4752B)


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