Fix interpreter errors yoo 🥰💙

This commit is contained in:
Anas Elgarhy 2022-10-10 22:55:50 +02:00
parent 3ee4f37354
commit 6dd7888aaf
18 changed files with 542 additions and 230 deletions

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerExplorerSettingsProvider">
<option name="initialNoticeShown" value="true" />
</component>
</project>

View file

@ -2,6 +2,6 @@
<project version="4"> <project version="4">
<component name="DiscordProjectSettings"> <component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" /> <option name="show" value="PROJECT_FILES" />
<option name="description" value="" /> <option name="description" value="Fixing loop bug 🥲" />
</component> </component>
</project> </project>

View file

@ -37,6 +37,7 @@ exclude = [
clap = { version = "4.0.10", features = ["derive", "color", "cargo"] } 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"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.3.0" pretty_assertions = "1.3.0"

View file

@ -18,10 +18,11 @@ pub struct Args {
#[derive(Debug, PartialEq, Copy, Clone, ValueEnum)] #[derive(Debug, PartialEq, Copy, Clone, ValueEnum)]
pub enum Feature { 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 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, set the value to 0, otherwise increment 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.
ReverseValue, NoReverseValue,
/// If the pointer at the end of the array, set the pointer to 0, otherwise increment the pointer. /// 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. /// If the pointer at the beginning of the array, set the pointer to the end of the array, otherwise decrement the pointer.
ReversePointer, ReversePointer,
AllowUtf8,
} }

View file

@ -36,9 +36,10 @@ impl std::error::Error for InterpreterError {
pub enum InterpreterErrorKind { pub enum InterpreterErrorKind {
PointerOutOfBounds(usize), // takes pointer value PointerOutOfBounds(usize), // takes pointer value
ValueOutOfBounds, ValueOutOfBounds,
ByteReadError(std::io::Error), IoError(std::io::Error),
ReadError, FlushError(std::io::Error),
UnmatchedClosingBracket(usize), // takes position UnmatchedBracket,
InvalidUtf8,
} }
impl InterpreterErrorKind { impl InterpreterErrorKind {
@ -50,9 +51,10 @@ impl InterpreterErrorKind {
match self { match self {
InterpreterErrorKind::PointerOutOfBounds(_) => 11, InterpreterErrorKind::PointerOutOfBounds(_) => 11,
InterpreterErrorKind::ValueOutOfBounds => 12, InterpreterErrorKind::ValueOutOfBounds => 12,
InterpreterErrorKind::ByteReadError(_) => 13, InterpreterErrorKind::IoError(_) => 13,
InterpreterErrorKind::ReadError => 14, InterpreterErrorKind::FlushError(_) => 14,
InterpreterErrorKind::UnmatchedClosingBracket(_) => 15, InterpreterErrorKind::UnmatchedBracket => 15,
InterpreterErrorKind::InvalidUtf8 => 16,
} }
} }
} }
@ -62,10 +64,11 @@ impl Display for InterpreterErrorKind {
match self { match self {
InterpreterErrorKind::PointerOutOfBounds(pointer) => write!(f, "Pointer out of bounds {}", pointer), InterpreterErrorKind::PointerOutOfBounds(pointer) => write!(f, "Pointer out of bounds {}", pointer),
InterpreterErrorKind::ValueOutOfBounds => write!(f, "Value out of bounds"), 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), 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::FlushError(e) => write!(f, "Failed to flush stdout: {}", e),
InterpreterErrorKind::UnmatchedClosingBracket(pos) => write!(f, "Unmatched closing bracket at position {}", pos), 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.to_string(), "Value out of bounds");
assert_eq!(error.code, 12); 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.to_string(), "Failed to read byte from stdin: no bytes available: test");
assert_eq!(error.code, 13); 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.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(); let error = InterpreterErrorKind::UnmatchedBracket.to_error();
assert_eq!(error.to_string(), "Unmatched closing bracket at position 10"); assert_eq!(error.to_string(), "Unmatched bracket");
assert_eq!(error.code, 15); assert_eq!(error.code, 15);
let error = InterpreterErrorKind::InvalidUtf8.to_error();
assert_eq!(error.to_string(), "Invalid utf8");
assert_eq!(error.code, 16);
} }
#[test] #[test]

