Last active
February 4, 2025 06:01
-
-
Save SimonMeskens/5106836798561491eaa385aef4d54e77 to your computer and use it in GitHub Desktop.
Brainfuck variant with 2 stacks: https://gist.cafe/5106836798561491eaa385aef4d54e77?preview
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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>></code>: Shift top of left to top of right</li> | |
<li><code><</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