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