From 6dd7888aafcd915ac758e42a00fff4af667e14dd Mon Sep 17 00:00:00 2001 From: Anas Elgarhy Date: Mon, 10 Oct 2022 22:55:50 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20interpreter=20errors=20yoo=20=F0=9F=A5=B0?= =?UTF-8?q?=F0=9F=92=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/compilerexplorer.settings.xml | 6 + .idea/discord.xml | 2 +- Cargo.toml | 1 + src/arguments.rs | 7 +- src/bf_interpreter/error.rs | 35 +- src/bf_interpreter/interpreter.rs | 423 ++++++++++-------- src/main.rs | 8 +- src/mode.rs | 5 - src/repl.rs | 43 +- src/utils.rs | 21 +- test_code/print_7_formatted.bf | 20 + test_code/print_hello_world_2.bf | 1 + test_code/print_hello_world_2_formated.bf | 43 ++ test_code/print_my_first_name.bf | 3 + .../print_my_first_name_and_last_name.bf | 3 + ...nt_my_first_name_and_last_name_formated.bf | 56 +++ ...t_my_first_name_and_last_name_formatted.bf | 45 ++ test_code/print_my_first_name_formatted.bf | 50 +++ 18 files changed, 542 insertions(+), 230 deletions(-) create mode 100644 .idea/compilerexplorer.settings.xml delete mode 100644 src/mode.rs create mode 100644 test_code/print_7_formatted.bf create mode 100644 test_code/print_hello_world_2.bf create mode 100644 test_code/print_hello_world_2_formated.bf create mode 100644 test_code/print_my_first_name.bf create mode 100644 test_code/print_my_first_name_and_last_name.bf create mode 100644 test_code/print_my_first_name_and_last_name_formated.bf create mode 100644 test_code/print_my_first_name_and_last_name_formatted.bf create mode 100644 test_code/print_my_first_name_formatted.bf diff --git a/.idea/compilerexplorer.settings.xml b/.idea/compilerexplorer.settings.xml new file mode 100644 index 0000000..cc062d5 --- /dev/null +++ b/.idea/compilerexplorer.settings.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml index d8e9561..bccc4d2 100644 --- a/.idea/discord.xml +++ b/.idea/discord.xml @@ -2,6 +2,6 @@ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 1951a52..a403740 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ exclude = [ clap = { version = "4.0.10", features = ["derive", "color", "cargo"] } log = "0.4.17" pretty_env_logger = "0.4.0" +colored = "2.0.0" [dev-dependencies] pretty_assertions = "1.3.0" \ No newline at end of file diff --git a/src/arguments.rs b/src/arguments.rs index bb3119a..0c0ecb0 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -18,10 +18,11 @@ pub struct Args { #[derive(Debug, PartialEq, Copy, Clone, ValueEnum)] pub enum Feature { - /// If the value is you want decrement the value and the value is 0, set the value to 255, otherwise decrement the value. - /// If the value is you want increment the value and the value is 255, set the value to 0, otherwise increment the value. - ReverseValue, + /// If the value is you want decrement the value and the value is 0, don't set the value to 255, otherwise decrement the value. + /// If the value is you want increment the value and the value is 255, don't set the value to 0, otherwise increment the value. + NoReverseValue, /// If the pointer at the end of the array, set the pointer to 0, otherwise increment the pointer. /// If the pointer at the beginning of the array, set the pointer to the end of the array, otherwise decrement the pointer. ReversePointer, + AllowUtf8, } diff --git a/src/bf_interpreter/error.rs b/src/bf_interpreter/error.rs index 103a16c..9f6c867 100644 --- a/src/bf_interpreter/error.rs +++ b/src/bf_interpreter/error.rs @@ -36,9 +36,10 @@ impl std::error::Error for InterpreterError { pub enum InterpreterErrorKind { PointerOutOfBounds(usize), // takes pointer value ValueOutOfBounds, - ByteReadError(std::io::Error), - ReadError, - UnmatchedClosingBracket(usize), // takes position + IoError(std::io::Error), + FlushError(std::io::Error), + UnmatchedBracket, + InvalidUtf8, } impl InterpreterErrorKind { @@ -50,9 +51,10 @@ impl InterpreterErrorKind { match self { InterpreterErrorKind::PointerOutOfBounds(_) => 11, InterpreterErrorKind::ValueOutOfBounds => 12, - InterpreterErrorKind::ByteReadError(_) => 13, - InterpreterErrorKind::ReadError => 14, - InterpreterErrorKind::UnmatchedClosingBracket(_) => 15, + InterpreterErrorKind::IoError(_) => 13, + InterpreterErrorKind::FlushError(_) => 14, + InterpreterErrorKind::UnmatchedBracket => 15, + InterpreterErrorKind::InvalidUtf8 => 16, } } } @@ -62,10 +64,11 @@ impl Display for InterpreterErrorKind { match self { InterpreterErrorKind::PointerOutOfBounds(pointer) => write!(f, "Pointer out of bounds {}", pointer), InterpreterErrorKind::ValueOutOfBounds => write!(f, "Value out of bounds"), - InterpreterErrorKind::ByteReadError(error) => + InterpreterErrorKind::IoError(error) => write!(f, "Failed to read byte from stdin: no bytes available: {}", error), - InterpreterErrorKind::ReadError => write!(f, "Failed to read byte from stdin: no bytes available"), - InterpreterErrorKind::UnmatchedClosingBracket(pos) => write!(f, "Unmatched closing bracket at position {}", pos), + InterpreterErrorKind::FlushError(e) => write!(f, "Failed to flush stdout: {}", e), + InterpreterErrorKind::UnmatchedBracket => write!(f, "Unmatched bracket"), + InterpreterErrorKind::InvalidUtf8 => write!(f, "Invalid utf8"), } } } @@ -85,17 +88,21 @@ mod tests { assert_eq!(error.to_string(), "Value out of bounds"); assert_eq!(error.code, 12); - let error = InterpreterErrorKind::ByteReadError(std::io::Error::new(std::io::ErrorKind::Other, "test")).to_error(); + let error = InterpreterErrorKind::IoError(std::io::Error::new(std::io::ErrorKind::Other, "test")).to_error(); assert_eq!(error.to_string(), "Failed to read byte from stdin: no bytes available: test"); assert_eq!(error.code, 13); - let error = InterpreterErrorKind::ReadError.to_error(); + /*let error = InterpreterErrorKind::FlushError(e).to_error(); assert_eq!(error.to_string(), "Failed to read byte from stdin: no bytes available"); - assert_eq!(error.code, 14); + assert_eq!(error.code, 14);*/ - let error = InterpreterErrorKind::UnmatchedClosingBracket(10).to_error(); - assert_eq!(error.to_string(), "Unmatched closing bracket at position 10"); + let error = InterpreterErrorKind::UnmatchedBracket.to_error(); + assert_eq!(error.to_string(), "Unmatched bracket"); assert_eq!(error.code, 15); + + let error = InterpreterErrorKind::InvalidUtf8.to_error(); + assert_eq!(error.to_string(), "Invalid utf8"); + assert_eq!(error.code, 16); } #[test] diff --git a/src/bf_interpreter/interpreter.rs b/src/bf_interpreter/interpreter.rs index 52b890f..f702344 100644 --- a/src/bf_interpreter/interpreter.rs +++ b/src/bf_interpreter/interpreter.rs @@ -1,4 +1,4 @@ -use crate::{arguments, mode}; +use crate::arguments; use crate::bf_interpreter::error::{InterpreterError, InterpreterErrorKind}; use std::io::{Read, Write}; use std::{char, usize, vec}; @@ -7,203 +7,204 @@ pub struct Interpreter { pub cells: Vec, pub pointer: usize, pub array_size: usize, - pub bf_code: Vec, + pub bf_commands: Vec, brackets: Vec, pub features: Vec, - mode: mode::RunMode, } impl Interpreter { pub fn new( array_size: usize, - bf_code: Option, features: Vec, - run_mode: mode::RunMode ) -> Self { - trace!("Run mode{run_mode:?}"); Self { cells: vec![0; array_size], pointer: 0, array_size, - bf_code: bf_code.unwrap_or_else(|| String::new()).chars().collect(), + bf_commands: vec![], brackets: Vec::new(), features, - mode: run_mode, } } - pub fn run(&mut self, bf_code: Option) -> Result { - let bf_code = match bf_code { - Some(bf_code) => { - bf_code.chars().collect() - } - None => self.bf_code.clone(), - }; + pub fn run(&mut self, bf_code: String) -> Result { + self.bf_commands = to_bf_commands(bf_code.chars().collect())?; - match self.run_brainfuck_code(bf_code, false) { + match self.run_brainfuck_code(&self.bf_commands.clone()) { Ok(_) => Ok(0), Err(e) => Err(e), } } + // +[>++<-] - fn iterate(&mut self, code: Vec) -> Result<(), InterpreterError> { + fn iterate(&mut self, code: &Vec) -> Result<(), InterpreterError> { trace!("Iterate: {:?}", code); while self.cells[self.pointer] != 0 { - self.run_brainfuck_code(code.clone(), true)?; + self.run_brainfuck_code(code)?; } Ok(()) } - fn run_brainfuck_code(&mut self, bf_code: Vec, from_loop: bool) -> Result<(), InterpreterError> { - let mut removed_num = 0_usize; - for (i, ch) in bf_code.iter().enumerate() { - match BfCommand::from_char(ch, i - removed_num) { - Some(cmd) => { - trace!("Executing command: {:?}", cmd); - self.execute(cmd)?; + fn run_brainfuck_code(&mut self, bf_code: &Vec) -> Result<(), InterpreterError> { + for command in bf_code { + match command { + BfCommand::IncPtr => self.increment_pointer()?, + BfCommand::DecPtr => self.decrement_pointer()?, + BfCommand::IncVal => self.increment_value()?, + BfCommand::DecVal => self.decrement_value()?, + BfCommand::Print => self.output_value()?, + BfCommand::Read => self.input_value()?, + BfCommand::Loop(loop_body) => self.iterate(loop_body)?, + } + } - // Push the char to the bf_code vector if isn't from loop and we run in REPL mode - if !from_loop && self.mode == mode::RunMode::Repl { - self.bf_code.push(ch.clone()); - } - } - None => { - trace!("Skipping character: \'{}\'", ch); - removed_num += 1; - } + Ok(()) + } + + fn increment_pointer(&mut self) -> Result<(), InterpreterError> { + trace!("Increment pointer"); + self.pointer += 1; + if self.pointer >= self.array_size { + if self.features.contains(&arguments::Feature::ReversePointer) { + self.pointer = 0; + } else { + return Err(InterpreterErrorKind::PointerOutOfBounds(self.pointer).to_error()); } } Ok(()) } - fn execute(&mut self, cmd: BfCommand) -> Result<(), InterpreterError> { - match cmd { - BfCommand::IncPtr => { - self.pointer += 1; - if self.pointer >= self.array_size { - if self.features.contains(&arguments::Feature::ReversePointer) { - self.pointer = 0; - } else { - return Err(InterpreterErrorKind::PointerOutOfBounds(self.pointer).to_error()) - } - } - } - BfCommand::DecPtr => { - if self.pointer == 0 { - if self.features.contains(&arguments::Feature::ReversePointer) { - self.pointer = self.array_size - 1; - } else { - return Err(InterpreterErrorKind::PointerOutOfBounds(self.pointer).to_error()); - } - } else { - self.pointer -= 1; - } - } - BfCommand::IncVal => { - if self.cells[self.pointer] == 255 { - if self.features.contains(&arguments::Feature::ReverseValue) { - self.cells[self.pointer] = 0; - } else { - return Err(InterpreterErrorKind::ValueOutOfBounds.to_error()); - } - } else { - self.cells[self.pointer] += 1; - } - } - BfCommand::DecVal => { - if self.cells[self.pointer] == 0 { - if self.features.contains(&arguments::Feature::ReverseValue) { - self.cells[self.pointer] = 255; - } else { - return Err(InterpreterErrorKind::ValueOutOfBounds.to_error()); - } - } else { - self.cells[self.pointer] -= 1; - } - } - BfCommand::Print => { - print!("{}", self.cells[self.pointer] as char); - std::io::stdout().flush().unwrap(); - } - BfCommand::Read => { - self.cells[self.pointer] = match std::io::stdin().bytes().next() { - Some(Ok(byte)) => byte, - Some(Err(e)) => { - return Err(InterpreterErrorKind::ByteReadError(e).to_error()); - } - None => { - return Err(InterpreterErrorKind::ReadError.to_error()); - } - }; - } - BfCommand::LoopStart(i) => { - self.brackets.push(BfCommand::LoopStart(i)); - } - BfCommand::LoopEnd(i) => { - let open_bracket = self.brackets.pop(); - match open_bracket { - Some(BfCommand::LoopStart(j)) => { - if self.cells[self.pointer] != 0 { - let start = match &self.mode { - mode::RunMode::Repl if self.bf_code.len() - j >= i => - self.bf_code.len() - j - i + 1, - _ => j + 1 - }; - debug!("bf_code array len: {}", self.bf_code.len()); - debug!("start index {}", start); - debug!("bf_code at start: {}", self.bf_code[start]); - debug!("i: {i}, j: {j}"); - // debug!("{}", self.bf_code[i]); - let end = match &self.mode { - mode::RunMode::Repl => { - let mut s = i + start - 2; - - if s >= self.bf_code.len() { - s = s - (self.bf_code.len() - start) + 1; - } - - s - }, - mode::RunMode::Execute => i - 1, - }; - let range = start..=end; - debug!("{range:?}"); - let code = self.bf_code[range].to_vec(); - self.iterate(code)?; - } - } - _ => { - return Err(InterpreterErrorKind::UnmatchedClosingBracket(i).to_error()); - } - } + fn decrement_pointer(&mut self) -> Result<(), InterpreterError> { + trace!("Decrement pointer"); + if self.pointer == 0 { + if self.features.contains(&arguments::Feature::ReversePointer) { + self.pointer = self.array_size - 1; + } else { + return Err(InterpreterErrorKind::PointerOutOfBounds(self.pointer).to_error()); } + } else { + self.pointer -= 1; } Ok(()) } + fn increment_value(&mut self) -> Result<(), InterpreterError> { + trace!("Increment value"); + if self.cells[self.pointer] == 255 { + if !self.features.contains(&arguments::Feature::NoReverseValue) { + self.cells[self.pointer] = 0; + } else { + return Err(InterpreterErrorKind::ValueOutOfBounds.to_error()); + } + } else { + self.cells[self.pointer] += 1; + } + Ok(()) + } + + fn decrement_value(&mut self) -> Result<(), InterpreterError> { + trace!("Decrement value"); + if self.cells[self.pointer] == 0 { + if !self.features.contains(&arguments::Feature::NoReverseValue) { + self.cells[self.pointer] = 255; + } else { + return Err(InterpreterErrorKind::ValueOutOfBounds.to_error()); + } + } else { + self.cells[self.pointer] -= 1; + } + Ok(()) + } + + fn output_value(&mut self) -> Result<(), InterpreterError> { + trace!("Output value"); + + if self.features.contains(&arguments::Feature::AllowUtf8) { + let c = char::from_u32(self.cells[self.pointer] as u32); + match c { + Some(c) => print!("{}", c), + None => return Err(InterpreterErrorKind::InvalidUtf8.to_error()), + } + } else { + print!("{}", self.cells[self.pointer] as char); + } + match std::io::stdout().flush() { + Ok(_) => Ok(()), + Err(e) => Err(InterpreterErrorKind::FlushError(e).to_error()), + } + } + + fn input_value(&mut self) -> Result<(), InterpreterError> { + trace!("Input value"); + let mut input = [0; 1]; + match std::io::stdin().read_exact(&mut input) { + Ok(_) => { + self.cells[self.pointer] = input[0]; + Ok(()) + } + Err(e) => Err(InterpreterErrorKind::IoError(e).to_error()), + } + } + pub fn reset(&mut self) { self.cells = vec![0; self.array_size]; self.pointer = 0; self.brackets = Vec::new(); - self.bf_code = Vec::new(); + self.bf_commands = Vec::new(); } } -#[derive(Debug, PartialEq)] -enum BfCommand { +#[derive(Debug, PartialEq, Clone)] +pub enum BfCommand { IncPtr, DecPtr, IncVal, DecVal, Print, Read, - LoopStart(usize), - LoopEnd(usize), + Loop(Vec), +} + +fn to_bf_commands(bf_code: Vec) -> Result, InterpreterError> { + let mut bf_commands = Vec::new(); + let mut i = 0; + while i < bf_code.len() { + match bf_code[i] { + '[' => { + let mut bracket_count = 1; + let mut j = i + 1; + while j < bf_code.len() { + match bf_code[j] { + '[' => bracket_count += 1, + ']' => bracket_count -= 1, + _ => (), + } + if bracket_count == 0 { + break; + } + j += 1; + } + if bracket_count != 0 { + return Err(InterpreterErrorKind::UnmatchedBracket.to_error()); + } + bf_commands.push(BfCommand::Loop(to_bf_commands(bf_code[i + 1..j].to_vec())?)); + i = j; + } + _ => { + match BfCommand::from(bf_code[i]) { + Some(command) => bf_commands.push(command), + None => (), + } + }, + } + i += 1; + } + Ok(bf_commands) } impl BfCommand { - fn from_char(c: &char, index: usize) -> Option { + fn from(c: char) -> Option { match c { '>' => Some(BfCommand::IncPtr), '<' => Some(BfCommand::DecPtr), @@ -211,8 +212,6 @@ impl BfCommand { '-' => Some(BfCommand::DecVal), '.' => Some(BfCommand::Print), ',' => Some(BfCommand::Read), - '[' => Some(BfCommand::LoopStart(index)), - ']' => Some(BfCommand::LoopEnd(index)), _ => None, } } @@ -221,91 +220,162 @@ impl BfCommand { #[cfg(test)] mod tests { use super::*; - use crate::mode::RunMode; - use pretty_assertions::assert_eq; - use crate::utils; // for testing only + use pretty_assertions::assert_eq; // for testing only + use crate::utils; #[test] fn print_h_combine_repl() { let mut interpreter = Interpreter::new( 30000, - None, vec![], - RunMode::Repl ); - assert_eq!(interpreter.run(None), Ok(0)); + assert_eq!(interpreter.run(String::from(">+++++++++[<++++ ++++>-]<.")), Ok(0)); - assert_eq!(interpreter.run(Some(String::from(">+++++++++[<++++ ++++>-]<."))), Ok(0)); + println!(); } + #[test] fn print_h_repl() { let mut interpreter = Interpreter::new( 30000, - None, vec![], - RunMode::Repl ); - assert_eq!(interpreter.run(None), Ok(0)); + assert_eq!(interpreter.run(String::from(">+++++++++")), Ok(0)); + assert_eq!(interpreter.run(String::from("[<++++ ++++>-]<.")), Ok(0)); - assert_eq!(interpreter.run(Some(String::from(">+++++++++"))), Ok(0)); - assert_eq!(interpreter.run(Some(String::from("[<++++ ++++>-]<."))), Ok(0)); + println!(); } + #[test] + fn nested_loop_level_1_combine() { + let mut interpreter = Interpreter::new( + 5, + vec![], + ); + + assert_eq!(interpreter.run(String::from("++[>++[>+<-]<-]")), Ok(0)); + assert_eq!(interpreter.cells[2], 4); + + println!(); + } + + + #[test] fn execute_hello_world_from_file() { let mut interpreter = Interpreter::new( 30000, - utils::read_brainfuck_code_if_any(&Some(String::from("test_code/hello_world.bf"))), vec![], - RunMode::Execute ); - assert_eq!(interpreter.run(None), Ok(0)); + + println!(); + + assert_eq!(interpreter.run( + utils::read_brainfuck_code( + &String::from("test_code/hello_world.bf"))), Ok(0)); } #[test] fn execute_print_hi_from_file() { let mut interpreter = Interpreter::new( 30000, - utils::read_brainfuck_code_if_any(&Some(String::from("test_code/print_hi.bf"))), vec![], - RunMode::Execute ); - assert_eq!(interpreter.run(None), Ok(0)); + println!(); + + assert_eq!(interpreter.run( + utils::read_brainfuck_code(&String::from("test_code/print_hi.bf"))), Ok(0)); } #[test] fn execute_print_hi_yooo_from_file() { let mut interpreter = Interpreter::new( 30000, - utils::read_brainfuck_code_if_any(&Some(String::from("test_code/print_hi_yooo.bf"))), vec![], - RunMode::Execute ); - assert_eq!(interpreter.run(None), Ok(0)); + println!(); + + assert_eq!(interpreter.run( + utils::read_brainfuck_code(&String::from("test_code/print_hi_yooo.bf"))), + Ok(0)); + } + + #[test] + fn execute_print_my_first_name_from_formatted_file() { + let mut interpreter = Interpreter::new( + 30000, + vec![], + ); + + println!(); + + assert_eq!(interpreter.run( + utils::read_brainfuck_code(&String::from("test_code/print_my_first_name_formatted.bf"))), + Ok(0)); + } + + #[test] + fn execute_print_my_first_name_from_file() { + let mut interpreter = Interpreter::new( + 30000, + vec![], + ); + + println!(); + + assert_eq!(interpreter.run( + utils::read_brainfuck_code(&String::from("test_code/print_my_first_name.bf"))), + Ok(0)); + } + + #[test] + fn execute_print_my_first_name_and_last_name_from_formatted_file() { + let mut interpreter = Interpreter::new( + 30000, + vec![], + ); + + println!(); + + assert_eq!(interpreter.run( + utils::read_brainfuck_code( + &String::from("test_code/print_my_first_name_and_last_name_formatted.bf"))), + Ok(0)); + } + + #[test] + fn execute_print_my_first_name_and_last_name_from_file() { + let mut interpreter = Interpreter::new( + 30000, + vec![], + ); + + println!(); + + assert_eq!(interpreter.run(utils::read_brainfuck_code( + &String::from("test_code/print_my_first_name_and_last_name.bf"))), + Ok(0)); } #[test] fn reset() { let mut interpreter = Interpreter::new( 30000, - None, vec![], - RunMode::Repl ); - assert_eq!(interpreter.run(None), Ok(0)); - assert_eq!(interpreter.run(Some(String::from(">++++"))), Ok(0)); + assert_eq!(interpreter.run(String::from(">++++")), Ok(0)); assert_eq!(interpreter.pointer, 1); assert_eq!(interpreter.cells[0], 0); assert_eq!(interpreter.cells[1], 4); - assert_eq!(interpreter.bf_code, vec!['>', '+', '+', '+' , '+']); + // assert_eq!(interpreter.commands, vec!['>', '+', '+', '+', '+']); // reset interpreter.reset(); @@ -313,21 +383,6 @@ mod tests { assert_eq!(interpreter.pointer, 0); assert_eq!(interpreter.cells[0], 0); assert_eq!(interpreter.cells[1], 0); - assert_eq!(interpreter.bf_code, Vec::::new()); - - assert_eq!(interpreter.run(None), Ok(0)); - } - - #[test] - fn test_from_char() { - assert_eq!(BfCommand::from_char(&'>', 0), Some(BfCommand::IncPtr)); - assert_eq!(BfCommand::from_char(&'<', 0), Some(BfCommand::DecPtr)); - assert_eq!(BfCommand::from_char(&'+', 0), Some(BfCommand::IncVal)); - assert_eq!(BfCommand::from_char(&'-', 0), Some(BfCommand::DecVal)); - assert_eq!(BfCommand::from_char(&'.', 0), Some(BfCommand::Print)); - assert_eq!(BfCommand::from_char(&',', 0), Some(BfCommand::Read)); - assert_eq!(BfCommand::from_char(&'[', 0), Some(BfCommand::LoopStart(0))); - assert_eq!(BfCommand::from_char(&']', 0), Some(BfCommand::LoopEnd(0))); - assert_eq!(BfCommand::from_char(&' ', 0), None); + assert_eq!(interpreter.bf_commands, Vec::::new()); } } diff --git a/src/main.rs b/src/main.rs index a64ed54..7202d74 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ mod arguments; mod repl; mod utils; mod bf_interpreter; -mod mode; use clap::Parser; extern crate pretty_env_logger; @@ -22,18 +21,13 @@ fn main() { info!("Initializing bf_interpreter"); let mut interpreter = Interpreter::new( args.array_size, - utils::read_brainfuck_code_if_any(&args.source), args.features.unwrap_or_else(|| vec![]), - match args.source { - Some(_) => mode::RunMode::Execute, - None => mode::RunMode::Repl - }, ); match args.source { Some(source) => { info!("Running brainfuck source code from file: {}", source); - match interpreter.run(None) { + match interpreter.run(utils::read_brainfuck_code(&source)) { Ok(exit_code) => { info!("Finished running brainfuck source code from file: {}", source); if !args.without_tiles { diff --git a/src/mode.rs b/src/mode.rs deleted file mode 100644 index b64f225..0000000 --- a/src/mode.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[derive(PartialEq, Debug)] -pub enum RunMode { - Execute, - Repl, -} \ No newline at end of file diff --git a/src/repl.rs b/src/repl.rs index a74c225..b6a6ed2 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -19,8 +19,15 @@ impl Repl { } pub fn run(mut self) { + let mut code_bat = String::new(); + let mut is_loop = false; loop { - print!("\n {}", PROMPT); + if is_loop { + print!("... "); + } else { + print!("{}", PROMPT); + } + std::io::stdout().flush().unwrap_or_else(|_| { error!("Failed to flush stdout"); std::process::exit(1); @@ -38,10 +45,18 @@ impl Repl { self.history.push(input.clone()); // Save input to history + if input.contains('[') && (!input.contains(']') && !is_loop) { + let loop_start_index = input.find('[').unwrap(); + + code_bat.push_str(&input[loop_start_index..]); + is_loop = true; + input = input[..loop_start_index].to_string(); + } + if input.starts_with(COMMAND_PREFIX) { self.run_repl_cmd(input); } else { - match self.interpreter.run(Some(input)) { + match self.interpreter.run(input) { Ok(_) => { info!("Successfully ran brainfuck source code from REPL"); } @@ -108,7 +123,7 @@ impl Repl { // Run all commands in history for cmd in self.history.iter() { - match self.interpreter.run(Some(cmd.clone())) { + match self.interpreter.run(cmd.clone()) { Ok(_) => { info!( "Successfully ran brainfuck source code from REPL" @@ -169,3 +184,25 @@ pub fn start(interpreter: Interpreter) { Repl::new(interpreter).run(); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn nested_loop_level_1() { + let mut interpreter = Interpreter::new( + 30000, + vec![], + ); + + + assert_eq!(interpreter.run(String::from("++")), Ok(0)); + assert_eq!(interpreter.run(String::from("[>++")), Ok(0)); + assert_eq!(interpreter.run(String::from("[>+<-]")), Ok(0)); + assert_eq!(interpreter.run(String::from("<-]")), Ok(0)); + assert_eq!(interpreter.cells[2], 4); + + println!(); + } +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs index dac20a3..1b73ede 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,17 +1,12 @@ -pub(crate) fn read_brainfuck_code_if_any(source: &Option) -> Option { - match source { - Some(source) => { - info!("Reading brainfuck source code from file: {}", source); - match std::fs::read_to_string(source) { - Ok(source) => Some(clean(source)), - Err(e) => { - error!("Failed to read source code file: {}", e); - eprintln!("Failed to read source code file: {}", e); - std::process::exit(1); - } - } +pub fn read_brainfuck_code(source: &String) -> String { + info!("Reading brainfuck source code from file: {}", source); + match std::fs::read_to_string(source) { + Ok(source) => clean(source), + Err(e) => { + error!("Failed to read source code file: {}", e); + eprintln!("Failed to read source code file: {}", e); + std::process::exit(1); } - None => None, } } diff --git a/test_code/print_7_formatted.bf b/test_code/print_7_formatted.bf new file mode 100644 index 0000000..55f7b29 --- /dev/null +++ b/test_code/print_7_formatted.bf @@ -0,0 +1,20 @@ +++ Cell c0 = 2 +> +++++ Cell c1 = 5 + +[ Start your loops with your cell pointer on the loop counter (c1 in our case) +< + Add 1 to c0 +> - Subtract 1 from c1 +] End your loops with the cell pointer on the loop counter + +At this point our program has added 5 to 2 leaving 7 in c0 and 0 in c1 +but we cannot output this value to the terminal since it is not ASCII encoded. + +To display the ASCII character "7" we must add 48 to the value 7. +We use a loop to compute 48 = 6 * 8. + +++++ ++++ c1 = 8 and this will be our loop counter again +[ +< +++ +++ Add 6 to c0 +> - Subtract 1 from c1 +] +< . Print out c0 which has the value 55 which translates to "7"! diff --git a/test_code/print_hello_world_2.bf b/test_code/print_hello_world_2.bf new file mode 100644 index 0000000..8fa0f72 --- /dev/null +++ b/test_code/print_hello_world_2.bf @@ -0,0 +1 @@ +++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++. diff --git a/test_code/print_hello_world_2_formated.bf b/test_code/print_hello_world_2_formated.bf new file mode 100644 index 0000000..3acacd3 --- /dev/null +++ b/test_code/print_hello_world_2_formated.bf @@ -0,0 +1,43 @@ +[ This program prints "Hello World!" and a newline to the screen, its + length is 106 active command characters. [It is not the shortest.] + + This loop is an "initial comment loop", a simple way of adding a comment + to a BF program such that you don't have to worry about any command + characters. Any ".", ",", "+", "-", "<" and ">" characters are simply + ignored, the "[" and "]" characters just have to be balanced. This + loop and the commands it contains are ignored because the current cell + defaults to a value of 0; the 0 value causes this loop to be skipped. +] +++++++++ Set Cell #0 to 8 +[ + >++++ Add 4 to Cell #1; this will always set Cell #1 to 4 + [ as the cell will be cleared by the loop + >++ Add 2 to Cell #2 + >+++ Add 3 to Cell #3 + >+++ Add 3 to Cell #4 + >+ Add 1 to Cell #5 + <<<<- Decrement the loop counter in Cell #1 + ] Loop until Cell #1 is zero; number of iterations is 4 + >+ Add 1 to Cell #2 + >+ Add 1 to Cell #3 + >- Subtract 1 from Cell #4 + >>+ Add 1 to Cell #6 + [<] Move back to the first zero cell you find; this will + be Cell #1 which was cleared by the previous loop + <- Decrement the loop Counter in Cell #0 +] Loop until Cell #0 is zero; number of iterations is 8 + +The result of this is: +Cell no : 0 1 2 3 4 5 6 +Contents: 0 0 72 104 88 32 8 +Pointer : ^ + +>>. Cell #2 has value 72 which is 'H' +>---. Subtract 3 from Cell #3 to get 101 which is 'e' ++++++++..+++. Likewise for 'llo' from Cell #3 +>>. Cell #5 is 32 for the space +<-. Subtract 1 from Cell #4 for 87 to give a 'W' +<. Cell #3 was set to 'o' from the end of 'Hello' ++++.------.--------. Cell #3 for 'rl' and 'd' +>>+. Add 1 to Cell #5 gives us an exclamation point +>++. And finally a newline from Cell #6 diff --git a/test_code/print_my_first_name.bf b/test_code/print_my_first_name.bf new file mode 100644 index 0000000..fbec664 --- /dev/null +++ b/test_code/print_my_first_name.bf @@ -0,0 +1,3 @@ +Print my first name (Anas) +Unformated: +++++++++[>++++[>++<-]>>>>>>++[<<<->>>-]<<<<<<<-]>>+.<<++++[>+++[>+++<-]>++<<-]>>+.<<+++[>+++[>-<-]>-<<-]>>-.<<++++++[>>+++<<-]>>. diff --git a/test_code/print_my_first_name_and_last_name.bf b/test_code/print_my_first_name_and_last_name.bf new file mode 100644 index 0000000..ad6dd5c --- /dev/null +++ b/test_code/print_my_first_name_and_last_name.bf @@ -0,0 +1,3 @@ +Print my first name and my last name (Anas Elgarhy) +Unformated: +++++++++[>++++[>++>+>++>+++<<<<-]>>>>+<<<<<-]>>+.<<++++[>+++[>+++<-]>++<<-]>>+.<<+++[>+++[>-<-]>-<<-]>>-.<<++++++[>>+++<<-]>>.>.>+++++.<<-------.-----.------.<+++++[>+++<-]>++.>>>.<<<+++++++. diff --git a/test_code/print_my_first_name_and_last_name_formated.bf b/test_code/print_my_first_name_and_last_name_formated.bf new file mode 100644 index 0000000..559dc49 --- /dev/null +++ b/test_code/print_my_first_name_and_last_name_formated.bf @@ -0,0 +1,56 @@ +++++ ++++ 8 +[ + >++++ + [ + >++ A + >+ space + >++ + >+++ + <<<<- + ] + >>>>+ + <<<<<- +] +>>+. Print cell 2: A +<<++++ +[ + >+++ + [ + >+++ + <- + ] + >++ + <<- +] +>>+. Print n +<<+++ +[ + >+++ + [ + >- + <- + ] + >- + <<- +] +>>-. Print a +<<++++++ +[ + >>+++ + <<- +] +>>. Print s + +>. Print spce from the cell 3 +>+++++. Print E from cell 4 +<<-------. Print l +-----. Print g +------. Print a +<+++++ +[ + >+++ + <- +] +>++. Print r +>>>. Print h +<<<+++++++. Print y diff --git a/test_code/print_my_first_name_and_last_name_formatted.bf b/test_code/print_my_first_name_and_last_name_formatted.bf new file mode 100644 index 0000000..eb28b3a --- /dev/null +++ b/test_code/print_my_first_name_and_last_name_formatted.bf @@ -0,0 +1,45 @@ +My first name and my last name (Anas Elgarhy) + +Formated: +++++ ++++ 8 +[ + >++++ + [ + >++ A + >+ space + >++ + >+++ + <<<<- + ] + >>>>+ + <<<<<- +] +>>+. Print cell 2: A +<<++++ +[ + >+++ + [ + >+++ + <- + ] + >++ + <<- +] +>>+. Print n +<<+++ +[ + >+++ + [ + >- + <- + ] + >- + <<- +] +>>-. Print a +<<++++++ +[ + >>+++ + <<- +] +>>. Print s diff --git a/test_code/print_my_first_name_formatted.bf b/test_code/print_my_first_name_formatted.bf new file mode 100644 index 0000000..1e2d08a --- /dev/null +++ b/test_code/print_my_first_name_formatted.bf @@ -0,0 +1,50 @@ +My first name (Anas) +Formated: +++++ ++++ 8 +[ + >++++ + [ + >++ A + >+++ a + >++++ + >+ space + <<<<- + ] + + >>>>>>++ + [ + <<<- + >>>- + ] + + <<<<<<<- +] +>>+. Print cell 2: A +<<++++ +[ + >+++ + [ + >+++ + <- + ] + >++ + <<- +] +>>+. Print n +<<+++ +[ + >+++ + [ + >- + <- + ] + >- + <<- +] +>>-. Print n +<<++++++ +[ + >>+++ + <<- +] +>>. Print s