//! Generate JavaScript and HTML loaders for running exported WASM in the browser. use crate::export::ExportMetadata; /// Generate a JavaScript loader that instantiates a WAFER `.wasm` module. /// /// The loader provides the six required imports (emit, memory, dsp, rsp, fsp, /// table) and host-function stubs, then calls `_start`. pub fn generate_js_loader(wasm_filename: &str, metadata: &ExportMetadata) -> String { let (dsp, rsp, fsp) = (metadata.dsp_init, metadata.rsp_init, metadata.fsp_init); let memory_pages = metadata.memory_size.div_ceil(65536).max(16); // Build the host function registration code. let mut host_registrations = String::new(); for (idx, name) in &metadata.host_functions { let js_impl = js_host_function(name); host_registrations.push_str(&format!(" table.set({idx}, {js_impl});\n")); } format!( r#"// WAFER JS Loader - generated by wafer build --js // Loads and runs {wasm_filename} in the browser. const WAFER = (() => {{ const CELL_SIZE = 4; const DATA_STACK_TOP = 0x1540; const SYSVAR_BASE = 0x0004; let outputCallback = (s) => {{ const el = document.getElementById('output'); if (el) el.textContent += s; else console.log(s); }}; async function run(opts) {{ if (opts && opts.output) outputCallback = opts.output; const memory = new WebAssembly.Memory({{ initial: {memory_pages} }}); const dsp = new WebAssembly.Global({{ value: 'i32', mutable: true }}, {dsp}); const rsp = new WebAssembly.Global({{ value: 'i32', mutable: true }}, {rsp}); const fsp = new WebAssembly.Global({{ value: 'i32', mutable: true }}, {fsp}); const table = new WebAssembly.Table({{ element: 'anyfunc', initial: 256 }}); function emit(code) {{ outputCallback(String.fromCharCode(code)); }} const importObject = {{ env: {{ emit, memory, dsp, rsp, fsp, table }} }}; // Register host functions const view = () => new DataView(memory.buffer); const pop = () => {{ const sp = dsp.value; const v = view().getInt32(sp, true); dsp.value = sp + CELL_SIZE; return v; }}; const push = (v) => {{ const sp = dsp.value - CELL_SIZE; view().setInt32(sp, v, true); dsp.value = sp; }}; {host_registrations} const response = await fetch('{wasm_filename}'); const bytes = await response.arrayBuffer(); const {{ instance }} = await WebAssembly.instantiate(bytes, importObject); if (instance.exports._start) {{ instance.exports._start(); }} return instance; }} return {{ run }}; }})(); "# ) } /// Generate a minimal HTML page that loads the JS loader. pub fn generate_html_page(wasm_filename: &str, js_filename: &str) -> String { format!( r#" WAFER - {wasm_filename}

WAFER Output

"# ) } /// Return a JS expression that creates a `WebAssembly.Function` for a known /// host word. Falls back to a stub that logs an error. fn js_host_function(name: &str) -> &'static str { match name { "." => { r#"new WebAssembly.Function({parameters:[], results:[]}, () => { const n = pop(); const base = view().getUint32(SYSVAR_BASE, true); outputCallback((base === 16 ? n.toString(16).toUpperCase() : n.toString()) + ' '); })"# } "U." => { r#"new WebAssembly.Function({parameters:[], results:[]}, () => { const n = pop() >>> 0; const base = view().getUint32(SYSVAR_BASE, true); outputCallback((base === 16 ? n.toString(16).toUpperCase() : n.toString()) + ' '); })"# } "TYPE" => { r#"new WebAssembly.Function({parameters:[], results:[]}, () => { const len = pop(); const addr = pop(); const bytes = new Uint8Array(memory.buffer, addr, len); outputCallback(new TextDecoder().decode(bytes)); })"# } "SPACES" => { r#"new WebAssembly.Function({parameters:[], results:[]}, () => { const n = pop(); if (n > 0) outputCallback(' '.repeat(n)); })"# } ".S" => { r#"new WebAssembly.Function({parameters:[], results:[]}, () => { const sp = dsp.value; const depth = (DATA_STACK_TOP - sp) / CELL_SIZE; let s = '<' + depth + '> '; for (let a = DATA_STACK_TOP - CELL_SIZE; a >= sp; a -= CELL_SIZE) { s += view().getInt32(a, true) + ' '; } outputCallback(s); })"# } "DEPTH" => { r#"new WebAssembly.Function({parameters:[], results:[]}, () => { const depth = (DATA_STACK_TOP - dsp.value) / CELL_SIZE; push(depth); })"# } _ => { r#"new WebAssembly.Function({parameters:[], results:[]}, () => { console.error('Host function not available in standalone mode'); })"# } } }