//! Executor implementation for Brainfuck. use std::io::Read; use miette::Diagnostic; use thiserror::Error; use crate::{constants::TapeInner, parser::Instruction}; /// Runtime errors that can occur in brainfuck executor. #[derive(Debug, Diagnostic, Error)] pub enum 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), /// Executor was unable to read an input byte from stdin. #[error("could not read input from stdin")] ReadInput(#[from] std::io::Error), } /// 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; 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.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, None => return Err(Error::IndexOutOfBounds(*data_pointer)), }, Instruction::OutputByte => print!( "{}", char::from(match tape.get(*data_pointer) { Some(value) => *value, None => return Err(Error::IndexOutOfBounds(*data_pointer)), }) ), Instruction::InputByte => { let mut input: [TapeInner; 1] = [0; 1]; std::io::stdin().read_exact(&mut input)?; match tape.get_mut(*data_pointer) { Some(value) => *value = input[0], 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 { execute(instructions, tape, data_pointer)?; } } } } Ok(()) }