Brainfuck…

…is a truly fascinating language. So simple, so sexy. It’s Turing-complete, so there’s literally nothing you can’t do with it. Well, at least as far as theory goes. Sure, there’s nothing you can’t do with it, but there’s nothing you can do easily with it either, unfortunately. One thing that has always bothered me whenever I attempted to write an application in BF is that all the addresses are relative. Relative in the sense of that you can’t say “go to register 3” but only “go 2 registers up”. Another thing that really bothered me is that all the addresses are absolute. Absolute in the sense of that they are all in the same array, that you can’t have your seperate set of registers for a sub-program.

So I tinkered a bit around and ended up with something I would call a “functional brainfuck compiler”. It lets you define functions and takes care of the variable<->register mappings for the whole program. An example:

main(0) {
	$0++
	$1+++
	power(0;1;2)
	asciify(2)
	$2.
}

power(3) {
	// a ^ b = c

	copy(0;2)
	copy(1;3)
	$3-[
		multiply(0;2;4)
		copy(4;2)
		$3-
	]
}

multiply(3) {
	// a * b = c
	
	clear(2)
	copy(1;3)
	$3[
		add(0;2)
		$3-
	]
}

copy(2) {
	// $0 = source
	// $1 = dest

	clear(1)
	add(0;1)
}

move(2) {
	// $0 = source
	// $1 = dest
	
	$0[-$1+$0]
}

add(2) {
	clear(2)
	$0[-$1+$2+$0]
	move(2;0)
}

clear(1) {
	$0[-]
}

asciify(1) {
	// only works for 0 >= numbers >= 9
	clear(1)
	clear(2)
	clear(3)
	$1+++++++
	copy(1;2)
	multiply(1;2;3)
	$3-
	add(3;0)
}

You can see the language is pretty close to C in some ways. The syntax is roughly:

function-specifier(argc) { code block }

A main() function is required and must have zero arguments given (should be evident ;-)). Function definitions are the only thing that is allowed in global scope, C/C++-like comments are supported. Function calls are of the form

function-specifier(arg1;arg2;argN) where arg1...argN are the numbers of the variables given as arguments. The character $ followed by a number is interpreted as a “go to the register assigned to that variable” and replaced with the necessary amount of < or > at compilation time.

So, running the code above through the compiler gives you this:

$ ./fbf bf.b
++>+++>[-]>>>[-]<<<<<[->>+>>>+<<<<<]>>>>>[-<<<<<+>>>>>]<<[-]>>[-]<<<<[->>+>>+
<<<<]>>>>[-<<<<+>>>>]<<-[>[-]>[-]>[-]<<<<[->>>+>+<<<<]>>>>[-<<<<+>>>>]<[>[-]<
<<<<<[->>>>+>>+<<<<<<]>>>>>>[-<<<<<<+>>>>>>]<-]<<<[-]>>>[-]<[-<<+>>>+<]>[-<+>
]<<-][-]>[-]>[-]<<+++++++>[-]>>[-]<<<[->+>>+<<<]>>>[-<<<+>>>]<[-]>[-]>[-]<<<[
->>+>+<<<]>>>[-<<<+>>>]<[>[-]<<<<[->>+>>+<<<<]>>>>[-<<<<+>>>>]<-]<->[-]<[-<<<
+>>>>+<]>[-<+>]<<<<.

This looks already whole dimensions more geeky. 😉 As you can see from the main() above, it calculates 2^3 and uses asciify() to then print the result.

To verify everything, i used dev-lang/bff, the fastest BF interpreter I could find:

$ bff bf.bc
8

Now, if you have a closer look at the output, you see that there is quite a lot of crap that could be just left out. For example, the first two [-]s are completely redundant. This comes from the fact that most functions clear() their local variables prior to usage to ensure that the result is correct.

So, the code could be optimized quite a bit, and I decided to write another app that cleans up the code the compiler throws out. It’s still a bit buggy and doesn’t work on every piece of code, but it’s progressing. The really hard problem is to debug that beast, though. Currently it only drops code that is not needed, it does not rearrange nor re-write code, so the easiest method to verify that it works correctly is to replace the dropped code with blanks. The outcome happens to look rather funny:

optimizer debugging

Cool, eh? 😉