tricu

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

wasm-worker.ts (6186B)


      1 import { importPlan } from './import-plan.js';
      2 import { localModuleInputs, validateLocalModule } from './local-modules.js';
      3 import { resumeValue } from './io.js';
      4 import { createRuntimeSession, type RuntimeSession } from './runtime-session.js';
      5 const selectedBackend = new URL(globalThis.location.href).searchParams.get('backend');
      6 import { publishedBundle } from './published-bundle.js';
      7 import { bundleHash } from './bundle.js';
      8 import { literals, outputType } from './codecs.js';
      9 import { sourceText } from './compiler.js';
     10 import { exportTree, treeFormat } from './tree-export.js';
     11 import type { CompilerMessage } from './compiler-protocol.js';
     12 import type { WorkerMessage, RequestId } from './protocol.js';
     13 const scope = globalThis as unknown as { postMessage(m: CompilerMessage | WorkerMessage): void; onmessage: ((e: MessageEvent<unknown>) => void) | null };
     14 const send = (m: CompilerMessage | WorkerMessage) => scope.postMessage(m);
     15 const running = () => send({ type: 'state', state: 'running' });
     16 let session: RuntimeSession | undefined, stored: Awaited<ReturnType<typeof publishedBundle>> | undefined;
     17 let busy = false, requestId = 0;
     18 let pending: { id: RequestId; resolve(value: string): void; reject(error: Error): void; kind: 'input' | 'step' } | undefined;
     19 function request(kind: 'input' | 'step'): Promise<string> {
     20   if (pending) throw new Error('Driver already waiting');
     21   const id = ++requestId as RequestId;
     22   return new Promise((resolve, reject) => { pending = { id, resolve, reject, kind }; send({ type: 'state', state: kind, id }); });
     23 }
     24 scope.onmessage = ({ data }) => {
     25   if (!data || typeof data !== 'object' || Array.isArray(data)) return;
     26   const m = data as Record<string, unknown>;
     27   if (m['type'] === 'resume') {
     28     if (!pending || m['id'] !== pending.id) return;
     29     const value = resumeValue(pending.kind, m['value']);
     30     if (value === undefined) return;
     31     const waiter = pending; pending = undefined; waiter.resolve(value);
     32     return;
     33   }
     34   if (busy) return;
     35   busy = true;
     36   void (async () => {
     37     if (m['type'] === 'check-import') {
     38       if (session) throw new Error('Already initialized');
     39       const source = sourceText(m['source']);
     40       if (typeof m['manifest'] !== 'string' || typeof m['alias'] !== 'string') throw new Error('Invalid import request');
     41       session = await createRuntimeSession(selectedBackend, true);
     42       const stage = await session.inspectModule(source);
     43       const plan = importPlan(source, stage, m['manifest'], m['alias']);
     44       if (!plan.existing) await session.compile(plan.source, 'empty', localModuleInputs(m['localModules']));
     45       send({ type: 'import-checked', plan });
     46     } else if (m['type'] === 'load') {
     47       if (session || stored) throw new Error('Already initialized');
     48       const localId = m['localId'];
     49       if (localId !== undefined && (typeof localId !== 'string' || localId.length > 128)) throw new Error('Invalid catalogue ID');
     50       running(); stored = await publishedBundle(bundleHash(m['hash']), localId);
     51       session = await createRuntimeSession(selectedBackend, false);
     52       await session.load(stored.bytes, stored.hash);
     53       send({ type: 'loaded', hash: stored.hash, exports: stored.bundle.exports, nodes: stored.bundle.nodes.length, metadata: stored.bundle.metadata, source: stored.source });
     54     } else if (m['type'] === 'compile') {
     55       if (session) throw new Error('Already initialized');
     56       const source = sourceText(m['source']), profile = m['environment'];
     57       if (profile !== undefined && (typeof profile !== 'string' || profile.length > 128)) throw new Error('Invalid demo environment');
     58       running(); const start = performance.now();
     59       session = await createRuntimeSession(selectedBackend, true);
     60       const names = await session.compile(source, profile, localModuleInputs(m['localModules']));
     61       send({ type: 'compiled', names, stats: session.stats(), milliseconds: performance.now() - start });
     62     } else if (m['type'] === 'export-module') {
     63       if (!session || stored) throw new Error('Compile a draft first');
     64       running();
     65       try { const module = await validateLocalModule(await session.exportModule()); send({ type: 'module-exported', bytes: module.bytes, hash: module.hash, manifest: module.manifest }); }
     66       catch (e) { if (session.disposed) throw e; send({ type: 'module-export-error', message: String(e) }); }
     67     } else if (m['type'] === 'run') {
     68       if (!session) throw new Error('Load or compile first');
     69       if (typeof m['export'] !== 'string' || m['export'].length > 512) throw new Error('Invalid export');
     70       if (m['mode'] !== 'pure' && m['mode'] !== 'io') throw new Error('Unsupported execution mode');
     71       const args = literals(m['arguments']), output = outputType(m['output']); running();
     72       const host: import('./io.js').IOHost = {
     73         async before(event) { send({ type: 'trace', phase: 'before', event }); if (m['step'] === true) await request('step'); running(); },
     74         after(event) { send({ type: 'trace', phase: 'after', event }); }, write(text) { send({ type: 'output', text }); },
     75         read() { return request('input'); }, running,
     76       };
     77       const result = m['expression'] === undefined
     78         ? await session.run(m['export'], args, output, m['mode'], host)
     79         : await session.runExpression(sourceText(m['expression']), output, m['mode'], host);
     80       send({ type: 'done', result, stats: session.stats() });
     81     } else if (m['type'] === 'export' && stored) {
     82       try {
     83         if (typeof m['export'] !== 'string') throw new Error('Invalid export');
     84         const format = treeFormat(m['format']);
     85         send({ type: 'exported', hash: stored.hash, export: m['export'], format, file: exportTree(stored.bundle, m['export'], format) });
     86       } catch (e) { send({ type: 'export-error', message: String(e) }); }
     87     } else if (m['type'] === 'download' && stored) send({ type: 'downloaded', hash: stored.hash, bytes: stored.bytes });
     88     else throw new Error('Unsupported backend operation');
     89   })().catch((e: unknown) => send({ type: 'error', message: e instanceof Error ? e.message : String(e), fatal: session?.disposed ?? true }))
     90     .finally(() => { busy = false; });
     91 };