tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

wasm-worker.js (9213B)


      1 import { autoInterpretation } from './auto-interpretation.js';
      2 import { importPlan } from './import-plan.js';
      3 import { localModuleInputs, validateLocalModule } from './local-modules.js';
      4 import { validStepResume } from './io.js';
      5 import { createRuntimeSession } from './runtime-session.js';
      6 const selectedBackend = new URL(globalThis.location.href).searchParams.get('backend');
      7 import { publishedBundle } from './published-bundle.js';
      8 import { bundleHash } from './bundle.js';
      9 import { literals, outputType } from './codecs.js';
     10 import { sourceText } from './compiler.js';
     11 import { exportTree, treeFormat } from './tree-export.js';
     12 const scope = globalThis;
     13 const send = (m) => scope.postMessage(m);
     14 const running = () => send({ type: 'state', state: 'running' });
     15 let session, stored;
     16 let busy = false, requestId = 0;
     17 let pending;
     18 function requestStep() {
     19     if (pending)
     20         throw new Error('Driver already waiting');
     21     if (requestId >= Number.MAX_SAFE_INTEGER)
     22         throw new Error('Driver request identity exhausted');
     23     const id = ++requestId;
     24     return new Promise((resolve, reject) => { pending = { id, resolve, reject }; send({ type: 'state', state: 'step', id }); });
     25 }
     26 let ioCommand, ioRun, stopRequested = false;
     27 function host(stepping) {
     28     stopRequested = false;
     29     return {
     30         selected(version) { send({ type: 'io-version', version }); },
     31         async before(event) { send({ type: 'trace', phase: 'before', event }); if (stepping)
     32             await requestStep(); running(); },
     33         structured: {
     34             stepping,
     35             open(run, command) { ioRun = run; ioCommand = command; send({ type: 'io-open', run }); if (stopRequested)
     36                 command({ run, kind: 'stop' }); },
     37             event(event) { send({ type: 'io-event', event }); },
     38             close(run, settled) { ioCommand = undefined; ioRun = undefined; pending = undefined; send({ type: 'io-close', run, settled }); },
     39         },
     40     };
     41 }
     42 scope.onmessage = ({ data }) => {
     43     if (!data || typeof data !== 'object' || Array.isArray(data))
     44         return;
     45     const m = data;
     46     if (m['type'] === 'io-stop') {
     47         if (!busy)
     48             return;
     49         stopRequested = true;
     50         if (ioRun)
     51             ioCommand?.({ run: ioRun, kind: 'stop' });
     52         if (pending) {
     53             const waiter = pending;
     54             pending = undefined;
     55             waiter.resolve();
     56         }
     57         return;
     58     }
     59     if (m['type'] === 'io-command') {
     60         const command = m['command'];
     61         if (command && typeof command === 'object')
     62             ioCommand?.(command);
     63         return;
     64     }
     65     if (m['type'] === 'resume') {
     66         if (!pending || m['id'] !== pending.id)
     67             return;
     68         if (!validStepResume(m['value']))
     69             return;
     70         const waiter = pending;
     71         pending = undefined;
     72         waiter.resolve();
     73         return;
     74     }
     75     if (busy)
     76         return;
     77     busy = true;
     78     void (async () => {
     79         if (m['type'] === 'check-import') {
     80             if (session)
     81                 throw new Error('Already initialized');
     82             const source = sourceText(m['source']);
     83             if (typeof m['manifest'] !== 'string' || typeof m['alias'] !== 'string')
     84                 throw new Error('Invalid import request');
     85             session = await createRuntimeSession(selectedBackend, true);
     86             const stage = await session.inspectModule(source);
     87             const plan = importPlan(source, stage, m['manifest'], m['alias']);
     88             if (!plan.existing)
     89                 await session.compile(plan.source, 'empty', localModuleInputs(m['localModules']));
     90             send({ type: 'import-checked', plan });
     91         }
     92         else if (m['type'] === 'load') {
     93             if (session || stored)
     94                 throw new Error('Already initialized');
     95             const localId = m['localId'];
     96             if (localId !== undefined && (typeof localId !== 'string' || localId.length > 128))
     97                 throw new Error('Invalid catalogue ID');
     98             running();
     99             stored = await publishedBundle(bundleHash(m['hash']), localId);
    100             session = await createRuntimeSession(selectedBackend, false);
    101             await session.load(stored.bytes, stored.hash);
    102             send({ type: 'loaded', hash: stored.hash, exports: stored.bundle.exports, nodes: stored.bundle.nodes.length, metadata: stored.bundle.metadata, source: stored.source });
    103         }
    104         else if (m['type'] === 'compile') {
    105             if (session)
    106                 throw new Error('Already initialized');
    107             const source = sourceText(m['source']), profile = m['environment'];
    108             if (profile !== undefined && (typeof profile !== 'string' || profile.length > 128))
    109                 throw new Error('Invalid demo environment');
    110             running();
    111             const start = performance.now();
    112             session = await createRuntimeSession(selectedBackend, true);
    113             const names = await session.compile(source, profile, localModuleInputs(m['localModules']));
    114             send({ type: 'compiled', names, stats: session.stats(), milliseconds: performance.now() - start });
    115         }
    116         else if (m['type'] === 'export-module') {
    117             if (!session || stored)
    118                 throw new Error('Compile a draft first');
    119             running();
    120             try {
    121                 const module = await validateLocalModule(await session.exportModule());
    122                 send({ type: 'module-exported', bytes: module.bytes, hash: module.hash, manifest: module.manifest });
    123             }
    124             catch (e) {
    125                 if (session.disposed)
    126                     throw e;
    127                 send({ type: 'module-export-error', message: String(e) });
    128             }
    129         }
    130         else if (m['type'] === 'inspect') {
    131             if (!session)
    132                 throw new Error('Load or compile first');
    133             if (!Number.isSafeInteger(m['id']) || typeof m['name'] !== 'string' || m['name'].length > 4096)
    134                 throw new Error('Invalid inspection');
    135             const id = m['id'];
    136             try {
    137                 // Observe the existing binding with no arguments, expression compilation,
    138                 // or IO interpreter. Observation/decoding can still require reduction.
    139                 const name = m['name'];
    140                 if (m['output'] === 'auto' && await session.probeIO(name)) {
    141                     send({ type: 'inspected', id, io: true });
    142                     return;
    143                 }
    144                 const result = m['output'] === 'auto'
    145                     ? await autoInterpretation(output => session.inspectDefinition(name, output), () => session.disposed)
    146                     : await session.inspectDefinition(name, outputType(m['output']));
    147                 send({ type: 'inspected', id, result });
    148             }
    149             catch (e) {
    150                 send({ type: 'inspection-error', id, message: e instanceof Error ? e.message : String(e), fatal: session.disposed });
    151             }
    152         }
    153         else if (m['type'] === 'run-io') {
    154             if (!session || typeof m['name'] !== 'string' || m['name'].length > 4096)
    155                 throw new Error('Invalid IO definition');
    156             const output = outputType(m['output']);
    157             running();
    158             const result = await session.runDefinitionIO(m['name'], output, host(m['step'] === true));
    159             send({ type: 'done', result, stats: session.stats() });
    160         }
    161         else if (m['type'] === 'run') {
    162             if (!session)
    163                 throw new Error('Load or compile first');
    164             if (typeof m['export'] !== 'string' || m['export'].length > 512)
    165                 throw new Error('Invalid export');
    166             if (m['mode'] !== 'pure' && m['mode'] !== 'io')
    167                 throw new Error('Unsupported execution mode');
    168             const args = literals(m['arguments']), output = outputType(m['output']);
    169             running();
    170             const ioHost = host(m['step'] === true);
    171             const result = m['expression'] === undefined
    172                 ? await session.run(m['export'], args, output, m['mode'], ioHost)
    173                 : await session.runExpression(sourceText(m['expression']), output, m['mode'], ioHost);
    174             send({ type: 'done', result, stats: session.stats() });
    175         }
    176         else if (m['type'] === 'export' && stored) {
    177             try {
    178                 if (typeof m['export'] !== 'string')
    179                     throw new Error('Invalid export');
    180                 const format = treeFormat(m['format']);
    181                 send({ type: 'exported', hash: stored.hash, export: m['export'], format, file: exportTree(stored.bundle, m['export'], format) });
    182             }
    183             catch (e) {
    184                 send({ type: 'export-error', message: String(e) });
    185             }
    186         }
    187         else if (m['type'] === 'download' && stored)
    188             send({ type: 'downloaded', hash: stored.hash, bytes: stored.bytes });
    189         else
    190             throw new Error('Unsupported backend operation');
    191     })().catch((e) => send({ type: 'error', message: e instanceof Error ? e.message : String(e), fatal: session?.disposed ?? true }))
    192         .finally(() => { busy = false; });
    193 };