tricu

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

worker.js (5456B)


      1 import { sourceText } from './compiler.js';
      2 import { compileExpression } from './expression.js';
      3 import { resumeValue } from './io.js';
      4 import { publishedBundle } from './published-bundle.js';
      5 import { exportTree, treeFormat } from './tree-export.js';
      6 import { BROWSER_BUDGET, collectionPolicy, PACKAGE_COLLECTION } from './execution.js';
      7 import { Graph } from './graph.js';
      8 import { bundleHash, loadBundle } from './bundle.js';
      9 import { literals, makeLiteral, outputType, view } from './codecs.js';
     10 import { runIO } from './io.js';
     11 const scope = globalThis;
     12 const send = (message) => scope.postMessage(message);
     13 const running = () => send({ type: 'state', state: 'running' });
     14 let phase = 'empty';
     15 let graph, exports;
     16 let stored;
     17 let requestId = 0;
     18 let pending;
     19 function request(kind) {
     20     if (pending)
     21         throw new Error('Driver already waiting');
     22     const id = ++requestId;
     23     return new Promise((resolve, reject) => { pending = { id, kind, resolve, reject }; send({ type: 'state', state: kind, id }); });
     24 }
     25 async function load(hash, localId) {
     26     phase = 'loading';
     27     running();
     28     const loaded = await publishedBundle(hash, localId);
     29     const { bundle, source } = loaded;
     30     stored = loaded;
     31     const budget = BROWSER_BUDGET;
     32     graph = new Graph(budget, collectionPolicy(budget, PACKAGE_COLLECTION.threshold));
     33     exports = loadBundle(graph, bundle);
     34     phase = 'ready';
     35     send({ type: 'loaded', hash, exports: bundle.exports, nodes: bundle.nodes.length, metadata: bundle.metadata, source });
     36 }
     37 async function run(message) {
     38     if (phase !== 'ready' || !graph || !exports)
     39         throw new Error('Load a bundle before running');
     40     phase = 'running';
     41     running();
     42     const fn = message.expression === undefined ? exports.get(message.export) : await compileExpression(graph, exports, message.expression);
     43     if (fn === undefined)
     44         throw new Error('Unknown export');
     45     if (message.mode !== 'pure' && message.mode !== 'io')
     46         throw new Error('Unsupported execution mode');
     47     const type = outputType(message.output), args = literals(message.arguments).map(value => makeLiteral(graph, value));
     48     let result = message.expression === undefined ? graph.apply(fn, args) : fn;
     49     graph.collect([result]); // Exclusive one-run session: only this application remains owned.
     50     if (message.mode === 'io')
     51         result = await runIO(graph, result, {
     52             async before(event) { send({ type: 'trace', phase: 'before', event }); if (message.step === true)
     53                 await request('step'); running(); },
     54             after(event) { send({ type: 'trace', phase: 'after', event }); },
     55             write(text) { send({ type: 'output', text }); }, read() { return request('input'); }, running,
     56         }, true);
     57     const displayed = view(graph, result, type);
     58     phase = 'done';
     59     send({ type: 'done', result: displayed, stats: graph.stats });
     60 }
     61 scope.onmessage = ({ data }) => {
     62     if (!data || typeof data !== 'object' || Array.isArray(data))
     63         return;
     64     const message = data;
     65     if (message['type'] === 'resume') {
     66         if (!pending || message['id'] !== pending.id)
     67             return;
     68         const value = resumeValue(pending.kind, message['value']);
     69         if (value === undefined)
     70             return;
     71         const waiter = pending;
     72         pending = undefined;
     73         waiter.resolve(value);
     74         return;
     75     }
     76     if (message['type'] === 'download' && phase === 'ready' && stored) {
     77         send({ type: 'downloaded', hash: stored.hash, bytes: stored.bytes });
     78         return;
     79     }
     80     if (message['type'] === 'export' && phase === 'ready' && stored) {
     81         try {
     82             if (typeof message['export'] !== 'string' || message['export'].length > 512)
     83                 throw new Error('Invalid export selection');
     84             const format = treeFormat(message['format']);
     85             const file = exportTree(stored.bundle, message['export'], format);
     86             send({ type: 'exported', hash: stored.hash, export: message['export'], format, file });
     87         }
     88         catch (error) {
     89             send({ type: 'export-error', message: error instanceof Error ? error.message : String(error) });
     90         }
     91         return;
     92     }
     93     let operation;
     94     try {
     95         if (message['type'] === 'load' && phase === 'empty') {
     96             const localId = message['localId'];
     97             if (localId !== undefined && (typeof localId !== 'string' || localId.length > 128))
     98                 throw new Error('Invalid catalogue ID');
     99             operation = load(bundleHash(message['hash']), localId);
    100         }
    101         else if (message['type'] === 'run' && phase === 'ready') {
    102             if (typeof message['export'] !== 'string' || message['export'].length > 512)
    103                 throw new Error('Invalid export selection');
    104             operation = run({ type: 'run', export: message['export'], arguments: message['arguments'],
    105                 ...(message['expression'] === undefined ? {} : { expression: sourceText(message['expression']) }),
    106                 output: outputType(message['output']), mode: message['mode'], step: message['step'] === true });
    107         }
    108         else
    109             return; // A Worker owns exactly one load and one run, no racing sessions.
    110     }
    111     catch (error) {
    112         operation = Promise.reject(error);
    113     }
    114     operation.catch((error) => { phase = 'done'; send({ type: 'error', message: error instanceof Error ? error.message : String(error) }); });
    115 };