From 620db0e5d1f2d5e79eb583449d8880aad4f05bf6 Mon Sep 17 00:00:00 2001 From: viridian Date: Thu, 9 May 2024 18:55:17 +0200 Subject: [PATCH] Initial commit --- Cargo.lock | 160 ++++++++++++++++ Cargo.toml | 9 + src/main.rs | 512 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 681 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..20c1324 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,160 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "brainfuck" +version = "0.1.0" +dependencies = [ + "indicatif", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "indicatif" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "unicode-width" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..15a288d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "brainfuck" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +indicatif = "*" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..da4eeed --- /dev/null +++ b/src/main.rs @@ -0,0 +1,512 @@ +use std::{ + ops::{Add, Sub}, + process::exit, + usize, +}; + +use indicatif::ProgressStyle; + +fn main() { + let args: Vec = std::env::args().collect(); + let program = std::fs::read_to_string(args.get(1).unwrap()).unwrap(); + + let mut machine = Machine::new(program); + if let Some(_) = args.get(2) { + loop { + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + for s in input.trim().chars() { + match s { + 's' => { + let state = machine.step(); + if state == MachineState::EOI { + println!("Reached end of program printing end state"); + machine.instruction_pointer -= 1; + machine.dbg(); + machine.mem(); + machine.code(); + exit(0); + } + } + 'd' => { + machine.dbg(); + } + 'm' => { + machine.mem(); + } + 'i' => { + machine.code(); + } + 'w' => { + machine.write(); + } + 'e' => { + machine.execute(); + machine.instruction_pointer -= 1; + } + 'r' => machine.run_for(), + _ => { + println!("Option unrecognized") + } + } + } + } + } else { + machine.execute(); + } +} + +#[derive(Debug)] +struct Machine { + instruction_pointer: usize, + data_pointer: usize, + data: Vec, + instructions: Vec, +} +#[derive(Debug, Default, Clone, Copy, PartialEq)] +struct Cell { + value: u8, +} + +impl Machine { + fn new(program: String) -> Self { + Machine { + instruction_pointer: 0, + data_pointer: 0, + data: vec![Cell { value: 0 }], + instructions: parse(program), + } + } + + fn execute(&mut self) { + loop { + match self.step() { + MachineState::Ok => continue, + MachineState::EOI => break, + } + } + } + + fn step(&mut self) -> MachineState { + let instruction = self.instructions.get(self.instruction_pointer).unwrap(); + match instruction { + Instruction::BINC => self.binc(), + Instruction::BDEC => self.bdec(), + Instruction::PINC => self.pinc(), + Instruction::PDEC => self.pdec(), + Instruction::JIZ(target) => self.jiz(*target), + Instruction::JINZ(target) => self.jinz(*target), + Instruction::OUT => self.out(), + Instruction::IN => self.inp(), + Instruction::DBG => self.dbg(), + Instruction::MEM => self.mem(), + } + self.instruction_pointer += 1; + if self.instruction_pointer + 1 > self.instructions.len() { + MachineState::EOI + } else { + MachineState::Ok + } + } + + fn binc(&mut self) { + let cell = *self + .data + .get_mut(self.data_pointer) + .unwrap_or(&mut Cell { value: 0 }) + + Cell { value: 1 }; + self.data[self.data_pointer] = cell; + } + + fn bdec(&mut self) { + let cell = *self + .data + .get_mut(self.data_pointer) + .unwrap_or(&mut Cell { value: 0 }) + - Cell { value: 1 }; + self.data[self.data_pointer] = cell; + } + + fn pinc(&mut self) { + self.data_pointer += 1; + if self.data.get(self.data_pointer).is_none() { + self.data.push(Cell { value: 0 }); + } + } + + fn pdec(&mut self) { + if self.data_pointer != 0 { + self.data_pointer -= 1; + } + } + + fn jiz(&mut self, target: usize) { + if self + .data + .get(self.data_pointer) + .unwrap_or(&Cell { value: 0 }) + .value + == 0 + { + self.instruction_pointer = target; + } + } + + fn jinz(&mut self, target: usize) { + if self + .data + .get(self.data_pointer) + .unwrap_or(&Cell { value: 0 }) + .value + != 0 + { + self.instruction_pointer = target; + } + } + + fn out(&self) { + let value = self + .data + .get(self.data_pointer) + .unwrap_or(&Cell { value: 0 }) + .value; + let c: char = (value as u8).into(); + print!("{c}"); + } + + fn inp(&mut self) { + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + + let value = *input.into_bytes().first().unwrap() as u8; + + let cell = Cell { value }; + + self.data[self.data_pointer] = cell; + } + + fn dbg(&self) { + println!( + "Instruction pointer: {} \nCurrent Instruction: {:?}\nData pointer: {}", + self.instruction_pointer, + self.instructions[self.instruction_pointer], + self.data_pointer + ); + } + + fn mem(&self) { + println!("Memory: {:?}", self.data); + } + + fn code(&self) { + println!("Instructions: {:?}", self.instructions); + } + + fn write(&mut self) { + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + + let addres: usize = input.trim().parse().unwrap(); + + input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + let value: u8 = input.trim().parse().unwrap(); + + let cell = Cell { value }; + + if self.data.len() <= self.data_pointer { + self.data.insert(addres, cell); + } else { + self.data[addres] = cell; + } + + println!("Wrote {value} to {addres}"); + } + + fn run_for(&mut self) { + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + + let repetitions: usize = input.trim().parse().unwrap(); + + let mut bar = indicatif::ProgressBar::new(repetitions as u64); + bar.set_style( + ProgressStyle::with_template( + "{bar:40} | {human_pos}/{human_len} | {eta_precise} | {elapsed_precise}", + ) + .unwrap(), + ); + for _ in 0..repetitions { + bar.inc(1); + match self.step() { + MachineState::Ok => continue, + MachineState::EOI => { + self.instruction_pointer -= 1; + break; + } + } + } + bar.finish(); + } +} + +impl Add for Cell { + type Output = Cell; + + fn add(self, rhs: Self) -> Self::Output { + Cell { + value: self.value + rhs.value, + } + } +} + +impl Sub for Cell { + type Output = Cell; + + fn sub(self, rhs: Self) -> Self::Output { + if self.value == 0 { + Cell { value: 0 } + } else { + Cell { + value: self.value - rhs.value, + } + } + } +} +#[allow(clippy::upper_case_acronyms)] +#[derive(PartialEq, Eq, Debug)] +enum Instruction { + PINC, // > + PDEC, // < + BINC, // + + BDEC, // - + OUT, // . + IN, // , + JIZ(usize), // [ + JINZ(usize), // ] + DBG, // ? (Special instruction not standart) + MEM, // ! (Special instruction not standart) +} + +#[derive(Debug)] +enum ParseError { + NoTargetForJIZ(usize), + NoTargetForJINZ(usize), +} +#[derive(Debug, PartialEq)] +#[allow(clippy::upper_case_acronyms)] +enum MachineState { + Ok, + EOI, // End of Instructions +} +fn parse(program: String) -> Vec { + let mut instruction_vec: Vec = vec![]; + + for (i, c) in program.chars().enumerate() { + if let Some(instruction) = + match_single_char_populate(&c, &program, i, instruction_vec.len()) + { + instruction_vec.push(instruction); + } + } + + instruction_vec +} + +fn match_single_char_populate( + c: &char, + program: &String, + index: usize, + instruction_index: usize, +) -> Option { + match c { + '>' => Some(Instruction::PINC), + '<' => Some(Instruction::PDEC), + '+' => Some(Instruction::BINC), + '-' => Some(Instruction::BDEC), + '.' => Some(Instruction::OUT), + ',' => Some(Instruction::IN), + '[' => Some(Instruction::JIZ( + locate_jmp_target_jiz(program, index, instruction_index).unwrap(), + )), + ']' => Some(Instruction::JINZ( + locate_jmp_target_jinz(program, index, instruction_index).unwrap(), + )), + '?' => Some(Instruction::DBG), + '!' => Some(Instruction::MEM), + _ => None, + } +} + +fn match_single_char(c: &char) -> Option { + match c { + '>' => Some(Instruction::PINC), + '<' => Some(Instruction::PDEC), + '+' => Some(Instruction::BINC), + '-' => Some(Instruction::BDEC), + '.' => Some(Instruction::OUT), + ',' => Some(Instruction::IN), + '[' => Some(Instruction::JIZ(0)), + ']' => Some(Instruction::JINZ(0)), + '?' => Some(Instruction::DBG), + '!' => Some(Instruction::MEM), + _ => None, + } +} + +fn locate_jmp_target_jiz( + program: &String, + index: usize, + instruction_index: usize, +) -> Result { + let program_vector: Vec = program.chars().collect(); + let mut data_pointer = index + 1; + let mut searched_jinz = 1; + let mut instruction_index = instruction_index; + if instruction_index != 0 { + instruction_index -= 1; + } + + while searched_jinz != 0 { + if data_pointer == program_vector.len() { + return Err(ParseError::NoTargetForJIZ(index)); + } + + if let Some(instruction) = match_single_char(program_vector.get(data_pointer).unwrap()) { + match instruction { + Instruction::JIZ(_) => searched_jinz += 1, + Instruction::JINZ(_) => searched_jinz -= 1, + _ => (), + } + instruction_index += 1; + } + data_pointer += 1; + } + + Ok(instruction_index) +} + +fn locate_jmp_target_jinz( + program: &String, + index: usize, + instruction_index: usize, +) -> Result { + let program_vector: Vec = program.chars().collect(); + let mut data_pointer = index - 1; + let mut searched_jiz = 1; + let mut instruction_index = instruction_index - 1; + + while searched_jiz != 0 { + if let Some(instruction) = match_single_char(program_vector.get(data_pointer).unwrap()) { + match instruction { + Instruction::JINZ(_) => searched_jiz += 1, + Instruction::JIZ(_) => searched_jiz -= 1, + _ => (), + } + instruction_index -= 1; + } else if data_pointer == 0 { + return Err(ParseError::NoTargetForJINZ(index)); + } + data_pointer -= 1; + } + + Ok(instruction_index + 1) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn locate_jmp_target_for_jiz_single_jmp() { + let program = String::from("[++>]-"); + let index = 0; + + let return_value = locate_jmp_target_jiz(&program, index, index).unwrap(); + + assert_eq!(return_value, 4); + } + + #[test] + fn locate_jmp_target_for_jiz_multi_jmp() { + let program = String::from("+[>+[<]-]+"); + let index = 1; + + let return_value = locate_jmp_target_jiz(&program, index, index).unwrap(); + + assert_eq!(return_value, 7); + } + + #[test] + fn locate_jmp_target_for_jinz_single_jmp() { + let program = String::from("+[>>+]"); + let index = 5; + + let return_value = locate_jmp_target_jinz(&program, index, index).unwrap(); + + assert_eq!(return_value, 1); + } + + #[test] + fn locate_jmp_target_for_jinz_multi_jmp() { + let program = String::from("+[>[<]>+]"); + let index = 8; + + let return_value = locate_jmp_target_jinz(&program, index, index).unwrap(); + + assert_eq!(return_value, 1); + } + + #[test] + fn parse_program_no_jmp() { + let program = String::from("+><-.>,"); + + let program_vec = parse(program); + let correct_output = vec![ + Instruction::BINC, + Instruction::PINC, + Instruction::PDEC, + Instruction::BDEC, + Instruction::OUT, + Instruction::PINC, + Instruction::IN, + ]; + + for (index, instruction) in program_vec.iter().enumerate() { + assert_eq!(instruction, correct_output.get(index).unwrap()); + } + } + + #[test] + fn parse_program_single_jmp() { + let program = String::from("+>[<]-.>,"); + + let program_vec = parse(program); + let correct_output = vec![ + Instruction::BINC, + Instruction::PINC, + Instruction::JIZ(3), + Instruction::PDEC, + Instruction::JINZ(2), + Instruction::BDEC, + Instruction::OUT, + Instruction::PINC, + Instruction::IN, + ]; + + for (index, instruction) in program_vec.iter().enumerate() { + dbg!(&index); + assert_eq!(instruction, correct_output.get(index).unwrap()); + } + } + + #[test] + fn add_values() { + let program = String::from("+++>++++[<+>-]"); + let mut machine = Machine::new(program); + + machine.execute(); + let cell0 = Cell { value: 7 }; + assert_eq!(machine.data[0], cell0); + } +}