Basic REPL in the web UI

This commit is contained in:
Yura Dupyn 2026-02-13 18:59:43 +01:00
parent e2354fb9ce
commit 182307a81f
8 changed files with 467 additions and 3 deletions

107
src/ui/REPL.tsx Normal file
View file

@ -0,0 +1,107 @@
import { createSignal, Match, Switch } from 'solid-js';
import { useProgram } from './ProgramProvider';
import { eval_start } from 'src/lang/eval/evaluator';
import { Value } from 'src/lang/eval/value';
import { RuntimeError } from 'src/lang/eval/error';
import { SourceText, sourceText } from 'src/lang/parser/source_text';
import { ParseError, parseExpr } from 'src/lang/parser/parser';
import { ShowParseError } from './ParseError';
import { Val } from './Value';
namespace ReplResult {
export type Idle =
{ tag: "idle" }
export type Success =
{ tag: "success", value: Value }
export type Parse_Error =
{ tag: "parse_error", text: SourceText, err: ParseError }
export type Runtime_Error =
{ tag: "runtime_error", err: RuntimeError }
}
type ReplResult =
| ReplResult.Idle
| ReplResult.Success
| ReplResult.Parse_Error
| ReplResult.Runtime_Error
export function ExprREPL() {
const program = useProgram();
const [input, setInput] = createSignal("");
const [result, setResult] = createSignal<ReplResult>({ tag: "idle" });
function runExecution() {
const raw = input();
if (input().trim() === "") {
return;
}
const text = sourceText(raw);
const parseResult = parseExpr(text);
if (parseResult.tag === "error") {
setResult({ tag: "parse_error", text: text, err: parseResult.error });
} else {
const evalResult = eval_start(program, parseResult.value);
if (evalResult.tag === "ok") {
setResult({ tag: "success", value: evalResult.value });
} else {
setResult({ tag: "runtime_error", err: evalResult.error });
}
}
}
return (
<section>
<textarea
placeholder="+(3, 4)"
rows="5"
value={input()}
onInput={(e) => setInput(e.currentTarget.value)}
onKeyDown={(e) => {
// Check for Enter + (Ctrl or Command for Mac)
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
e.preventDefault(); // Stop the newline from being added
runExecution();
}
}}
/>
<button onClick={runExecution}>Run</button>
<hr />
<div>
<Switch>
<Match when={result().tag === "idle"}>
{ "" }
</Match>
<Match when={result().tag === "success" && result() as ReplResult.Success }>
{(res) => (
<article>
<header>Result</header>
<Val value={ res().value } />
</article>
)}
</Match>
<Match when={result().tag === "parse_error" && result() as ReplResult.Parse_Error }>
{(res) => (
<ShowParseError text={res().text} err={res().err} />
)}
</Match>
<Match when={result().tag === "runtime_error" && result() as ReplResult.Runtime_Error }>
{(res) => (
<article style={{ "border-color": "var(--pico-del-color)" }}>
<header style={{ color: "var(--pico-del-color)" }}>Runtime Error</header>
<pre>{JSON.stringify(res().err, null, 2)}</pre>
</article>
)}
</Match>
</Switch>
</div>
</section>
);
}