From f5f789540ad7d3f7f4f855c9db69d65cfc190ee0 Mon Sep 17 00:00:00 2001 From: Sophie Forrest Date: Fri, 30 Aug 2024 23:35:45 +1200 Subject: feat(engine): allow choosing engine per executor call --- src/constants.rs | 9 ---- src/engine.rs | 21 +++++++--- src/executor.rs | 125 ++++++++++++++++++++++++++++--------------------------- src/lib.rs | 52 ++++++++++++++++++----- src/main.rs | 10 +++-- src/utility.rs | 16 ++++--- 6 files changed, 139 insertions(+), 94 deletions(-) delete mode 100644 src/constants.rs (limited to 'src') 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 { +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; + fn read_byte() -> Result; /// Write the provided byte to stdout. /// @@ -22,10 +27,12 @@ pub trait Engine { /// /// 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 for Executor { +impl Engine for executor::U8 { + type TapeInner = u8; + fn read_byte() -> Result { let mut input: [u8; 1] = [0; 1]; @@ -41,7 +48,9 @@ impl Engine for Executor { } } -impl Engine for Executor { +impl Engine for executor::U16 { + type TapeInner = u16; + fn read_byte() -> Result { 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( + 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::(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 = vec![0; 1024]; + let mut tape: Vec = vec![0; 1024]; - utility::execute_from_file("./test_programs/hello_world.bf", &mut tape)?; + utility::execute_from_file::("./test_programs/hello_world.bf", &mut tape)?; + + Ok(()) + } + + #[test] + fn hello_world_u16() -> Result<(), Error> { + let mut tape: Vec = vec![0; 1024]; + + utility::execute_from_file::("./test_programs/hello_world.bf", &mut tape)?; + + Ok(()) + } + + #[test] + fn hello_world_from_hell() -> Result<(), Error> { + let mut tape: Vec = vec![0; 1024]; + + utility::execute_from_file::( + "./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 = vec![0; 1024]; + let mut tape: Vec = vec![0; 1024]; #[allow(clippy::expect_used)] - utility::execute_from_file("./test_programs/hello_world.bf", &mut tape) + utility::execute_from_file::("./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 = vec![0; 1024]; + + #[allow(clippy::expect_used)] + utility::execute_from_file::( + "./test_programs/hello_world.bf", + &mut tape, + ) + .expect("failed to run"); + }); + } + #[test] fn hello_world_short() -> Result<(), Error> { - let mut tape: Vec = vec![0; 1024]; + let mut tape: Vec = vec![0; 1024]; - utility::execute_from_str( + utility::execute_from_str::( "--[+++++++<---->>-->+>+>+<<<<]<.>++++[-<++++>>->--<<]>>-.>--..>+.<<<.<<-.>>+>->>.\ +++[.<]", &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::(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::(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, tape: &mut [TapeInner]) -> Result<(), Error> { +pub fn execute_from_file( + path: impl AsRef, + 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, tape: &mut [TapeInner]) -> Resu let mut data_pointer = 0; - Executor::execute(&instructions, tape, &mut data_pointer)?; + execute::(&instructions, tape, &mut data_pointer)?; Ok(()) } @@ -34,14 +37,17 @@ pub fn execute_from_file(path: impl AsRef, 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>( + 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::(&instructions, tape, &mut data_pointer)?; Ok(()) } -- cgit 1.4.1