Need help with bflisp?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

135 Stars 10 Forks 121 Commits 0 Opened issues


C compiler and Lisp interpreter in Brainfuck

Services available


Need anything else?

Contributors list

# 16,334
Common ...
109 commits
# 194,492
Common ...
5 commits
# 361,703
1 commit

Note: you'd be more interested in ELVM, the successor of this project. In addition to Lisp on Brainfuck, ELVM can compile C code into more esoteric languages such as Unlambda, INTERCAL, Befunge, Piet, Vim script, TeX, C++'s constexpr, etc.

The original goal of this project was to run a Lisp interpreter on Brainfuck. The goal was extended and this project also has a C compiler which runs on Brainfuck. The Lisp interpreter and the C compiler are generated from C code by modified 8cc. This means we have a toolchain which can translate a subset of C to Brainfuck code.

A C compiler in Brainfuck is the C compiler in Brainfuck.


Here's an overall picture:

Overall picture

The modified 8cc (a small but full featured C compiler) outputs assembly code for a virtual 16bit/24bit Harvard architecture CPU I defined. The CPU has only a handful instructions - mov, add, sub, load, store, comparison, conditional jumps, putc, getc, and exit. See the top comment in bfasm.rb or test/*.bfs for the detail.

Then, bfcore.rb translates the assembly code to Brainfuck code. The CPU has seven 16bit/24bit registers and they are consist of two memory cells in 8bit Brainfuck (btw, I believe 8bit Brainfuck is the best choice for this project). For each cycle, a big (~10k-way for switch statement in Brainfuck is executed and each case statement represents the virtual CPU instruction.

Memory operations are done by a loop which finds a corresponding row using the higher bits and a 256-way switch statement for the lower 8bits. The memory module consumes ~1MB (~12MB for 24bit mode) Brainfuck code space.

As the resulted Brainfuck code is big, was developed. This implementation merges consecutive +- and <>. This also optimizes simple loops with balanced <>.

To make debugging easier, there's a simulator for the virtual CPU (bfsim.rb). Lisp on the virtual CPU simulator is much faster than Lisp on the virtual CPU implemented in Brainfuck. You can use "./bfsim.rb bflisp.bfs" instead of "opt/bfopt" for faster execution.

echo '(+ 3 4)' | ./bfsim.rb bflisp.bfs
> 7

The BF-targeted 8cc

For simplicity, the BF-targeted 8cc is different from the original 8cc:

  • It lacks bit operations.
  • It lacks floating point numbers.
  • All types are unsigned.
  • sizeof(char)==sizeof(int)==sizeof(void*)==sizeof(long long)==1.
  • One "byte" has 24 bits.
  • Cannot handle #include.
  • It reads C code from the standard input.

With all the restriction above, however, can compile 8cc.c itself. In other words, is a self-hosted implementation. You can confirm this by (note you need TCC installed):

$ time make out/  # Takes ~10 hours
./ > out/8cc.c.tmp && mv out/8cc.c.tmp out/8cc.c
8cc/8cc -S -o out/8cc.bfs.tmp out/8cc.c && mv out/8cc.bfs.tmp out/8cc.bfs
BFS24=1 ./bfcore.rb out/8cc.bfs > out/ && mv out/ out/
out/bfopt -c out/ out/ && mv out/ out/
tcc -c out/ -o out/
cc out/ -o out/
out/ < out/8cc.c > out/8cc.2.bfs.tmp && mv out/8cc.2.bfs.tmp out/8cc.2.bfs
BFS24=1 ./bfcore.rb out/8cc.2.bfs > out/ && mv out/ out/
out/bfopt -c out/ out/ && mv out/ out/
tcc -c out/ -o out/
cc out/ -o out/
out/ < out/8cc.c > out/8cc.3.bfs.tmp && mv out/8cc.3.bfs.tmp out/8cc.3.bfs
BFS24=1 ./bfcore.rb out/8cc.3.bfs > out/ && mv out/ out/
make out/  36979.51s user 1.38s system 99% cpu 10:16:50.68 total
$ cmp out/ out/


Lisp implementation in Brainfuck is a Lisp interpreter in Brainfuck. This Brainfuck code is generated from lisp.c by modified 8cc.

How to Use

$ make out/bfopt
$ out/bfopt test/  # check if bfopt is a valid BF interpreter
Hello, world!
$ echo 2038 01 | out/bfopt test/  # check once more
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
$ out/bfopt
> (car (quote (a b c)))
> (cdr (quote (a b c)))
(b c)
> (cons 1 (cons 2 (cons 3 ())))
(1 2 3)
> (defun fact (n) (if (eq n 0) 1 (* n (fact (- n 1)))))
(lambda (n) (if (eq n 0) 1 (* n (fact (- n 1)))))
> (fact 4)
> (defun fib (n) (if (eq n 1) 1 (if (eq n 0) 1 (+ (fib (- n 1)) (fib (- n 2))))))
(lambda (n) (if (eq n 1) 1 (if (eq n 0) 1 (+ (fib (- n 1)) (fib (- n 2))))))
> (fib 4)
> (defun gen (n) ((lambda (x y) y) (define G n) (lambda (m) (define G (+ G m)))))
(lambda (n) ((lambda (x y) y) (define G n) (lambda (m) (define G (+ G m)))))
> (define x (gen 100))
(lambda (m) (define G (+ G m)))
> (x 10)
> (x 90)
> (x 300)

Builtin Functions

  • car
  • cdr
  • cons
  • eq
  • atom
  • +, -, *, /, mod
  • neg?
  • print

Special Forms

  • quote
  • if
  • lambda
  • defun
  • define

More Complicated Examples

You can test a few more examples.

FizzBuzz (took ~4 mins for me):

$ cat fizzbuzz.l | opt/bfopt
(lambda (n) (if (eq n 101) nil (if (print (if (eq (mod n 15) 0) FizzBuzz (if (eq (mod n 5) 0) Buzz (if (eq (mod n 3) 0) Fizz n)))) (fizzbuzz (+ n 1)) nil)))

Sort (took ~3 mins for me):

$ cat sort.l | opt/bfopt
(1 2 3 4 5 6 7)


There should be a lot of limitations. bflisp behaves very strangely when you pass a broken Lisp code. should run on any 8bit Brainfuck implementations. However, only optimized implementation can run the Lisp interpreter. bff4.c is an implementation which can run with little modification (see bff4.patch).


  • Re-implement bfcore.rb in C so assemble can be done by BF itself.
  • Implement the virtual CPU with other esoteric language.

See also


I'd like to thank Rui Ueyama for his easy-to-hack compiler and suggesting the basic idea which made this possible.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.