From fa39f358ce8278265d15725a5fc4e6c1fd465676 Mon Sep 17 00:00:00 2001 From: Anas Elgarhy Date: Wed, 12 Oct 2022 18:31:07 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20the=20repl=20and=20improve=20it=20=20yo?= =?UTF-8?q?=20=F0=9F=A4=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + src/bf_interpreter/interpreter.rs | 12 +- src/main.rs | 13 +- src/repl.rs | 298 +++++++++++++++++++++--------- 4 files changed, 224 insertions(+), 100 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a403740..fed4edd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ clap = { version = "4.0.10", features = ["derive", "color", "cargo"] } log = "0.4.17" pretty_env_logger = "0.4.0" colored = "2.0.0" +no-panic = "0.1.16" [dev-dependencies] pretty_assertions = "1.3.0" \ No newline at end of file diff --git a/src/bf_interpreter/interpreter.rs b/src/bf_interpreter/interpreter.rs index bf83aae..12c2b7e 100644 --- a/src/bf_interpreter/interpreter.rs +++ b/src/bf_interpreter/interpreter.rs @@ -1,23 +1,19 @@ use crate::arguments; use crate::bf_interpreter::error::{InterpreterError, InterpreterErrorKind}; -use std::io::{Read, Write}; +use std::io::{BufRead, Read, Write}; use std::{char, usize, vec}; -pub struct Interpreter<'a> { +pub struct Interpreter { pub cells: Vec, pub pointer: usize, pub bf_commands: Vec, brackets: Vec, - pub input: &'a Box, - pub output: &'a Box, pub features: Vec, } -impl<'a> Interpreter<'a> { +impl Interpreter { pub fn new( array_size: usize, - input: &'a mut Box, - output: &'a mut Box, features: Vec, ) -> Self { Self { @@ -25,8 +21,6 @@ impl<'a> Interpreter<'a> { pointer: 0, bf_commands: vec![], brackets: Vec::new(), - input, - output, features, } } diff --git a/src/main.rs b/src/main.rs index 21d28bc..15f4954 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod repl; mod utils; mod bf_interpreter; -use std::io::{Read, Write}; +use std::io::{BufRead, Write}; use clap::Parser; extern crate pretty_env_logger; #[macro_use] @@ -20,13 +20,9 @@ fn main() { let args = Args::parse(); info!("Parsed command line arguments: {:?}", args); - let mut stdin: Box = Box::new(std::io::stdin()); - let mut stdout: Box = Box::new(std::io::stdout()); info!("Initializing interpreter"); let mut interpreter = Interpreter::new( args.array_size, - &mut stdin, - &mut stdout, args.features.unwrap_or_else(|| vec![]), ); @@ -41,8 +37,9 @@ fn main() { "Successfully ran brainfuck source code from file: {}", source ).bold().green()); - println!("Exiting with code: {exit_code}"); - std::process::exit(0); + println!("{}{}", "Exiting with code: ".truecolor(33, 97, 61), + exit_code.to_string().bold().green()); + std::process::exit(exit_code); } } Err(e) => { @@ -51,6 +48,6 @@ fn main() { } } } - None => repl::start(&mut interpreter, &mut std::io::stdin(), &mut std::io::stdout()), + None => repl::start(interpreter), } } diff --git a/src/repl.rs b/src/repl.rs index 83a5804..bd3364e 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,41 +1,45 @@ use crate::bf_interpreter::interpreter::Interpreter; -use std::io::{Write, Read}; +use std::io::{Write, Read, BufRead, BufReader, Stdout, Stdin}; use colored::Colorize; +use no_panic::no_panic; -struct Repl<'a> { - interpreter: &'a mut Interpreter<'a>, +struct Repl { + interpreter: Interpreter, history: Vec, + loop_body: String, + loop_depth: usize, } const PROMPT: &str = "bf-interpreter> "; const HISTORY_FILE: &str = "bf-interpreter-history.bfr"; const COMMAND_PREFIX: &str = "!"; -impl Repl<'_> { - pub fn new<'a>(interpreter: &'a mut Interpreter<'a>) -> Repl<'a> { +impl Repl { + pub fn new(interpreter: Interpreter) -> Repl { Repl { interpreter, history: Vec::new(), + loop_body: String::new(), + loop_depth: 0, } } - pub fn run(mut self, - input: &mut impl Read, - output: &mut impl Write) -> Result<(), std::io::Error> { - let mut code_bat = String::new(); - let mut is_loop = false; + // #[no_panic] + pub fn run(mut self) -> Result<(), std::io::Error> { loop { - output.write_all( - if is_loop { - format!("{}", "... ".yellow()) - } else { - format!("{}", PROMPT) - }.as_bytes() - )?; + print!("{}", + if self.loop_depth != 0 { + "........ ".yellow() + } else { + PROMPT.to_string().truecolor(54, 76, 76) + } + ); + + std::io::stdout().flush()?; let mut user_input = String::new(); - match input.read_to_string(&mut user_input) { + match std::io::stdin().read_line(&mut user_input) { Ok(_) => {} Err(e) => { error!("Failed to read input: {}", e); @@ -44,72 +48,106 @@ impl Repl<'_> { } user_input = user_input.trim().to_string(); // Remove trailing newline - self.history.push(user_input.clone()); // Save input to history - - if user_input.contains('[') && (!user_input.contains(']') && !is_loop) { - let loop_start_index = user_input.find('[').unwrap(); - - code_bat.push_str(&user_input[loop_start_index..]); - is_loop = true; - user_input = user_input[..loop_start_index].to_string(); + if !user_input.is_empty() && user_input.len() > 0 { + self.history.push(user_input.clone()); // Save input to history + self.process(user_input); // Process the input } + } + } - if user_input.starts_with(COMMAND_PREFIX) { - self.run_repl_cmd(user_input, output)?; - } else { - match self.interpreter.run(user_input) { - Ok(_) => { - info!("Successfully ran brainfuck source code from REPL"); + pub fn process(&mut self, mut user_input: String) { + match user_input.find('[') { + Some(index) if self.loop_depth == 0 => { + self.loop_body.push_str(&user_input[index..]); + self.loop_depth = 1; + user_input = user_input[..index].to_string(); + }, + Some(_) => { + self.loop_body.push_str(&user_input); + user_input.matches('[').for_each(|_| self.loop_depth += 1); + user_input.matches(']').for_each(|_| self.loop_depth -= 1); + return; + }, + _ => { + if user_input.contains(']') { + if self.loop_depth == 0 { + error!("Found ']' without matching '['"); + return; } - Err(e) => { - error!("Failed to run brainfuck source code from REPL: {}", e); + self.loop_depth -= 1; + if self.loop_depth == 0 { + self.loop_body.push_str(&user_input); + user_input = self.loop_body.clone(); + self.loop_body = String::new(); } } + if self.loop_depth != 0 { + self.loop_body.push_str(&user_input); + return; + } + } + } + + if user_input.is_empty() || user_input.len() == 0 { + return; + } + + if user_input.starts_with(COMMAND_PREFIX) { + self.run_repl_cmd(user_input); + } else { + match self.interpreter.run(user_input) { + Ok(_) => { + info!("Successfully ran brainfuck source code from REPL"); + } + Err(e) => { + error!("Failed to run brainfuck source code from REPL: {}", e); + } } } } - fn run_repl_cmd(&mut self, user_input: String, output: &mut impl Write) -> Result<(), std::io::Error> { + fn run_repl_cmd(&mut self, user_input: String) { let mut cmd = user_input.split_whitespace(); match cmd.next() { Some(repl_cmd) => { match repl_cmd.get(COMMAND_PREFIX.len()..).unwrap_or("") { "fuck" => { - output.write_all("Bye bye :D\n".green().as_bytes())?; + println!("{}", "Bye bye :D".green()); std::process::exit(0); } "array" | "a" => { - output.write_all(format!("Current array: {:?}\n", self.interpreter.cells).as_bytes())?; + println!("{}", format!("Current array: {:?}", self.interpreter.cells)); } "array_size" | "as" => { - output.write_all(format!("Current array size: {}\n", - self.interpreter.array_size.to_string().bold().green()).as_bytes())?; + println!("{}", format!("Current array size: {}", + self.interpreter.cells.len() + .to_string().bold().green())); } "pointer" | "p" => { - output.write_all(format!("Current pointer: {}\n", - self.interpreter.pointer.to_string().bold().green()).as_bytes())?; + println!("{}", format!("Current pointer: {}", + self.interpreter.pointer.to_string().bold().green())); } "pointer_value" | "pv" => { - format!( - "Current pointer value: {} = \'{}\' (char)\n", + println!( + "Current pointer value: {} = \'{}\' (char)", self.interpreter.cells[self.interpreter.pointer], self.interpreter.cells[self.interpreter.pointer] as char ); } "history" | "h" => { - output.write_all("History:\n".underline().green().as_bytes())?; + println!("{}", "History:".underline().green()); for (i, cmd) in self.history.iter().enumerate() { - output.write_all(format!("{}: {}", i, cmd).as_bytes())?; + println!("{}", format!("{}: {}", i, cmd)); } } "save" | "s" => { let file_name = cmd.next().unwrap_or(HISTORY_FILE); - output.write_all(format!("Saving history to file: {file_name}").yellow().as_bytes())?; + println!("{}", format!("Saving history to file: {file_name}").yellow()); match std::fs::write(file_name, self.history.join("\n")) { Ok(_) => { - output.write_all(format!("Successfully saved history to file: {file_name}") - .green().as_bytes())?; + println!("{}", format!("Successfully saved history to file: {file_name}") + .green()); } Err(e) => { error!("Failed to save history to file: {}", e); @@ -119,11 +157,11 @@ impl Repl<'_> { "load" | "l" => { let file_name = cmd.next().unwrap_or(HISTORY_FILE); - output.write_all(format!("Loading history from file: {file_name}\n").yellow().as_bytes())?; + println!("{}", format!("Loading history from file: {file_name}").yellow()); match std::fs::read_to_string(file_name) { Ok(history) => { - output.write_all(format!("Successfully loaded history from file: {file_name}") - .green().as_bytes())?; + println!("{}", format!("Successfully loaded history from file: {file_name}") + .green()); self.history = history.split("\n").map(|s| s.to_string()).collect(); // Run all commands in history @@ -149,12 +187,12 @@ impl Repl<'_> { } } "reset" | "r" => { - output.write_all("Resetting REPL\n".truecolor(56, 33, 102).as_bytes())?; + println!("{}", "Resetting REPL".truecolor(56, 33, 102)); self.interpreter.reset(); self.history = Vec::new(); } "help" => { - output.write_all("!array, !a: print the current array\n\ + println!("!array, !a: print the current array\n\ !array_size, !as: print the current array size\n\ !pointer, !p: print the current pointer\n\ !pointer_value, !pv: print the current pointer value\n\ @@ -163,43 +201,43 @@ impl Repl<'_> { !load, !l: load the REPL history from a file\n\ !reset, !r: reset the REPL\n\ !help: print this help message\n\ - !fuck: exit the REPL\n".as_bytes())?; + !fuck: exit the REPL"); } - _ => output.write_all(format!("Unknown command: {}, type {} to show the help", - user_input, (COMMAND_PREFIX.to_string() + "help").green() - ).red().as_bytes())?, + _ => println!("{}", format!("Unknown command: {}, type {} to show the help", + user_input, (COMMAND_PREFIX.to_string() + "help").green() + ).red()), } } None => {} } - Ok(()) + } + + /// Get the interpreter + /// for testing purposes only! + pub fn interpreter(&self) -> &Interpreter { + &self.interpreter } } /// Run the REPL /// # Arguments /// * `interpreter` - The interpreter to use -pub fn start<'a>(interpreter: &'a mut Interpreter<'a>, - input: &mut impl Read, - output: &mut impl Write) { +pub fn start(interpreter: Interpreter) { info!("Entering REPL mode"); - output.write_all( - format!( - "{}\n\ + println!("{}\n\ Brainfuck interpreter v {}\nBy {}\n\ {}\n\ Type {} to exit :D\n\ - type {} to get more fu*king help\n", - "Welcome to the brainfuck REPL mode! :)".green(), - clap::crate_version!().to_string().yellow(), - clap::crate_authors!().to_string().green(), - "Enter your brainfuck code and press enter to run it.".italic().blue(), - (COMMAND_PREFIX.to_string() + "fuck").bold().red(), - (COMMAND_PREFIX.to_string() + "help").bold().green(), - ).as_bytes()).unwrap_or_else(|e| error!("Failed to write to output: {}", e)); + type {} to get more fu*king help", + "Welcome to the brainfuck REPL mode! :)".green(), + clap::crate_version!().to_string().yellow(), + clap::crate_authors!().to_string().green(), + "Enter your brainfuck code and press enter to run it.".italic().blue(), + (COMMAND_PREFIX.to_string() + "fuck").bold().red(), + (COMMAND_PREFIX.to_string() + "help").bold().green(), + ); - - match Repl::new(interpreter).run(input, output) { + match Repl::new(interpreter).run() { Ok(_) => { info!("Successfully ran REPL"); } @@ -213,19 +251,113 @@ pub fn start<'a>(interpreter: &'a mut Interpreter<'a>, #[cfg(test)] mod tests { use super::*; + use pretty_assertions::assert_eq; - /*#[test] + #[test] fn nested_loop_level_1() { - let mut interpreter = Interpreter::new( - 30000, + let interpreter = Interpreter::new( + 4, vec![], ); + let mut repl = Repl::new(interpreter); - 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); - }*/ + repl.process("++".to_string()); + repl.process("[>++".to_string()); + repl.process("[>+<-]".to_string()); + repl.process("<-]".to_string()); + + let cells = &repl.interpreter().cells; + + assert_eq!(cells[0], 0); + assert_eq!(cells[1], 0); + assert_eq!(cells[2], 4); + } + + #[test] + fn nested_loop_level_2() { + let interpreter = Interpreter::new( + 4, + vec![], + ); + + let mut repl = Repl::new(interpreter); + + repl.process("++".to_string()); + repl.process("[>++".to_string()); + repl.process("[>+<-]".to_string()); + repl.process("[>++".to_string()); + repl.process("[>+<-]".to_string()); + repl.process("<-]".to_string()); + repl.process("<-]".to_string()); + + let cells = &repl.interpreter().cells; + + assert_eq!(cells[0], 0); + assert_eq!(cells[1], 0); + assert_eq!(cells[2], 4); + } + + #[test] + fn print_my_first_name() { + let interpreter = Interpreter::new( + 10, + vec![], + ); + + let mut repl = Repl::new(interpreter); + + let code = "++++ ++++ 8 + [ + >++++ + [ + >++ A + >+++ a + >++++ + >+ space + <<<<- + ] + + >>>>>>++ + [ + <<<- + >>>- + ] + + <<<<<<<- + ] + >>+. Print cell 2: A + <<++++ + [ + >+++ + [ + >+++ + <- + ] + >++ + <<- + ] + >>+. Print n + <<+++ + [ + >+++ + [ + >- + <- + ] + >- + <<- + ] + >>-. Print n + <<++++++ + [ + >>+++ + <<- + ] + >>. Print s".to_string().split("\n").map(|s| s.to_string()).collect::>(); + + for line in code { + repl.process(line); + } + } } \ No newline at end of file