diff --git a/wasm/c/lumbda-c.loader.js b/wasm/c/lumbda-c.loader.js index 181d4fe..1e2e8e2 100644 --- a/wasm/c/lumbda-c.loader.js +++ b/wasm/c/lumbda-c.loader.js @@ -19,6 +19,14 @@ async function _bootstrap() { // through immediately so the worker can postMessage a chunk to // the UI as each line emerges (vs the user staring at a blank // panel for 18 seconds during the heavy bend call). + // + // Emscripten's print convention is "one call per line WITHOUT + // the trailing newline" — the caller is expected to add a \n + // when reassembling. Our outBuf join adds it back, but the + // streaming path needs it explicitly or successive chunks + // concatenate horizontally in the output panel (saw with + // (let loop ((i 0)) ... (display i) (newline) ... (loop ...)) + // — every tick ended up on the same line). let outBuf = []; let errBuf = []; let currentOnChunk = null; @@ -26,11 +34,11 @@ async function _bootstrap() { locateFile: (p) => wasmDir + p, print: (line) => { outBuf.push(line); - if (currentOnChunk) currentOnChunk(line); + if (currentOnChunk) currentOnChunk(line + "\n"); }, printErr: (line) => { errBuf.push(line); - if (currentOnChunk) currentOnChunk(line); + if (currentOnChunk) currentOnChunk(line + "\n"); }, }); diff --git a/wasm/dist-repl/c/lumbda-c.loader.js b/wasm/dist-repl/c/lumbda-c.loader.js index 181d4fe..1e2e8e2 100644 --- a/wasm/dist-repl/c/lumbda-c.loader.js +++ b/wasm/dist-repl/c/lumbda-c.loader.js @@ -19,6 +19,14 @@ async function _bootstrap() { // through immediately so the worker can postMessage a chunk to // the UI as each line emerges (vs the user staring at a blank // panel for 18 seconds during the heavy bend call). + // + // Emscripten's print convention is "one call per line WITHOUT + // the trailing newline" — the caller is expected to add a \n + // when reassembling. Our outBuf join adds it back, but the + // streaming path needs it explicitly or successive chunks + // concatenate horizontally in the output panel (saw with + // (let loop ((i 0)) ... (display i) (newline) ... (loop ...)) + // — every tick ended up on the same line). let outBuf = []; let errBuf = []; let currentOnChunk = null; @@ -26,11 +34,11 @@ async function _bootstrap() { locateFile: (p) => wasmDir + p, print: (line) => { outBuf.push(line); - if (currentOnChunk) currentOnChunk(line); + if (currentOnChunk) currentOnChunk(line + "\n"); }, printErr: (line) => { errBuf.push(line); - if (currentOnChunk) currentOnChunk(line); + if (currentOnChunk) currentOnChunk(line + "\n"); }, }); diff --git a/www/playground/c/lumbda-c.loader.js b/www/playground/c/lumbda-c.loader.js index 181d4fe..1e2e8e2 100644 --- a/www/playground/c/lumbda-c.loader.js +++ b/www/playground/c/lumbda-c.loader.js @@ -19,6 +19,14 @@ async function _bootstrap() { // through immediately so the worker can postMessage a chunk to // the UI as each line emerges (vs the user staring at a blank // panel for 18 seconds during the heavy bend call). + // + // Emscripten's print convention is "one call per line WITHOUT + // the trailing newline" — the caller is expected to add a \n + // when reassembling. Our outBuf join adds it back, but the + // streaming path needs it explicitly or successive chunks + // concatenate horizontally in the output panel (saw with + // (let loop ((i 0)) ... (display i) (newline) ... (loop ...)) + // — every tick ended up on the same line). let outBuf = []; let errBuf = []; let currentOnChunk = null; @@ -26,11 +34,11 @@ async function _bootstrap() { locateFile: (p) => wasmDir + p, print: (line) => { outBuf.push(line); - if (currentOnChunk) currentOnChunk(line); + if (currentOnChunk) currentOnChunk(line + "\n"); }, printErr: (line) => { errBuf.push(line); - if (currentOnChunk) currentOnChunk(line); + if (currentOnChunk) currentOnChunk(line + "\n"); }, }); diff --git a/www/repl/c/lumbda-c.loader.js b/www/repl/c/lumbda-c.loader.js index 64e78c2..1e2e8e2 100644 --- a/www/repl/c/lumbda-c.loader.js +++ b/www/repl/c/lumbda-c.loader.js @@ -13,12 +13,33 @@ async function _bootstrap() { const wasmDir = new URL("./", import.meta.url).href; const { default: createLumbdaC } = await import(/* @vite-ignore */ factoryURL); + // Streaming output: every printf in the wasm fires Module.print + // synchronously. We buffer normally for the final return, but if + // the current eval registered an onChunk callback we also call + // through immediately so the worker can postMessage a chunk to + // the UI as each line emerges (vs the user staring at a blank + // panel for 18 seconds during the heavy bend call). + // + // Emscripten's print convention is "one call per line WITHOUT + // the trailing newline" — the caller is expected to add a \n + // when reassembling. Our outBuf join adds it back, but the + // streaming path needs it explicitly or successive chunks + // concatenate horizontally in the output panel (saw with + // (let loop ((i 0)) ... (display i) (newline) ... (loop ...)) + // — every tick ended up on the same line). let outBuf = []; let errBuf = []; + let currentOnChunk = null; const module = await createLumbdaC({ locateFile: (p) => wasmDir + p, - print: (line) => outBuf.push(line), - printErr: (line) => errBuf.push(line), + print: (line) => { + outBuf.push(line); + if (currentOnChunk) currentOnChunk(line + "\n"); + }, + printErr: (line) => { + errBuf.push(line); + if (currentOnChunk) currentOnChunk(line + "\n"); + }, }); const _init = module.cwrap("lumbda_wasm_init", null, []); @@ -56,10 +77,12 @@ async function _bootstrap() { const path = `/tmp/${name}.portal`; module.FS.writeFile(path, blob); }, - async evalLisp(src) { + async evalLisp(src, onChunk) { outBuf = []; errBuf = []; + currentOnChunk = onChunk || null; const errPtr = _eval(src); + currentOnChunk = null; let errMsg = ""; if (errPtr) { errMsg = module.UTF8ToString(errPtr);