<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ruchy WASM REPL - E2E Test Harness</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
background: #1e1e1e;
color: #d4d4d4;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
color: #569cd6;
margin-bottom: 10px;
}
#status {
padding: 10px;
margin-bottom: 20px;
border-radius: 4px;
font-weight: bold;
}
#status.status-loading {
background: #856404;
color: #ffc107;
}
#status.status-ready {
background: #155724;
color: #28a745;
}
#status.status-error {
background: #721c24;
color: #f8d7da;
}
#output {
background: #252526;
border: 1px solid #3c3c3c;
border-radius: 4px;
padding: 15px;
min-height: 400px;
margin-bottom: 20px;
overflow-y: auto;
font-size: 14px;
line-height: 1.6;
white-space: pre-wrap;
}
.input-container {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
#repl-input {
flex: 1;
background: #3c3c3c;
border: 1px solid #555;
color: #d4d4d4;
padding: 10px;
font-family: inherit;
font-size: 14px;
border-radius: 4px;
}
#repl-input:focus {
outline: none;
border-color: #569cd6;
}
#repl-input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
button {
background: #0e639c;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
font-size: 14px;
}
button:hover {
background: #1177bb;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.controls {
display: flex;
gap: 10px;
}
.error {
color: #f48771;
}
.success {
color: #4ec9b0;
}
.prompt {
color: #608b4e;
}
.result {
color: #ce9178;
}
</style>
</head>
<body>
<div class="container">
<h1>🦀 Ruchy WASM REPL</h1>
<div id="status" class="status-loading">Loading WASM...</div>
<div id="output">Welcome to Ruchy REPL v3.67.0 WASM Edition
Type expressions to evaluate, or commands:
:help - Show help
:clear - Clear output
:quit - Not applicable in browser
</div>
<div class="input-container">
<input
type="text"
id="repl-input"
placeholder="Enter Ruchy expression..."
disabled
autocomplete="off"
/>
</div>
<div class="controls">
<button id="clear-history">Clear History</button>
<button id="reset-env">Reset Environment</button>
</div>
</div>
<script type="module">
import initWasm, { WasmRepl } from './pkg/ruchy.js';
let wasmRepl = null;
let history = [];
let historyIndex = -1;
const status = document.getElementById('status');
const output = document.getElementById('output');
const input = document.getElementById('repl-input');
const clearHistoryBtn = document.getElementById('clear-history');
const resetEnvBtn = document.getElementById('reset-env');
function loadHistory() {
const saved = localStorage.getItem('ruchy-repl-history');
if (saved) {
try {
history = JSON.parse(saved);
history.forEach(entry => {
appendOutput(`<span class="prompt">ruchy></span> ${entry.input}`, false);
if (entry.output) {
appendOutput(`<span class="result">${entry.output}</span>`, false);
}
if (entry.error) {
appendOutput(`<span class="error">Error: ${entry.error}</span>`, false);
}
});
if (history.length > 0) {
appendOutput('\n(from history)\n', false);
}
} catch (e) {
console.error('Failed to load history:', e);
}
}
}
function saveHistory() {
try {
localStorage.setItem('ruchy-repl-history', JSON.stringify(history));
} catch (e) {
console.error('Failed to save history:', e);
}
}
function appendOutput(text, saveToHistory = true) {
output.innerHTML += text + '\n';
output.scrollTop = output.scrollHeight;
}
function setStatus(state, message) {
status.className = `status-${state}`;
status.textContent = message;
}
async function executeCommand(code) {
if (!code.trim()) return;
appendOutput(`<span class="prompt">ruchy></span> ${code}`);
if (code === ':help') {
appendOutput('<span class="success">Available commands:</span>');
appendOutput(' :help - Show this help');
appendOutput(' :clear - Clear output');
appendOutput(' :quit - Not applicable in browser');
history.push({ input: code, output: 'Help displayed' });
saveHistory();
return;
}
if (code === ':clear') {
output.innerHTML = 'Welcome to Ruchy REPL v3.67.0 WASM Edition\n\n';
appendOutput('<span class="success">Output cleared</span>');
history.push({ input: code, output: 'Output cleared' });
saveHistory();
return;
}
try {
const resultJson = wasmRepl.eval(code);
const result = JSON.parse(resultJson);
if (result.success) {
appendOutput(`<span class="result">${result.display || 'OK'}</span>`);
history.push({ input: code, output: result.display });
} else {
appendOutput(`<span class="error">Error: ${result.error}</span>`);
history.push({ input: code, error: result.error });
}
saveHistory();
} catch (err) {
const errorMsg = err.message || String(err);
appendOutput(`<span class="error">Error: ${errorMsg}</span>`);
history.push({ input: code, error: errorMsg });
saveHistory();
}
}
input.addEventListener('keydown', async (e) => {
if (e.key === 'Enter') {
const code = input.value;
input.value = '';
historyIndex = -1;
await executeCommand(code);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (history.length > 0) {
if (historyIndex === -1) {
historyIndex = history.length - 1;
} else if (historyIndex > 0) {
historyIndex--;
}
input.value = history[historyIndex].input;
}
} else if (e.key === 'ArrowDown') {
e.preventDefault();
if (historyIndex !== -1) {
if (historyIndex < history.length - 1) {
historyIndex++;
input.value = history[historyIndex].input;
} else {
historyIndex = -1;
input.value = '';
}
}
}
});
clearHistoryBtn.addEventListener('click', () => {
history = [];
localStorage.removeItem('ruchy-repl-history');
output.innerHTML = 'Welcome to Ruchy REPL v3.67.0 WASM Edition\n\n';
appendOutput('<span class="success">History cleared</span>');
});
resetEnvBtn.addEventListener('click', () => {
wasmRepl = new WasmRepl();
output.innerHTML = 'Welcome to Ruchy REPL v3.67.0 WASM Edition\n\n';
appendOutput('<span class="success">Environment reset</span>');
});
async function init() {
try {
setStatus('loading', 'Loading WASM module...');
await initWasm();
wasmRepl = new WasmRepl();
loadHistory();
setStatus('ready', 'Ready');
input.disabled = false;
input.focus();
appendOutput('<span class="success">✅ WASM loaded successfully</span>\n');
} catch (err) {
setStatus('error', 'Failed to load WASM');
appendOutput(`<span class="error">❌ Failed to load WASM: ${err.message}</span>`);
}
}
init();
</script>
</body>
</html>