breakfast 0.1.4

A Brainfuck interpreter in Rust
Documentation
//! # The Breakfast Brainfuck Interpreter
//!
//! Breakfast is a minimal brainfuck (BF for short) interpreter in Rust. 
//!
//! It offers most of the suggested BF features, including multiple EOF 
//! behaviors and `#` for debug purposes. 
//! 
//! ## Example
//!
//! Here is a simple piece of code to run the "Hello World" BF program:
//! ```
//! use breakfast::*;
//!
//! fn main() -> std::io::Result<()> {
//!     let program = Breakfast::parse(
//!         r#"
//!             >++++++++[<+++++++++>-]<.
//!             >++++[<+++++++>-]<+.
//!             +++++++..
//!             +++.
//!             >>++++++[<+++++++>-]<++.
//!             ------------.
//!             >++++++[<+++++++++>-]<+.
//!             <.
//!             +++.
//!             ------.
//!             --------.
//!             >>>++++[<++++++++>-]<+.
//!         "#
//!     );
//!
//!     let mut bf = Breakfast::new(Default::default());
//!     bf.run(program)?;
//!
//!     Ok(())
//! }
//! ```

use std::{
    io::{self, Read, Write},
};

/// The number of cells available in a program.
const NUM_CELLS: usize = 30000;

/// The interpreter `struct`. 
pub struct Breakfast {
    memory: [u8; NUM_CELLS],
    program: Vec<u8>,
    brackets: Vec<Option<usize>>,
    pub config: Config,
}

impl Breakfast {
    /// Returns a configured interpreter ready for running BF programs.
    pub fn new(config: Config) -> Breakfast {
        Breakfast {
            memory: [0; NUM_CELLS],
            program: vec![0],
            brackets: vec![None],
            config,
        }
    }

    /// Returns a `Vec` of bytes converted from the raw BF code.
    pub fn parse(program: &str) -> Vec<u8> {
        program
            .bytes()
            .filter(|b| matches!(
                    b,
                    b'>' | b'<' | b'+' | b'-' |
                    b'.' | b',' | b'[' | b']' |
                    b'#')
            )
            .collect()
    }

    /// Returns a 'Vec' of preprocessed matched pairs of brackets.
    /// If there exists an unclosed bracket, an error will be raised.
    fn build_brackets(commands: &[u8]) ->
        Result<Vec<Option<usize>>, &'static str>
    {
        let mut map = vec![None; commands.len()];
        let mut stack = Vec::new();

        for (i, cmd) in commands.iter().enumerate() {
            match cmd {
                b'[' => stack.push(i),
                b']' => {
                    if let Some(start) = stack.pop() {
                        map[start] = Some(i);
                        map[i] = Some(start);
                    } else {
                        return Err("Unmatched ']'");
                    }
                }
                _ => (),
            }
        }

        if !stack.is_empty() {
            return Err("Unmatched '['");
        }

        Ok(map)
    }

    /// Runs the BF program code.
    ///
    /// # Panics
    /// The function might panic if there exists unclosed brackets in the BF 
    /// program code.
    pub fn run(&mut self, program: Vec<u8>) -> io::Result<()> {
        let brackets = match Self::build_brackets(&program) {
            Ok(map) => map,
            Err(e) => {
                panic!("{}", e);
            }
        };

        self.program = program;
        self.brackets = brackets;

        let mut ptr: usize = 0;
        let mut loc: usize = 0;

        while loc < self.program.len() {
            let command = self.program[loc];
            match command {
                b'>' => ptr = (ptr + 1).min(29999),
                b'<' => ptr = ptr.saturating_sub(1),
                b'+' => self.memory[ptr] = self.memory[ptr].wrapping_add(1),
                b'-' => self.memory[ptr] = self.memory[ptr].wrapping_sub(1),
                b'.' => {
                    io::stdout().write_all(&[self.memory[ptr]]).unwrap();
                    io::stdout().flush().unwrap();
                }
                b',' => {
                    let mut buf = [0u8];
                    if io::stdin().read_exact(&mut buf).is_ok() {
                        self.memory[ptr] = buf[0];
                    } else {
                        match self.config.eof_behavior {
                            EofBehavior::Keep => {}
                            EofBehavior::Zero => self.memory[ptr] = 0,
                            EofBehavior::Max => self.memory[ptr] = 255,
                        }
                    }
                }
                b'[' => {
                    if self.memory[ptr] == 0 {
                        loc = self.brackets[loc].unwrap();
                    }
                }
                b']' => {
                    if self.memory[ptr] != 0 {
                        loc = self.brackets[loc].unwrap();
                    }
                }
                b'#' if self.config.dbg => {
                    println!(
                        "\n[DEBUG] commmand index: {}, pointer index: {}, cell value: {:?}\n",
                        loc, ptr, self.memory[ptr]
                    );
                }
                _ => {}
            }
            loc += 1;
        }
        
        println!();
        Ok(())
    }
}

/// An `EofBehavior` indicates the behavior to handle empty inputs.
pub enum EofBehavior {
    /// Does nothing to the current cell.
    Keep,
    /// Sets the value of the cell to `0u8`.
    Zero,
    /// Sets the value of the cell to `255u8`, or `-1`.
    Max,
}

impl Default for EofBehavior {
    fn default() -> EofBehavior { EofBehavior::Keep }
}

/// The configuration of the Breakfast interpreter. 
#[derive(Default)]
pub struct Config {
    pub eof_behavior: EofBehavior,
    pub dbg: bool,
}