Fix interpreter errors yoo 🥰💙
This commit is contained in:
parent
3ee4f37354
commit
6dd7888aaf
18 changed files with 542 additions and 230 deletions
6
.idea/compilerexplorer.settings.xml
Normal file
6
.idea/compilerexplorer.settings.xml
Normal 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>
|
|
@ -2,6 +2,6 @@
|
|||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
<option name="description" value="Fixing loop bug 🥲" />
|
||||
</component>
|
||||
</project>
|
|
@ -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"
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,89 +7,76 @@ pub struct Interpreter {
|
|||
pub cells: Vec<u8>,
|
||||
pub pointer: usize,
|
||||
pub array_size: usize,
|
||||
pub bf_code: Vec<char>,
|
||||
pub bf_commands: Vec<BfCommand>,
|
||||
brackets: Vec<BfCommand>,
|
||||
pub features: Vec<arguments::Feature>,
|
||||
mode: mode::RunMode,
|
||||
}
|
||||
|
||||
impl Interpreter {
|
||||
pub fn new(
|
||||
array_size: usize,
|
||||
bf_code: Option<String>,
|
||||
features: Vec<arguments::Feature>,
|
||||
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<String>) -> Result<i32, InterpreterError> {
|
||||
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<i32, InterpreterError> {
|
||||
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<char>) -> Result<(), InterpreterError> {
|
||||
fn iterate(&mut self, code: &Vec<BfCommand>) -> 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<char>, 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<BfCommand>) -> 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 execute(&mut self, cmd: BfCommand) -> Result<(), InterpreterError> {
|
||||
match cmd {
|
||||
BfCommand::IncPtr => {
|
||||
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())
|
||||
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.features.contains(&arguments::Feature::ReversePointer) {
|
||||
self.pointer = self.array_size - 1;
|
||||
|
@ -99,10 +86,13 @@ impl Interpreter {
|
|||
} else {
|
||||
self.pointer -= 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
BfCommand::IncVal => {
|
||||
|
||||
fn increment_value(&mut self) -> Result<(), InterpreterError> {
|
||||
trace!("Increment value");
|
||||
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;
|
||||
} else {
|
||||
return Err(InterpreterErrorKind::ValueOutOfBounds.to_error());
|
||||
|
@ -110,10 +100,13 @@ impl Interpreter {
|
|||
} else {
|
||||
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.features.contains(&arguments::Feature::ReverseValue) {
|
||||
if !self.features.contains(&arguments::Feature::NoReverseValue) {
|
||||
self.cells[self.pointer] = 255;
|
||||
} else {
|
||||
return Err(InterpreterErrorKind::ValueOutOfBounds.to_error());
|
||||
|
@ -121,89 +114,97 @@ impl Interpreter {
|
|||
} 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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<BfCommand>),
|
||||
}
|
||||
|
||||
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 {
|
||||
fn from_char(c: &char, index: usize) -> Option<BfCommand> {
|
||||
fn from(c: char) -> Option<Self> {
|
||||
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::<char>::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::<BfCommand>::new());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
#[derive(PartialEq, Debug)]
|
||||
pub enum RunMode {
|
||||
Execute,
|
||||
Repl,
|
||||
}
|
43
src/repl.rs
43
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!();
|
||||
}
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
pub(crate) fn read_brainfuck_code_if_any(source: &Option<String>) -> Option<String> {
|
||||
match source {
|
||||
Some(source) => {
|
||||
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) => Some(clean(source)),
|
||||
Ok(source) => clean(source),
|
||||
Err(e) => {
|
||||
error!("Failed to read source code file: {}", e);
|
||||
eprintln!("Failed to read source code file: {}", e);
|
||||
|
@ -11,9 +9,6 @@ pub(crate) fn read_brainfuck_code_if_any(source: &Option<String>) -> Option<Stri
|
|||
}
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn clean(source: String) -> String {
|
||||
source
|
||||
|
|
20
test_code/print_7_formatted.bf
Normal file
20
test_code/print_7_formatted.bf
Normal 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"!
|
1
test_code/print_hello_world_2.bf
Normal file
1
test_code/print_hello_world_2.bf
Normal file
|
@ -0,0 +1 @@
|
|||
++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
|
43
test_code/print_hello_world_2_formated.bf
Normal file
43
test_code/print_hello_world_2_formated.bf
Normal 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
|
3
test_code/print_my_first_name.bf
Normal file
3
test_code/print_my_first_name.bf
Normal file
|
@ -0,0 +1,3 @@
|
|||
Print my first name (Anas)
|
||||
Unformated:
|
||||
++++++++[>++++[>++<-]>>>>>>++[<<<->>>-]<<<<<<<-]>>+.<<++++[>+++[>+++<-]>++<<-]>>+.<<+++[>+++[>-<-]>-<<-]>>-.<<++++++[>>+++<<-]>>.
|
3
test_code/print_my_first_name_and_last_name.bf
Normal file
3
test_code/print_my_first_name_and_last_name.bf
Normal file
|
@ -0,0 +1,3 @@
|
|||
Print my first name and my last name (Anas Elgarhy)
|
||||
Unformated:
|
||||
++++++++[>++++[>++>+>++>+++<<<<-]>>>>+<<<<<-]>>+.<<++++[>+++[>+++<-]>++<<-]>>+.<<+++[>+++[>-<-]>-<<-]>>-.<<++++++[>>+++<<-]>>.>.>+++++.<<-------.-----.------.<+++++[>+++<-]>++.>>>.<<<+++++++.
|
56
test_code/print_my_first_name_and_last_name_formated.bf
Normal file
56
test_code/print_my_first_name_and_last_name_formated.bf
Normal 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
|
45
test_code/print_my_first_name_and_last_name_formatted.bf
Normal file
45
test_code/print_my_first_name_and_last_name_formatted.bf
Normal 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
|
50
test_code/print_my_first_name_formatted.bf
Normal file
50
test_code/print_my_first_name_formatted.bf
Normal file
|
@ -0,0 +1,50 @@
|
|||
My first name (Anas)
|
||||
Formated:
|
||||
++++ ++++ 8
|
||||
[
|
||||
>++++
|
||||
[
|
||||
>++ A
|
||||
>+++ a
|
||||
>++++
|
||||
>+ space
|
||||
<<<<-
|
||||
]
|
||||
|
||||
>>>>>>++
|
||||
[
|
||||
<<<-
|
||||
>>>-
|
||||
]
|
||||
|
||||
<<<<<<<-
|
||||
]
|
||||
>>+. Print cell 2: A
|
||||
<<++++
|
||||
[
|
||||
>+++
|
||||
[
|
||||
>+++
|
||||
<-
|
||||
]
|
||||
>++
|
||||
<<-
|
||||
]
|
||||
>>+. Print n
|
||||
<<+++
|
||||
[
|
||||
>+++
|
||||
[
|
||||
>-
|
||||
<-
|
||||
]
|
||||
>-
|
||||
<<-
|
||||
]
|
||||
>>-. Print n
|
||||
<<++++++
|
||||
[
|
||||
>>+++
|
||||
<<-
|
||||
]
|
||||
>>. Print s
|
Loading…
Reference in a new issue