tricu

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

wasm-worker.js (6464B)


      1 import { importPlan } from './import-plan.js';
      2 import { localModuleInputs, validateLocalModule } from './local-modules.js';
      3 import { resumeValue } from './io.js';
      4 import { createRuntimeSession } 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 const scope = globalThis;
     12 const send = (m) => scope.postMessage(m);
     13 const running = () => send({ type: 'state', state: 'running' });
     14 let session, stored;
     15 let busy = false, requestId = 0;
     16 let pending;
     17 function request(kind) {
     18     if (pending)
     19         throw new Error('Driver already waiting');
     20     const id = ++requestId;
     21     return new Promise((resolve, reject) => { pending = { id, resolve, reject, kind }; send({ type: 'state', state: kind, id }); });
     22 }
     23 scope.onmessage = ({ data }) => {
     24     if (!data || typeof data !== 'object' || Array.isArray(data))
     25         return;
     26     const m = data;
     27     if (m['type'] === 'resume') {
     28         if (!pending || m['id'] !== pending.id)
     29             return;
     30         const value = resumeValue(pending.kind, m['value']);
     31         if (value === undefined)
     32             return;
     33         const waiter = pending;
     34         pending = undefined;
     35         waiter.resolve(value);
     36         return;
     37     }
     38     if (busy)
     39         return;
     40     busy = true;
     41     void (async () => {
     42         if (m['type'] === 'check-import') {
     43             if (session)
     44                 throw new Error('Already initialized');
     45             const source = sourceText(m['source']);
     46             if (typeof m['manifest'] !== 'string' || typeof m['alias'] !== 'string')
     47                 throw new Error('Invalid import request');
     48             session = await createRuntimeSession(selectedBackend, true);
     49             const stage = await session.inspectModule(source);
     50             const plan = importPlan(source, stage, m['manifest'], m['alias']);
     51             if (!plan.existing)
     52                 await session.compile(plan.source, 'empty', localModuleInputs(m['localModules']));
     53             send({ type: 'import-checked', plan });
     54         }
     55         else if (m['type'] === 'load') {
     56             if (session || stored)
     57                 throw new Error('Already initialized');
     58             const localId = m['localId'];
     59             if (localId !== undefined && (typeof localId !== 'string' || localId.length > 128))
     60                 throw new Error('Invalid catalogue ID');
     61             running();
     62             stored = await publishedBundle(bundleHash(m['hash']), localId);
     63             session = await createRuntimeSession(selectedBackend, false);
     64             await session.load(stored.bytes, stored.hash);
     65             send({ type: 'loaded', hash: stored.hash, exports: stored.bundle.exports, nodes: stored.bundle.nodes.length, metadata: stored.bundle.metadata, source: stored.source });
     66         }
     67         else if (m['type'] === 'compile') {
     68             if (session)
     69                 throw new Error('Already initialized');
     70             const source = sourceText(m['source']), profile = m['environment'];
     71             if (profile !== undefined && (typeof profile !== 'string' || profile.length > 128))
     72                 throw new Error('Invalid demo environment');
     73             running();
     74             const start = performance.now();
     75             session = await createRuntimeSession(selectedBackend, true);
     76             const names = await session.compile(source, profile, localModuleInputs(m['localModules']));
     77             send({ type: 'compiled', names, stats: session.stats(), milliseconds: performance.now() - start });
     78         }
     79         else if (m['type'] === 'export-module') {
     80             if (!session || stored)
     81                 throw new Error('Compile a draft first');
     82             running();
     83             try {
     84                 const module = await validateLocalModule(await session.exportModule());
     85                 send({ type: 'module-exported', bytes: module.bytes, hash: module.hash, manifest: module.manifest });
     86             }
     87             catch (e) {
     88                 if (session.disposed)
     89                     throw e;
     90                 send({ type: 'module-export-error', message: String(e) });
     91             }
     92         }
     93         else if (m['type'] === 'run') {
     94             if (!session)
     95                 throw new Error('Load or compile first');
     96             if (typeof m['export'] !== 'string' || m['export'].length > 512)
     97                 throw new Error('Invalid export');
     98             if (m['mode'] !== 'pure' && m['mode'] !== 'io')
     99                 throw new Error('Unsupported execution mode');
    100             const args = literals(m['arguments']), output = outputType(m['output']);
    101             running();
    102             const host = {
    103                 async before(event) { send({ type: 'trace', phase: 'before', event }); if (m['step'] === true)
    104                     await request('step'); running(); },
    105                 after(event) { send({ type: 'trace', phase: 'after', event }); }, write(text) { send({ type: 'output', text }); },
    106                 read() { return request('input'); }, running,
    107             };
    108             const result = m['expression'] === undefined
    109                 ? await session.run(m['export'], args, output, m['mode'], host)
    110                 : await session.runExpression(sourceText(m['expression']), output, m['mode'], host);
    111             send({ type: 'done', result, stats: session.stats() });
    112         }
    113         else if (m['type'] === 'export' && stored) {
    114             try {
    115                 if (typeof m['export'] !== 'string')
    116                     throw new Error('Invalid export');
    117                 const format = treeFormat(m['format']);
    118                 send({ type: 'exported', hash: stored.hash, export: m['export'], format, file: exportTree(stored.bundle, m['export'], format) });
    119             }
    120             catch (e) {
    121                 send({ type: 'export-error', message: String(e) });
    122             }
    123         }
    124         else if (m['type'] === 'download' && stored)
    125             send({ type: 'downloaded', hash: stored.hash, bytes: stored.bytes });
    126         else
    127             throw new Error('Unsupported backend operation');
    128     })().catch((e) => send({ type: 'error', message: e instanceof Error ? e.message : String(e), fatal: session?.disposed ?? true }))
    129         .finally(() => { busy = false; });
    130 };