Yet Another Brain F*ck Interpreter
-- See project here
What is BF?
Brain F*ck, also known as BF and brain-lang, is an esoteric programming language. If you are unfamiliar this video is a great primer. With only 8 symbols +-[].,><
the language is deceptively simple, almost unusable. At its core BF has a static sized buffer. This buffer stores a byte, initialized with 0. To interact with the buffer you use +-
to increment and decrement the buffer at the current pointer. ><
shifts the pointer to the right and left. .
prints the current value at the pointer as an ascii character. ,
requests a number from STDIN and places it in the pointer position.
What is YABFI?
Yet Another Brain F*ck Interpreter is just another interpreter for BF, written in Rust. While the world didn't need ANOTHER Rust powered BFI, I really wanted to write one. I figured since I'm still learning Rust it would make for another awesome project.
Constraints
As will all of my projects I started with constraints and project steps. The constraints were as follows...
- Accepts common BF code and print the expected result
- Accepts BF code from STDIN
- Accepts BF code in an python CLI like environment
- Has extra features to make the CLI interpreter environment easier to use
Challenges
Ultimately this project went smoother than I expect, because Brain-lang is very well documented. So to not spoil the fun I avoided looking at other BFI implementations.
The first issue I ran into was supporting wrap around addition and subtraction. In BF if I execute 0-1
it should return 255, which does not play well with Rust's u8
type, it immediately throws an overflow error. So I considered switching to i16
to abuse the signed bit, but there were many issues with this and the Rust compile caught me out. After digging through the Rust docs I found wrapping_add()
(Read more here). This allows me to do that math but ignore the carrying digit. Additionally the wrapping_sub()
does the same for me going in the opposite direction.
The final challenge was figuring out how to handle the loops. BF Loops are pretty simple. When you encounter a [
if the current pointer value is 0
you skip to the matching ]
. When you encounter a ]
you jump back to the matching [
. The way I handled this was actually pretty simple.
I do this with a single Vec<usize>
. All I do it I push()
the program cursor position to the stack when I see a [
. When I see a ]
I pop()
the loop stack and store that. If the buf[pointer]
value is 0
I move forward exiting the loop otherwise I skip the cursor back the popped value. The stack of course is so I can have multiple nested loops and keep track of them all.
match program[cursor] as char {
// -- snip --
'[' => stack.push(cursor),
']' => {
let start = stack.pop().unwrap();
// Set the cursor back to the start of the loop
if arr[p] != 0 {
cursor = start;
continue;
}
}
}
Conclusion
YABFI is a silly little program that was written in about 3 hours. I had a lot of fun writing it and was given an opportunity to dive into Rust's primitive type methods. I think the idea of creating a Brain-lang CLI interpreter with a consistent buffer is fairly novel. 10/10 would write again.