Last active
April 20, 2023 23:53
-
-
Save mythmon/6f347777ec6de70a22b1 to your computer and use it in GitHub Desktop.
A simple Brainfuck interpreter in Rust
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | |
| ]); | |
| } | |
| } |
Author
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
can I use this?