Skip to main content

breakfast/
lib.rs

1//! # The Breakfast Brainfuck Interpreter
2//!
3//! Breakfast is a minimal brainfuck (BF for short) interpreter in Rust. 
4//!
5//! It offers most of the suggested BF features, including multiple EOF 
6//! behaviors and `#` for debug purposes. 
7//! 
8//! ## Example
9//!
10//! Here is a simple piece of code to run the "Hello World" BF program:
11//! ```
12//! use breakfast::*;
13//!
14//! fn main() -> std::io::Result<()> {
15//!     let program = Breakfast::parse(
16//!         r#"
17//!             >++++++++[<+++++++++>-]<.
18//!             >++++[<+++++++>-]<+.
19//!             +++++++..
20//!             +++.
21//!             >>++++++[<+++++++>-]<++.
22//!             ------------.
23//!             >++++++[<+++++++++>-]<+.
24//!             <.
25//!             +++.
26//!             ------.
27//!             --------.
28//!             >>>++++[<++++++++>-]<+.
29//!         "#
30//!     );
31//!
32//!     let mut bf = Breakfast::new(Default::default());
33//!     bf.run(program)?;
34//!
35//!     Ok(())
36//! }
37//! ```
38
39use std::{
40    io::{self, Read, Write},
41};
42
43/// The number of cells available in a program.
44const NUM_CELLS: usize = 30000;
45
46/// The interpreter `struct`. 
47pub struct Breakfast {
48    memory: [u8; NUM_CELLS],
49    program: Vec<u8>,
50    brackets: Vec<Option<usize>>,
51    pub config: Config,
52}
53
54impl Breakfast {
55    /// Returns a configured interpreter ready for running BF programs.
56    pub fn new(config: Config) -> Breakfast {
57        Breakfast {
58            memory: [0; NUM_CELLS],
59            program: vec![0],
60            brackets: vec![None],
61            config,
62        }
63    }
64
65    /// Returns a `Vec` of bytes converted from the raw BF code.
66    pub fn parse(program: &str) -> Vec<u8> {
67        program
68            .bytes()
69            .filter(|b| matches!(
70                    b,
71                    b'>' | b'<' | b'+' | b'-' |
72                    b'.' | b',' | b'[' | b']' |
73                    b'#')
74            )
75            .collect()
76    }
77
78    /// Returns a 'Vec' of preprocessed matched pairs of brackets.
79    /// If there exists an unclosed bracket, an error will be raised.
80    fn build_brackets(commands: &[u8]) ->
81        Result<Vec<Option<usize>>, &'static str>
82    {
83        let mut map = vec![None; commands.len()];
84        let mut stack = Vec::new();
85
86        for (i, cmd) in commands.iter().enumerate() {
87            match cmd {
88                b'[' => stack.push(i),
89                b']' => {
90                    if let Some(start) = stack.pop() {
91                        map[start] = Some(i);
92                        map[i] = Some(start);
93                    } else {
94                        return Err("Unmatched ']'");
95                    }
96                }
97                _ => (),
98            }
99        }
100
101        if !stack.is_empty() {
102            return Err("Unmatched '['");
103        }
104
105        Ok(map)
106    }
107
108    /// Runs the BF program code.
109    ///
110    /// # Panics
111    /// The function might panic if there exists unclosed brackets in the BF 
112    /// program code.
113    pub fn run(&mut self, program: Vec<u8>) -> io::Result<()> {
114        let brackets = match Self::build_brackets(&program) {
115            Ok(map) => map,
116            Err(e) => {
117                panic!("{}", e);
118            }
119        };
120
121        self.program = program;
122        self.brackets = brackets;
123
124        let mut ptr: usize = 0;
125        let mut loc: usize = 0;
126
127        while loc < self.program.len() {
128            let command = self.program[loc];
129            match command {
130                b'>' => ptr = (ptr + 1).min(29999),
131                b'<' => ptr = ptr.saturating_sub(1),
132                b'+' => self.memory[ptr] = self.memory[ptr].wrapping_add(1),
133                b'-' => self.memory[ptr] = self.memory[ptr].wrapping_sub(1),
134                b'.' => {
135                    io::stdout().write_all(&[self.memory[ptr]]).unwrap();
136                    io::stdout().flush().unwrap();
137                }
138                b',' => {
139                    let mut buf = [0u8];
140                    if io::stdin().read_exact(&mut buf).is_ok() {
141                        self.memory[ptr] = buf[0];
142                    } else {
143                        match self.config.eof_behavior {
144                            EofBehavior::Keep => {}
145                            EofBehavior::Zero => self.memory[ptr] = 0,
146                            EofBehavior::Max => self.memory[ptr] = 255,
147                        }
148                    }
149                }
150                b'[' => {
151                    if self.memory[ptr] == 0 {
152                        loc = self.brackets[loc].unwrap();
153                    }
154                }
155                b']' => {
156                    if self.memory[ptr] != 0 {
157                        loc = self.brackets[loc].unwrap();
158                    }
159                }
160                b'#' if self.config.dbg => {
161                    println!(
162                        "\n[DEBUG] commmand index: {}, pointer index: {}, cell value: {:?}\n",
163                        loc, ptr, self.memory[ptr]
164                    );
165                }
166                _ => {}
167            }
168            loc += 1;
169        }
170        
171        println!();
172        Ok(())
173    }
174}
175
176/// An `EofBehavior` indicates the behavior to handle empty inputs.
177pub enum EofBehavior {
178    /// Does nothing to the current cell.
179    Keep,
180    /// Sets the value of the cell to `0u8`.
181    Zero,
182    /// Sets the value of the cell to `255u8`, or `-1`.
183    Max,
184}
185
186impl Default for EofBehavior {
187    fn default() -> EofBehavior { EofBehavior::Keep }
188}
189
190/// The configuration of the Breakfast interpreter. 
191#[derive(Default)]
192pub struct Config {
193    pub eof_behavior: EofBehavior,
194    pub dbg: bool,
195}