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 }