Created
October 7, 2025 02:41
-
-
Save combatwombat/53bc88b8199a42bb0928254bbe2db469 to your computer and use it in GitHub Desktop.
brainfuckT - Typed Brainfuck
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"> | |
| <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">></span> advance by type size</div> | |
| <div class="command"><span class="command-char"><</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">&</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">++++++++++[>+++<-]>++++++$.</span> Output $ (char 36)</div> | |
| <div class="example"><span class="example-code">++++[>+++++<-]>+++#.$.&.</span> 23 as Double, Char, Long (wrapping)</div> | |
| <div class="example"><span class="example-code">++++++++[>++++++++<-]>$.</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