//! Executor implementation for Brainfuck. use miette::Diagnostic; use num_traits::{One, WrappingAdd, WrappingSub, Zero}; use thiserror::Error; use crate::{engine::Engine, parser::Instruction}; /// Runtime errors that can occur in brainfuck executor. #[derive(Debug, Diagnostic, Error)] pub enum Error { /// Brainfuck engine ran into an error at runtime. #[error(transparent)] Engine(#[from] crate::engine::Error), /// Brainfuck code performed an out of bounds index on the tape during /// runtime. #[error("tape indexed out of bounds, attempted index at `{0}`")] IndexOutOfBounds(usize), } /// Struct for executor implementation, allows u8 Engine to be implemented. #[derive(Clone, Copy, Debug)] pub struct U8; /// Struct for executor implementation, allows u16 Engine to be implemented. #[derive(Clone, Copy, Debug)] #[cfg(feature = "engine-u16")] pub struct U16; /// Struct for executor implementation, allows u32 Engine to be implemented. #[derive(Clone, Copy, Debug)] #[cfg(feature = "engine-u32")] pub struct U32; /// Trait that must be implemented by all executors. pub trait 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. fn execute( instructions: &[Instruction], tape: &mut [E::TapeInner], data_pointer: &mut usize, ) -> Result<(), Error>; } impl Executor for E where E: Engine, { #[inline] fn execute( instructions: &[Instruction], tape: &mut [::TapeInner], data_pointer: &mut usize, ) -> Result<(), Error> { execute::(instructions, tape, data_pointer) } } /// Executes the provided instruction set, utilising the provided tape. This /// function allows specifying the Brainfuck engine implementation per call. /// /// # 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::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::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)), } != &E::TapeInner::zero() { execute::(instructions, tape, data_pointer)?; } } } } Ok(()) }