tricu

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

compiler-worker.js (7017B)


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