Skip to content

Instantly share code, notes, and snippets.

@combatwombat
Created October 7, 2025 02:41
Show Gist options
  • Select an option

  • Save combatwombat/53bc88b8199a42bb0928254bbe2db469 to your computer and use it in GitHub Desktop.

Select an option

Save combatwombat/53bc88b8199a42bb0928254bbe2db469 to your computer and use it in GitHub Desktop.
brainfuckT - Typed Brainfuck
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>brainfuckT - Typed Brainfuck</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
color: #fff;
padding: 2rem;
min-height: 100vh;
}
.container {
max-width: 800px;
margin: 0 auto;
}
h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
}
.subtitle {
color: #cbd5e1;
margin-bottom: 2rem;
}
.info-box {
background: #334155;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.info-title {
font-weight: 600;
margin-bottom: 1rem;
font-size: 0.9rem;
}
.commands-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
margin-bottom: 1rem;
}
.command {
color: #cbd5e1;
}
.command-char {
color: #4ade80;
font-weight: bold;
}
.type-cycle {
color: #cbd5e1;
font-size: 0.85rem;
}
.editor-section {
margin-bottom: 1.5rem;
}
label {
display: block;
font-weight: 600;
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
textarea {
width: 100%;
height: 150px;
padding: 0.75rem;
border: none;
border-radius: 6px;
font-family: Monaco, 'Courier New', monospace;
font-weight: 400;
font-size: 14px;
resize: vertical;
background: #000;
color: #ccc;
}
textarea:focus {
outline: none;
border-color: #3b82f6;
}
button {
background: #3b82f6;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
margin-top: 1rem;
font-size: 1rem;
}
button:hover {
background: #2563eb;
}
.output-section {
margin-bottom: 1.5rem;
}
.output-box {
background: #000000;
border-radius: 6px;
padding: 1rem;
min-height: 100px;
font-family: Monaco, 'Courier New', monospace;
font-size: 14px;
color: #ccc;
white-space: pre-wrap;
word-break: break-all;
font-weight: 400;
}
.error {
color: #dc2626;
}
.examples {
background: #334155;
border-radius: 8px;
padding: 1.5rem;
}
.examples-title {
font-weight: 600;
margin-bottom: 0.75rem;
font-size: 0.9rem;
}
.example {
font-family: Monaco, 'Courier New', monospace;
font-size: 0.8rem;
margin-bottom: 0.5rem;
color: #cbd5e1;
}
.example-code {
color: #4ade80;
}
a {
color: #5566ff;
}
</style>
</head>
<body>
<div class="container">
<h1>brainfuckT</h1>
<p class="subtitle">Typed brainfuck with QBasic-style type suffixes. <a href="https://hachyderm.io/@aburka/115330276116413046">Because</a></p>
<div class="info-box">
<div class="info-title">Commands:</div>
<div class="commands-grid">
<div class="command"><span class="command-char">+</span> increment value</div>
<div class="command"><span class="command-char">-</span> decrement value</div>
<div class="command"><span class="command-char">&gt;</span> advance by type size</div>
<div class="command"><span class="command-char">&lt;</span> move back by type size</div>
<div class="command"><span class="command-char">%</span> set type: Int16</div>
<div class="command"><span class="command-char">&amp;</span> set type: Long (Int32)</div>
<div class="command"><span class="command-char">#</span> set type: Double (Float64)</div>
<div class="command"><span class="command-char">$</span> set type: Char</div>
<div class="command"><span class="command-char">.</span> output value</div>
<div class="command"><span class="command-char">[</span> loop start</div>
<div class="command"><span class="command-char">]</span> loop end</div>
</div>
<div class="type-cycle">
Default type: Int16 (%). Type changes convert values with wrapping.
</div>
</div>
<div class="editor-section">
<label>Code</label>
<textarea id="code">+++.&.#.$.</textarea>
<button onclick="run()">▶ Run</button>
</div>
<div class="output-section">
<label>Output</label>
<div id="output" class="output-box"></div>
</div>
<div class="examples">
<div class="examples-title">Examples:</div>
<div class="example"><span class="example-code">+++.&.#.$.</span> Output 3 as different types</div>
<div class="example"><span class="example-code">++++++++++[>+++&lt;-]>++++++$.</span> Output $ (char 36)</div>
<div class="example"><span class="example-code">++++[>+++++&lt;-]>+++#.$.&amp;.</span> 23 as Double, Char, Long (wrapping)</div>
<div class="example"><span class="example-code">++++++++[>++++++++&lt;-]>$.</span> Output @ (char 64)</div>
<div class="example"><span class="example-code">++++++[>++++++++++<-]>#.$.</span> 60 as Double (60.00) then Char (<)</div>
<div class="example"><span class="example-code">-$.</span> -1 wraps to 255 as Char (ÿ)</div>
</div>
</div>
<script>
const typeInfo = {
'%': { name: 'Int16', size: 2, min: -32768, max: 32767, isFloat: false },
'&': { name: 'Long', size: 4, min: -2147483648, max: 2147483647, isFloat: false },
'#': { name: 'Double', size: 8, min: -Number.MAX_VALUE, max: Number.MAX_VALUE, isFloat: true },
'$': { name: 'Char', size: 1, min: 0, max: 255, isFloat: false }
};
function convertValue(value, fromType, toType) {
// First, get the actual numeric value
let numValue = value;
// If converting from float to int, truncate
if (typeInfo[fromType].isFloat && !typeInfo[toType].isFloat) {
numValue = Math.trunc(numValue);
}
// Now wrap to target range
const target = typeInfo[toType];
if (target.name === 'Char') {
// Char is unsigned 0-255
return ((numValue % 256) + 256) % 256;
} else if (!target.isFloat) {
// Signed integer types
const range = target.max - target.min + 1;
let wrapped = ((numValue - target.min) % range + range) % range + target.min;
return wrapped;
}
// Float types don't wrap
return numValue;
}
function run() {
const code = document.getElementById('code').value;
const outputEl = document.getElementById('output');
try {
const tape = new Map();
let ptr = 0;
let codePtr = 0;
let output = '';
tape.set(0, { type: '%', value: 0 });
const getCell = (pos) => {
if (!tape.has(pos)) {
tape.set(pos, { type: '%', value: 0 });
}
return tape.get(pos);
};
const setValue = (pos, val) => {
const cell = getCell(pos);
const info = typeInfo[cell.type];
if (info.name === 'Char') {
cell.value = ((val % 256) + 256) % 256;
} else if (!info.isFloat) {
const range = info.max - info.min + 1;
cell.value = ((val - info.min) % range + range) % range + info.min;
} else {
cell.value = val;
}
};
const matchBracket = (pos, forward) => {
let depth = 1;
let i = pos + (forward ? 1 : -1);
while (i >= 0 && i < code.length && depth > 0) {
if (code[i] === '[') depth += forward ? 1 : -1;
if (code[i] === ']') depth += forward ? -1 : 1;
if (depth === 0) return i;
i += forward ? 1 : -1;
}
throw new Error('Unmatched bracket at position ' + pos);
};
let steps = 0;
const maxSteps = 100000;
while (codePtr < code.length && steps++ < maxSteps) {
const cmd = code[codePtr];
const cell = getCell(ptr);
switch (cmd) {
case '+':
setValue(ptr, cell.value + 1);
break;
case '-':
setValue(ptr, cell.value - 1);
break;
case '>':
ptr += typeInfo[cell.type].size;
break;
case '<':
ptr -= typeInfo[cell.type].size;
if (ptr < 0) ptr = 0;
break;
case '%':
case '&':
case '#':
case '$':
// Convert value when changing type
const oldType = cell.type;
const newType = cmd;
cell.value = convertValue(cell.value, oldType, newType);
cell.type = newType;
break;
case '.':
if (cell.type === '$') {
output += String.fromCharCode(cell.value);
} else if (cell.type === '#') {
output += cell.value.toFixed(2);
} else {
output += cell.value;
}
break;
case '[':
if (cell.value === 0) {
codePtr = matchBracket(codePtr, true);
}
break;
case ']':
if (cell.value !== 0) {
codePtr = matchBracket(codePtr, false);
}
break;
}
codePtr++;
}
if (steps >= maxSteps) {
outputEl.innerHTML = '<span class="error">Execution stopped: too many steps (possible infinite loop)</span>';
} else {
outputEl.textContent = output || '(no output)';
}
} catch (e) {
outputEl.innerHTML = '<span class="error">' + e.message + '</span>';
}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment