tricu

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

io.js (3029B)


      1 import { readNatural, readText, line } from './codecs.js';
      2 // collectActions requires exclusive ownership: unrelated caller handles may be
      3 // invalidated unless included in retainedRoots. Normal callers retain the
      4 // non-collecting API by default. Snapshot extra roots before the first action.
      5 export async function runIO(graph, handle, host, collectActions = false, retainedRoots = []) {
      6     const retained = [...retainedRoots];
      7     const [sentinel, body] = graph.pair(handle, 'IO envelope');
      8     if (readText(graph, sentinel, 7) !== 'tricuIO')
      9         throw new Error('Missing tricuIO sentinel');
     10     const [version, initial] = graph.pair(body, 'IO version/action');
     11     if (readNatural(graph, version) !== 1n)
     12         throw new Error('Unsupported tricuIO version');
     13     const frames = [];
     14     let action = initial, output = 0;
     15     const names = new Map([[0n, 'pure'], [1n, 'bind'], [2n, 'abortIO'], [10n, 'putStr'], [11n, 'getLine']]);
     16     for (let step = 1; step <= 128; step++) {
     17         if (collectActions)
     18             graph.collect([action, ...frames, ...retained]);
     19         const [tag, payload] = graph.pair(action, 'action');
     20         const code = readNatural(graph, tag), name = names.get(code);
     21         if (!name)
     22             throw new Error(`Unsupported browser IO action ${code}`);
     23         // Don't inspect arbitrary pure/abort payloads merely to build a trace.
     24         const detail = code === 10n ? readText(graph, payload) : `node #${payload}`;
     25         await host.before({ step, name, detail, depth: frames.length });
     26         if (code === 1n) {
     27             const [left, continuation] = graph.pair(payload, 'bind');
     28             frames.push(continuation);
     29             action = left;
     30             host.after({ step, name, value: 'continuation handle saved' });
     31             continue;
     32         }
     33         if (code === 2n)
     34             throw new Error(`Execution aborted (diagnostic node #${payload}; no payload forcing).`);
     35         let value;
     36         if (code === 0n)
     37             value = payload;
     38         else if (code === 10n) {
     39             output += Array.from(detail).length;
     40             if (output > 16_384)
     41                 throw new Error('Terminal output limit exceeded');
     42             host.write(detail);
     43             value = graph.leaf;
     44         }
     45         else
     46             value = line(graph, await host.read());
     47         host.after({ step, name, value: `node #${value}` });
     48         if (!frames.length)
     49             return value;
     50         host.running();
     51         action = graph.app(frames.pop(), value);
     52     }
     53     throw new Error('IO action limit exceeded');
     54 }
     55 // Ignore malformed/wrong-kind/stale resumes without consuming the waiting token.
     56 // The page validates interactively; the Worker must also retain a retryable wait.
     57 export function resumeValue(kind, value) {
     58     if (kind === 'step')
     59         return value === undefined ? '' : undefined;
     60     if (typeof value !== 'string' || value.length > 512 || /[\r\n\uD800-\uDFFF]/u.test(value) || Array.from(value).length > 256)
     61         return undefined;
     62     return value;
     63 }