Compiles brainfuck programs to ia32 or x86_64 backends, via the GNU assembler.
The brainfuck to GNU assembler compiler is itself written in brainfuck.
Since brainfuck is a somewhat difficult language to work with, we write the brainfuck to GNU assembler compiler in a higher-level macro language, and then compile that macro language down to brainfuck. This higher-level macro language is a DSL implemented in Python, and is only higher-level relative to brainfuck.
Also included is a brainfuck to GNU assembler compiler written in Python, which is used as part of the build pipeline.
- macro-language source for brainfuck to GNU assembler compiler:
self_hosting_bf_compiler.py - generated brainfuck source for brainfuck to GNU assembler compiler:
bf_compiler.brainfuck - output produced by feeding the brainfuck compiler its own brainfuck source:
bf_compiler_self_hosted.s - a simple brainfuck to GNU assembler compiler, implemented in Python:
bootstrap_bf.py - Python string literals containing the GNU assembler fragments for the
ia32andx86_64code generation. - Python code implementing the macro-language to brainfuck compiler:
abcf/
Here's what happens when we run make:
-
First we run
abfc/compile_macro.pyto compile the macro-language filemacros/self_hosting_bf_compiler.py. This generates the brainfuck source filebuild/bf_compiler.brainfuck, which contains the code for the brainfuck to GNU assembler compiler:python2 abfc/compile_macro.py --x86-64 macros/self_hosting_bf_compiler.py > build/bf_compiler.brainfuck -
We then compile the generated brainfuck source for the brainfuck compiler to GNU assembler syntax using a Python implementation of a simple brainfuck compiler, and compile the resulting
.sfile usinggcc.python2 abfc/bootstrap_bf.py --x86-64 build/bf_compiler.brainfuck > build/bf_compiler_boot.s gcc build/bf_compiler_boot.s -nostdlib -Wl,--build-id=none -o build/bf_compiler_boot.outThis gives us our first brainfuck compiler running in machine code:
build/bf_compiler_boot.out. -
Next, we compile the brainfuck source code to the brainfuck compiler using the program produced during the previous step
cat build/bf_compiler.brainfuck | ./build/bf_compiler_boot.out > build/bf_compiler_self_hosted.s gcc build/bf_compiler_self_hosted.s -nostdlib -Wl,--build-id=none -o build/bf_compiler_self_hosted.outAgain,
gccis used to assemble the output produced by the brainfuck compiler. This produces our second brainfuck compiler running in machine code:build/bf_compiler_self_hosted.out. -
To convince ourselves we've hit a fixed point, we feed the second brainfuck compiler it's own source code, and ensure the GNU assembly it emits is a perfect match for the assembly produced during the previous stage:
cat build/bf_compiler.brainfuck | build/bf_compiler_self_hosted.out > build/bf_compiler_self_hosted_2.s diff build/bf_compiler_self_hosted.s build/bf_compiler_self_hosted_2.s -
For a final demonstration of the awesome power and practical utility of our brainfuck compiler, we compile the brainfuck "hello world" example from wikipedia:
cat bf_demos/hello.brainfuck ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>. cat bf_demos/hello.brainfuck | build/bf_compiler_self_hosted.out> build/hello.s gcc build/hello.s -nostdlib -Wl,--build-id=none -o build/hello.out build/hello.out Hello World!
