summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSophie Forrest <git@sophieforrest.com>2024-08-30 23:35:45 +1200
committerSophie Forrest <git@sophieforrest.com>2024-08-30 23:35:45 +1200
commitf5f789540ad7d3f7f4f855c9db69d65cfc190ee0 (patch)
treef532988e9a35a0d2c58efbad9daf6e66288f4a1f
parentc9ab8d38765c7c80f2ea9083ce8d326f407110ac (diff)
feat(engine): allow choosing engine per executor call
-rw-r--r--Cargo.lock16
-rw-r--r--Cargo.toml4
-rw-r--r--src/constants.rs9
-rw-r--r--src/engine.rs21
-rw-r--r--src/executor.rs125
-rw-r--r--src/lib.rs52
-rw-r--r--src/main.rs10
-rw-r--r--src/utility.rs16
8 files changed, 157 insertions, 96 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ad806f1..0be8c23 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -67,6 +67,12 @@ dependencies = [
]
[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
name = "backtrace"
version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -103,6 +109,7 @@ dependencies = [
"clap",
"fs-err",
"miette",
+ "num-traits",
"thiserror",
]
@@ -290,6 +297,15 @@ dependencies = [
]
[[package]]
+name = "num-traits"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "object"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 3d83881..eca44e9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,11 +6,11 @@ resolver = "2"
[dependencies]
clap = { features = ["derive"], version = "4.3.21" }
+num-traits = "0.2.16"
fs-err = "2.9.0"
miette = { features = ["fancy"], version = "5.10.0" }
thiserror = "1.0.44"
[features]
-default = ["utilities", "u8-engine"]
+default = ["utilities"]
utilities = []
-u8-engine = []
diff --git a/src/constants.rs b/src/constants.rs
deleted file mode 100644
index 6ca1bc5..0000000
--- a/src/constants.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-//! Constants and types used throughout the Brainfuck interpreter.
-
-/// The inner type used by a Brainfuck tape.
-#[cfg(feature = "u8-engine")]
-pub type TapeInner = u8;
-
-/// The inner type used by a Brainfuck tape.
-#[cfg(feature = "u16-engine")]
-pub type TapeInner = u16;
diff --git a/src/engine.rs b/src/engine.rs
index 508178a..8cf2726 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -4,17 +4,22 @@
use std::io::Read;
-use crate::executor::{Error, Executor};
+use num_traits::{One, Unsigned, WrappingAdd, WrappingSub, Zero};
+
+use crate::executor::{self, Error};
/// Generic engine implementation for the Brainfuck interpreter.
-pub trait Engine<T> {
+pub trait Engine {
+ /// Inner type of the Tape.
+ type TapeInner: Clone + Copy + Unsigned + WrappingAdd + WrappingSub + One + Zero;
+
/// Read one byte from stdin.
///
/// # Errors
///
/// This function will return an error if it is unable to read from stdin,
/// or if it indexes out of bounds.
- fn read_byte() -> Result<T, Error>;
+ fn read_byte() -> Result<Self::TapeInner, Error>;
/// Write the provided byte to stdout.
///
@@ -22,10 +27,12 @@ pub trait Engine<T> {
///
/// This function will return an error if it is unable to write a byte to
/// stdout.
- fn write_byte(byte: T) -> Result<(), Error>;
+ fn write_byte(byte: Self::TapeInner) -> Result<(), Error>;
}
-impl Engine<u8> for Executor {
+impl Engine for executor::U8 {
+ type TapeInner = u8;
+
fn read_byte() -> Result<u8, Error> {
let mut input: [u8; 1] = [0; 1];
@@ -41,7 +48,9 @@ impl Engine<u8> for Executor {
}
}
-impl Engine<u16> for Executor {
+impl Engine for executor::U16 {
+ type TapeInner = u16;
+
fn read_byte() -> Result<u16, Error> {
let mut input: [u8; 2] = [0; 2];
diff --git a/src/executor.rs b/src/executor.rs
index 24b2f47..eddf443 100644
--- a/src/executor.rs
+++ b/src/executor.rs
@@ -1,9 +1,10 @@
//! Executor implementation for Brainfuck.
use miette::Diagnostic;
+use num_traits::{One, WrappingAdd, WrappingSub, Zero};
use thiserror::Error;
-use crate::{constants::TapeInner, engine::Engine, parser::Instruction};
+use crate::{engine::Engine, parser::Instruction};
/// Runtime errors that can occur in brainfuck executor.
#[derive(Debug, Diagnostic, Error)]
@@ -18,74 +19,76 @@ pub enum Error {
ReadInput(#[from] std::io::Error),
}
-/// Struct for executor implementation, allows Engine to be implemented.
+/// Struct for executor implementation, allows u8 Engine to be implemented.
#[derive(Clone, Copy, Debug)]
-pub struct Executor;
+pub struct U8;
-impl Executor {
- /// Executes the provided instruction set, utilising the provided tape..
- ///
- /// # Errors
- ///
- /// This function will return an error if the Brainfuck code indexes out of
- /// bounds of the tape, or if the executor cannot read an input byte from
- /// stdin.
- pub fn execute(
- instructions: &[Instruction],
- tape: &mut [TapeInner],
- data_pointer: &mut usize,
- ) -> Result<(), Error> {
- for instruction in instructions {
- match *instruction {
- Instruction::IncrementPointer => {
- let tape_len: usize = tape.len() - 1;
+/// Struct for executor implementation, allows u16 Engine to be implemented.
+#[derive(Clone, Copy, Debug)]
+pub struct U16;
- if *data_pointer == tape_len {
- *data_pointer = 0;
- } else {
- *data_pointer += 1;
- }
- }
- Instruction::DecrementPointer => {
- *data_pointer = match *data_pointer {
- 0 => tape.len() - 1,
- _ => *data_pointer - 1,
- };
+/// Executes the provided instruction set, utilising the provided tape..
+///
+/// # Errors
+///
+/// This function will return an error if the Brainfuck code indexes out of
+/// bounds of the tape, or if the executor cannot read an input byte from
+/// stdin.
+pub fn execute<E: Engine>(
+ instructions: &[Instruction],
+ tape: &mut [E::TapeInner],
+ data_pointer: &mut usize,
+) -> Result<(), Error> {
+ for instruction in instructions {
+ match *instruction {
+ Instruction::IncrementPointer => {
+ let tape_len: usize = tape.len() - 1;
+
+ if *data_pointer == tape_len {
+ *data_pointer = 0;
+ } else {
+ *data_pointer += 1;
}
- Instruction::IncrementByte => match tape.get_mut(*data_pointer) {
- Some(value) => *value = value.overflowing_add(1).0,
- None => return Err(Error::IndexOutOfBounds(*data_pointer)),
- },
- Instruction::DecrementByte => match tape.get_mut(*data_pointer) {
- Some(value) => *value = value.overflowing_sub(1).0,
+ }
+ Instruction::DecrementPointer => {
+ *data_pointer = match *data_pointer {
+ 0 => tape.len() - 1,
+ _ => *data_pointer - 1,
+ };
+ }
+ Instruction::IncrementByte => match tape.get_mut(*data_pointer) {
+ Some(value) => *value = value.wrapping_add(&E::TapeInner::one()),
+ None => return Err(Error::IndexOutOfBounds(*data_pointer)),
+ },
+ Instruction::DecrementByte => match tape.get_mut(*data_pointer) {
+ Some(value) => *value = value.wrapping_sub(&E::TapeInner::one()),
+ None => return Err(Error::IndexOutOfBounds(*data_pointer)),
+ },
+ Instruction::OutputByte => {
+ E::write_byte(match tape.get(*data_pointer) {
+ Some(value) => *value,
None => return Err(Error::IndexOutOfBounds(*data_pointer)),
- },
- Instruction::OutputByte => {
- Self::write_byte(match tape.get(*data_pointer) {
- Some(value) => *value,
- None => return Err(Error::IndexOutOfBounds(*data_pointer)),
- })?;
- }
- Instruction::InputByte => {
- let input = Self::read_byte()?;
+ })?;
+ }
+ Instruction::InputByte => {
+ let input = E::read_byte()?;
- match tape.get_mut(*data_pointer) {
- Some(value) => *value = input,
- None => return Err(Error::IndexOutOfBounds(*data_pointer)),
- };
- }
- Instruction::Loop(ref instructions) => {
- while match tape.get(*data_pointer) {
- Some(value) => *value,
- None => return Err(Error::IndexOutOfBounds(*data_pointer)),
- } != 0
- {
- Self::execute(instructions, tape, data_pointer)?;
- }
+ match tape.get_mut(*data_pointer) {
+ Some(value) => *value = input,
+ None => return Err(Error::IndexOutOfBounds(*data_pointer)),
+ };
+ }
+ Instruction::Loop(ref instructions) => {
+ while match tape.get(*data_pointer) {
+ Some(value) => value,
+ None => return Err(Error::IndexOutOfBounds(*data_pointer)),
+ } != &E::TapeInner::zero()
+ {
+ execute::<E>(instructions, tape, data_pointer)?;
}
}
}
-
- Ok(())
}
+
+ Ok(())
}
diff --git a/src/lib.rs b/src/lib.rs
index 4a7950a..b806b4c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -82,7 +82,6 @@
#[cfg(test)]
extern crate test;
-mod constants;
mod engine;
pub mod executor;
pub mod lexer;
@@ -90,7 +89,6 @@ pub mod parser;
#[cfg(feature = "utilities")]
pub mod utility;
-pub use executor::Executor;
pub use lexer::{lex, OperatorCode};
use miette::Diagnostic;
pub use parser::{parse, Instruction};
@@ -118,33 +116,67 @@ mod tests {
use test::Bencher;
use super::*;
- use crate::constants::TapeInner;
#[test]
fn hello_world() -> Result<(), Error> {
- let mut tape: Vec<TapeInner> = vec![0; 1024];
+ let mut tape: Vec<u8> = vec![0; 1024];
- utility::execute_from_file("./test_programs/hello_world.bf", &mut tape)?;
+ utility::execute_from_file::<executor::U8>("./test_programs/hello_world.bf", &mut tape)?;
+
+ Ok(())
+ }
+
+ #[test]
+ fn hello_world_u16() -> Result<(), Error> {
+ let mut tape: Vec<u16> = vec![0; 1024];
+
+ utility::execute_from_file::<executor::U16>("./test_programs/hello_world.bf", &mut tape)?;
+
+ Ok(())
+ }
+
+ #[test]
+ fn hello_world_from_hell() -> Result<(), Error> {
+ let mut tape: Vec<u16> = vec![0; 1024];
+
+ utility::execute_from_file::<executor::U16>(
+ "./test_programs/hello_world_from_hell.bf",
+ &mut tape,
+ )?;
Ok(())
}
#[bench]
- fn hello_world_from_hell(b: &mut Bencher) {
+ fn hello_world_from_hell_bench_u8(b: &mut Bencher) {
b.iter(|| {
- let mut tape: Vec<TapeInner> = vec![0; 1024];
+ let mut tape: Vec<u8> = vec![0; 1024];
#[allow(clippy::expect_used)]
- utility::execute_from_file("./test_programs/hello_world.bf", &mut tape)
+ utility::execute_from_file::<executor::U8>("./test_programs/hello_world.bf", &mut tape)
.expect("failed to run");
});
}
+ #[bench]
+ fn hello_world_from_hell_bench_u16(b: &mut Bencher) {
+ b.iter(|| {
+ let mut tape: Vec<u16> = vec![0; 1024];
+
+ #[allow(clippy::expect_used)]
+ utility::execute_from_file::<executor::U16>(
+ "./test_programs/hello_world.bf",
+ &mut tape,
+ )
+ .expect("failed to run");
+ });
+ }
+
#[test]
fn hello_world_short() -> Result<(), Error> {
- let mut tape: Vec<TapeInner> = vec![0; 1024];
+ let mut tape: Vec<u8> = vec![0; 1024];
- utility::execute_from_str(
+ utility::execute_from_str::<executor::U8>(
"--[+++++++<---->>-->+>+>+<<<<]<.>++++[-<++++>>->--<<]>>-.>--..>+.<<<.<<-.>>+>->>.\
+++[.<]",
&mut tape,
diff --git a/src/main.rs b/src/main.rs
index 5412735..f5c4740 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -79,7 +79,10 @@
use std::path::PathBuf;
-use brainf_rs::utility::{execute_from_file, execute_from_str};
+use brainf_rs::{
+ executor,
+ utility::{execute_from_file, execute_from_str},
+};
use clap::{Parser, Subcommand};
use miette::Context;
@@ -116,9 +119,10 @@ fn main() -> miette::Result<()> {
match app.command {
Command::File { ref path } => {
- execute_from_file(path, &mut tape).wrap_err("when executing from file")?;
+ execute_from_file::<executor::U8>(path, &mut tape)
+ .wrap_err("when executing from file")?;
}
- Command::Text { ref input } => execute_from_str(input, &mut tape)?,
+ Command::Text { ref input } => execute_from_str::<executor::U8>(input, &mut tape)?,
};
Ok(())
diff --git a/src/utility.rs b/src/utility.rs
index 58ac3e2..4eba853 100644
--- a/src/utility.rs
+++ b/src/utility.rs
@@ -2,7 +2,7 @@
use std::path::Path;
-use crate::{constants::TapeInner, lex, parse, Error, Executor};
+use crate::{engine::Engine, executor::execute, lex, parse, Error};
/// Utility function to execute a Brainfuck file. Lexes, parses and executes the
/// input file.
@@ -12,7 +12,10 @@ use crate::{constants::TapeInner, lex, parse, Error, Executor};
/// This function will return an error if reading the input file, parsing or
/// execution fails. See documentation for [`crate::parser::parse`] and
/// [`crate::executor::execute`].
-pub fn execute_from_file(path: impl AsRef<Path>, tape: &mut [TapeInner]) -> Result<(), Error> {
+pub fn execute_from_file<E: Engine>(
+ path: impl AsRef<Path>,
+ tape: &mut [E::TapeInner],
+) -> Result<(), Error> {
let input = fs_err::read_to_string(path.as_ref())?;
let operator_codes = lex(&input);
@@ -21,7 +24,7 @@ pub fn execute_from_file(path: impl AsRef<Path>, tape: &mut [TapeInner]) -> Resu
let mut data_pointer = 0;
- Executor::execute(&instructions, tape, &mut data_pointer)?;
+ execute::<E>(&instructions, tape, &mut data_pointer)?;
Ok(())
}
@@ -34,14 +37,17 @@ pub fn execute_from_file(path: impl AsRef<Path>, tape: &mut [TapeInner]) -> Resu
/// This function will return an error if parsing or
/// execution fails. See documentation for [`crate::parser::parse`] and
/// [`crate::executor::execute`].
-pub fn execute_from_str(input: &str, tape: &mut [TapeInner]) -> Result<(), Error> {
+pub fn execute_from_str<E: Engine<TapeInner = u8>>(
+ input: &str,
+ tape: &mut [E::TapeInner],
+) -> Result<(), Error> {
let operator_codes = lex(input);
let instructions = parse(input, &operator_codes)?;
let mut data_pointer = 0;
- Executor::execute(&instructions, tape, &mut data_pointer)?;
+ execute::<E>(&instructions, tape, &mut data_pointer)?;
Ok(())
}