Writing a .bf interpreter
Curious about what “.bf” is? Don’t worry—you’re not alone! The “.bf” extension belongs to Brainfuck, an "esoteric language" or esolang. Unlike conventional programming languages, esolangs are designed to push the boundaries of how we think about programming. Brainfuck, invented by Urban Müller in 1993, is a classic in this genre. Müller created it with the ambitious goal of making a language for which he could write the smallest possible compiler—a feat he achieved with a compiler just 240 bytes in size 🤯.
I’ve always been fascinated by the quirks of esoteric languages, so I decided to dive into Brainfuck by creating my own interpreter for it. And, just to add a personal twist, I built the interpreter in a language I developed myself, called Danfe! Brainfuck, despite its intimidating name, only uses eight characters: >, <, +, -, ., ,, [, and ]. Each character has a unique function, and together, they create a surprisingly powerful (and challenging!) language. Let’s break down what each symbol does and explore how they fit together in this mind-bending language.
Explaining Characters
| Keyword | Info | C equivalent |
|---|---|---|
> | Increment the pointer. | ++p |
< | Decrement the pointer. | --p |
+ | Increment the byte at the pointer. | ++*p |
- | Decrement the byte at the pointer. | --*p |
. | Output the byte at the pointer. | putchar(*p) |
, | Input a byte and store it in the byte at the pointer. | *p = getchar() |
[ | Jump forward past the matching ] if the byte at the pointer is zero. | while (*p) { |
] | Jump backward to the matching [ unless the byte at the pointer is zero. | } |
INFO
p has been previously defined as a char* in c equivalent.
Making Interpreter
Now let's make the actual interpreter in danfe:
if len(__args__) < 2 {
panic("Path to the bf code file is needed!")
}
key = __args__ >>
key = key[0]
Breakdown:
__args__provides the arguments passed by user while running the program.- We check the length of the
__args__and if it is less than 2 meaning we don't have an input.bffile wepanicand stop the program. __agrs__ >>pops the last value from__args__and puts the value in keykey[0]takes the key value from the table and puts it again in key
Now let's read contents of the file defined in the identifier key:
code = v {
import os
return(os.read_file("%i{key}")!)
} endv
Breakdown:
- The
v {initiates a vblock. Everything written inside it is executed through v binary. - The output of the v code is stored in the variable
code.
Now let's make the memory block of our interpreter.
memory = []
pointer = 0
code_ptr = 0
loop_stack = []
x = 0
# create memory block
for x < 3000 {
memory << 0
x = x + 1
}
Breakdown:
- Initialize all the arrays needed, and fill in the memory array with
3000zeros.
Now the block which does the actual interpretation.
for code_ptr < len(code) {
command = code[code_ptr]
if command == ">" {
pointer = pointer + 1
}else if command == "<" {
pointer = pointer - 1
}else if command == "+" {
memory[pointer] = (memory[pointer] + 1) % 256
}else if command == "-" {
memory[pointer] = (memory[pointer] - 1) % 256
}else if command == "." {
print(chr(memory[pointer]))
}else if command == ","{
got_input = input("")
memory[pointer] = chr(got_input[0])
}else if command == '[' {
if memory[pointer] == 0 {
open_brackets = 1
for open_brackets {
code_ptr = code_ptr + 1
if code[code_ptr] == '[' {
open_brackets = open_brackets + 1
} else if code[code_ptr] == ']' {
open_brackets = open_brackets - 1
}
}
} else {
loop_stack << code_ptr
}
} else if command == ']' {
if memory[pointer] != 0 {
code_ptr = loop_stack[len(loop_stack) - 1]
} else {
loop_stack >>
}
}
code_ptr = code_ptr + 1
}
Breakdown:
- The current code in pointer is stored in the variable
command. - All of the other operations done in the if else sections are done as it is defined in the Explaining Characters
Let's test the interpreter, by writing a "simple" brainfuck program:
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.
The code above is to print Hello World!
If we run the interpreter by saving the bf program in helloworld.bf.
danfe run main.df ./helloworld.bf
# Output
Hello World!
The full code is provided in this github repo: Link
Main Takeaway
Writing an interpreter for brainfuck is easier than writing an actual brainfuck hello world program. Even tho, I created the whole interpreter I still can't write a bf program properly. It just shows how incredibly and masterfully the brainfuck esolang was created.
Date: 2024-11-10
