Fix interpreter repl errors and add some tsests and create the base
readme file 🥰💙
This commit is contained in:
parent
353bdec211
commit
3ee4f37354
16 changed files with 366 additions and 33 deletions
21
.idea/runConfigurations/Run_bf_interpreter.xml
Normal file
21
.idea/runConfigurations/Run_bf_interpreter.xml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Run bf-interpreter" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
|
<option name="command" value="run --package bf-interpreter --bin bf-interpreter " />
|
||||||
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
|
<option name="channel" value="DEFAULT" />
|
||||||
|
<option name="requiredFeatures" value="true" />
|
||||||
|
<option name="allFeatures" value="false" />
|
||||||
|
<option name="emulateTerminal" value="true" />
|
||||||
|
<option name="withSudo" value="false" />
|
||||||
|
<option name="buildTarget" value="REMOTE" />
|
||||||
|
<option name="backtrace" value="FULL" />
|
||||||
|
<envs>
|
||||||
|
<env name="RUST_LOG" value="trace" />
|
||||||
|
</envs>
|
||||||
|
<option name="isRedirectInput" value="false" />
|
||||||
|
<option name="redirectInputPath" value="" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -37,3 +37,6 @@ 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"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions = "1.3.0"
|
88
README.md
Normal file
88
README.md
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
![brainfuc*k interpreter](./assets/cover.png)
|
||||||
|
# brainfuc*k interpreter: a simple brainfuc*k interpreter and REPL writen in rust 🦀
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
- from crates.io
|
||||||
|
```bash
|
||||||
|
crago install bf-bf_interpreter
|
||||||
|
```
|
||||||
|
- From aur:
|
||||||
|
```shell
|
||||||
|
yay -S bf-interpreter
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options and arguments
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bf-bf_interpreter --help
|
||||||
|
```
|
||||||
|
```text
|
||||||
|
Brainfu*k interpreter and REPL written in Rust
|
||||||
|
|
||||||
|
Usage: bf-interpreter [OPTIONS] [SOURCE]
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
[SOURCE]
|
||||||
|
The brainfuck source code file to run (if not will be entered in REPL mode)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-f, --features <FEATURES>
|
||||||
|
Possible values:
|
||||||
|
- reverse-counter:
|
||||||
|
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
|
||||||
|
- reverse-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
|
||||||
|
|
||||||
|
-a, --array-size <ARRAY_SIZE>
|
||||||
|
The brainfuck array size
|
||||||
|
|
||||||
|
[default: 30000]
|
||||||
|
|
||||||
|
-w, --without-tiles
|
||||||
|
Dont print the tiles (e.g. exit code, file name, etc)
|
||||||
|
|
||||||
|
-h, --help
|
||||||
|
Print help information (use `-h` for a summary)
|
||||||
|
|
||||||
|
-V, --version
|
||||||
|
Print version information
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bf-bf_interpreter test_code/hello_world.bf
|
||||||
|
```
|
||||||
|
```text
|
||||||
|
Hello world!
|
||||||
|
Successfully ran brainfuck source code from file: test_code/hello_world.bf
|
||||||
|
Exiting with code: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bf-bf_interpreter -w test_code/hello_world.bf
|
||||||
|
```
|
||||||
|
```text
|
||||||
|
Hello world!
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bf-bf_interpreter test_code/print_hi_yooo.bf
|
||||||
|
```
|
||||||
|
```text
|
||||||
|
Hi yoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo!Successfully ran brainfuck source code from file: test_code/print_hi_yooo.bf
|
||||||
|
Exiting with code: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bf-bf_interpreter -w test_code/print_hi_yooo.bf
|
||||||
|
```
|
||||||
|
```text
|
||||||
|
Hi yoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo!
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bf-bf_interpreter test_code/like_cat.bf
|
||||||
|
```
|
||||||
|
![output](./screenshots/like_cat_output.png)
|
BIN
assets/cover.jpg
Normal file
BIN
assets/cover.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
BIN
assets/cover.png
Normal file
BIN
assets/cover.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 87 KiB |
BIN
assets/cover.xcf
Normal file
BIN
assets/cover.xcf
Normal file
Binary file not shown.
BIN
screenshots/like_cat_output.png
Normal file
BIN
screenshots/like_cat_output.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -1,5 +1,6 @@
|
||||||
use std::fmt::{Debug, Formatter, Display};
|
use std::fmt::{Debug, Formatter, Display};
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub struct InterpreterError {
|
pub struct InterpreterError {
|
||||||
message: String,
|
message: String,
|
||||||
pub code: i32,
|
pub code: i32,
|
||||||
|
@ -33,8 +34,7 @@ impl std::error::Error for InterpreterError {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum InterpreterErrorKind {
|
pub enum InterpreterErrorKind {
|
||||||
PointerOutOfBounds(usize),
|
PointerOutOfBounds(usize), // takes pointer value
|
||||||
// takes pointer value
|
|
||||||
ValueOutOfBounds,
|
ValueOutOfBounds,
|
||||||
ByteReadError(std::io::Error),
|
ByteReadError(std::io::Error),
|
||||||
ReadError,
|
ReadError,
|
||||||
|
@ -69,3 +69,45 @@ impl Display for InterpreterErrorKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq; // for testing only
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_kind_display() {
|
||||||
|
let error = InterpreterErrorKind::PointerOutOfBounds(10).to_error();
|
||||||
|
assert_eq!(error.to_string(), "Pointer out of bounds 10");
|
||||||
|
assert_eq!(error.code, 11);
|
||||||
|
|
||||||
|
let error = InterpreterErrorKind::ValueOutOfBounds.to_error();
|
||||||
|
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();
|
||||||
|
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();
|
||||||
|
assert_eq!(error.to_string(), "Failed to read byte from stdin: no bytes available");
|
||||||
|
assert_eq!(error.code, 14);
|
||||||
|
|
||||||
|
let error = InterpreterErrorKind::UnmatchedClosingBracket(10).to_error();
|
||||||
|
assert_eq!(error.to_string(), "Unmatched closing bracket at position 10");
|
||||||
|
assert_eq!(error.code, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_display() {
|
||||||
|
let error = InterpreterError::new("test".to_string(), 10);
|
||||||
|
assert_eq!(error.to_string(), "test");
|
||||||
|
assert_eq!(error.code, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_debug() {
|
||||||
|
let error = InterpreterError::new("test".to_string(), 10);
|
||||||
|
assert_eq!(format!("{:?}", error), "test, code: 10");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
use crate::arguments;
|
use crate::{arguments, mode};
|
||||||
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::usize;
|
use std::{char, usize, vec};
|
||||||
|
|
||||||
pub struct Interpreter {
|
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: String,
|
pub bf_code: Vec<char>,
|
||||||
brackets: Vec<BfCommand>,
|
brackets: Vec<BfCommand>,
|
||||||
pub features: Vec<arguments::Feature>,
|
pub features: Vec<arguments::Feature>,
|
||||||
|
mode: mode::RunMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interpreter {
|
impl Interpreter {
|
||||||
|
@ -17,49 +18,59 @@ impl Interpreter {
|
||||||
array_size: usize,
|
array_size: usize,
|
||||||
bf_code: Option<String>,
|
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()),
|
bf_code: bf_code.unwrap_or_else(|| String::new()).chars().collect(),
|
||||||
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: Option<String>) -> Result<i32, InterpreterError> {
|
||||||
let bf_code = match bf_code {
|
let bf_code = match bf_code {
|
||||||
Some(bf_code) => {
|
Some(bf_code) => {
|
||||||
self.bf_code.push_str(&*bf_code);
|
bf_code.chars().collect()
|
||||||
bf_code
|
|
||||||
}
|
}
|
||||||
None => self.bf_code.clone(),
|
None => self.bf_code.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.run_brainfuck_code(&bf_code) {
|
match self.run_brainfuck_code(bf_code, false) {
|
||||||
Ok(_) => Ok(0),
|
Ok(_) => Ok(0),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// +[>++<-]
|
// +[>++<-]
|
||||||
fn iterate(&mut self, code: String) -> Result<(), InterpreterError> {
|
fn iterate(&mut self, code: Vec<char>) -> Result<(), InterpreterError> {
|
||||||
|
trace!("Iterate: {:?}", code);
|
||||||
while self.cells[self.pointer] != 0 {
|
while self.cells[self.pointer] != 0 {
|
||||||
self.run_brainfuck_code(&code)?;
|
self.run_brainfuck_code(code.clone(), true)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_brainfuck_code(&mut self, bf_code: &str) -> Result<(), InterpreterError> {
|
fn run_brainfuck_code(&mut self, bf_code: Vec<char>, from_loop: bool) -> Result<(), InterpreterError> {
|
||||||
for (i, ch) in bf_code.chars().enumerate() {
|
let mut removed_num = 0_usize;
|
||||||
match BfCommand::from_char(ch, i) {
|
for (i, ch) in bf_code.iter().enumerate() {
|
||||||
|
match BfCommand::from_char(ch, i - removed_num) {
|
||||||
Some(cmd) => {
|
Some(cmd) => {
|
||||||
trace!("Executing command: {:?}", cmd);
|
trace!("Executing command: {:?}", cmd);
|
||||||
self.execute(cmd)?
|
self.execute(cmd)?;
|
||||||
|
|
||||||
|
// 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 => {
|
None => {
|
||||||
trace!("Skipping character: {}", ch);
|
trace!("Skipping character: \'{}\'", ch);
|
||||||
|
removed_num += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,15 +145,36 @@ impl Interpreter {
|
||||||
match open_bracket {
|
match open_bracket {
|
||||||
Some(BfCommand::LoopStart(j)) => {
|
Some(BfCommand::LoopStart(j)) => {
|
||||||
if self.cells[self.pointer] != 0 {
|
if self.cells[self.pointer] != 0 {
|
||||||
let code = self.bf_code[j..i].to_string();
|
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)?;
|
self.iterate(code)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(InterpreterError::new(
|
return Err(InterpreterErrorKind::UnmatchedClosingBracket(i).to_error());
|
||||||
format!("Unmatched closing bracket at position {}", i),
|
|
||||||
15,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,6 +186,7 @@ impl Interpreter {
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +203,7 @@ enum BfCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BfCommand {
|
impl BfCommand {
|
||||||
fn from_char(c: char, index: usize) -> Option<BfCommand> {
|
fn from_char(c: &char, index: usize) -> Option<BfCommand> {
|
||||||
match c {
|
match c {
|
||||||
'>' => Some(BfCommand::IncPtr),
|
'>' => Some(BfCommand::IncPtr),
|
||||||
'<' => Some(BfCommand::DecPtr),
|
'<' => Some(BfCommand::DecPtr),
|
||||||
|
@ -185,3 +218,116 @@ impl BfCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::mode::RunMode;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use crate::utils; // for testing only
|
||||||
|
|
||||||
|
#[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(Some(String::from(">+++++++++[<++++ ++++>-]<."))), Ok(0));
|
||||||
|
}
|
||||||
|
#[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(Some(String::from(">+++++++++"))), Ok(0));
|
||||||
|
assert_eq!(interpreter.run(Some(String::from("[<++++ ++++>-]<."))), Ok(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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.pointer, 1);
|
||||||
|
assert_eq!(interpreter.cells[0], 0);
|
||||||
|
assert_eq!(interpreter.cells[1], 4);
|
||||||
|
assert_eq!(interpreter.bf_code, vec!['>', '+', '+', '+' , '+']);
|
||||||
|
|
||||||
|
// reset
|
||||||
|
interpreter.reset();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@ 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;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
@ -24,6 +24,10 @@ fn main() {
|
||||||
args.array_size,
|
args.array_size,
|
||||||
utils::read_brainfuck_code_if_any(&args.source),
|
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 {
|
||||||
|
|
5
src/mode.rs
Normal file
5
src/mode.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum RunMode {
|
||||||
|
Execute,
|
||||||
|
Repl,
|
||||||
|
}
|
26
src/repl.rs
26
src/repl.rs
|
@ -6,6 +6,10 @@ struct Repl {
|
||||||
history: Vec<String>,
|
history: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PROMPT: &str = "bf-interpreter> ";
|
||||||
|
const HISTORY_FILE: &str = "bf-interpreter-history.bfr";
|
||||||
|
const COMMAND_PREFIX: &str = "!";
|
||||||
|
|
||||||
impl Repl {
|
impl Repl {
|
||||||
pub fn new(interpreter: Interpreter) -> Self {
|
pub fn new(interpreter: Interpreter) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -16,8 +20,11 @@ impl Repl {
|
||||||
|
|
||||||
pub fn run(mut self) {
|
pub fn run(mut self) {
|
||||||
loop {
|
loop {
|
||||||
print!("\n> ");
|
print!("\n {}", PROMPT);
|
||||||
std::io::stdout().flush().unwrap();
|
std::io::stdout().flush().unwrap_or_else(|_| {
|
||||||
|
error!("Failed to flush stdout");
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
|
||||||
match std::io::stdin().read_line(&mut input) {
|
match std::io::stdin().read_line(&mut input) {
|
||||||
|
@ -31,7 +38,7 @@ impl Repl {
|
||||||
|
|
||||||
self.history.push(input.clone()); // Save input to history
|
self.history.push(input.clone()); // Save input to history
|
||||||
|
|
||||||
if input.starts_with("!") {
|
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(Some(input)) {
|
||||||
|
@ -50,7 +57,7 @@ impl Repl {
|
||||||
let mut cmd = input.split_whitespace();
|
let mut cmd = input.split_whitespace();
|
||||||
match cmd.next() {
|
match cmd.next() {
|
||||||
Some(repl_cmd) => {
|
Some(repl_cmd) => {
|
||||||
match repl_cmd.get(1..).unwrap_or("") {
|
match repl_cmd.get(COMMAND_PREFIX.len()..).unwrap_or("") {
|
||||||
"fuck" => {
|
"fuck" => {
|
||||||
println!("Bye bye :D");
|
println!("Bye bye :D");
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
|
@ -78,7 +85,7 @@ impl Repl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"save" | "s" => {
|
"save" | "s" => {
|
||||||
let file_name = cmd.next().unwrap_or("brainfuck_repl_history.bfr");
|
let file_name = cmd.next().unwrap_or(HISTORY_FILE);
|
||||||
|
|
||||||
println!("Saving history to file: {file_name}");
|
println!("Saving history to file: {file_name}");
|
||||||
match std::fs::write(file_name, self.history.join("\n")) {
|
match std::fs::write(file_name, self.history.join("\n")) {
|
||||||
|
@ -91,7 +98,7 @@ impl Repl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"load" | "l" => {
|
"load" | "l" => {
|
||||||
let file_name = cmd.next().unwrap_or("brainfuck_repl_history.bfr");
|
let file_name = cmd.next().unwrap_or(HISTORY_FILE);
|
||||||
|
|
||||||
println!("Loading history from file: {file_name}");
|
println!("Loading history from file: {file_name}");
|
||||||
match std::fs::read_to_string(file_name) {
|
match std::fs::read_to_string(file_name) {
|
||||||
|
@ -133,7 +140,7 @@ impl Repl {
|
||||||
println!("!history, !h: print the history of the commands");
|
println!("!history, !h: print the history of the commands");
|
||||||
println!("!save, !s: save the history to a file");
|
println!("!save, !s: save the history to a file");
|
||||||
println!("!load, !l: load the history from a file");
|
println!("!load, !l: load the history from a file");
|
||||||
println!("!reset, !r: reset the bf_interpreter");
|
println!("!reset, !r: reset the REPL");
|
||||||
println!("!help: show this fu*king help message");
|
println!("!help: show this fu*king help message");
|
||||||
println!("!fuck: exit the REPL mode");
|
println!("!fuck: exit the REPL mode");
|
||||||
}
|
}
|
||||||
|
@ -145,6 +152,9 @@ impl Repl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run the REPL
|
||||||
|
/// # Arguments
|
||||||
|
/// * `interpreter` - The interpreter to use
|
||||||
pub fn start(interpreter: Interpreter) {
|
pub fn start(interpreter: Interpreter) {
|
||||||
info!("Entering REPL mode");
|
info!("Entering REPL mode");
|
||||||
println!("Welcome to the brainfuck REPL mode! :)");
|
println!("Welcome to the brainfuck REPL mode! :)");
|
||||||
|
@ -155,7 +165,7 @@ pub fn start(interpreter: Interpreter) {
|
||||||
);
|
);
|
||||||
println!("Enter your brainfuck code and press enter to run it.");
|
println!("Enter your brainfuck code and press enter to run it.");
|
||||||
println!("Enter !fuck to exit :D");
|
println!("Enter !fuck to exit :D");
|
||||||
println!("Enter !help fuck to get more help");
|
println!("Enter !help to get more fu*king help");
|
||||||
|
|
||||||
Repl::new(interpreter).run();
|
Repl::new(interpreter).run();
|
||||||
}
|
}
|
||||||
|
|
12
src/utils.rs
12
src/utils.rs
|
@ -3,7 +3,7 @@ pub(crate) fn read_brainfuck_code_if_any(source: &Option<String>) -> Option<Stri
|
||||||
Some(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(source),
|
Ok(source) => Some(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);
|
||||||
|
@ -14,3 +14,13 @@ pub(crate) fn read_brainfuck_code_if_any(source: &Option<String>) -> Option<Stri
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clean(source: String) -> String {
|
||||||
|
source
|
||||||
|
.chars()
|
||||||
|
.filter(|c| match c {
|
||||||
|
'+' | '-' | '<' | '>' | '[' | ']' | '.' | ',' => true,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
3
test_code/hello_world.bf
Normal file
3
test_code/hello_world.bf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]
|
||||||
|
>++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++
|
||||||
|
.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++.
|
1
test_code/like_cat.bf
Normal file
1
test_code/like_cat.bf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
+[>,.]
|
|
@ -1 +1 @@
|
||||||
>+++++++++[<++++ ++++>-]<.>++++++++[<++++>-]<+.>+++++++++[<-------->-]<.
|
>+++++++++[ <++++ +++ + > - ] < . > + + ++++++[<++++>-]<+.>+++++++++[<-------->-]<.
|
||||||
|
|
Loading…
Reference in a new issue