tricu

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

compiler-worker.ts (7273B)


      1 // Test-only JS evaluator driver; excluded from the production site.
      2 import { compileExpression } from './expression.js';
      3 import { resumeValue } from '../io.js';
      4 import { environmentProfile } from '../packages.js';
      5 import { BROWSER_BUDGET, collectionPolicy } from '../execution.js';
      6 import { contractEnvironment } from '../contracts.js';
      7 import { Graph, type NodeId } from '../graph.js';
      8 import { COMPILER_BUNDLE_LIMIT, fetchBundle, fetchBytes, loadBundle } from '../bundle.js';
      9 import { literals, makeLiteral, outputType, view, list, text } from '../codecs.js';
     10 import { runIO } from '../io.js';
     11 import { COMPILER_EXPORT, compilerDescriptor, sourceLiteral, sourceText, compiledDefinitions } from '../compiler.js';
     12 import type { CompilerClient, CompilerMessage } from './compiler-protocol.js';
     13 import type { RequestId } from '../protocol.js';
     14 const scope = globalThis as unknown as { postMessage(message: CompilerMessage): void; onmessage: ((event: MessageEvent<unknown>) => void) | null };
     15 const base = new URL('../../', import.meta.url);
     16 const send = (message: CompilerMessage): void => scope.postMessage(message);
     17 const running = (): void => send({ type: 'state', state: 'running' });
     18 let phase: 'empty' | 'compiling' | 'ready' | 'running' | 'done' = 'empty';
     19 let graph: Graph | undefined, exports: Map<string, NodeId> | undefined;
     20 let requestId = 0;
     21 let pending: { id: RequestId; kind: 'step' | 'input'; resolve(value: string): void; reject(error: Error): void } | undefined;
     22 function request(kind: 'step' | 'input'): Promise<string> {
     23   if (pending) throw new Error('Driver already waiting');
     24   const id = ++requestId as RequestId;
     25   return new Promise((resolve, reject) => { pending = { id, kind, resolve, reject }; send({ type: 'state', state: kind, id }); });
     26 }
     27 async function compile(source: unknown, profileName?: string): Promise<void> {
     28   phase = 'compiling'; running(); const start = performance.now();
     29   const budget = BROWSER_BUDGET;
     30   graph = new Graph(budget, collectionPolicy(budget));
     31   const input = sourceLiteral(graph, source); // Validate before downloading a compiler.
     32   const bytes = await fetchBytes(new URL('compiler.json', base), 4096);
     33   const raw: unknown = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
     34   const { hash } = compilerDescriptor(raw);
     35   const bundle = await fetchBundle(new URL(`/_arboricx/bundle/hash/${hash}`, base), hash, COMPILER_BUNDLE_LIMIT);
     36   const library = loadBundle(graph, bundle), compiler = library.get(COMPILER_EXPORT);
     37   if (compiler === undefined) throw new Error('Missing stable compiler entrypoint');
     38   let environment: NodeId;
     39   if (profileName === undefined) environment = contractEnvironment(graph, library);
     40   else {
     41     const bytes = await fetchBytes(new URL('environments.json', base), 32_768);
     42     const selected = environmentProfile(JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)) as unknown, profileName);
     43     if (selected.compiler !== hash) throw new Error('Demo environment/compiler pin mismatch');
     44     const bindings: NodeId[] = [];
     45     if (selected.profile) {
     46       const support = await fetchBundle(new URL(`/_arboricx/bundle/hash/${selected.profile.hash}`, base), selected.profile.hash);
     47       const values = loadBundle(graph, support);
     48       for (const name of selected.profile.names) {
     49         const tree = values.get(name);
     50         if (tree === undefined) throw new Error(`Missing demo environment binding ${name}`);
     51         bindings.push(graph.fork(text(graph, name), tree));
     52       }
     53     }
     54     environment = list(graph, bindings);
     55   }
     56   exports = compiledDefinitions(graph, graph.apply(compiler, [environment, input]));
     57   graph.collect(exports.values()); // Safe phase boundary; compiler/input temporaries are no longer caller roots.
     58   phase = 'ready'; send({ type: 'compiled', names: [...exports.keys()], draft: { graph: graph.checkpoint(), exports: [...exports] }, stats: graph.stats, milliseconds: performance.now() - start });
     59 }
     60 async function run(message: Extract<CompilerClient, { type: 'run' }>): Promise<void> {
     61   if (!graph || !exports) throw new Error('Compile a unit first');
     62   phase = 'running'; running();
     63   const fn = message.expression === undefined ? exports.get(message.export) : await compileExpression(graph, exports, message.expression);
     64   if (fn === undefined) throw new Error('Unknown compiled definition');
     65   if (message.mode !== 'pure' && message.mode !== 'io') throw new Error('Unsupported execution mode');
     66   const type = outputType(message.output), args = literals(message.arguments).map(value => makeLiteral(graph!, value));
     67   let result = message.expression === undefined ? graph.apply(fn, args) : fn;
     68   if (message.mode === 'io') result = await runIO(graph, result, {
     69     async before(event) { send({ type: 'trace', phase: 'before', event }); if (message.step) await request('step'); running(); },
     70     after(event) { send({ type: 'trace', phase: 'after', event }); }, write(text) { send({ type: 'output', text }); },
     71     read() { return request('input'); }, running,
     72   }, true, exports.values());
     73   const displayed = view(graph, result, type);
     74   graph.collect(exports.values()); // Drop this run's temporaries, retaining every definition.
     75   phase = 'ready'; send({ type: 'done', result: displayed, stats: graph.stats });
     76 }
     77 scope.onmessage = ({ data }) => {
     78   if (!data || typeof data !== 'object' || Array.isArray(data)) return;
     79   const message = data as Record<string, unknown>;
     80   if (message['type'] === 'resume') {
     81     if (!pending || message['id'] !== pending.id) return;
     82     const value = resumeValue(pending.kind, message['value']);
     83     if (value === undefined) return;
     84     const waiter = pending; pending = undefined; waiter.resolve(value);
     85     return;
     86   }
     87   let operation: Promise<void>;
     88   try {
     89     if (message['type'] === 'restore' && phase === 'empty') {
     90       // Only the owning conformance harness supplies this trusted in-memory checkpoint.
     91       const restored = data as Extract<CompilerClient, { type: 'restore' }>;
     92       const budget = BROWSER_BUDGET;
     93       graph = Graph.restore(restored.draft.graph, budget, collectionPolicy(budget));
     94       exports = new Map(restored.draft.exports); phase = 'ready'; return;
     95     } else if (message['type'] === 'compile' && phase === 'empty') {
     96       if (message['environment'] !== undefined && (typeof message['environment'] !== 'string' || message['environment'].length > 128)) throw new Error('Invalid demo environment');
     97       operation = compile(message['source'], message['environment'] as string | undefined);
     98     } else if (message['type'] === 'run' && phase === 'ready') {
     99       if (typeof message['export'] !== 'string' || message['export'].length > 128) throw new Error('Invalid compiled selection');
    100       operation = run({ type: 'run', export: message['export'], arguments: message['arguments'],
    101         ...(message['expression'] === undefined ? {} : { expression: sourceText(message['expression']) }),
    102         output: outputType(message['output']), mode: message['mode'] as 'pure' | 'io', step: message['step'] === true });
    103     } else return;
    104   } catch (error) { operation = Promise.reject(error); }
    105   operation.catch((error: unknown) => { phase = 'done'; send({ type: 'error', message: error instanceof Error ? error.message : String(error) }); });
    106 };