Skip to content

Commit ee634e4

Browse files
Update use of REPL
1 parent de4ffce commit ee634e4

File tree

1 file changed

+59
-193
lines changed

1 file changed

+59
-193
lines changed

assets/js/main.js

Lines changed: 59 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -2,173 +2,8 @@
22
(function () {
33
'use strict';
44

5-
/* ================================================================
6-
WASM MODULE LOADER
7-
Lazily instantiates assets/wasm/multilingual.wasm — the demo.ml
8-
program compiled to WASM by `multilingual build-wasm-bundle` via
9-
WATCodeGenerator (multilingualprogramming[wasm]).
10-
11-
The WAT backend uses host-import callbacks for all output rather
12-
than returning values. The browser must supply these imports:
13-
14-
env.print_str(ptr: i32, len: i32) — print a UTF-8 string slice
15-
env.print_f64(val: f64) — print a number
16-
env.print_bool(val: i32) — print True (1) or False (0)
17-
env.print_sep() — print an argument separator (space)
18-
env.print_newline() — print a newline
19-
20-
Output is captured into a string buffer that is reset before each
21-
call to __main().
22-
23-
The Run buttons on individual code examples call __main() and show
24-
the captured output. For a fully interactive experience with
25-
arbitrary code, visit the live playground linked on the WASM page.
26-
27-
The loader resolves the base URL from the <meta name="base-url"> tag
28-
so paths work identically on local dev and under /docs on GitHub Pages.
29-
================================================================ */
30-
315
const baseUrl = (document.querySelector('meta[name="base-url"]') || {}).content || '';
326

33-
const MLWasm = (() => {
34-
let _memory = null;
35-
let _outputBuf = '';
36-
let _modulePromise = null;
37-
38-
const dec = new TextDecoder();
39-
40-
/* Build a host import object whose callbacks write into `bufRef`.
41-
* Using an object reference lets us set `bufRef.mem` after instantiation
42-
* while keeping the closure intact. */
43-
function makeImports(bufRef) {
44-
return {
45-
env: {
46-
print_str(ptr, len) {
47-
bufRef.v += dec.decode(new Uint8Array(bufRef.mem.buffer, ptr, len));
48-
},
49-
print_f64(val) { bufRef.v += String(val); },
50-
print_bool(val) { bufRef.v += val ? 'True' : 'False'; },
51-
print_sep() { bufRef.v += ' '; },
52-
print_newline() { bufRef.v += '\n'; },
53-
},
54-
};
55-
}
56-
57-
/* Host imports for the shared demo module (uses module-level state). */
58-
const importObject = {
59-
env: {
60-
print_str(ptr, len) {
61-
_outputBuf += dec.decode(new Uint8Array(_memory.buffer, ptr, len));
62-
},
63-
print_f64(val) { _outputBuf += String(val); },
64-
print_bool(val) { _outputBuf += val ? 'True' : 'False'; },
65-
print_sep() { _outputBuf += ' '; },
66-
print_newline() { _outputBuf += '\n'; },
67-
},
68-
};
69-
70-
function load() {
71-
if (_modulePromise) return _modulePromise;
72-
73-
const wasmUrl = baseUrl + '/assets/wasm/multilingual.wasm';
74-
75-
_modulePromise = WebAssembly.instantiateStreaming(fetch(wasmUrl), importObject)
76-
.then(({ instance }) => {
77-
_memory = instance.exports.memory;
78-
return true;
79-
});
80-
81-
return _modulePromise;
82-
}
83-
84-
/* ---- WAT text loader ------------------------------------------------- */
85-
let _watPromise = null;
86-
87-
function loadWat() {
88-
if (_watPromise) return _watPromise;
89-
const watUrl = baseUrl + '/assets/wasm/multilingual.wat';
90-
_watPromise = fetch(watUrl).then(r => {
91-
if (!r.ok) throw new Error(`WAT fetch failed: ${r.status}`);
92-
return r.text();
93-
});
94-
return _watPromise;
95-
}
96-
97-
/* ---- Per-block WASM loader ------------------------------------------ */
98-
/* Each code block is compiled to its own binary during the CI build by
99-
* _scripts/compile_blocks.py. The binary is identified by the first 16
100-
* hex characters of SHA-256(code), matching the hash computed here. */
101-
const _blockModules = new Map(); // hash16 → WebAssembly.Module | null
102-
103-
async function blockHash(src) {
104-
const buf = await crypto.subtle.digest(
105-
'SHA-256', new TextEncoder().encode(src)
106-
);
107-
return Array.from(new Uint8Array(buf))
108-
.map(b => b.toString(16).padStart(2, '0'))
109-
.join('')
110-
.slice(0, 16);
111-
}
112-
113-
async function loadBlockModule(hash16) {
114-
if (_blockModules.has(hash16)) return _blockModules.get(hash16);
115-
const url = baseUrl + '/assets/wasm/blocks/' + hash16 + '.wasm';
116-
try {
117-
const mod = await WebAssembly.compileStreaming(fetch(url));
118-
_blockModules.set(hash16, mod);
119-
return mod;
120-
} catch (_) {
121-
/* Binary not available for this block — will fall back to demo. */
122-
_blockModules.set(hash16, null);
123-
return null;
124-
}
125-
}
126-
127-
/* Public API */
128-
return {
129-
/* Execute a specific code block: loads its per-block WASM binary.
130-
* hash16 is the pre-computed data-block-hash attribute injected at
131-
* build time by _scripts/inject_hashes.py — no browser-side hashing. */
132-
async execute(src, hash16) {
133-
if (!hash16) {
134-
return {
135-
stdout: '',
136-
stderr: 'This block could not be executed: no WASM binary was\n'
137-
+ 'compiled for it during the CI build (the compiler may not\n'
138-
+ 'yet support all constructs used here).\n'
139-
+ 'Try the REPL panel to run the full demo program.',
140-
};
141-
}
142-
const mod = await loadBlockModule(hash16);
143-
144-
if (!mod) {
145-
/* No per-block binary available for this code. Rather than
146-
* running the unrelated demo program, tell the user clearly. */
147-
return {
148-
stdout: '',
149-
stderr: 'This block could not be executed: no WASM binary was\n'
150-
+ 'compiled for it during the CI build (the compiler may not\n'
151-
+ 'yet support all constructs used here).\n'
152-
+ 'Try the REPL panel to run the full demo program.',
153-
};
154-
}
155-
156-
/* Instantiate the per-block module with fresh state for each run. */
157-
const bufRef = { v: '', mem: null };
158-
const instance = await WebAssembly.instantiate(mod, makeImports(bufRef));
159-
bufRef.mem = instance.exports.memory;
160-
bufRef.v = '';
161-
try { instance.exports.__main(); }
162-
catch (e) { return { stdout: bufRef.v, stderr: String(e) }; }
163-
return { stdout: bufRef.v, stderr: '' };
164-
},
165-
/* Expose the load promise so the toggle button can show readiness. */
166-
get ready() { return load(); },
167-
/* Returns a Promise<string> with the WAT text format. */
168-
get wat() { return loadWat(); },
169-
};
170-
})();
171-
1727

1738
/* ================================================================
1749
PYODIDE REPL
@@ -444,28 +279,35 @@ except Exception as _e:
444279
runBtn.setAttribute('aria-label', 'Run this code');
445280
pre.appendChild(runBtn);
446281

447-
runBtn.addEventListener('click', () => {
448-
const src = code.textContent.trim();
449-
const hash16 = pre.dataset.blockHash || '';
282+
runBtn.addEventListener('click', async () => {
283+
const src = code.textContent.trim();
284+
const lang = pre.dataset.lang || 'en';
450285
runBtn.textContent = '…';
451286
runBtn.disabled = true;
452287
outputPanel.hidden = false;
453288
outputPanel.dataset.mode = 'output';
454289
outputPanel.innerHTML = '<span class="output-running">Running…</span>';
455290

456-
/* hash16 is injected at build time by inject_hashes.py — no browser
457-
* hash computation needed. */
458-
MLWasm.execute(src, hash16)
459-
.then(result => {
460-
renderOutput(outputPanel, result);
461-
})
462-
.catch(err => {
463-
outputPanel.innerHTML = `<pre class="output-stderr">${err}</pre>`;
464-
})
465-
.finally(() => {
466-
runBtn.textContent = 'Run';
467-
runBtn.disabled = false;
291+
try {
292+
await ensureReplPyodide();
293+
_pyodide.globals.set('_block_code', src);
294+
_pyodide.globals.set('_block_lang', lang);
295+
await _pyodide.runPythonAsync(`
296+
from multilingualprogramming.codegen.executor import ProgramExecutor
297+
_r = ProgramExecutor(language=_block_lang).execute(_block_code)
298+
_block_out = _r.output or ''
299+
_block_errs = '\\n'.join(_r.errors) if _r.errors else ''
300+
`);
301+
renderOutput(outputPanel, {
302+
stdout: _pyodide.globals.get('_block_out'),
303+
stderr: _pyodide.globals.get('_block_errs'),
468304
});
305+
} catch (err) {
306+
outputPanel.innerHTML = `<pre class="output-stderr">${err}</pre>`;
307+
} finally {
308+
runBtn.textContent = 'Run';
309+
runBtn.disabled = false;
310+
}
469311
});
470312

471313
/* View WAT button — shows the compiled WebAssembly text format. */
@@ -476,7 +318,7 @@ except Exception as _e:
476318
watBtn.setAttribute('aria-label', 'View WebAssembly text format');
477319
pre.appendChild(watBtn);
478320

479-
watBtn.addEventListener('click', () => {
321+
watBtn.addEventListener('click', async () => {
480322
/* Toggle: if already showing WAT, hide the panel. */
481323
if (!outputPanel.hidden && outputPanel.dataset.mode === 'wat') {
482324
outputPanel.hidden = true;
@@ -485,20 +327,44 @@ except Exception as _e:
485327
}
486328
outputPanel.hidden = false;
487329
outputPanel.dataset.mode = 'wat';
488-
outputPanel.innerHTML = '<span class="output-running">Loading WAT…</span>';
330+
outputPanel.innerHTML = '<span class="output-running">Generating WAT…</span>';
331+
332+
const src = code.textContent.trim();
333+
const lang = pre.dataset.lang || 'en';
489334

490-
MLWasm.wat
491-
.then(text => {
335+
try {
336+
await ensureReplPyodide();
337+
_pyodide.globals.set('_block_code', src);
338+
_pyodide.globals.set('_block_lang', lang);
339+
await _pyodide.runPythonAsync(`
340+
from multilingualprogramming.lexer.lexer import Lexer
341+
from multilingualprogramming.parser.parser import Parser
342+
from multilingualprogramming.codegen.wat_generator import WATCodeGenerator
343+
try:
344+
_toks = Lexer(_block_code, language=_block_lang).tokenize()
345+
_prog = Parser(_toks, source_language=_block_lang).parse()
346+
_block_wat = WATCodeGenerator().generate(_prog)
347+
_block_wat_err = ''
348+
except Exception as _e:
349+
_block_wat = ''
350+
_block_wat_err = str(_e)
351+
`);
352+
const watSrc = _pyodide.globals.get('_block_wat');
353+
const watErr = _pyodide.globals.get('_block_wat_err');
354+
if (watErr) {
355+
outputPanel.innerHTML = `<pre class="output-stderr">${watErr}</pre>`;
356+
outputPanel.dataset.mode = '';
357+
} else {
492358
outputPanel.innerHTML = '';
493-
const pre = document.createElement('pre');
494-
pre.className = 'output-wat';
495-
pre.textContent = text;
496-
outputPanel.appendChild(pre);
497-
})
498-
.catch(() => {
499-
outputPanel.innerHTML =
500-
'<span class="output-stderr">WAT not available — run the CI build first.</span>';
501-
});
359+
const watPre = document.createElement('pre');
360+
watPre.className = 'output-wat';
361+
watPre.textContent = watSrc;
362+
outputPanel.appendChild(watPre);
363+
}
364+
} catch (err) {
365+
outputPanel.innerHTML = `<pre class="output-stderr">${err}</pre>`;
366+
outputPanel.dataset.mode = '';
367+
}
502368
});
503369
});
504370

0 commit comments

Comments
 (0)