Fix the repl and improve it yo 🤪

This commit is contained in:
Anas Elgarhy 2022-10-12 18:31:07 +02:00
parent 639c39c9f6
commit fa39f358ce
4 changed files with 224 additions and 100 deletions

View file

@ -38,6 +38,7 @@ clap = { version = "4.0.10", features = ["derive", "color", "cargo"] }
log = "0.4.17" log = "0.4.17"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
colored = "2.0.0" colored = "2.0.0"
no-panic = "0.1.16"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"

View file

@ -1,23 +1,19 @@
use crate::arguments; use crate::arguments;
use crate::bf_interpreter::error::{InterpreterError, InterpreterErrorKind}; use crate::bf_interpreter::error::{InterpreterError, InterpreterErrorKind};
use std::io::{Read, Write}; use std::io::{BufRead, Read, Write};
use std::{char, usize, vec}; use std::{char, usize, vec};
pub struct Interpreter<'a> { pub struct Interpreter {
pub cells: Vec<u8>, pub cells: Vec<u8>,
pub pointer: usize, pub pointer: usize,
pub bf_commands: Vec<BfCommand>, pub bf_commands: Vec<BfCommand>,
brackets: Vec<BfCommand>, brackets: Vec<BfCommand>,
pub input: &'a Box<dyn Read>,
pub output: &'a Box<dyn Write>,
pub features: Vec<arguments::Feature>, pub features: Vec<arguments::Feature>,
} }
impl<'a> Interpreter<'a> { impl Interpreter {
pub fn new( pub fn new(
array_size: usize, array_size: usize,
input: &'a mut Box<dyn Read>,
output: &'a mut Box<dyn Write>,
features: Vec<arguments::Feature>, features: Vec<arguments::Feature>,
) -> Self { ) -> Self {
Self { Self {
@ -25,8 +21,6 @@ impl<'a> Interpreter<'a> {
pointer: 0, pointer: 0,
bf_commands: vec![], bf_commands: vec![],
brackets: Vec::new(), brackets: Vec::new(),
input,
output,
features, features,
} }
} }

View file

@ -3,7 +3,7 @@ mod repl;
mod utils; mod utils;
mod bf_interpreter; mod bf_interpreter;
use std::io::{Read, Write}; use std::io::{BufRead, Write};
use clap::Parser; use clap::Parser;
extern crate pretty_env_logger; extern crate pretty_env_logger;
#[macro_use] #[macro_use]
@ -20,13 +20,9 @@ fn main() {
let args = Args::parse(); let args = Args::parse();
info!("Parsed command line arguments: {:?}", args); info!("Parsed command line arguments: {:?}", args);
let mut stdin: Box<dyn Read> = Box::new(std::io::stdin());
let mut stdout: Box<dyn Write> = Box::new(std::io::stdout());
info!("Initializing interpreter"); info!("Initializing interpreter");
let mut interpreter = Interpreter::new( let mut interpreter = Interpreter::new(
args.array_size, args.array_size,
&mut stdin,
&mut stdout,
args.features.unwrap_or_else(|| vec![]), args.features.unwrap_or_else(|| vec![]),
); );
@ -41,8 +37,9 @@ fn main() {
"Successfully ran brainfuck source code from file: {}", "Successfully ran brainfuck source code from file: {}",
source source
).bold().green()); ).bold().green());
println!("Exiting with code: {exit_code}"); println!("{}{}", "Exiting with code: ".truecolor(33, 97, 61),
std::process::exit(0); exit_code.to_string().bold().green());
std::process::exit(exit_code);
} }
} }
Err(e) => { 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),
} }
} }

View file

