Skip to content

Commit de4ffce

Browse files
Support REPL
1 parent cbcd3eb commit de4ffce

File tree

3 files changed

+165
-32
lines changed

3 files changed

+165
-32
lines changed

_layouts/default.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,25 @@ <h4>Project</h4>
130130
<span class="repl-badge" id="repl-badge">loading…</span>
131131
</span>
132132
<div class="repl-actions">
133+
<select id="repl-lang" class="repl-lang-select" title="Human language">
134+
<option value="en">English</option>
135+
<option value="fr">Français</option>
136+
<option value="de">Deutsch</option>
137+
<option value="es">Español</option>
138+
<option value="it">Italiano</option>
139+
<option value="pt">Português</option>
140+
<option value="nl">Nederlands</option>
141+
<option value="pl">Polski</option>
142+
<option value="sv">Svenska</option>
143+
<option value="da">Dansk</option>
144+
<option value="fi">Suomi</option>
145+
<option value="hi">हिन्दी</option>
146+
<option value="ar">العربية</option>
147+
<option value="bn">বাংলা</option>
148+
<option value="ta">தமிழ்</option>
149+
<option value="zh">中文</option>
150+
<option value="ja">日本語</option>
151+
</select>
133152
<button type="button" class="repl-btn" id="repl-run-btn">Run</button>
134153
<button type="button" class="repl-btn repl-btn-ghost" id="repl-wat-btn" title="Show compiled WebAssembly text format">WAT</button>
135154
<button type="button" class="repl-btn repl-btn-ghost" id="repl-clear-btn">Clear</button>

assets/css/main.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,17 @@ pre:hover .wat-btn { opacity: 1; }
13591359
gap: 0.4rem;
13601360
align-items: center;
13611361
}
1362+
.repl-lang-select {
1363+
background: var(--bg-secondary);
1364+
border: 1px solid var(--border-secondary);
1365+
border-radius: 4px;
1366+
color: var(--text-muted);
1367+
font-family: var(--font-mono);
1368+
font-size: 0.75rem;
1369+
padding: 0.25rem 0.4rem;
1370+
cursor: pointer;
1371+
}
1372+
.repl-lang-select:focus { outline: none; border-color: var(--accent-cyan); color: var(--text-primary); }
13621373
.repl-btn {
13631374
background: rgba(0, 255, 159, 0.1);
13641375
border: 1px solid rgba(0, 255, 159, 0.3);

assets/js/main.js

Lines changed: 135 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,76 @@
170170
})();
171171

172172

173+
/* ================================================================
174+
PYODIDE REPL
175+
Lazily loads Pyodide the first time the user opens the REPL panel,
176+
then uses ProgramExecutor(language=lang) to compile and execute
177+
arbitrary multilingual source code client-side.
178+
================================================================ */
179+
180+
let _pyodide = null;
181+
let _pyodideReady = false;
182+
let _pyodideInit = null; // singleton Promise
183+
184+
function _loadPyodideScript() {
185+
return new Promise((resolve, reject) => {
186+
if (typeof loadPyodide === 'function') { resolve(); return; }
187+
const s = document.createElement('script');
188+
s.src = 'https://cdn.jsdelivr.net/pyodide/v0.26.0/full/pyodide.js';
189+
s.onload = resolve;
190+
s.onerror = () => reject(new Error('Failed to load Pyodide script'));
191+
document.head.appendChild(s);
192+
});
193+
}
194+
195+
async function _initReplPyodide() {
196+
if (_pyodideReady) return;
197+
setReplStatus('loading', 'loading…');
198+
await _loadPyodideScript();
199+
200+
_pyodide = await loadPyodide({ // eslint-disable-line no-undef
201+
indexURL: 'https://cdn.jsdelivr.net/pyodide/v0.26.0/full/',
202+
});
203+
setReplStatus('loading', 'installing…');
204+
await _pyodide.loadPackage('micropip');
205+
const micropip = _pyodide.pyimport('micropip');
206+
await micropip.install('roman');
207+
await micropip.install('python-dateutil');
208+
209+
// Try a locally-built wheel first, fall back to PyPI
210+
let installed = false;
211+
try {
212+
const resp = await fetch(baseUrl + '/assets/wheel_info.json');
213+
if (resp.ok) {
214+
const info = await resp.json();
215+
const url = new URL(baseUrl + '/assets/' + info.wheel, window.location.href).href;
216+
await micropip.install(url);
217+
installed = true;
218+
}
219+
} catch (_) { /* wheel_info.json absent — fall back to PyPI */ }
220+
221+
if (!installed) {
222+
await micropip.install('multilingualprogramming');
223+
}
224+
225+
// Warm-up import so the first Run is fast
226+
await _pyodide.runPythonAsync(
227+
'from multilingualprogramming.codegen.executor import ProgramExecutor'
228+
);
229+
230+
_pyodideReady = true;
231+
setReplStatus('ready', 'ready');
232+
}
233+
234+
function ensureReplPyodide() {
235+
if (!_pyodideInit) _pyodideInit = _initReplPyodide().catch(err => {
236+
setReplStatus('error', 'error');
237+
console.error('Pyodide init failed:', err);
238+
_pyodideInit = null; // allow retry
239+
});
240+
return _pyodideInit;
241+
}
242+
173243
/* ================================================================
174244
REPL PANEL
175245
================================================================ */
@@ -224,13 +294,6 @@
224294
if (badge) badge.textContent = label;
225295
}
226296

227-
/* Warm up the WASM module and update status indicators. */
228-
function initWasm() {
229-
MLWasm.ready
230-
.then(() => setReplStatus('ready', 'ready'))
231-
.catch(() => setReplStatus('error', 'unavailable'));
232-
}
233-
234297
/* Wire up the REPL panel controls. */
235298
const replPanel = document.getElementById('repl-panel');
236299
const replToggle = document.getElementById('repl-toggle');
@@ -242,8 +305,8 @@
242305
replPanel.hidden = open;
243306
replToggle.setAttribute('aria-expanded', String(!open));
244307
if (!open) {
245-
/* Start loading WASM when the user first opens the REPL. */
246-
initWasm();
308+
/* Start loading Pyodide the first time the user opens the REPL. */
309+
ensureReplPyodide();
247310
document.getElementById('repl-input').focus();
248311
}
249312
});
@@ -261,50 +324,90 @@
261324
document.getElementById('repl-input').value = '';
262325
});
263326

264-
/* Run button. */
265-
document.getElementById('repl-run-btn').addEventListener('click', () => {
327+
/* Run button — compile and execute via Pyodide ProgramExecutor. */
328+
document.getElementById('repl-run-btn').addEventListener('click', async () => {
266329
const src = document.getElementById('repl-input').value.trim();
330+
const lang = document.getElementById('repl-lang').value;
267331
const output = document.getElementById('repl-output');
268332
if (!src) return;
269333

334+
if (!_pyodideReady) {
335+
output.innerHTML = '<span class="repl-output-placeholder">Still loading — please wait…</span>';
336+
await ensureReplPyodide();
337+
}
338+
270339
setReplStatus('running', 'running…');
271340
output.innerHTML = '<span class="repl-output-placeholder">Running…</span>';
272341

273-
MLWasm.execute(src)
274-
.then(result => {
275-
renderOutput(output, result);
276-
setReplStatus('ready', 'ready');
277-
})
278-
.catch(err => {
279-
output.innerHTML = `<pre class="output-stderr">${err}</pre>`;
280-
setReplStatus('error', 'error');
342+
try {
343+
_pyodide.globals.set('_repl_code', src);
344+
_pyodide.globals.set('_repl_lang', lang);
345+
await _pyodide.runPythonAsync(`
346+
from multilingualprogramming.codegen.executor import ProgramExecutor
347+
_r = ProgramExecutor(language=_repl_lang).execute(_repl_code)
348+
_repl_out = _r.output or ''
349+
_repl_errs = '\\n'.join(_r.errors) if _r.errors else ''
350+
`);
351+
renderOutput(output, {
352+
stdout: _pyodide.globals.get('_repl_out'),
353+
stderr: _pyodide.globals.get('_repl_errs'),
281354
});
355+
setReplStatus('ready', 'ready');
356+
} catch (err) {
357+
output.innerHTML = `<pre class="output-stderr">${err}</pre>`;
358+
setReplStatus('error', 'error');
359+
}
282360
});
283361

284-
/* WAT button — show compiled WebAssembly text in the output pane. */
285-
document.getElementById('repl-wat-btn').addEventListener('click', () => {
362+
/* WAT button — generate WAT for the current user code via Pyodide. */
363+
document.getElementById('repl-wat-btn').addEventListener('click', async () => {
286364
const output = document.getElementById('repl-output');
287-
/* Toggle off if already showing WAT. */
288365
if (output.dataset.mode === 'wat') {
289366
output.innerHTML = '<span class="repl-output-placeholder">Output will appear here</span>';
290367
delete output.dataset.mode;
291368
return;
292369
}
293-
output.innerHTML = '<span class="repl-output-placeholder">Loading WAT…</span>';
370+
371+
const src = document.getElementById('repl-input').value.trim();
372+
const lang = document.getElementById('repl-lang').value;
373+
374+
output.innerHTML = '<span class="repl-output-placeholder">Generating WAT…</span>';
294375
output.dataset.mode = 'wat';
295-
MLWasm.wat
296-
.then(text => {
376+
377+
if (!_pyodideReady) { await ensureReplPyodide(); }
378+
379+
try {
380+
_pyodide.globals.set('_repl_code', src || '');
381+
_pyodide.globals.set('_repl_lang', lang);
382+
await _pyodide.runPythonAsync(`
383+
from multilingualprogramming.lexer.lexer import Lexer
384+
from multilingualprogramming.parser.parser import Parser
385+
from multilingualprogramming.codegen.wat_generator import WATCodeGenerator
386+
try:
387+
_toks = Lexer(_repl_code, language=_repl_lang).tokenize()
388+
_prog = Parser(_toks, source_language=_repl_lang).parse()
389+
_wat_out = WATCodeGenerator().generate(_prog)
390+
_wat_err = ''
391+
except Exception as _e:
392+
_wat_out = ''
393+
_wat_err = str(_e)
394+
`);
395+
const watSrc = _pyodide.globals.get('_wat_out');
396+
const watErr = _pyodide.globals.get('_wat_err');
397+
if (watErr) {
398+
output.innerHTML = `<pre class="output-stderr">${watErr}</pre>`;
399+
delete output.dataset.mode;
400+
} else {
297401
output.innerHTML = '';
298402
const pre = document.createElement('pre');
299403
pre.className = 'output-wat';
300-
pre.textContent = text;
404+
pre.textContent = watSrc;
301405
output.appendChild(pre);
302-
})
303-
.catch(() => {
304-
output.innerHTML =
305-
'<span class="output-stderr">WAT not available — build the WASM module first.</span>';
306-
delete output.dataset.mode;
307-
});
406+
}
407+
} catch (err) {
408+
output.innerHTML = `<pre class="output-stderr">${err}</pre>`;
409+
delete output.dataset.mode;
410+
}
308411
});
309412

310413
/* Ctrl/Cmd+Enter to run. */

0 commit comments

Comments
 (0)