csbf is a CLI tool for the Brainfuck language written in C#.
It implements a full language pipeline:
Source → Parser → IR → VM / Codegen
Try in your browser — run client-side via Blazor WebAssembly (no install).
- Interactive CLI with REPL
- Optimizer with individually toggleable passes: run-length folding (
></+-cancel), clear-loop lowering ([-]/[+]→ set), and scan-loop lowering ([>]/[<]→ memchr-style skip) - Built-in
benchcommand to compare execution across optimizer passes - Code generation for emitting target-language source files (e.g. Go)
- Debugger with instruction-index breakpoints and time-travel (reverse stepping)
- Brainfuck parser producing a structured IR
- Virtual machine with step-by-step execution, registers and memory inspection
- .NET 10 SDK (pinned via
global.json)
Run from source:
# Start the interactive REPL
dotnet run --project src/Csbf.Cli
# One-shot mode: any REPL command also works as CLI arguments
dotnet run --project src/Csbf.Cli -- run samples/hello_world.bf
dotnet run --project src/Csbf.Cli -- eval '++++++++[>++++++++<-]>+++++.'
# Run the tests
dotnet testA
dotnet toolpackage (dotnet tool install -g csbf) is planned; for now, run from source.
On load, source is parsed and optimized, so instruction indices below refer to the
optimized ops (inspect them with bytecode).
> load samples/hello_world.bf
> break 21
breakpoint set at 0x15
> run
hit breakpoint at 0x15
> regs
IP: 0x15
DP: 0x0
CELL: 0x0
> step
0x15: ADD_BYTE 8
> run
Hello World!
Note:
breaktakes a decimal instruction index, whileregs/step/bytecodeprint addresses in hex (sobreak 21sets a breakpoint at0x15).
| Command | Aliases | Description |
|---|---|---|
load <file> |
Load a Brainfuck source file into the VM | |
run [file] |
Optionally load file, then run until a breakpoint or program end |
|
step |
Execute one instruction and print it | |
back [n] |
stepb |
Step backward n instructions (default 1), restoring tape + registers |
break <n> |
Toggle a breakpoint at instruction index n (decimal) |
|
regs |
Show registers: IP, DP, and the current cell | |
mem <from> <len> |
Dump len bytes of tape memory starting at offset from |
|
eval <src> |
Load and run inline Brainfuck source | |
emit <lang> <in> [out] |
Compile in to <lang> (currently go); prints to stdout, or writes out |
|
opt [pass] [on|off] |
Show optimizer passes, or toggle one (collapse, clear, scan); opt all/opt none set all |
|
bench <file> [runs] |
Time a program across optimizer passes (best of runs, default 3) |
|
bytecode |
bc |
Disassemble the loaded program |
help |
List commands | |
exit |
quit |
Exit the CLI |
The optimizer lowers common Brainfuck idioms to dedicated IR ops, so both the VM and the code generators do less work:
| Pass | Recognizes | Lowers to |
|---|---|---|
collapse |
runs like +++, >>, and cancelling >< / +- |
one folded ADD_BYTE / ADD_PTR |
clear |
clear loops [-] / [+] |
SET 0 |
scan |
scan loops [>] / [<] (any stride) |
SCAN <stride> (memchr-style skip) |
Each pass is independent and can be toggled from the REPL (it affects both execution and emitted code):
> opt # show which passes are on
> opt scan off # disable just the scan-loop pass
> emit go x.bf # emitted Go now keeps the raw scan loop
Compare the effect on a program with bench:
> bench samples/mandelbrot.bf 1
level ops steps time(ms) speedup
none 11451 2,000,000,000 ... 1.00x (capped)
all 3619 ... ... ~5x
See docs/benchmark.md for full numbers (mandelbrot runs ~5× faster with all passes on).
Compile a Brainfuck program to Go and run it:
dotnet run --project src/Csbf.Cli -- emit go samples/hello_world.bf hello.go
go run hello.go # -> Hello World!