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 }