diff options
| author | Sophie Forrest <git@sophieforrest.com> | 2024-08-30 23:35:45 +1200 |
|---|---|---|
| committer | Sophie Forrest <git@sophieforrest.com> | 2024-08-30 23:35:45 +1200 |
| commit | f5f789540ad7d3f7f4f855c9db69d65cfc190ee0 (patch) | |
| tree | f532988e9a35a0d2c58efbad9daf6e66288f4a1f | |
| parent | c9ab8d38765c7c80f2ea9083ce8d326f407110ac (diff) | |
feat(engine): allow choosing engine per executor call
Diffstat (limited to '')
| -rw-r--r-- | Cargo.lock | 16 | ||||
| -rw-r--r-- | Cargo.toml | 4 | ||||
| -rw-r--r-- | src/constants.rs | 9 | ||||
| -rw-r--r-- | src/engine.rs | 21 | ||||
| -rw-r--r-- | src/executor.rs | 125 | ||||
| -rw-r--r-- | src/lib.rs | 52 | ||||
| -rw-r--r-- | src/main.rs | 10 | ||||
| -rw-r--r-- | src/utility.rs | 16 |
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(()) } |