Compile Go to Brainfuck!
FizzBuzz
package main
func main() {
for i := byte(1); i <= 100; i++ {
if i%15 == 0 {
print("FizzBuzz")
} else if i%3 == 0 {
print("Fizz")
} else if i%5 == 0 {
print("Buzz")
} else {
print(i)
}
println()
}
}Compile to Brainfuck:
$ go2bf fizzbuzz.go
>>>>>>>>+>>>>>>>>+>>>>>>>>+>>>>>>>>+[>>>>>>>>]>>>[>>>]+++[<+++++>-]<[>+<-[>>>+<<
<-]>>>]<<[<<<]<<<<<<<<[<<<<<<<<]>[-]>>>>>[-]>[-]<<<<<<[>>>>>+>+<<<<<<-]>>>>>>[<<
<<<<+>>>>>>-]<<<<<[-]+>>>>[<<<<[-]>>>>[-]][-]>>[>>>>>>>>]>>>>>>>[-]<[<<<]<<<<<<<
...
Compile and run:
$ go2bf run fizzbuzz.go
1
2
Fizz
4
Buzz
Fizz
...
Recursive Fibonacci
package main
func fib(n byte) byte {
if n <= 1 {
return n
}
return fib(n-1) + fib(n-2)
}
func main() {
for i := byte(1); i <= 10; i++ {
print("fib(")
print(i)
print(") = ")
println(fib(i))
}
} $ go2bf run fibonacci.go
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
fib(8) = 21
fib(9) = 34
fib(10) = 55
Factorial numbers
package main
func factorial(n byte) uint32 {
if n == 0 {
return 1
}
return uint32(n) * factorial(n-1)
}
func main() {
for i := byte(0); i <= 12; i++ {
print(i)
print("! = ")
println(factorial(i))
}
} $ go2bf run factorial.go
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
11! = 39916800
12! = 479001600
Structs with methods
package main
type Point struct {
x byte
y byte
}
func (p Point) add(q Point) Point {
return Point{x: p.x + q.x, y: p.y + q.y}
}
func main() {
a := Point{x: 1, y: 2}
b := Point{x: 3, y: 4}
c := a.add(b)
print("(")
print(c.x)
print(", ")
print(c.y)
println(")")
} $ go2bf run point.go
(4, 6)
Roman numerals
package main
type RomanRule struct {
value uint16
symbol string
}
var rules = [...]RomanRule{
{value: 1000, symbol: "M"},
{value: 900, symbol: "CM"},
{value: 500, symbol: "D"},
{value: 400, symbol: "CD"},
{value: 100, symbol: "C"},
{value: 90, symbol: "XC"},
{value: 50, symbol: "L"},
{value: 40, symbol: "XL"},
{value: 10, symbol: "X"},
{value: 9, symbol: "IX"},
{value: 5, symbol: "V"},
{value: 4, symbol: "IV"},
{value: 1, symbol: "I"},
}
func main() {
for _, n := range [...]uint16{
1, 4, 13, 49, 99, 133, 444, 1999, 3888,
} {
k, s := n, ""
for _, r := range rules {
for k >= r.value {
s += r.symbol
k -= r.value
}
}
println(n, "=", s)
}
} $ go2bf run roman.go
1 = I
4 = IV
13 = XIII
49 = XLIX
99 = XCIX
133 = CXXXIII
444 = CDXLIV
1999 = MCMXCIX
3888 = MMMDCCCLXXXVIII
Reversi
You can play Reversi written in Brainfuck!
$ go2bf run testdata/reversi.go
ABCDEFGH
1 ........
2 ........
3 ...*....
4 ..*OX...
5 ...XO*..
6 ....*...
7 ........
8 ........
X:2 O:2
X move: D3
ABCDEFGH
1 ........
2 ........
3 ..*X*...
4 ...XX...
5 ..*XO...
6 ........
7 ........
8 ........
X:4 O:1
O move: E3
...
go install github.com/itchyny/go2bf@latest# Compile Go to Brainfuck
go2bf source.go > output.bf
# Compile and run
go2bf run source.go
# Compile multiple files
go2bf run main.go helper.go
# Compile from stdin
echo 'package main ...' | go2bf -
# Compile with debug comments
go2bf -debug source.goThe compiler pipeline:
- Parse - Uses Go's
go/astparser to parse the source code - Analyze - Builds call graph, detects recursion and tail calls
- Lower - Converts AST to a structured IR (intermediate representation)
- Optimize IR - Constant folding, delta conversion, fresh-zero elimination
- Generate - Converts IR to Brainfuck using a register-cache CPU model with optimized register allocation and stack traffic reduction
- Optimize BF - Peephole optimization on the generated Brainfuck code (merging, cancellation, dead loop elimination)
The generated Brainfuck uses a CPU-like execution model:
- 5 registers at positions 1,2,4,5,7 interleaved with algorithm temps for neighbor optimization
- Register cache with LRU eviction (consecutive operations on same variables stay in registers, dead temporaries skipped)
- Stride-8 highway markers at positions 8, 16, 24, 32 for fast tape navigation
- Phase temps at fixed tape positions 25-39 for recursive dispatch computation
- Stride-3 stack with guard/value/zero cells for variable storage
- Counter-walk technique for navigating to stack slots via the zero column
- Breadcrumb technique for far stack access (guard=0 marks target slot)
- Phase dispatch loop for general recursion with dynamic stack frames
byte(uint8, 0-255),uint16(0-65535),uint32(0-4294967295),uint64(0-2^64-1)- Arithmetic:
+,-,*,/,%,++,--,+=,-=,*=,/=,%=, unary- - Bitwise:
&,|,^,&^,<<,>>,^x,&=,|=,^=,<<=,>>= - Comparison:
==,!=,<,>,<=,>=(including array and struct equality) - Logical:
&&,||,!(0 is false, nonzero is true) - Type conversion:
byte(expr),uintN(expr),string(byte)inprint/println - Constants:
const n = 10,const nl = '\n',const msg = "hello",constblocks withiota - Top-level
vardeclarations ofbyte,uintN, arrays, structs, and slices.
if,else if,elsestatementsforloops withbreakandcontinue, including labeledbreakandcontinueto escape or resume an outer loopswitchstatement onbyte,uintNvalues (including multiple values per case,default, andfallthrough)
- Parameters, return values, multiple return values, named return values
- Multi-return tuples of any type combination
(
func f() (P, P),func f() ([3]byte, [3]byte),func f() (string, byte)) - Multi-return spread into another call:
f(g())wheng's returns matchf's parameters positionally - Methods on value and pointer receivers
(
func (p P) m(),func (p *P) m()) - Methods on struct literals (
P{x: 10}.method()) - Method chaining including pointer-returning methods
(
s.push(1).push(2).pop()) and explicit address-of receivers ((&b).method()) - Tail-call recursion optimization
- General recursion (via stack-based dispatch) including nested recursive calls (e.g., Ackermann function)
- Multi-byte (
uint16/uint32) parameters, locals, and return values in recursive functions; binary recursion (fib, Pascal's triangle viachoose) and accumulator patterns deferfor function calls (LIFO order; not supported inside loops)init()function, inlined beforemain(one per package; multipleinits not yet supported)
[N]byte,[N]uintN,[N]Point,[N][M]byte,[N][M]uintN,[N][M]Point,[N][M][K]byte- Constant and variable indexing
- Composite literals:
[N]byte{...},[N]byte{0: v} len(array),cap(array),for i, v := range a- Copy assignment,
a[i]++,a[i] += v - Pass to and return from functions
- Top-level and function-local type definitions
- Fields:
byte,uintN, struct,*Struct, array, nested array, string,[]byte,[]uintN,[]Struct,[][]bytetypes - Nested struct array fields (
[N][M]Inner) - Field access, nested field access (
p.a.x) - Composite literals, copy assignment
p.x++,p.x += v,a[i].x = v,s.vals[i] = v,s.ps[i].x = v(struct slice field write through index)- Pass to and return from functions
- Value and pointer receivers (
func (v T) m(),func (p *T) m())
[]byte,[]uintN,[]Point,[][N]byte,[][]byte,[]*byte,[]*Point- Composite literals:
[]byte{1, 2, 3},[]Point{Point{1, 2}, Point{3, 4}} make([]byte, n),make([]Point, n, cap)- Indexing:
s[i],s[i].xfor struct slices len(s),cap(s),for _, v := range sappend(s, v),append(s, a, b, c),append(s, t...)with automatic reallocationcopy(dst, src),clear(s)- Array slicing:
a[i:j],a[i:],a[:j],a[i:j:k],a[:] - Reslicing:
s[i:j],s[i:],s[:j],s[i:j:k] s == nil,s != nilcomparison- Copy assignment,
s[i]++,s[i].x++ - Pass to and return from functions
- Backed by a dynamic allocator with in-place growth when possible (old arrays not freed otherwise)
- String variables backed by
[]byteslices:s := "hello",var s string,s = "world",print(s),println(s) len(s),s[i],s[i:j],s[i:],s[:j]- Range over string (by byte, not rune):
for i, b := range s - Equality, lexicographic ordering:
s == t,s != t,s < t,s > t,s <= t,s >= t - Concatenation
s + tand compound assigns += t - Conversions:
[]byte(s),string(bs),string(byte('A')) - Function parameters and returns, including named string
returns (
func g() (msg string)) and multi-return tuples containing strings (func f() (string, byte)) defer println(s + "!"),switch s { case "test": ... }- String constants and concatenations of them in
constblocks - String fields in structs, struct equality compares string content
- Slices and arrays of strings or byte slices:
[]string{"a", "b"},[N]string{"a", "b", "c"},[N][]byte{{'h','i'}, {'b','y','e'}},make([]string, n)
*byte,*uintNpointers:&x,*p,*p = v,*p++,*p--&myStruct,&myArray,&Point{x: 1, y: 2}&a[i],&s[i]-- address of array/slice elements&p.field-- address of struct fieldptr.xread/write for struct pointers (ptr := &myStruct)(*ptr).xread/write (equivalent via auto-deref)q := pppointer alias (both observe the same target)q := *ppfull-struct copy through a pointerptr[i]read/write,ptr[i][j]read/write for array pointersptr[i].xread/write for array-of-structs pointersptr.data[i]read/write for struct-with-array pointers, including multi-byte (pp.x[i] = uint16(...)) and struct (pp.items[i].field) element types- Pointer-typed struct fields (
type Outer struct { p *Inner }):out.p.vread/write follows the field's pointer to the target len(ptr),len(*ptr),cap(ptr)for array pointersptr == nil,ptr != nilcomparison- Typed pointer parameters:
func f(p *[N]byte),func f(p *Point),func f(p *uintN) - Pass pointers to functions for by-reference semantics
print,println-- decimal output, string literals, multi-return expansionlen(x),cap(x)-- arrays, slices, pointersmake([]T, n),make([]T, n, cap)-- slices of byte, struct, or array typesappend(s, v),append(s, a, b, c),append(s, t...)-- with automatic reallocationcopy(dst, src)-- copy slice elementsclear(s)-- zero all slice elementsmin(a, b, ...),max(a, b, ...)-- variadic
go2bf extensions:
putchar(byte)-- raw byte outputgetchar()-- read a byte from stdin
- No import statements.
- No
int, signed integer, floating-point number types. - No maps, interfaces, channels, closures, function pointers.
- Array nesting up to
[N][M][K]byte.[N][M][K][L]byte,[N][M][K]Point,[N][M][K]uintNare not supported. - Slice nesting up to
[][]byte.[][][]byte,[][]Point,[][]uintN,[][N]uintNare not supported. - Recursive functions are scalar-only: parameters,
return values, and locals must be
byte,uint16, oruint32. Pointers, struct, array, slice,uint64, mutual recursion, recursive calls insideforloops, recursive calls to other recursive functions, and lexical shadowing of locals are not supported. - Top-level
var/constdeclarations are not accessible from recursive functions; pass them as arguments or use iterative form. - Maximum 255 stack slots (variables + temporaries) per program. Slice backing arrays share this space; programs that allocate many or large slices at runtime may silently overflow with no error.
- The built-in Brainfuck interpreter uses a 30,000-cell tape with 8-bit wrapping cells.
More than a decade ago, a friend of mine was writing a compiler that turned a high-level language into Brainfuck. He was a brilliant programmer -- far beyond my level at the time -- but his ambition was equally outsized: he wanted to compile Haskell all the way down to Brainfuck. "Imagine losing a game of Reversi to a Brainfuck program," he said with a grin. In the end, the Brainfuck code his compiler generated was simply too slow even for a simple "Hello world" program, and the Reversi dream remained just that -- a dream.
Years passed, and I grew as a programmer. I set out to build my own Python-to-Brainfuck compiler written in Haskell, layering abstractions one on top of another from raw Brainfuck primitives upward. I got as far as 32-bit integer arithmetic on the 8-bit Brainfuck tape, but then hit a wall: I could not figure out how to represent a stack. Without a stack, there could be no function calls, no arrays -- no Reversi. I, too, set the dream aside.
More years went by. At some point, an idea for implementing a stack on the Brainfuck tape came to me, but the sheer amount of work it would take kept me from ever starting. Then the age of AI arrived. With Claude, I built the entire compiler in just three days -- from the first line of code to a working Reversi game. A dream I had carried for over a decade, realized in a long weekend. The compiler supports functions, recursion, arrays, structs -- and yes, it runs Reversi (also written by Claude).
The day has come, old friend. You can now lose to Reversi written in Brainfuck.
Report bug at Issues - itchyny/go2bf - GitHub.
itchyny (https://github.com/itchyny)
This software is released under the MIT License, see LICENSE.