// The only way we can provide values to wasm_bindgen's generated code is to set globals function setGlobalsForWasmBindgen() { window.js_create_app = js_create_app; window.js_run_app = js_run_app; window.js_get_result_and_memory = js_get_result_and_memory; // The only place we use console.error is in wasm_bindgen, where it gets a single string argument. console.error = function displayErrorInHistoryPanel(string) { const html = `
${string}
`; updateHistoryEntry(repl.inputHistoryIndex, false, html); }; } setGlobalsForWasmBindgen(); import * as roc_repl_wasm from "./roc_repl_wasm.js"; import { getMockWasiImports } from "./wasi.js"; // ---------------------------------------------------------------------------- // REPL state // ---------------------------------------------------------------------------- const repl = { elemHistory: document.getElementById("history-text"), elemSourceInput: document.getElementById("source-input"), inputQueue: [], inputHistory: [], inputHistoryIndex: 0, inputStash: "", // stash the user input while we're toggling through history with up/down arrows textDecoder: new TextDecoder(), textEncoder: new TextEncoder(), compiler: null, app: null, // Temporary storage for values passing back and forth between JS and Wasm result: { addr: 0, buffer: new ArrayBuffer() }, }; // Initialise repl.elemSourceInput.addEventListener("change", onInputChange); repl.elemSourceInput.addEventListener("keyup", onInputKeyup); roc_repl_wasm.default("/repl/roc_repl_wasm_bg.wasm").then((instance) => { repl.elemHistory.querySelector("#loading-message").remove(); repl.elemSourceInput.disabled = false; repl.elemSourceInput.placeholder = "Type some Roc code and press Enter. (Use Shift+Enter for multi-line input)"; repl.compiler = instance; }); // ---------------------------------------------------------------------------- // Handle inputs // ---------------------------------------------------------------------------- function onInputChange(event) { const inputText = event.target.value.trim(); event.target.value = ""; repl.inputQueue.push(inputText); if (repl.inputQueue.length === 1) { processInputQueue(); } } function onInputKeyup(event) { const UP = 38; const DOWN = 40; const ENTER = 13; const { keyCode } = event; const el = repl.elemSourceInput; switch (keyCode) { case UP: if (repl.inputHistoryIndex == repl.inputHistory.length - 1) { repl.inputStash = el.value; } setInput(repl.inputHistory[repl.inputHistoryIndex]); if (repl.inputHistoryIndex > 0) { repl.inputHistoryIndex--; } break; case DOWN: if (repl.inputHistoryIndex === repl.inputHistory.length - 1) { setInput(repl.inputStash); } else { repl.inputHistoryIndex++; setInput(repl.inputHistory[repl.inputHistoryIndex]); } break; case ENTER: if (!event.shiftKey) { onInputChange({ target: repl.elemSourceInput }); } break; default: break; } } function setInput(value) { const el = repl.elemSourceInput; el.value = value; el.selectionStart = value.length; el.selectionEnd = value.length; } // Use a queue just in case we somehow get inputs very fast // We want the REPL to only process one at a time, since we're using some global state. // In normal usage we shouldn't see this edge case anyway. Maybe with copy/paste? async function processInputQueue() { while (repl.inputQueue.length) { const inputText = repl.inputQueue[0]; repl.inputHistoryIndex = createHistoryEntry(inputText); repl.inputStash = ""; let outputText = ""; let ok = true; if (inputText) { try { outputText = await roc_repl_wasm.entrypoint_from_js(inputText); } catch (e) { outputText = `${e}`; ok = false; } } updateHistoryEntry(repl.inputHistoryIndex, ok, outputText); repl.inputQueue.shift(); } } // ---------------------------------------------------------------------------- // Callbacks to JS from Rust // ---------------------------------------------------------------------------- // Create an executable Wasm instance from an array of bytes // (Browser validates the module and does the final compilation.) async function js_create_app(wasm_module_bytes) { const wasiLinkObject = {}; // gives the WASI functions a reference to the app so they can write to its memory const importObj = getMockWasiImports(wasiLinkObject); const { instance } = await WebAssembly.instantiate( wasm_module_bytes, importObj ); wasiLinkObject.instance = instance; repl.app = instance; } // Call the main function of the app, via the test wrapper // Cache the result and return the size of the app's memory function js_run_app() { const { wrapper, memory } = repl.app.exports; const addr = wrapper(); const { buffer } = memory; repl.result = { addr, buffer }; // Tell Rust how much space to reserve for its copy of the app's memory buffer. // This is not predictable, since the app can resize its own memory via malloc. return buffer.byteLength; } // After the Rust app has allocated space for the app's memory buffer, // it calls this function and we copy it, and return the result too function js_get_result_and_memory(buffer_alloc_addr) { const { addr, buffer } = repl.result; const appMemory = new Uint8Array(buffer); const compilerMemory = new Uint8Array(repl.compiler.memory.buffer); compilerMemory.set(appMemory, buffer_alloc_addr); return addr; } // ---------------------------------------------------------------------------- // Rendering // ---------------------------------------------------------------------------- function createHistoryEntry(inputText) { const historyIndex = repl.inputHistory.length; repl.inputHistory.push(inputText); const firstLinePrefix = '» '; const otherLinePrefix = '\n'; const inputLines = inputText.split("\n"); if (inputLines[inputLines.length - 1] === "") { inputLines.pop(); } const inputWithPrefixes = firstLinePrefix + inputLines.join(otherLinePrefix); const inputElem = document.createElement("pre"); inputElem.innerHTML = inputWithPrefixes; inputElem.classList.add("input"); const historyItem = document.createElement("div"); historyItem.appendChild(inputElem); historyItem.classList.add("history-item"); repl.elemHistory.appendChild(historyItem); repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight; return historyIndex; } function updateHistoryEntry(index, ok, outputText) { const outputElem = document.createElement("pre"); outputElem.innerHTML = outputText; outputElem.classList.add("output"); outputElem.classList.add(ok ? "output-ok" : "output-error"); const historyItem = repl.elemHistory.children[index]; historyItem.appendChild(outputElem); repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight; }