The Brainfuck programming language stays a somewhat current topic at the office ever since Maya's 9-digit program, so much so, that now we've even started designing our own Brainfuck computer using first principle logic gates only. But more on that later. Today and in my next post, I'm going to write about compiling register machines to Brainfuck.
The register machine is an abstract mathematical machine much like Turing machines or finite automata, and it can be shown to be Turing-complete. On the other hand, it models very closely how real-world computers operate. The program of a register machine (RM for short) is a labelled list of instructions, each operating on one of a finite number of memory cells called registers, holding values of natural numbers. The allowed instructions in the specific formulation that we'll be using here is:
- Increment (inc) r, Decrement (dec) r
- Clear (clr) r: Sets the value of register r to 0
- Copy (mov) rdest rsrc: Overwrite the contents of register rdest with the contents of register rsrc
- Jump to (jmp) label: Continue execution from the instruction marked with label
- Jump if (jz) r is zero to label: If the contents of register r is non-zero, continue execution as normal. If it is zero,
- Output (out) r, Input (in) r: Not strictly necessary for the theory to work out, it's convenient to include serial input/output in the model
Note that this set of operations is redundant in that both clr and mov can be easily implemented in terms of the others — they are included here only for clarity later on.
For example, to add the contents of register a to b, we can write the following program using a temporary register z:
- clr z
- jz a 7
- dec a
- inc z
- inc b
- jmp 2
- jz z 11
- dec z
- inc a
- jmp 7
The Haskell code for parsing register machine programs and an instantiator for a simple little macro language is available on GitHub.
Brainfuck is a joke programming language, one designed to be as much of a pain to use as possible, while also being powerful enough to solve real problems with it (languages like that are commonly called Turing tar-pits). The link above explains the intentionally minimalistic syntax and the semantics of the language in detail. The sketchy version is that there is a linear array of memory cells and a pointer moving on this array, and the operations can move the pointer and change the contents of the currently pointed cell:
- Increment cell (+), Decrement cell (-)
- Move pointer left (<) or right (>)
- Loop (): Repeatedly execute the statements between the [ and the ], as long as the value of the pointed cell is non-zero at the start of each iteration.
- Input (,), Output (.): These modify or print the contents of the currently pointed cell.
You can find my parser, interpreter and x86 compiler for Brainfuck here.
In the next post, we will prove that Brainfuck is Turing-complete by translating register machine programs into Brainfuck. As you can see, there are two differences between RM and Brainfuck: one is that RM uses random access for the registers, whereas Brainfuck can access only one register at a time; the other, more major one is that you can't change the order of execution arbitrarily in Brainfuck. This is why we will have to use some trickery to come up with a translation scheme.
If you're feeling impatient, you can, of course, take a peek in the meantime at my RM → Brainfuck compiler in GitHub.