Skip to content

Instantly share code, notes, and snippets.

@mythmon
Last active April 20, 2023 23:53
Show Gist options
  • Select an option

  • Save mythmon/6f347777ec6de70a22b1 to your computer and use it in GitHub Desktop.

Select an option

Save mythmon/6f347777ec6de70a22b1 to your computer and use it in GitHub Desktop.
A simple Brainfuck interpreter in Rust
use std::convert::Into;
use std::io::{self, Read, Bytes};
use std::str::FromStr;
fn main() {
let mut input = io::stdin().bytes();
let program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.";
let mut machine: BrainFuck = program.parse().unwrap();
match machine.execute_all(&mut input) {
Ok(s) => print!("{}", s),
Err(e) => println!("Error: {:?}", e),
}
}
struct BrainFuck {
instructions: Vec<Instruction>,
program_counter: usize,
data_pointer: usize,
memory: Vec<u8>,
}
impl BrainFuck {
fn new(instructions: Vec<Instruction>) -> BrainFuck {
BrainFuck {
instructions: instructions,
program_counter: 0,
data_pointer: 0,
memory: vec![0],
}
}
fn execute_all<T: Read>(&mut self, input: &mut Bytes<T>) -> Result<String, ()> {
let mut output = String::new();
loop {
let res = self.execute_once(input);
if let Ok((_, Some(c))) = res {
output.push(c);
}
match res {
Ok((true, _)) => (),
Ok((false, _)) => break,
Err(e) => return Err(e),
}
}
Ok(output)
}
fn left(&mut self) -> Result<(), ()> {
if self.data_pointer > 0 {
self.data_pointer -= 1;
Ok(())
} else {
Err(())
}
}
fn right(&mut self) {
self.data_pointer += 1;
while self.data_pointer >= self.memory.len() {
self.memory.push(0u8);
}
}
fn incr(&mut self, amount: i16) {
let new = self.memory[self.data_pointer] as i16 + amount;
self.memory[self.data_pointer] = new as u8;
}
fn output(&self) -> Option<char> {
Some(self.memory[self.data_pointer] as char)
}
fn input<T: Read>(&mut self, input: &mut Bytes<T>) -> Result<(), ()> {
if let Some(Ok(c)) = input.next() {
self.memory[self.data_pointer] = c as u8;
Ok(())
} else {
Err(())
}
}
fn begin(&mut self) -> Result<(), ()> {
if self.memory[self.data_pointer] == 0 {
let mut to_match = 1;
while to_match > 0 {
if self.program_counter >= self.instructions.len() - 1 {
return Err(());
}
self.program_counter += 1;
match self.instructions[self.program_counter] {
Instruction::Begin => to_match += 1,
Instruction::End => to_match -= 1,
_ => (),
}
}
}
Ok(())
}
fn end(&mut self) -> Result<(), ()> {
if self.memory[self.data_pointer] != 0 {
let mut to_match = 1;
while to_match > 0 {
if self.program_counter == 0 {
return Err(());
}
self.program_counter -= 1;
match self.instructions[self.program_counter] {
Instruction::Begin => to_match -= 1,
Instruction::End => to_match += 1,
_ => (),
}
}
self.program_counter -= 1; // back one because the end of the loop increments the PC.
}
Ok(())
}
fn execute_once<T: Read>(&mut self, input: &mut Bytes<T>) -> Result<(bool, Option<char>), ()> {
let output = match *&self.instructions[self.program_counter] {
Instruction::Noop => None,
Instruction::Left => { try!(self.left()); None }
Instruction::Right => { self.right(); None }
Instruction::Plus => { self.incr(1); None }
Instruction::Minus => { self.incr(-1); None }
Instruction::Out => self.output(),
Instruction::In => { try!(self.input(input)); None }
Instruction::Begin => { try!(self.begin()); None }
Instruction::End => { try!(self.end()); None }
};
self.program_counter += 1;
Ok((self.program_counter < self.instructions.len(), output))
}
}
impl FromStr for BrainFuck {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let instructions: Vec<Instruction> = s.chars().map(|c| c.into()).collect();
Ok(BrainFuck::new(instructions))
}
}
#[derive(Debug, PartialEq)]
pub enum Instruction {
Noop,
Right,
Left,
Plus,
Minus,
Out,
In,
Begin,
End,
}
impl Into<Instruction> for char {
fn into(self) -> Instruction {
match self {
'>' => Instruction::Right,
'<' => Instruction::Left,
'+' => Instruction::Plus,
'-' => Instruction::Minus,
'.' => Instruction::Out,
',' => Instruction::In,
'[' => Instruction::Begin,
']' => Instruction::End,
_ => Instruction::Noop,
}
}
}
impl ToString for Instruction {
fn to_string(&self) -> String {
match *self {
Instruction::Noop => ' ',
Instruction::Right => '>',
Instruction::Left => '<',
Instruction::Plus => '+',
Instruction::Minus => '-',
Instruction::Out => '.',
Instruction::In => ',',
Instruction::Begin => '[',
Instruction::End => ']',
}.to_string()
}
}
#[cfg(test)]
mod tests {
use std::io::Read;
use super::{BrainFuck, Instruction};
#[test]
fn test_left_at_beginning() {
let mut machine = BrainFuck::new(vec![Instruction::Left]);
let input_data: Vec<u8> = vec![];
let mut input = (&input_data[..]).bytes();
assert_eq!(machine.execute_once(&mut input), Err(()));
assert_eq!(machine.program_counter, 0);
}
#[test]
fn test_unmatched_begin() {
let mut machine = BrainFuck::new(vec![Instruction::Begin]);
let input_data: Vec<u8> = vec![];
let mut input = (&input_data[..]).bytes();
assert_eq!(machine.execute_once(&mut input), Err(()));
assert_eq!(machine.program_counter, 0);
}
#[test]
fn test_unmatched_end() {
let mut machine = BrainFuck::new(vec![Instruction::Plus, Instruction::End]);
let input_data: Vec<u8> = vec![];
let mut input = (&input_data[..]).bytes();
assert_eq!(machine.execute_once(&mut input), Ok((true, None)));
assert_eq!(machine.execute_once(&mut input), Err(()));
assert_eq!(machine.program_counter, 0);
}
#[test]
fn test_plus() {
let mut machine = BrainFuck::new(vec![Instruction::Plus]);
let input_data: Vec<u8> = vec![];
let mut input = (&input_data[..]).bytes();
assert_eq!(machine.execute_once(&mut input), Ok((false, None)));
assert_eq!(machine.memory, vec![1]);
assert_eq!(machine.program_counter, 1);
}
#[test]
fn test_wrapping_minus() {
let mut machine = BrainFuck::new(vec![Instruction::Minus]);
let input_data: Vec<u8> = vec![];
let mut input = (&input_data[..]).bytes();
assert_eq!(machine.execute_once(&mut input), Ok((false, None)));
assert_eq!(machine.memory, vec![255]);
assert_eq!(machine.program_counter, 1);
}
#[test]
fn test_right_left_plus() {
let mut machine = BrainFuck::new(vec![
Instruction::Plus,
Instruction::Right,
Instruction::Plus,
Instruction::Left,
Instruction::Plus,
]);
let input_data: Vec<u8> = vec![];
let mut input = (&input_data[..]).bytes();
assert_eq!(machine.execute_once(&mut input), Ok((true, None)));
assert_eq!(machine.execute_once(&mut input), Ok((true, None)));
assert_eq!(machine.execute_once(&mut input), Ok((true, None)));
assert_eq!(machine.execute_once(&mut input), Ok((true, None)));
assert_eq!(machine.execute_once(&mut input), Ok((false, None)));
assert_eq!(machine.memory, vec![2, 1]);
assert_eq!(machine.data_pointer, 0);
assert_eq!(machine.program_counter, 5);
}
#[test]
fn test_out() {
let mut machine = BrainFuck::new(vec![Instruction::Out]);
let input_data: Vec<u8> = vec![];
let mut input = (&input_data[..]).bytes();
assert_eq!(machine.execute_once(&mut input), Ok((false, Some('\0'))));
assert_eq!(machine.memory, vec![0]);
assert_eq!(machine.program_counter, 1);
}
#[test]
fn test_in() {
let mut machine = BrainFuck::new(vec![Instruction::In]);
let input_data: Vec<u8> = vec![42];
let mut input = (&input_data[..]).bytes();
assert_eq!(machine.execute_once(&mut input), Ok((false, None)));
assert_eq!(machine.memory, vec![42]);
assert_eq!(machine.program_counter, 1);
}
#[test]
fn test_loops() {
let mut machine: BrainFuck = "++++[>+<-]".parse().unwrap();
let input_data: Vec<u8> = vec![];
let mut input = (&input_data[..]).bytes();
assert_eq!(machine.execute_all(&mut input), Ok("".to_string()));
assert_eq!(machine.memory, vec![0, 4]);
assert_eq!(machine.program_counter, 10);
}
#[test]
fn test_parse_program() {
let machine: BrainFuck = " ><+-.,[]".parse().unwrap();
assert_eq!(machine.instructions, vec![
Instruction::Noop,
Instruction::Right,
Instruction::Left,
Instruction::Plus,
Instruction::Minus,
Instruction::Out,
Instruction::In,
Instruction::Begin,
Instruction::End,
]);
}
}
@mr-cheffy
Copy link

can I use this?

@mythmon
Copy link
Author

mythmon commented Dec 31, 2021

Yes. I'll put this code under the MIT license.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment