summary refs log tree commit diff
path: root/src
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 /src
parentc9ab8d38765c7c80f2ea9083ce8d326f407110ac (diff)
feat(engine): allow choosing engine per executor call
Diffstat (limited to '')
-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
6 files changed, 139 insertions, 94 deletions
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(())
 }