rust-bf
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 (
--debugor-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
Install / Build
To install the CLI tool, you can use Cargo:
If you want to build from source:
- Build:
cargo build - Run tests:
cargo test - Run example:
cargo run --example usage
CLI usage (read)
The read command interprets and runs Brainfuck code. It prints a trailing newline after execution.
Flags:
--debugor-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)--helpor-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:
Zfollowed 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.
CLI usage (write)
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).
CLI usage (REPL)
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.
REPL Features
- 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
-nto print line numbers - add
-stderrto send everything to stderr
- add
REPL modes and I/O policy
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.
:dumpdefaults: 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.
CLI usage (IDE)
IDE for Brainfuck code authoring.
- Start the IDE:
cargo run -- idecargo run -- ide --file ./example.bfto 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.
Color themes
Create the file ~/.config/bf.toml. Customize with your selected ANSI colors.
Here's the default theme:
[]
= "cyan" # Editor pane title when focused
= "gray" # Editor pane title when unfocused
= "darkgray" # Gutter (line numbers) text color
= "cyan" # Output pane title when focused
= "gray" # Output pane title when unfocused
= "cyan" # Tape pane border when focused
= "gray" # Tape pane border when unfocused
= "darkgray" # Empty tape cell (0)
= "white" # Non-zero tape cell
= "yellow" # Current cell (pointer)
= "white" # Status bar text color
= "white" # Dialog title text color
= "black" # Dialog background color
= "red" # Error dialog text color
= "white" # Dialog normal text color
= "gray" # Help hint text color
= "cyan" # '>'
= "green" # '<'
= "lightgreen" # '+'
= "red" # '-'
= "yellow" # '.'
= "magenta" # ','
= "lightmagenta" # '[' or ']'
= "gray" # non-Brainfuck characters
Library usage
Add this crate to your project. Then:
use Brainfuck;
Debug run (no real I/O; prints a table):
use Brainfuck;
Custom memory size
use Brainfuck;
let mut bf = new_with_memory;
let _ = bf.run;
Behavior details
- 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).
Testing
- Unit tests live in
src/lib.rs. - Integration tests:
tests/stdin_read.rsverifies stdin handling for the CLItests/debug_flag.rsverifies the--debugtable output
- Run all tests with:
cargo test
Examples
examples/usage.rsshows a minimal library usage example.examples/debug.rsshows how to run a program in debug mode (prints a step-by-step table).
Run:
cargo run --example usagecargo run --example debug
License
Apache 2.0