compiler.ts (3844B)
1 import { bundleHash, type BundleHash } from './bundle.js'; 2 import { Graph, type NodeId } from './graph.js'; 3 import { text, readText, readList, readNatural } from './codecs.js'; 4 export const COMPILER_EXPORT = 'source_unit.compileStableModuleDefinitions'; 5 export function compilerDescriptor(raw: unknown): { hash: BundleHash } { 6 if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('Invalid compiler descriptor'); 7 const descriptor = raw as Record<string, unknown>; 8 if (descriptor['schema'] !== 1 || descriptor['backend'] !== 'stable' || descriptor['export'] !== COMPILER_EXPORT) throw new Error('Invalid stable compiler descriptor'); 9 return { hash: bundleHash(descriptor['hash']) }; 10 } 11 export const COMPILER_COLLECTION = Object.freeze({ threshold: 250_000 }); 12 export const SOURCE_LIMIT = 16_384; 13 export function sourceText(source: unknown): string { 14 if (typeof source !== 'string' || !source.trim()) throw new Error('Enter a source unit or native module'); 15 if (source.length > SOURCE_LIMIT * 2) throw new Error('Source limit exceeded'); 16 let count = 0; 17 for (const char of source) { 18 if (++count > SOURCE_LIMIT) throw new Error('Source limit exceeded'); 19 const scalar = char.codePointAt(0)!; 20 if (scalar >= 0xd800 && scalar <= 0xdfff) throw new Error('Unpaired source surrogate'); 21 } 22 return source; 23 } 24 export function sourceLiteral(graph: Graph, source: unknown): NodeId { 25 return text(graph, sourceText(source), SOURCE_LIMIT); 26 } 27 export function compilerDiagnostic(graph: Graph, payload: NodeId): string { 28 const [diagnostic] = graph.pair(payload, 'compiler error payload'); 29 const [code, tail] = graph.pair(diagnostic, 'diagnostic code'); 30 const [expected, fields] = graph.pair(tail, 'diagnostic expected'); 31 const [, locationFields] = graph.pair(fields, 'diagnostic found'); 32 const [location] = graph.pair(locationFields, 'diagnostic location'); 33 let where = ''; 34 if (graph.observe(location).node.kind !== 'leaf') { 35 const [line, rest] = graph.pair(location, 'source position'), [column] = graph.pair(rest, 'source column'); 36 where = ` at ${readNatural(graph, line)}:${readNatural(graph, column)}`; 37 } 38 // Expected can be a token-kind number or a label; found/context/remainder 39 // may be arbitrary trees. Decode only the compiler's known diagnostic profile. 40 const number = readNatural(graph, code); 41 const label = number === 2n ? `: expected token kind ${readNatural(graph, expected)}` 42 : number === 20n ? `: unresolved name ${JSON.stringify(readText(graph, expected, 512))}` 43 : number >= 30n ? `: expected ${JSON.stringify(readText(graph, expected, 512))}` : ''; 44 return `Compiler diagnostic ${number}${where}${label}`; 45 } 46 // Decode only the result spine/names. Compiled program trees remain lazy handles. 47 // Do not normalize/serialize them merely to populate an export selector. 48 export function compiledDefinitions(graph: Graph, result: NodeId, allowEmpty = false): Map<string, NodeId> { 49 const [tag, payload] = graph.pair(result, 'compiler result'); 50 const flag = graph.observe(tag).node; 51 if (flag.kind === 'leaf') throw new Error(compilerDiagnostic(graph, payload)); 52 if (flag.kind !== 'stem' || graph.observe(flag.child).node.kind !== 'leaf') throw new Error('Invalid compiler result tag'); 53 const [definitions] = graph.pair(payload, 'compiler definitions payload'); 54 const bindings = readList(graph, definitions, 16), exports = new Map<string, NodeId>(); 55 for (const binding of bindings) { 56 const [nameTree, program] = graph.pair(binding, 'compiled binding'); 57 const name = readText(graph, nameTree, 128); 58 if (!name || exports.has(name)) throw new Error('Empty/duplicate compiled definition name'); 59 exports.set(name, program); 60 } 61 if (!exports.size && !allowEmpty) throw new Error('Source unit produced no definitions'); 62 return exports; 63 }