@ -1,41 +1,45 @@
use crate::bf_interpreter::interpreter::Interpreter; use crate::bf_interpreter::interpreter::Interpreter;
use std::io::{Write, Read}; use std::io::{Write, Read, BufRead, BufReader, Stdout, Stdin};
use colored::Colorize; use colored::Colorize;
use no_panic::no_panic;
struct Repl<'a> { struct Repl {
interpreter: &'a mut Interpreter<'a>, interpreter: Interpreter,
history: Vec<String>, history: Vec<String>,
loop_body: String,
loop_depth: usize,
} }
const PROMPT: &str = "bf-interpreter> "; const PROMPT: &str = "bf-interpreter> ";
const HISTORY_FILE: &str = "bf-interpreter-history.bfr"; const HISTORY_FILE: &str = "bf-interpreter-history.bfr";
const COMMAND_PREFIX: &str = "!"; const COMMAND_PREFIX: &str = "!";
impl Repl<'_> { impl Repl {
pub fn new<'a>(interpreter: &'a mut Interpreter<'a>) -> Repl<'a> { pub fn new(interpreter: Interpreter) -> Repl {
Repl { Repl {
interpreter, interpreter,
history: Vec::new(), history: Vec::new(),
loop_body: String::new(),
loop_depth: 0,
} }
} }
pub fn run(mut self, // #[no_panic]
input: &mut impl Read, pub fn run(mut self) -> Result<(), std::io::Error> {
output: &mut impl Write) -> Result<(), std::io::Error> {
let mut code_bat = String::new();
let mut is_loop = false;
loop { loop {
output.write_all( print!("{}",
if is_loop { if self.loop_depth != 0 {
format!("{}", "... ".yellow()) "........ ".yellow()
} else { } else {
format!("{}", PROMPT) PROMPT.to_string().truecolor(54, 76, 76)
}.as_bytes() }
)?; );
std::io::stdout().flush()?;
let mut user_input = String::new(); let mut user_input = String::new();
match input.read_to_string(&mut user_input) { match std::io::stdin().read_line(&mut user_input) {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
error!("Failed to read input: {}", e); error!("Failed to read input: {}", e);
@ -44,72 +48,106 @@ impl Repl<'_> {
} }
user_input = user_input.trim().to_string(); // Remove trailing newline user_input = user_input.trim().to_string(); // Remove trailing newline
self.history.push(user_input.clone()); // Save input to history if !user_input.is_empty() && user_input.len() > 0 {
self.history.push(user_input.clone()); // Save input to history
if user_input.contains('[') && (!user_input.contains(']') && !is_loop) { self.process(user_input); // Process the input
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.starts_with(COMMAND_PREFIX) { pub fn process(&mut self, mut user_input: String) {
self.run_repl_cmd(user_input, output)?; match user_input.find('[') {
} else { Some(index) if self.loop_depth == 0 => {
match self.interpreter.run(user_input) { self.loop_body.push_str(&user_input[index..]);
Ok(_) => { self.loop_depth = 1;
info!("Successfully ran brainfuck source code from REPL"); 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) => { self.loop_depth -= 1;
error!("Failed to run brainfuck source code from REPL: {}", e); 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(); let mut cmd = user_input.split_whitespace();
match cmd.next() { match cmd.next() {
Some(repl_cmd) => { Some(repl_cmd) => {
match repl_cmd.get(COMMAND_PREFIX.len()..).unwrap_or("") { match repl_cmd.get(COMMAND_PREFIX.len()..).unwrap_or("") {
"fuck" => { "fuck" => {
output.write_all("Bye bye :D\n".green().as_bytes())?; println!("{}", "Bye bye :D".green());
std::process::exit(0); std::process::exit(0);
} }
"array" | "a" => { "array" | "a" => {
output.write_all(format!("Current array: {:?}\n", self.interpreter.cells).as_bytes())?; println!("{}", format!("Current array: {:?}", self.interpreter.cells));
} }
"array_size" | "as" => { "array_size" | "as" => {
output.write_all(format!("Current array size: {}\n", println!("{}", format!("Current array size: {}",
self.interpreter.array_size.to_string().bold().green()).as_bytes())?; self.interpreter.cells.len()
.to_string().bold().green()));
} }
"pointer" | "p" => { "pointer" | "p" => {
output.write_all(format!("Current pointer: {}\n", println!("{}", format!("Current pointer: {}",
self.interpreter.pointer.to_string().bold().green()).as_bytes())?; self.interpreter.pointer.to_string().bold().green()));
} }
"pointer_value" | "pv" => { "pointer_value" | "pv" => {
format!( println!(
"Current pointer value: {} = \'{}\' (char)\n", "Current pointer value: {} = \'{}\' (char)",
self.interpreter.cells[self.interpreter.pointer], self.interpreter.cells[self.interpreter.pointer],
self.interpreter.cells[self.interpreter.pointer] as char self.interpreter.cells[self.interpreter.pointer] as char
); );
} }
"history" | "h" => { "history" | "h" => {
output.write_all("History:\n".underline().green().as_bytes())?; println!("{}", "History:".underline().green());
for (i, cmd) in self.history.iter().enumerate() { for (i, cmd) in self.history.iter().enumerate() {
output.write_all(format!("{}: {}", i, cmd).as_bytes())?; println!("{}", format!("{}: {}", i, cmd));
} }
} }
"save" | "s" => { "save" | "s" => {
let file_name = cmd.next().unwrap_or(HISTORY_FILE); 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")) { match std::fs::write(file_name, self.history.join("\n")) {
Ok(_) => { Ok(_) => {
output.write_all(format!("Successfully saved history to file: {file_name}") println!("{}", format!("Successfully saved history to file: {file_name}")
.green().as_bytes())?; .green());
} }
Err(e) => { Err(e) => {
error!("Failed to save history to file: {}", e); error!("Failed to save history to file: {}", e);
@ -119,11 +157,11 @@ impl Repl<'_> {
"load" | "l" => { "load" | "l" => {
let file_name = cmd.next().unwrap_or(HISTORY_FILE); 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) { match std::fs::read_to_string(file_name) {
Ok(history) => { Ok(history) => {
output.write_all(format!("Successfully loaded history from file: {file_name}") println!("{}", format!("Successfully loaded history from file: {file_name}")
.green().as_bytes())?; .green());
self.history = history.split("\n").map(|s| s.to_string()).collect(); self.history = history.split("\n").map(|s| s.to_string()).collect();
// Run all commands in history // Run all commands in history
@ -149,12 +187,12 @@ impl Repl<'_> {
} }
} }
"reset" | "r" => { "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.interpreter.reset();
self.history = Vec::new(); self.history = Vec::new();
} }
"help" => { "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\ !array_size, !as: print the current array size\n\
!pointer, !p: print the current pointer\n\ !pointer, !p: print the current pointer\n\
!pointer_value, !pv: print the current pointer value\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\ !load, !l: load the REPL history from a file\n\
!reset, !r: reset the REPL\n\ !reset, !r: reset the REPL\n\
!help: print this help message\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", _ => println!("{}", format!("Unknown command: {}, type {} to show the help",
user_input, (COMMAND_PREFIX.to_string() + "help").green() user_input, (COMMAND_PREFIX.to_string() + "help").green()
).red().as_bytes())?, ).red()),
} }
} }
None => {} None => {}
} }
Ok(()) }
/// Get the interpreter
/// for testing purposes only!
pub fn interpreter(&self) -> &Interpreter {
&self.interpreter
} }
} }
/// Run the REPL /// Run the REPL
/// # Arguments /// # Arguments
/// * `interpreter` - The interpreter to use /// * `interpreter` - The interpreter to use
pub fn start<'a>(interpreter: &'a mut Interpreter<'a>, pub fn start(interpreter: Interpreter) {
input: &mut impl Read,
output: &mut impl Write) {
info!("Entering REPL mode"); info!("Entering REPL mode");
output.write_all( println!("{}\n\
format!(
"{}\n\
Brainfuck interpreter v {}\nBy {}\n\ Brainfuck interpreter v {}\nBy {}\n\
{}\n\ {}\n\
Type {} to exit :D\n\ Type {} to exit :D\n\
type {} to get more fu*king help\n", type {} to get more fu*king help",
"Welcome to the brainfuck REPL mode! :)".green(), "Welcome to the brainfuck REPL mode! :)".green(),
clap::crate_version!().to_string().yellow(), clap::crate_version!().to_string().yellow(),
clap::crate_authors!().to_string().green(), clap::crate_authors!().to_string().green(),
"Enter your brainfuck code and press enter to run it.".italic().blue(), "Enter your brainfuck code and press enter to run it.".italic().blue(),
(COMMAND_PREFIX.to_string() + "fuck").bold().red(), (COMMAND_PREFIX.to_string() + "fuck").bold().red(),
(COMMAND_PREFIX.to_string() + "help").bold().green(), (COMMAND_PREFIX.to_string() + "help").bold().green(),
).as_bytes()).unwrap_or_else(|e| error!("Failed to write to output: {}", e)); );
match Repl::new(interpreter).run() {
match Repl::new(interpreter).run(input, output) {
Ok(_) => { Ok(_) => {
info!("Successfully ran REPL"); info!("Successfully ran REPL");
} }
@ -213,19 +251,113 @@ pub fn start<'a>(interpreter: &'a mut Interpreter<'a>,
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use pretty_assertions::assert_eq;
/*#[test] #[test]
fn nested_loop_level_1() { fn nested_loop_level_1() {
let mut interpreter = Interpreter::new( let interpreter = Interpreter::new(
30000, 4,
vec![], vec![],
); );
let mut repl = Repl::new(interpreter);
assert_eq!(interpreter.run(String::from("++")), Ok(0)); repl.process("++".to_string());
assert_eq!(interpreter.run(String::from("[>++")), Ok(0)); repl.process("[>++".to_string());
assert_eq!(interpreter.run(String::from("[>+<-]")), Ok(0)); repl.process("[>+<-]".to_string());
assert_eq!(interpreter.run(String::from("<-]")), Ok(0)); repl.process("<-]".to_string());
assert_eq!(interpreter.cells[2], 4);
}*/ 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::<Vec<String>>();
for line in code {
repl.process(line);
}
}
} }