From 91b7598b22f89319f64054daf42c950de3eb6451 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Wed, 7 Jan 2026 19:29:30 -0800 Subject: Adding some of my favorite toys --- src/toys/godel/js/ui.js | 228 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 src/toys/godel/js/ui.js (limited to 'src/toys/godel/js/ui.js') diff --git a/src/toys/godel/js/ui.js b/src/toys/godel/js/ui.js new file mode 100644 index 0000000..2952140 --- /dev/null +++ b/src/toys/godel/js/ui.js @@ -0,0 +1,228 @@ +import parser from "./parser.js"; +import { compileProgram } from "./compiler.js"; + +const SAMPLE_PROGRAM = `// THIS PROGRAM COMPUTES X1 + X2 + +// Y <- X1 +[ A1 ] IF X1 != 0 GOTO A2 + GOTO B1 +[ A2 ] X1 <- X1 - 1 + Y <- Y + 1 + GOTO A1 + +// Z1 <- X2 +[ B1 ] IF X2 != 0 GOTO B2 + GOTO C1 +[ B2 ] X2 <- X2 - 1 + Z1 <- Z1 + 1 + GOTO B1 + +// Y <- Y + Z1 +[ C1 ] IF Z1 != 0 GOTO C2 + GOTO E1 +[ C2 ] Z1 <- Z1 - 1 + Y <- Y + 1 + GOTO C1`; + +const STATUS_VARIANTS = { + success: "text-success", + error: "text-error", + info: "muted" +}; + +export class GodelPlayground { + constructor() { + this.elements = { + compileBtn: document.getElementById("compile-btn"), + copyBtn: document.getElementById("copy-btn"), + evalBtn: document.getElementById("eval-btn"), + computeNumberBtn: document.getElementById("compute-godel-btn"), + compileStatus: document.getElementById("compile-status"), + evalStatus: document.getElementById("eval-status"), + godelSequence: document.getElementById("godel-sequence"), + godelNumber: document.getElementById("godel-number") + }; + + this.sourceEditor = null; + this.compiledEditor = null; + this.worker = null; + this.latestSequence = []; + + this.init(); + } + + init() { + this.setupEditors(); + this.bindEvents(); + this.hydrateFromParams(); + if (!this.getSource().trim()) { + this.setSource(SAMPLE_PROGRAM); + } + this.compileSource(); + } + + setupEditors() { + this.sourceEditor = adelieEditor.init("#source-editor", { + language: "javascript" + }); + this.compiledEditor = adelieEditor.init("#compiled-editor", { + language: "javascript" + }); + this.setSource(SAMPLE_PROGRAM); + } + + bindEvents() { + this.elements.compileBtn.addEventListener("click", () => this.compileSource()); + this.elements.evalBtn.addEventListener("click", () => this.evaluateCompiled()); + this.elements.copyBtn.addEventListener("click", () => this.copyShareLink()); + this.elements.computeNumberBtn.addEventListener("click", () => this.computeGodelNumber()); + + document.addEventListener("keydown", (event) => { + if (event.ctrlKey && event.key === "Enter") { + event.preventDefault(); + this.compileSource(); + } + }); + } + + hydrateFromParams() { + const params = new URLSearchParams(window.location.search); + const encoded = params.get("instructions"); + if (encoded) { + try { + const decoded = atob(encoded); + this.setSource(decoded); + } catch (error) { + console.warn("Failed to decode instructions from URL", error); + } + } + } + + getSource() { + return this.sourceEditor.state.doc.toString(); + } + + setSource(content) { + const docLength = this.sourceEditor.state.doc.toString().length; + this.sourceEditor.dispatch({ + changes: { from: 0, to: docLength, insert: content } + }); + } + + getCompiled() { + return this.compiledEditor.state.doc.toString(); + } + + setCompiled(content) { + const docLength = this.compiledEditor.state.doc.toString().length; + this.compiledEditor.dispatch({ + changes: { from: 0, to: docLength, insert: content } + }); + } + + prepareSource(source) { + return source.replace(/\/\/.*$/gm, "").trim(); + } + + compileSource() { + const raw = this.getSource(); + const prepared = this.prepareSource(raw); + + if (!prepared) { + this.setStatus("compile", "Provide some source to compile", "error"); + return; + } + + try { + const ast = parser.parse(prepared); + const { js, godelSequence } = compileProgram(ast); + this.latestSequence = godelSequence; + this.setCompiled(js); + this.renderSequence(godelSequence); + this.setStatus( + "compile", + `Compiled ${godelSequence.length} instruction${godelSequence.length === 1 ? "" : "s"}.`, + "success" + ); + this.elements.computeNumberBtn.disabled = godelSequence.length === 0; + } catch (error) { + this.latestSequence = []; + this.renderSequence([]); + this.elements.computeNumberBtn.disabled = true; + this.setStatus("compile", error.message || "Failed to compile program", "error"); + } + } + + evaluateCompiled() { + const js = this.getCompiled(); + if (!js.trim()) { + this.setStatus("eval", "Compile a program first", "error"); + return; + } + + try { + const result = (0, eval)(js); + this.setStatus( + "eval", + `Result: ${typeof result === "undefined" ? "(no return)" : result}`, + "success" + ); + } catch (error) { + this.setStatus("eval", error.message || "Failed to evaluate program", "error"); + } + } + + renderSequence(sequence) { + if (!sequence.length) { + this.elements.godelSequence.textContent = "Compile to view the Gödel sequence."; + this.elements.godelNumber.textContent = ""; + return; + } + this.elements.godelSequence.textContent = `[${sequence.join(", ")}]`; + this.elements.godelNumber.textContent = ""; + } + + copyShareLink() { + const data = btoa(this.getSource()); + const url = `${window.location.href.split("?")[0]}?instructions=${data}`; + + navigator.clipboard.writeText(url) + .then(() => alert("Shareable link copied to clipboard")) + .catch(() => alert("Failed to copy link")); + } + + computeGodelNumber() { + if (!this.latestSequence.length) { + this.setStatus("compile", "Compile a program to produce its Gödel sequence", "error"); + return; + } + + this.elements.godelNumber.textContent = "Working..."; + this.elements.computeNumberBtn.disabled = true; + + const worker = this.getWorker(); + worker.onmessage = (event) => { + this.elements.godelNumber.textContent = event.data; + this.elements.computeNumberBtn.disabled = false; + }; + worker.onerror = () => { + this.elements.godelNumber.textContent = "Failed to compute Gödel number"; + this.elements.computeNumberBtn.disabled = false; + }; + worker.postMessage(this.latestSequence); + } + + getWorker() { + if (!this.worker) { + const workerUrl = new URL("./godel-worker.js", import.meta.url); + this.worker = new Worker(workerUrl, { type: "module" }); + } + return this.worker; + } + + setStatus(kind, message, variant = "info") { + const element = kind === "compile" ? this.elements.compileStatus : this.elements.evalStatus; + element.textContent = message; + element.className = `status-text ${STATUS_VARIANTS[variant] || ""}`; + } +} -- cgit v1.2.3-70-g09d2