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.