View file

@ -1,4 +1,4 @@
use crate::{arguments, mode}; 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::{Read, Write};
use std::{char, usize, vec}; use std::{char, usize, vec};
@ -7,89 +7,76 @@ pub struct Interpreter {
pub cells: Vec<u8>, pub cells: Vec<u8>,
pub pointer: usize, pub pointer: usize,
pub array_size: usize, pub array_size: usize,
pub bf_code: Vec<char>, pub bf_commands: Vec<BfCommand>,
brackets: Vec<BfCommand>, brackets: Vec<BfCommand>,
pub features: Vec<arguments::Feature>, pub features: Vec<arguments::Feature>,
mode: mode::RunMode,
} }
impl Interpreter { impl Interpreter {
pub fn new( pub fn new(
array_size: usize, array_size: usize,
bf_code: Option<String>,
features: Vec<arguments::Feature>, features: Vec<arguments::Feature>,
run_mode: mode::RunMode
) -> Self { ) -> Self {
trace!("Run mode{run_mode:?}");
Self { Self {
cells: vec![0; array_size], cells: vec![0; array_size],
pointer: 0, pointer: 0,
array_size, array_size,
bf_code: bf_code.unwrap_or_else(|| String::new()).chars().collect(), bf_commands: vec![],
brackets: Vec::new(), brackets: Vec::new(),
features, features,
mode: run_mode,
} }
} }
pub fn run(&mut self, bf_code: Option<String>) -> Result<i32, InterpreterError> { pub fn run(&mut self, bf_code: String) -> Result<i32, InterpreterError> {
let bf_code = match bf_code { self.bf_commands = to_bf_commands(bf_code.chars().collect())?;
Some(bf_code) => {
bf_code.chars().collect()
}
None => self.bf_code.clone(),
};
match self.run_brainfuck_code(bf_code, false) { match self.run_brainfuck_code(&self.bf_commands.clone()) {
Ok(_) => Ok(0), Ok(_) => Ok(0),
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
// +[>++<-] // +[>++<-]
fn iterate(&mut self, code: Vec<char>) -> Result<(), InterpreterError> { fn iterate(&mut self, code: &Vec<BfCommand>) -> Result<(), InterpreterError> {
trace!("Iterate: {:?}", code); trace!("Iterate: {:?}", code);
while self.cells[self.pointer] != 0 { while self.cells[self.pointer] != 0 {
self.run_brainfuck_code(code.clone(), true)?; self.run_brainfuck_code(code)?;
} }
Ok(()) Ok(())
} }
fn run_brainfuck_code(&mut self, bf_code: Vec<char>, from_loop: bool) -> Result<(), InterpreterError> { fn run_brainfuck_code(&mut self, bf_code: &Vec<BfCommand>) -> Result<(), InterpreterError> {
let mut removed_num = 0_usize; for command in bf_code {
for (i, ch) in bf_code.iter().enumerate() { match command {
match BfCommand::from_char(ch, i - removed_num) { BfCommand::IncPtr => self.increment_pointer()?,
Some(cmd) => { BfCommand::DecPtr => self.decrement_pointer()?,
trace!("Executing command: {:?}", cmd); BfCommand::IncVal => self.increment_value()?,
self.execute(cmd)?; 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(()) Ok(())
} }
fn execute(&mut self, cmd: BfCommand) -> Result<(), InterpreterError> { fn increment_pointer(&mut self) -> Result<(), InterpreterError> {
match cmd { trace!("Increment pointer");
BfCommand::IncPtr => {
self.pointer += 1; self.pointer += 1;
if self.pointer >= self.array_size { if self.pointer >= self.array_size {
if self.features.contains(&arguments::Feature::ReversePointer) { if self.features.contains(&arguments::Feature::ReversePointer) {
self.pointer = 0; self.pointer = 0;
} else { } else {
return Err(InterpreterErrorKind::PointerOutOfBounds(self.pointer).to_error()) return Err(InterpreterErrorKind::PointerOutOfBounds(self.pointer).to_error());
} }
} }
Ok(())
} }
BfCommand::DecPtr => {
fn decrement_pointer(&mut self) -> Result<(), InterpreterError> {
trace!("Decrement pointer");
if self.pointer == 0 { if self.pointer == 0 {
if self.features.contains(&arguments::Feature::ReversePointer) { if self.features.contains(&arguments::Feature::ReversePointer) {
self.pointer = self.array_size - 1; self.pointer = self.array_size - 1;
@ -99,10 +86,13 @@ impl Interpreter {
} else { } else {
self.pointer -= 1; self.pointer -= 1;
} }
Ok(())
} }
BfCommand::IncVal => {
fn increment_value(&mut self) -> Result<(), InterpreterError> {
trace!("Increment value");
if self.cells[self.pointer] == 255 { if self.cells[self.pointer] == 255 {
if self.features.contains(&arguments::Feature::ReverseValue) { if !self.features.contains(&arguments::Feature::NoReverseValue) {
self.cells[self.pointer] = 0; self.cells[self.pointer] = 0;
} else { } else {
return Err(InterpreterErrorKind::ValueOutOfBounds.to_error()); return Err(InterpreterErrorKind::ValueOutOfBounds.to_error());
@ -110,10 +100,13 @@ impl Interpreter {
} else { } else {
self.cells[self.pointer] += 1; self.cells[self.pointer] += 1;
} }
Ok(())
} }
BfCommand::DecVal => {
fn decrement_value(&mut self) -> Result<(), InterpreterError> {
trace!("Decrement value");
if self.cells[self.pointer] == 0 { if self.cells[self.pointer] == 0 {
if self.features.contains(&arguments::Feature::ReverseValue) { if !self.features.contains(&arguments::Feature::NoReverseValue) {
self.cells[self.pointer] = 255; self.cells[self.pointer] = 255;
} else { } else {
return Err(InterpreterErrorKind::ValueOutOfBounds.to_error()); return Err(InterpreterErrorKind::ValueOutOfBounds.to_error());
@ -121,89 +114,97 @@ impl Interpreter {
} else { } else {
self.cells[self.pointer] -= 1; 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());
}
}
}
}
Ok(()) 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) { pub fn reset(&mut self) {
self.cells = vec![0; self.array_size]; self.cells = vec![0; self.array_size];
self.pointer = 0; self.pointer = 0;
self.brackets = Vec::new(); self.brackets = Vec::new();
self.bf_code = Vec::new(); self.bf_commands = Vec::new();
} }
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
enum BfCommand { pub enum BfCommand {
IncPtr, IncPtr,
DecPtr, DecPtr,
IncVal, IncVal,
DecVal, DecVal,
Print, Print,
Read, Read,
LoopStart(usize), Loop(Vec<BfCommand>),
LoopEnd(usize), }
fn to_bf_commands(bf_code: Vec<char>) -> Result<Vec<BfCommand>, 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 { impl BfCommand {
fn from_char(c: &char, index: usize) -> Option<BfCommand> { fn from(c: char) -> Option<Self> {
match c { match c {
'>' => Some(BfCommand::IncPtr), '>' => Some(BfCommand::IncPtr),
'<' => Some(BfCommand::DecPtr), '<' => Some(BfCommand::DecPtr),
@ -211,8 +212,6 @@ impl BfCommand {
'-' => Some(BfCommand::DecVal), '-' => Some(BfCommand::DecVal),
'.' => Some(BfCommand::Print), '.' => Some(BfCommand::Print),
',' => Some(BfCommand::Read), ',' => Some(BfCommand::Read),
'[' => Some(BfCommand::LoopStart(index)),
']' => Some(BfCommand::LoopEnd(index)),
_ => None, _ => None,
} }
} }
@ -221,91 +220,162 @@ impl BfCommand {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::mode::RunMode; use pretty_assertions::assert_eq; // for testing only
use pretty_assertions::assert_eq; use crate::utils;
use crate::utils; // for testing only
#[test] #[test]
fn print_h_combine_repl() { fn print_h_combine_repl() {
let mut interpreter = Interpreter::new( let mut interpreter = Interpreter::new(
30000, 30000,
None,
vec![], 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] #[test]
fn print_h_repl() { fn print_h_repl() {
let mut interpreter = Interpreter::new( let mut interpreter = Interpreter::new(
30000, 30000,
None,
vec![], 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)); println!();
assert_eq!(interpreter.run(Some(String::from("[<++++ ++++>-]<."))), Ok(0));
} }
#[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] #[test]
fn execute_hello_world_from_file() { fn execute_hello_world_from_file() {
let mut interpreter = Interpreter::new( let mut interpreter = Interpreter::new(
30000, 30000,
utils::read_brainfuck_code_if_any(&Some(String::from("test_code/hello_world.bf"))),
vec![], 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] #[test]
fn execute_print_hi_from_file() { fn execute_print_hi_from_file() {
let mut interpreter = Interpreter::new( let mut interpreter = Interpreter::new(
30000, 30000,
utils::read_brainfuck_code_if_any(&Some(String::from("test_code/print_hi.bf"))),
vec![], 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] #[test]
fn execute_print_hi_yooo_from_file() { fn execute_print_hi_yooo_from_file() {
let mut interpreter = Interpreter::new( let mut interpreter = Interpreter::new(
30000, 30000,
utils::read_brainfuck_code_if_any(&Some(String::from("test_code/print_hi_yooo.bf"))),
vec![], 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] #[test]
fn reset() { fn reset() {
let mut interpreter = Interpreter::new( let mut interpreter = Interpreter::new(
30000, 30000,
None,
vec![], 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.pointer, 1);
assert_eq!(interpreter.cells[0], 0); assert_eq!(interpreter.cells[0], 0);
assert_eq!(interpreter.cells[1], 4); assert_eq!(interpreter.cells[1], 4);
assert_eq!(interpreter.bf_code, vec!['>', '+', '+', '+' , '+']); // assert_eq!(interpreter.commands, vec!['>', '+', '+', '+', '+']);
// reset // reset
interpreter.reset(); interpreter.reset();
@ -313,21 +383,6 @@ mod tests {
assert_eq!(interpreter.pointer, 0); assert_eq!(interpreter.pointer, 0);
assert_eq!(interpreter.cells[0], 0); assert_eq!(interpreter.cells[0], 0);
assert_eq!(interpreter.cells[1], 0); assert_eq!(interpreter.cells[1], 0);
assert_eq!(interpreter.bf_code, Vec::<char>::new()); assert_eq!(interpreter.bf_commands, Vec::<BfCommand>::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);
} }
} }

View file

@ -2,7 +2,6 @@ mod arguments;
mod repl; mod repl;
mod utils; mod utils;
mod bf_interpreter; mod bf_interpreter;
mod mode;
use clap::Parser; use clap::Parser;
extern crate pretty_env_logger; extern crate pretty_env_logger;
@ -22,18 +21,13 @@ fn main() {
info!("Initializing bf_interpreter"); info!("Initializing bf_interpreter");
let mut interpreter = Interpreter::new( let mut interpreter = Interpreter::new(
args.array_size, args.array_size,
utils::read_brainfuck_code_if_any(&args.source),
args.features.unwrap_or_else(|| vec![]), args.features.unwrap_or_else(|| vec![]),
match args.source {
Some(_) => mode::RunMode::Execute,
None => mode::RunMode::Repl
},
); );
match args.source { match args.source {
Some(source) => { Some(source) => {
info!("Running brainfuck source code from file: {}", source); info!("Running brainfuck source code from file: {}", source);
match interpreter.run(None) { match interpreter.run(utils::read_brainfuck_code(&source)) {
Ok(exit_code) => { Ok(exit_code) => {
info!("Finished running brainfuck source code from file: {}", source); info!("Finished running brainfuck source code from file: {}", source);
if !args.without_tiles { if !args.without_tiles {

View file

@ -1,5 +0,0 @@
#[derive(PartialEq, Debug)]
pub enum RunMode {
Execute,
Repl,
}

View file

@ -19,8 +19,15 @@ impl Repl {
} }
pub fn run(mut self) { pub fn run(mut self) {
let mut code_bat = String::new();
let mut is_loop = false;
loop { loop {
print!("\n {}", PROMPT); if is_loop {
print!("... ");
} else {
print!("{}", PROMPT);
}
std::io::stdout().flush().unwrap_or_else(|_| { std::io::stdout().flush().unwrap_or_else(|_| {
error!("Failed to flush stdout"); error!("Failed to flush stdout");
std::process::exit(1); std::process::exit(1);
@ -38,10 +45,18 @@ impl Repl {
self.history.push(input.clone()); // Save input to history 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) { if input.starts_with(COMMAND_PREFIX) {
self.run_repl_cmd(input); self.run_repl_cmd(input);
} else { } else {
match self.interpreter.run(Some(input)) { match self.interpreter.run(input) {
Ok(_) => { Ok(_) => {
info!("Successfully ran brainfuck source code from REPL"); info!("Successfully ran brainfuck source code from REPL");
} }
@ -108,7 +123,7 @@ impl Repl {
// Run all commands in history // Run all commands in history
for cmd in self.history.iter() { for cmd in self.history.iter() {
match self.interpreter.run(Some(cmd.clone())) { match self.interpreter.run(cmd.clone()) {
Ok(_) => { Ok(_) => {
info!( info!(
"Successfully ran brainfuck source code from REPL" "Successfully ran brainfuck source code from REPL"
@ -169,3 +184,25 @@ pub fn start(interpreter: Interpreter) {
Repl::new(interpreter).run(); 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!();
}
}

View file

@ -1,18 +1,13 @@
pub(crate) fn read_brainfuck_code_if_any(source: &Option<String>) -> Option<String> { pub fn read_brainfuck_code(source: &String) -> String {
match source {
Some(source) => {
info!("Reading brainfuck source code from file: {}", source); info!("Reading brainfuck source code from file: {}", source);
match std::fs::read_to_string(source) { match std::fs::read_to_string(source) {
Ok(source) => Some(clean(source)), Ok(source) => clean(source),
Err(e) => { Err(e) => {
error!("Failed to read source code file: {}", e); error!("Failed to read source code file: {}", e);
eprintln!("Failed to read source code file: {}", e); eprintln!("Failed to read source code file: {}", e);
std::process::exit(1); std::process::exit(1);
} }
} }
}
None => None,
}
} }
fn clean(source: String) -> String { fn clean(source: String) -> String {

View file

@ -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"!

View file

@ -0,0 +1 @@
++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.

View file

@ -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

View file

@ -0,0 +1,3 @@
Print my first name (Anas)
Unformated:
++++++++[>++++[>++<-]>>>>>>++[<<<->>>-]<<<<<<<-]>>+.<<++++[>+++[>+++<-]>++<<-]>>+.<<+++[>+++[>-<-]>-<<-]>>-.<<++++++[>>+++<<-]>>.

View file

@ -0,0 +1,3 @@
Print my first name and my last name (Anas Elgarhy)
Unformated:
++++++++[>++++[>++>+>++>+++<<<<-]>>>>+<<<<<-]>>+.<<++++[>+++[>+++<-]>++<<-]>>+.<<+++[>+++[>-<-]>-<<-]>>-.<<++++++[>>+++<<-]>>.>.>+++++.<<-------.-----.------.<+++++[>+++<-]>++.>>>.<<<+++++++.

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,50 @@
My first name (Anas)
Formated:
++++ ++++ 8
[
>++++
[
>++ A
>+++ a
>++++
>+ space
<<<<-
]
>>>>>>++
[
<<<-
>>>-
]
<<<<<<<-
]
>>+. Print cell 2: A
<<++++
[
>+++
[
>+++
<-
]
>++
<<-
]
>>+. Print n
<<+++
[
>+++
[
>-
<-
]
>-
<<-
]
>>-. Print n
<<++++++
[
>>+++
<<-
]
>>. Print s