Skip to content

Instantly share code, notes, and snippets.

@SimonMeskens
Last active February 4, 2025 06:01
Show Gist options
  • Save SimonMeskens/5106836798561491eaa385aef4d54e77 to your computer and use it in GitHub Desktop.
Save SimonMeskens/5106836798561491eaa385aef4d54e77 to your computer and use it in GitHub Desktop.
const top = (stack) => stack.length - 1;
const zero = ({ left }) => left.push(0);
const pop = ({ right }) => right.pop();
const increment = ({ left }) => (left[top(left)] = (left[top(left)] + 1) % 256);
const decrement = ({ left }) =>
(left[top(left)] = (left[top(left)] + 255) % 256);
const shift = ({ left, right }) => right.push(left.pop());
const unshift = ({ left, right }) => left.push(right.pop());
const jump = (state) => {
if (state.left[top(state.left)] === 0) {
let depth = 1;
while (++state.ip < state.instructions.length) {
if (state.instructions[state.ip] === jump) depth++;
if (state.instructions[state.ip] === dejump) depth--;
if (depth === 0) break;
}
}
};
const dejump = (state) => {
if (state.right[top(state.right)] !== 0) {
let depth = 1;
while (--state.ip >= 0) {
if (state.instructions[state.ip] === dejump) depth++;
if (state.instructions[state.ip] === jump) depth--;
if (depth === 0) break;
}
}
};
const parse = (source, map) => {
const instructions = [];
const left = [];
const right = [];
let ip = 0;
for (let i = 0; i < source.length; i++) {
if (source[i] === "#") while (source[++i] !== "\n");
const op = map[source[i]];
if (op) instructions.push(op);
}
return { instructions, ip, left, right };
};
export const run = async (source, log, prompt) => {
// prettier-ignore
const map = {
"0": zero, "!": pop,
"+": increment, "-": decrement,
">": shift, "<": unshift,
"[": jump, "]": dejump,
};
map["."] = async ({ right }) =>
await log(`${right[top(right)].toString(16).padStart(2, "0")} `);
map[","] = async ({ left }) =>
(left[top(left)] = parseInt(await prompt("2-digit hex number"), 16));
const state = parse(source, map);
while (state.ip < state.instructions.length) {
await state.instructions[state.ip](state);
state.ip++;
}
return state;
};
<!DOCTYPE html>
<html lang="en" color-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Playground</title>
<link
rel="stylesheet"
type="text/css"
href="https://simonmeskens.github.io/doodlit-editor/src/theme.css"
/>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
min-height: 100%;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.2;
font-size: 1rem;
}
.playground {
position: relative;
display: grid;
grid-template-columns: 50% 1fr;
grid-template-rows: 1fr auto;
min-height: 50vh;
}
.playground .code-editor {
box-sizing: content-box;
margin: 0;
grid-column: 1;
grid-row: 1 / 3;
}
.playground .terminal {
grid-column: 2;
grid-row: 1;
overflow: auto;
padding: 1rem;
}
.playground .input {
grid-column: 2;
grid-row: 2;
display: block;
width: 100%;
padding: 0.2rem 0.5rem;
border: 0;
outline: 0;
color: #000;
background-color: #ddd;
font-size: 2rem;
font-weight: bold;
}
.playground .button {
position: absolute;
z-index: 100;
right: 50%;
bottom: 0;
padding: 0.2rem 0.5rem;
background-color: #ddd;
color: #f74;
border: 0;
font-size: 2rem;
font-weight: bold;
cursor: pointer;
}
.playground .button:hover {
background-color: #ccc;
color: #d41;
}
.playground .button:active {
background: #ccc;
color: #000;
}
.documentation {
color: #111;
padding: 2em;
}
.documentation code {
background-color: #ddd;
color: #f74;
font-size: 1.2rem;
padding: 0 0.5rem;
}
.theme-selector {
font-size: 2em;
border: 0;
outline: 0;
background: transparent;
cursor: pointer;
}
[color-theme="dark"] body {
background-color: #000;
color: #fff;
color-scheme: dark;
}
[color-theme="dark"] .playground .input {
color: #fff;
background-color: #222;
}
[color-theme="dark"] .playground .button {
background-color: #222;
color: #f74;
}
[color-theme="dark"] .playground .button:hover {
background-color: #333;
color: #fb4;
}
[color-theme="dark"] .playground .button:active {
background: #333;
color: #fff;
}
[color-theme="dark"] .documentation {
color: #eee;
}
[color-theme="dark"] .documentation code {
background-color: #222;
color: #f74;
}
[color-theme="dark"] {
--doodlit-theme-background: #111;
--doodlit-theme-text: #fff;
--doodlit-theme-text-muted: #aaa;
}
[color-theme]:not([color-theme="dark"]) {
--doodlit-theme-background: #eee;
--doodlit-theme-text: #000;
--doodlit-theme-text-muted: #666;
}
</style>
<link
rel="stylesheet"
type="text/css"
href="https://simonmeskens.github.io/doodlit-editor/src/editor.css"
/>
<script type="module">
import { createEditors } from "https://simonmeskens.github.io/doodlit-editor/src/editor.js";
import { highlight as microlight } from "https://simonmeskens.github.io/doodlit-editor/demo/microlight.js";
import { run } from "./2PDA.js";
createEditors(".code-editor", { highlight: microlight });
const playground = document.querySelector(".playground");
const editor = playground.querySelector(".code-editor textarea");
const terminal = playground.querySelector(".terminal");
const form = playground.querySelector("form");
const button = playground.querySelector(".button");
const input = playground.querySelector(".input");
const themeSelector = document.createElement("button");
themeSelector.textContent =
document.documentElement.getAttribute("color-theme") === "dark"
? "🌚"
: "🌞";
themeSelector.classList.add("theme-selector");
document.body.prepend(themeSelector);
themeSelector.addEventListener("click", (e) => {
const theme =
document.documentElement.getAttribute("color-theme") !== "dark"
? "dark"
: "light";
themeSelector.textContent = theme === "dark" ? "🌚" : "🌞";
document.documentElement.setAttribute("color-theme", theme);
});
let callback = null;
form.addEventListener("submit", (e) => {
if (/[0-9a-fA-F]{2}/.test(input.value)) {
if (callback) {
callback(input.value);
terminal.innerHTML += `<span style="color: #f74">${input.value} </span>`;
}
callback = null;
}
e.preventDefault();
return false;
});
input.addEventListener("keydown", (e) => {
if (!/[0-9a-fA-F]/.test(e.key)) e.preventDefault();
});
const log = (text) => {
terminal.innerHTML += text;
};
const prompt = (placeholder) => {
if (callback) return e.preventDefault();
button.value = "ENTER";
input.disabled = false;
input.value = "";
input.placeholder = placeholder;
input.focus();
return new Promise((resolve) => (callback = resolve));
};
let running = false;
document.body
.querySelector(".playground .button")
.addEventListener("click", async (e) => {
if (running) return;
terminal.textContent = "";
running = true;
const source = editor.value;
const output = await run(source, log, prompt);
console.log(output);
running = false;
button.value = "RUN";
input.disabled = true;
input.value = "";
input.placeholder = "";
editor.focus();
});
</script>
</head>
<body>
<section class="playground">
<pre class="code-editor">
<code># Let's add two bytes
0, # Create and input cell 1
0, # Create and input cell 2
> # Loop expects cell 2 on the right
[ # If cell 1 is zero, jump to the end
<- # Deshift cell 2 to decrement it
>+ # Shift cell 2 to increment cell 1
] # While cell 2 is not zero, jump back
>. # Output cell 1
!! # Delete cells
</code>
</pre>
<pre class="terminal"></pre>
<form>
<input type="submit" value="RUN" class="button" />
<input type="text" maxlength="2" class="input" disabled />
</form>
</section>
<section class="documentation">
<p>Instructions operating on two stacks of bytes:</p>
<ul>
<li><code>0</code>: Push 00 to the left stack</li>
<li><code>!</code>: Pop the top of right off the stack</li>
<li><code>+</code>: Increment top of left by 1</li>
<li><code>-</code>: Decrement top of left by 1</li>
<li><code>&gt;</code>: Shift top of left to top of right</li>
<li><code>&lt;</code>: Shift top of right to top of left</li>
<li><code>.</code>: Output top of right to screen</li>
<li>
<code>,</code>: Prompts the user to enter a single hex byte and stores
it in top of left
</li>
<li>
<code>[</code>: If top of left is zero, move the instruction pointer
until after the matching ]
</li>
<li>
<code>]</code>: If top of right is nonzero, move the instruction
pointer backwards until the instruction after the matching [
</li>
</ul>
<p>
Source:
<a
href="https://gist.github.com/SimonMeskens/5106836798561491eaa385aef4d54e77"
target="_blank"
>https://gist.github.com/SimonMeskens/5106836798561491eaa385aef4d54e77</a
>
</p>
</section>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment