Compile Go to Brainfuck!
FizzBuzz
Uses for loops, if-else chains, and modulo.
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
Uses recursion returning a byte.
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
Uses recursion with 32-bit integers and type conversion.
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
Uses structs, value-receiver methods, and composite literals.
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
Uses a global array and string concatenation.
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
Prime numbers
Uses a slice that grows with append and range over it.
package main
func main() {
var primes []byte
for n := byte(2); n < 100; n++ {
prime := byte(1)
for _, p := range primes {
if p*p > n {
break
}
if n%p == 0 {
prime = 0
break
}
}
if prime == 1 {
primes = append(primes, n)
}
}
for i, p := range primes {
if i > 0 {
print(" ")
}
print(p)
}
println()
} $ go2bf run primes.go
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
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,fallthrough, andbreakto exit a case early)
- 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,arr[i].inner.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)- Chained field operations on indexed elements: read/write
through
a[i].inner.x,arr[i].inner = Inner{...}, parallel swapa[i].x, a[i].y = a[i].y, a[i].x, and multi-returna[i].x, a[i].y = f() - Composite equality and copy at indexed elements
(
a[i] == a[j],a[i] == Point{...},a[j] = a[i]) - Return composite by value from indexed base (
return a[i],return s[i]for struct slices) - Pass to and return from functions
- Value and pointer receivers (
func (v T) m(),func (p *T) m()) - Struct embedding with field and method promotion, including multi-level embedding and promotion through pointers and indexing.
[]byte,[]uintN,[]Point,[][N]byte,[][]byte,[]*byte,[]*Point- Composite literals:
[]byte{1, 2, 3},[]byte{2: 5, 4: 9},[]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 reallocation, including a string source (append(b, str...))copy(dst, src),clear(s), including copying a string into a byte slice (copy(b, str))- 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 field, including through indexed bases and chains (&arr[i].x,&arr[i].inner.x,&arr[i].xs[1])ptr.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(...)), struct (pp.items[i].field), and nested-array (pp.grid[i][j]for a[N][M]Tfield) 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 *[N][M]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 reallocation;tmay be a string whensis a byte slicecopy(dst, src)-- copy slice elements;srcmay be a string whendstis a byte sliceclear(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.[][][]byt
0 comments
log in to comment.