tricu

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

compiler-worker.js (5976B)


      1 // Test-only JS evaluator driver; excluded from the production site.
      2 import { compileExpression } from './expression.js';
      3 import { environmentProfile } from '../packages.js';
      4 import { BROWSER_BUDGET, collectionPolicy } from '../execution.js';
      5 import { contractEnvironment } from '../contracts.js';
      6 import { Graph } from '../graph.js';
      7 import { COMPILER_BUNDLE_LIMIT, fetchBundle, fetchBytes, loadBundle } from '../bundle.js';
      8 import { literals, makeLiteral, outputType, view, list, text } from '../codecs.js';
      9 import { COMPILER_EXPORT, compilerDescriptor, sourceLiteral, sourceText, compiledDefinitions } from '../compiler.js';
     10 const scope = globalThis;
     11 const base = new URL('../../', import.meta.url);
     12 const send = (message) => scope.postMessage(message);
     13 const running = () => send({ type: 'state', state: 'running' });
     14 let phase = 'empty';
     15 let graph, exports;
     16 async function compile(source, profileName) {
     17     phase = 'compiling';
     18     running();
     19     const start = performance.now();
     20     const budget = BROWSER_BUDGET;
     21     graph = new Graph(budget, collectionPolicy(budget));
     22     const input = sourceLiteral(graph, source); // Validate before downloading a compiler.
     23     const bytes = await fetchBytes(new URL('compiler.json', base), 4096);
     24     const raw = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
     25     const { hash } = compilerDescriptor(raw);
     26     const bundle = await fetchBundle(new URL(`/_arboricx/bundle/hash/${hash}`, base), hash, COMPILER_BUNDLE_LIMIT);
     27     const library = loadBundle(graph, bundle), compiler = library.get(COMPILER_EXPORT);
     28     if (compiler === undefined)
     29         throw new Error('Missing stable compiler entrypoint');
     30     let environment;
     31     if (profileName === undefined)
     32         environment = contractEnvironment(graph, library);
     33     else {
     34         const bytes = await fetchBytes(new URL('environments.json', base), 32_768);
     35         const selected = environmentProfile(JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)), profileName);
     36         if (selected.compiler !== hash)
     37             throw new Error('Demo environment/compiler pin mismatch');
     38         const bindings = [];
     39         if (selected.profile) {
     40             const support = await fetchBundle(new URL(`/_arboricx/bundle/hash/${selected.profile.hash}`, base), selected.profile.hash);
     41             const values = loadBundle(graph, support);
     42             for (const name of selected.profile.names) {
     43                 const tree = values.get(name);
     44                 if (tree === undefined)
     45                     throw new Error(`Missing demo environment binding ${name}`);
     46                 bindings.push(graph.fork(text(graph, name), tree));
     47             }
     48         }
     49         environment = list(graph, bindings);
     50     }
     51     exports = compiledDefinitions(graph, graph.apply(compiler, [environment, input]));
     52     graph.collect(exports.values()); // Safe phase boundary; compiler/input temporaries are no longer caller roots.
     53     phase = 'ready';
     54     send({ type: 'compiled', names: [...exports.keys()], draft: { graph: graph.checkpoint(), exports: [...exports] }, stats: graph.stats, milliseconds: performance.now() - start });
     55 }
     56 async function run(message) {
     57     if (!graph || !exports)
     58         throw new Error('Compile a unit first');
     59     phase = 'running';
     60     running();
     61     const fn = message.expression === undefined ? exports.get(message.export) : await compileExpression(graph, exports, message.expression);
     62     if (fn === undefined)
     63         throw new Error('Unknown compiled definition');
     64     if (message.mode !== 'pure' && message.mode !== 'io')
     65         throw new Error('Unsupported execution mode');
     66     const type = outputType(message.output), args = literals(message.arguments).map(value => makeLiteral(graph, value));
     67     let result = message.expression === undefined ? graph.apply(fn, args) : fn;
     68     if (message.mode === 'io')
     69         throw new Error('Reference graph IO execution is retired; use a native runtime');
     70     const displayed = view(graph, result, type);
     71     graph.collect(exports.values()); // Drop this run's temporaries, retaining every definition.
     72     phase = 'ready';
     73     send({ type: 'done', result: displayed, stats: graph.stats });
     74 }
     75 scope.onmessage = ({ data }) => {
     76     if (!data || typeof data !== 'object' || Array.isArray(data))
     77         return;
     78     const message = data;
     79     let operation;
     80     try {
     81         if (message['type'] === 'restore' && phase === 'empty') {
     82             // Only the owning conformance harness supplies this trusted in-memory checkpoint.
     83             const restored = data;
     84             const budget = BROWSER_BUDGET;
     85             graph = Graph.restore(restored.draft.graph, budget, collectionPolicy(budget));
     86             exports = new Map(restored.draft.exports);
     87             phase = 'ready';
     88             return;
     89         }
     90         else if (message['type'] === 'compile' && phase === 'empty') {
     91             if (message['environment'] !== undefined && (typeof message['environment'] !== 'string' || message['environment'].length > 128))
     92                 throw new Error('Invalid demo environment');
     93             operation = compile(message['source'], message['environment']);
     94         }
     95         else if (message['type'] === 'run' && phase === 'ready') {
     96             if (typeof message['export'] !== 'string' || message['export'].length > 128)
     97                 throw new Error('Invalid compiled selection');
     98             operation = run({ type: 'run', export: message['export'], arguments: message['arguments'],
     99                 ...(message['expression'] === undefined ? {} : { expression: sourceText(message['expression']) }),
    100                 output: outputType(message['output']), mode: message['mode'], step: message['step'] === true });
    101         }
    102         else
    103             return;
    104     }
    105     catch (error) {
    106         operation = Promise.reject(error);
    107     }
    108     operation.catch((error) => { phase = 'done'; send({ type: 'error', message: error instanceof Error ? error.message : String(error) }); });
    109 };