A Brainfuck interpreter written in Rust, exposed as a library, a reader, a writer, a REPL, and an IDE.
- Memory tape defaults to 30,000 cells initialized to 0
- Strict pointer bounds (moving left of 0 or beyond the last cell is an error)
- Input
,
reads a single byte from stdin (EOF sets current cell to 0) - Output
.
prints the byte as a character (no newline); the CLI appends a trailing newline for readability - Proper handling of nested loops
[]
; unmatched brackets are an error - Any non-Brainfuck character results in an error
- Arithmetic wraps at 8 bits (
u8
) for+
and-
- Debug mode (
--debug
or-d
) prints a step-by-step execution table instead of performing I/O - Configurable memory size, execution timeout, and step limit
- Color theme support
- REPL with multi-line editing, command history, meta-commands, and non-blocking execution
- Generates Brainfuck code to print given input (text or raw bytes)
- Comprehensive error handling with descriptive messages
- Unit and integration tests included
To install the CLI tool, you can use Cargo:
cargo install --locked rust-bf
If you want to build from source:
- Build:
cargo build
- Run tests:
cargo test
- Run example:
cargo run --example usage
The read
command interprets and runs Brainfuck code. It prints a trailing newline after execution.
Flags:
--debug
or-d
: run in debug mode (prints a step-by-step table)--memory <size>
or-m <size>
: set custom memory tape size (default: 30,000 cells)--max-steps <steps>
or-s <steps>
: limit execution to a maximum number of steps (default: unlimited)--timeout <seconds>
or-t <seconds>
: limit execution time (default: unlimited)--help
or-h
: show help information
Env vars:
BF_TIMEOUT
: set default timeout in seconds (overridden by--timeout
)BF_MAX_STEPS
: set default max steps (overridden by--max-steps
)
Examples:
-
Hello World
cargo run -- read "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++. ------.--------.>+.>."
-
Echo a single byte from stdin (",.")
printf 'Z' | cargo run -- read ",."
- Output:
Z
followed by a newline from the CLI
-
Debug mode (prints a table instead of executing I/O)
cargo run -- read --debug ">+.<"
- Useful for understanding control flow;
,
behaves as EOF (cell set to 0) and.
output is suppressed
-
From a file
cargo run -- read --file ./hello.bf
-
From a file with custom memory size and max steps
cargo run -- read --file ./hello.bf --memory 10000 --max-steps 100000
-
From a file with a timeout of 2 seconds
cargo run -- read --file ./hello.bf --timeout 2
Notes:
- Non-Brainfuck characters cause an error.
- Unmatched
[
or]
cause an error. - Moving the pointer out of bounds causes an error.
Generate Brainfuck code that prints the provided input.
Examples:
- From positional args (recommended with Cargo; note the
--
separator):cargo run -- write "Hello world"
- From STDIN (UTF-8 text):
echo -n 'Hello' | cargo run --bin bf -- write
- From a file:
cargo run -- write --file ./message.txt
- Raw bytes from a file:
cargo run -- write --bytes --file ./image.bin
The output is Brainfuck code printed to stdout (a trailing newline is added for readability).
Interactive REPL for Brainfuck code execution.
- Start the REPL:
cargo run -- repl
- Type Brainfuck code directly into the REPL.
- Invalid instructions are ignored.
- Tape and pointer are reset for each execution. No state is maintained.
- Press Ctrl-D (Unix/macOS) or Ctrl-Z and then Enter (Windows) to signal EOF and execute the code.
- Alt-Up/Down and Ctrl-Up/Down navigate command history.
- The REPL will print the output of the Brainfuck program.
- Press Ctrl-C to exit the REPL immediately with exit code 0.
- Multi-line buffer editing
- Non-blocking execution
- Configurable with environment variables:
BF_REPL_TIMEOUT
- max execution time in seconds (default: 2,000)BF_REPL_MAX_STEPS
- max execution steps (default: unlimited)
- Default timeout: 2,000 seconds, default max steps: unlimited
- Configurable with environment variables:
- Command history (up/down arrows on a blank buffer)
- Meta-commands (start with
:
)::help
- show help:exit
- exit the REPL:reset
- clear the current buffer:dump
- print the current buffer- add
-n
to print line numbers - add
-stderr
to send everything to stderr
- add
Submission model and Ctrl-C
- Edit a multi-line buffer; Enter inserts a newline.
- Submit the buffer by sending EOF:
- macOS/Linux: Ctrl-D
- Windows: Ctrl-Z then Enter
- Ctrl-C exits immediately and cleanly with exit code 0 (does not submit the buffer).
Stream separation
- Program output (produced by your Brainfuck code): stdout only.
- REPL/meta output (prompt, help, errors, :dump framing): stderr.
:dump
defaults: content to stdout, framing to stderr; flags can change this (see below).
Modes and navigation
- Edit mode (default):
- Up/Down move within the multi-line buffer.
- At the very start of the buffer (row 0, col 0), pressing Up enters History-Browse.
- History-Browse:
- Up/Down navigate past submissions.
- Enter accepts the selected entry into the buffer (returns to Edit).
- Esc cancels browsing and restores your in-progress edits (returns to Edit).
- Shortcuts: Alt-Up/Down and Ctrl-Up/Down also navigate history (when supported by your terminal).
Interactive vs bare mode
- Auto-detect:
- If stdin is a TTY: start the interactive editor REPL.
- If stdin is not a TTY (piped/redirected): bare mode — read all input once, execute, exit 0.
- Flags:
--bare
(alias:--non-interactive
): force bare mode even on a TTY.--editor
: force interactive mode; on non-TTY stdin prints an error to stderr and exits 1.
- Prompt suppression: if stderr is not a TTY, prompts/banners are suppressed to keep pipeline output clean.
Timeouts and step limits
- Defaults (interactive REPL):
- Timeout: 2,000 seconds
- Max steps: unlimited
- Configuration:
- CLI flags (on the repl command): --timeout , --max-steps
- Env vars: BF_REPL_TIMEOUT (seconds), BF_REPL_MAX_STEPS
- Precedence: CLI flags > environment variables > defaults
- Behavior:
- If the step limit is exceeded: “Execution aborted: step limit exceeded (N).”
- If the timeout is exceeded: “Execution aborted: wall-clock timeout (T s).”
Meta commands (start a line with “:”)
:exit
— Exit immediately with code 0.:help
— Show key bindings, modes, EOF per OS, timeout/step-limit policy, and examples.:reset
— Clear the current buffer; history is unchanged.:dump
— Print the current buffer for inspection.- Defaults: raw content to stdout; framing markers to stderr.
- Flags:
-n
— include line numbers (stdout)--stderr
— send everything (content + framing) to stderr
- Examples:
:dump
:dump -n
:dump --stderr
Key bindings (quick reference)
- Cursor: Left/Right within a line; Up/Down within the buffer.
- History-Browse:
- Enter at (0,0) after Up: accept history item to buffer.
- Esc: leave history, restore your edits.
- Up/Down or Alt/Ctrl + Up/Down: move through history.
- Submission: EOF (Ctrl-D on macOS/Linux; Ctrl-Z then Enter on Windows).
- Exit: Ctrl-C (immediate), or :exit.
IDE for Brainfuck code authoring.
- Start the IDE:
cargo run -- ide
cargo run -- ide --file ./example.bf
to open a file on startup
- Type Brainfuck code directly into the IDE.
- Invalid instructions are ignored.
- Tape and pointer are reset for each execution. No state is maintained.
- Tab to switch focus between editor, output, and tape panes.
- Ctrl-R to execute the editor buffer.
- Ctrl-C to exit the IDE immediately with exit code 0.
- Ctrl-L to toggle line numbers.
- Ctrl-S to save the current buffer to a file.
- Ctrl-O to open a file into the current buffer.
- Ctrl-N to create a new file (prompts to save if the current buffer is dirty).
- Ctrl-P to navigate to the matching bracket (if on a
[
or]
). - Ctrl-Q to quit (prompts to save if the current buffer is dirty).
- Ctrl-H / F1 to show help overlay with keybindings and behaviors.
Create the file ~/.config/bf.toml
. Customize with your selected ANSI colors.
Here's the default theme:
[colors]
editor_title_focused = "cyan" # Editor pane title when focused
editor_title_unfocused = "gray" # Editor pane title when unfocused
gutter_text = "darkgray" # Gutter (line numbers) text color
output_title_focused = "cyan" # Output pane title when focused
output_title_unfocused = "gray" # Output pane title when unfocused
tape_border_focused = "cyan" # Tape pane border when focused
tape_border_unfocused = "gray" # Tape pane border when unfocused
tape_cell_empty = "darkgray" # Empty tape cell (0)
tape_cell_nonzero = "white" # Non-zero tape cell
tape_cell_pointer = "yellow" # Current cell (pointer)
status_text = "white" # Status bar text color
dialog_title = "white" # Dialog title text color
dialog_bg = "black" # Dialog background color
dialog_error = "red" # Error dialog text color
dialog_text = "white" # Dialog normal text color
help_hint = "gray" # Help hint text color
editor_op_right = "cyan" # '>'
editor_op_left = "green" # '<'
editor_op_inc = "lightgreen" # '+'
editor_op_dec = "red" # '-'
editor_op_output = "yellow" # '.'
editor_op_input = "magenta" # ','
editor_op_bracket = "lightmagenta" # '[' or ']'
editor_non_bf = "gray" # non-Brainfuck characters
Add this crate to your project. Then:
use rust_bf::Brainfuck;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Classic Hello World
let code = "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.";
let mut bf = Brainfuck::new(code.to_string());
bf.run()?;
println!(); // optional: newline for readability
Ok(())
}
Debug run (no real I/O; prints a table):
use rust_bf::Brainfuck;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let code = ">+.<"; // simple program
let mut bf = Brainfuck::new(code.to_string());
bf.run_debug()?; // prints a step-by-step table
Ok(())
}
use rust_bf::Brainfuck;
let mut bf = Brainfuck::new_with_memory(
"+>+<[->+<]".to_string(),
1024, // custom tape size
);
let _ = bf.run();
- Input
,
: reads exactly one byte from stdin. On EOF, sets current cell to0
. - Output
.
: prints the current cell as achar
(no newline). - Pointer
>
/<
: moving beyond the tape bounds returnsPointerOutOfBounds
. - Brackets: a pre-pass validates matching pairs; unmatched pairs produce
UnmatchedBrackets
. - Invalid chars: any char not in
><+-.,[]
producesInvalidCharacter
. - I/O errors: wrapped as
IoError(std::io::Error)
.
- Unit tests live in
src/lib.rs
. - Integration tests:
tests/stdin_read.rs
verifies stdin handling for the CLItests/debug_flag.rs
verifies the--debug
table output
- Run all tests with:
cargo test
examples/usage.rs
shows a minimal library usage example.examples/debug.rs
shows how to run a program in debug mode (prints a step-by-step table).
Run:
cargo run --example usage
cargo run --example debug
Apache 2.0