Search This Blog

brainfuck in Clojure. Part I: interpreter

Brainfuck is one among the most popular esoteric programming languages. Writing a Brainfuck interpreter is fun, in contrary to actually using this "language". The syntax is dead simple and semantics are rather clear. Thus writing such interpreter is a good candidate for Kata session, TDD practice, etc. Using Clojure for the task is slightly more challenging due to inherent impedance mismatch between imperative Brainfuck and functional Clojure. However you will find plenty of existing implementations ([1], [2], [3], [4]), many of them are less idiomatic as they use atoms to mutate state in-place ([5], [6], [7], [8], [9]).

Let's write a simple, idiomatic brainfuck interpreter ourselves, step by step. It turns out that the transition from mutability to immutability is quite straightforward - rather than mutating state in-place we simply exchange previous state with the new one. In Brainfuck state is represented by cells (memory), cell (pointer to one of the cells, an index within cells) and ip (instruction pointer, an instruction currently being executed):

I don't mutate any of the state variables (actually, I'cant by definition) but in each iteration I produce new set of state variables, discarding the old ones. Typically we will at least increment instruction pointer (to evaluate next instruction in the program) but possibly more. That's pretty much it, in each iteration we read one character of the program (sequence of brainfuck opcodes) and proceed with appropriately updated state:

This should be self-explanatory - > and < move cell pointer while + and - incremenet/decrement current cell accordingly. In all cases instruction pointer is incremented in order to execute next instruction during next iteration. So far so good. Code for > is actually slightly more complex to achieve infinite growing of cells vector but that's irrelevant. Handling loops in brainfuck is more interesting. Every time we encounter opening square bracket we conditionally jump to corresponding (not first encountered) closing bracket. A little bit of logic is required to handle that:

Opening bracket jumps to corresponding closing bracket if current cell is zero and proceeds to next instruction otherwise. Closing bracket jumps unconditionally to corresponding opening bracket. Think of them as nested while loops. Guess what, we just implemented brainfuck interpreter in functional language without mutating state, at all! The full source code follows, including impure I/O operations and all supporting code:

Using this interpreter is quite simple. It terminates when it encounters end of the program. brainfuck-interpreter returns state as it was upon termination to allow easier unit testing. This project is available on GitHub, but it was merely a warm-up. In the next article we shall write a brainfuck compiler in Clojure. In 100 lines of code. Stay tuned!