Skip to content

Commit 8f2f56b

Browse files
Add timeout safeguard and debug info to brainfuck runner (#103)
- add a 3-second execution timeout to the brainfuck runner - surface debug info (tape length, pointer position, nearby cells) when a timeout occurs - preserve status formatting for multiline debug messages ------ [Codex Task](https://chatgpt.com/codex/tasks/task_e_695146ebe1b88325b783712aa117a5cb)
1 parent 086d1db commit 8f2f56b

1 file changed

Lines changed: 44 additions & 1 deletion

File tree

brainfuck-interpreter.html

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
margin-top: 0.5rem;
9090
font-size: 0.98rem;
9191
color: var(--tx-2);
92+
white-space: pre-wrap;
9293
}
9394

9495
.status.error {
@@ -195,6 +196,14 @@ <h2 style="margin: 0;">Output</h2>
195196

196197
const STORAGE_KEY = 'brainfuck-interpreter-code';
197198

199+
class BrainfuckTimeoutError extends Error {
200+
constructor(message, debugInfo) {
201+
super(message);
202+
this.name = 'BrainfuckTimeoutError';
203+
this.debugInfo = debugInfo;
204+
}
205+
}
206+
198207
function updateStatus(message, isError = false) {
199208
statusEl.textContent = message;
200209
statusEl.classList.toggle('error', isError);
@@ -304,6 +313,26 @@ <h2 style="margin: 0;">Output</h2>
304313
return map;
305314
}
306315

316+
function formatTapeWindow(tape, pointer) {
317+
const windowSize = Math.min(30, tape.length || 1);
318+
const halfWindow = Math.floor(windowSize / 2);
319+
let start = Math.max(0, pointer - halfWindow);
320+
let end = Math.min(tape.length - 1, start + windowSize - 1);
321+
322+
if (end - start + 1 < windowSize) {
323+
start = Math.max(0, end - windowSize + 1);
324+
}
325+
326+
const leftEllipsis = start > 0;
327+
const rightEllipsis = end < tape.length - 1;
328+
const cells = tape.slice(start, end + 1).map((value, index) => {
329+
const absoluteIndex = start + index;
330+
return absoluteIndex === pointer ? `[${value}]` : `${value}`;
331+
});
332+
333+
return `${leftEllipsis ? '… ' : ''}${cells.join(' ')}${rightEllipsis ? ' …' : ''}`;
334+
}
335+
307336
function runProgram(program, inputs) {
308337
const commands = program.replace(/[^\>\<\+\-\.\,\[\]]/g, '');
309338
const jumpMap = buildJumpMap(commands);
@@ -313,8 +342,19 @@ <h2 style="margin: 0;">Output</h2>
313342
let inputIndex = 0;
314343
let ip = 0;
315344
const output = [];
345+
const timeoutMs = 3000;
346+
const startTime = performance.now();
316347

317348
while (ip < commands.length) {
349+
if (performance.now() - startTime > timeoutMs) {
350+
const debugInfo = [
351+
`Tape length: ${tape.length}`,
352+
`Pointer position: ${pointer}`,
353+
`Tape window: ${formatTapeWindow(tape, pointer)}`,
354+
].join('\n');
355+
throw new BrainfuckTimeoutError('Execution timed out after 3 seconds.', debugInfo);
356+
}
357+
318358
const instruction = commands[ip];
319359
switch (instruction) {
320360
case '>':
@@ -382,7 +422,10 @@ <h2 style="margin: 0;">Output</h2>
382422
renderOutput(output);
383423
updateStatus(`Program ran successfully. Output length: ${output.length} byte${output.length === 1 ? '' : 's'}.`);
384424
} catch (error) {
385-
updateStatus(error.message, true);
425+
const debugDetails = error instanceof BrainfuckTimeoutError && error.debugInfo
426+
? `${error.message}\n\nDebug info:\n${error.debugInfo}`
427+
: error.message;
428+
updateStatus(debugDetails, true);
386429
outputAscii.textContent = '(no output)';
387430
outputBytes.textContent = '(no output)';
388431
}

0 commit comments

Comments
 (0)