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}