brainfuck_exe/lib.rs
1//! # Brainfuck-exe
2//!
3//! a simple [`brainfuck`](https://esolangs.org/wiki/Brainfuck) interpreter crate implemented in rust 🦀
4//! with many available customizations for flexibility
5//!
6//! see the [`Brainfuck`] struct for more information on usage
7//!
8//! ## Usage
9//!
10//! In your `Cargo.toml`:
11//! ```toml
12//! brainfuck-exe = "*"
13//! ```
14//!
15//! If you are only using it as a library, and the CLI is not needed,
16//! disable the `cli` (included by default) feature to remove unecessary dependencies:
17//! ```toml
18//! brainfuck-exe = { version = "*", default-features = false }
19//! ```
20//!
21//! ## Example
22//! Below is a basic example on how to use the crate
23//!
24//! ```rust
25//!
26//! use std::fs::File;
27//! // import Result typealias and interpreter struct
28//! use brainfuck_exe::{Result, Brainfuck};
29//!
30//! fn main() -> Result<()> {
31//! // brainfuck code to print "Hello, World!"
32//! let code = ">++++++++[<+++++++++>-]<.>++++[<+++++++>-]<+.+++++++..+++.>>++++++[<+++++++>-]<+
33//! +.------------.>++++++[<+++++++++>-]<+.<.+++.------.--------.>>>++++[<++++++++>-]<+.";
34//! // instantiate a new interpreter instance with the code
35//! Brainfuck::new(code)
36//! // optional builder method to write the output into a file not STDOUT
37//! .with_output(
38//! File::options()
39//! .write(true)
40//! .open("tests/output.txt")
41//! .unwrap()
42//! )
43//! // executes the code
44//! .execute()?;
45//!
46//! // alternatively use this to retrieve the code from an existing source file
47//! Brainfuck::from_file("tests/hello_world.bf")?
48//! .execute()?;
49//!
50//! Ok(())
51//! }
52//! ```
53//!
54//! ## CLI
55//! You can also use this crate as a CLI program
56//!
57//! ```bash
58//! # installation
59//! $ cargo install brainfuck-exe
60//! # usage
61//! $ brainfuck --help
62//! $ brainfuck [CODE] [-f FILE] [OPTIONS]
63//! ```
64
65use std::{
66 fs::File,
67 path::Path,
68 io::{Read, Write},
69 ops::{Deref, DerefMut},
70 time::{Instant, Duration},
71};
72pub use error::{Error, Result};
73
74pub mod error;
75
76/// default max value a cell can have
77///
78/// it is `255`, the same as [`std::u8::MAX`]
79pub const DEFAULT_MAX_CELL_VALUE: u32 = 255;
80
81
82/// a helper wrapper enum that is used for storing the input stream
83/// this allows for it to be passed by value OR reference
84pub enum Reader<'a> {
85 /// used when passing in the input stream by value
86 Value(Box<dyn Read>),
87 /// used when passing in the input stream as a mutable reference
88 Ref(&'a mut dyn Read),
89}
90
91/// a helper wrapper enum that is used for storing the output stream
92/// this allows for it to be passed by value OR reference
93pub enum Writer<'a> {
94 /// used when passing in the output stream by value
95 Value(Box<dyn Write>),
96 /// used when passing in the output stream as a mutable reference
97 Ref(&'a mut dyn Write),
98}
99
100impl<'a> Deref for Reader<'a> {
101 type Target = dyn Read + 'a;
102
103 fn deref(&self) -> &Self::Target {
104 match self {
105 Self::Value(v) => &**v,
106 Self::Ref(r) => &**r,
107 }
108 }
109}
110
111impl<'a> DerefMut for Reader<'a> {
112 fn deref_mut(&mut self) -> &mut Self::Target {
113 match self {
114 Self::Value(v) => &mut **v,
115 Self::Ref(r) => &mut **r,
116 }
117 }
118}
119
120impl<'a> Deref for Writer<'a> {
121 type Target = dyn Write + 'a;
122
123 fn deref(&self) -> &Self::Target {
124 match self {
125 Self::Value(v) => &**v,
126 Self::Ref(r) => &**r,
127 }
128 }
129}
130
131impl<'a> DerefMut for Writer<'a> {
132 fn deref_mut(&mut self) -> &mut Self::Target {
133 match self {
134 Self::Value(v) => &mut **v,
135 Self::Ref(r) => &mut **r,
136 }
137 }
138}
139
140/// struct containing various information regarding the program execution
141/// such as the final memory array and the final pointer index etc.
142#[derive(Debug, Clone)]
143pub struct ExecutionInfo {
144 /// the final memory array (cells) of the brainfuck program
145 pub cells: Vec<u32>,
146 /// the size of the final memory array of the brainfuck program
147 pub mem_size: usize,
148 /// the final pointer index
149 pub pointer: usize,
150 /// the length of the brainfuck code
151 pub code_len: usize,
152 /// the amount of instructions execute
153 ///
154 /// this also can be retrieved with `Brainfuck::instructions_count`
155 pub instructions: usize,
156 /// the time it took for the program execution as a [`Duration`]
157 ///
158 /// it is [`None`] if it was not specified in [`Brainfuck`] to `bench_execution`
159 pub time: Option<Duration>,
160}
161
162/// The struct representing a brainfuck interpreter instance
163pub struct Brainfuck<'a> {
164 /// the brainfuck source code to execute
165 pub code: String,
166 /// the input stream used for `,` operations
167 pub input: Option<Reader<'a>>,
168 /// the output stream used for `.` operations
169 pub output: Option<Writer<'a>>,
170 /// sets the maximum value of a cell, defaults to `255`
171 pub max_cell_value: u32,
172 /// sets the maximum length of the memory array
173 ///
174 /// defaults to [`None`], which is "infinite"
175 pub memory_size: Option<usize>,
176 /// indicates whether or not to manually flush the output buffer every write
177 ///
178 /// if set to `false` it will let the process automatically flush (end of program or at every newline),
179 /// defaults to `true`
180 pub flush_output: bool,
181 /// this field is only of use if the input stream used is [`std::io::stdin`]
182 ///
183 /// it specifies whether or not to retrieve all the input data needed in one prompt the first time
184 /// or rather prompt the user every time for a character,
185 /// defaults to `false`
186 pub prompt_stdin_once: bool,
187 /// sets the limit on the amount of instructions we can process in one program
188 ///
189 /// defaults to [`None`], which is *no* limit
190 /// (for safety and debugging usage)
191 pub instructions_limit: Option<usize>,
192 /// specifies whether or not to bench the execution
193 ///
194 /// useful for use cases in `WASM` where the system clock cannot be accessed,
195 /// defaults to `true`
196 pub bench_execution: bool,
197 /// an optional fallback [`char`] for the input operation
198 /// in instances of EOF (end of input) on the input stream
199 pub fallback_input: Option<char>,
200 /// an instructions counter to count the number of instructions executed thus far
201 instructions_ctn: usize,
202}
203
204impl<'a> Default for Brainfuck<'a> {
205 fn default() -> Self {
206 Self::new(String::new())
207 }
208}
209
210impl<'a> Brainfuck<'a> {
211 /// creates a new instance of a brainfuck interpeter with the provided `code`
212 ///
213 /// - input and output streams default to [`std::io::stdin`] and [`std::io::stdout`] respectively
214 /// - the maximum value a cell can have is `255` (8 bits / 1 byte)
215 /// - the program's memory array can grow indefinitely
216 #[must_use]
217 pub fn new<S: AsRef<str>>(code: S) -> Self {
218 Self {
219 code: code
220 .as_ref()
221 .to_string(),
222 input: None,
223 output: None,
224 max_cell_value: DEFAULT_MAX_CELL_VALUE,
225 memory_size: None,
226 flush_output: true,
227 prompt_stdin_once: false,
228 instructions_limit: None,
229 bench_execution: true,
230 fallback_input: None,
231 instructions_ctn: 0,
232 }
233 }
234
235 /// an alternative to `Self::new`,
236 /// used when the code is in a source file instead of being directly accessible as a string in the code
237 ///
238 /// # Errors
239 /// - [`Error::FileReadError`]: propogated from [`std::io::Error`]
240 /// when opening or reading the source file
241 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
242 let mut buf = String::new();
243 let mut file = File::open(path)
244 .map_err(Error::FileReadError)?;
245
246 file.read_to_string(&mut buf)
247 .map_err(Error::FileReadError)?;
248 Ok(Self::new(buf))
249 }
250
251 /// builder method to specify the brainfuck code for the interpreter
252 #[must_use]
253 pub fn with_code<S: AsRef<str>>(mut self, code: S) -> Self {
254 self.code = code
255 .as_ref()
256 .to_string();
257 self
258 }
259
260 /// builder method to specify the input stream **passing by value**, for the `,` operation
261 #[must_use]
262 pub fn with_input<I>(mut self, input: I) -> Self
263 where
264 I: Read + 'static
265 {
266 self.input = Some(
267 Reader::Value(Box::new(input))
268 );
269 self
270 }
271
272 /// builder method to specify the output stream **passing by value**, for the `.` operation
273 #[must_use]
274 pub fn with_output<O>(mut self, output: O) -> Self
275 where
276 O: Write + 'static
277 {
278 self.output = Some(
279 Writer::Value(Box::new(output))
280 );
281 self
282 }
283
284 /// builder method to specify the input stream **passing by reference**, for the `,` operation
285 #[must_use]
286 pub fn with_input_ref<I>(mut self, input: &'a mut I) -> Self
287 where
288 I: Read + 'static
289 {
290 self.input = Some(
291 Reader::Ref(input)
292 );
293 self
294 }
295
296 /// builder method to specify the output stream **passing by reference**, for the `.` operation
297 #[must_use]
298 pub fn with_output_ref<O>(mut self, output: &'a mut O) -> Self
299 where
300 O: Write + 'static
301 {
302 self.output = Some(
303 Writer::Ref(output)
304 );
305 self
306 }
307
308 /// builder method to specify the max value of a cell
309 #[must_use]
310 pub const fn with_max_value(mut self, cell_value: u32) -> Self {
311 self.max_cell_value = cell_value;
312 self
313 }
314
315 /// builder method to specify the maximum memory array length
316 #[must_use]
317 pub const fn with_mem_size(mut self, mem_size: usize) -> Self {
318 self.memory_size = Some(mem_size);
319 self
320 }
321
322 /// builder method to indicate whether or not to flush the output stream on every write
323 #[must_use]
324 pub const fn with_flush(mut self, flush: bool) -> Self {
325 self.flush_output = flush;
326 self
327 }
328
329 /// builder method to indicate whether or not to only prompt [`std::io::stdin`] once
330 #[must_use]
331 pub const fn prompt_stdin_once(mut self, once: bool) -> Self {
332 self.prompt_stdin_once = once;
333 self
334 }
335
336 /// builder method to set the maximum amount of instructions we can process in one program
337 #[must_use]
338 pub const fn with_instructions_limit(mut self, limit: usize) -> Self {
339 self.instructions_limit = Some(limit);
340 self
341 }
342
343 /// builder method to specify whether or not to bench the program execution
344 #[must_use]
345 pub const fn with_bench_execution(mut self, bench: bool) -> Self {
346 self.bench_execution = bench;
347 self
348 }
349 /// builder method to set a fallback [`char`] for instances of EOF on the input stream
350 #[must_use]
351 pub const fn with_fallback_input(mut self, fallback: char) -> Self {
352 self.fallback_input = Some(fallback);
353 self
354 }
355
356 /// a getter that returns the number of instructions executed thus far
357 #[must_use]
358 pub const fn instructions_count(&self) -> usize {
359 self.instructions_ctn
360 }
361
362 /// consumes itself and returns the input stream in an [`Option`]
363 #[must_use]
364 #[allow(clippy::missing_const_for_fn)]
365 pub fn into_input(self) -> Option<Reader<'a>> {
366 self.input
367 }
368
369 /// consumes itself and returns the output stream in an [`Option`]
370 #[must_use]
371 #[allow(clippy::missing_const_for_fn)]
372 pub fn into_output(self) -> Option<Writer<'a>> {
373 self.output
374 }
375
376 /// basic helper function to retrieve the fallback char for the input stream
377 #[inline]
378 fn get_fallback_char(&self) -> u32 {
379 self.fallback_input
380 .map_or(0, u32::from)
381 }
382
383 /// helper method to read from [`std::io::stdin`]
384 ///
385 /// it accomplishes such in one prompt, retrieving all the data at once
386 /// as a fallback to if no other input stream is specified for the `,` operation
387 #[must_use]
388 fn read_from_stdin_once(&self) -> u32 {
389 let mut buffer = [0];
390 match std::io::stdin()
391 .read_exact(&mut buffer[0..1])
392 {
393 Ok(_) => u32::from(buffer[0]),
394 Err(_) => self.get_fallback_char(),
395 }
396 }
397
398 /// helper method to read from [`std::io::stdin`]
399 ///
400 /// it prompts every time this function is called however
401 /// as a fallback to if no other input stream is specified for the `,` operation
402 #[must_use]
403 fn read_from_stdin(&self) -> u32 {
404 let mut buffer = String::new();
405 match std::io::stdin()
406 .read_line(&mut buffer)
407 {
408 Ok(_) => buffer
409 .chars()
410 .next()
411 .map_or_else(
412 || self.get_fallback_char(),
413 u32::from,
414 ),
415 Err(_) => self.get_fallback_char(),
416 }
417 }
418
419 /// executes the provided brainfuck code
420 /// which is stored in the struct field: `code`
421 ///
422 /// brainfuck supports 8 operations which are as following:
423 /// `+ - < > . , [ ]`
424 ///
425 /// different implementations vary on wraparound rules
426 ///
427 /// # Operations
428 /// - `+`: increments the current cell by `1`
429 /// if the value exceeds `self.max_cell_value`, it gets wrapped back to `0`
430 /// - `-`: decrements the current cell by `1`
431 /// if the value goes below `0`, it gets wrapped back to `self.max_cell_value`
432 /// - `>`: moves the pointer up 1 cell
433 /// if the the pointer exceeds `self.memory_size`, it gets wrapped back to `0`;
434 /// however, if `self.memory_size` is [`None`], it will grow the array by 1 additional cell
435 /// - `<`: moves the pointer down 1 cell
436 /// if the value goes below `0`, it gets wrapped back to the end of the memory array
437 /// - `.`: writes the value of the current cell as ASCII into the provided output stream, `self.output`
438 /// defaulting to [`std::io::stdout`]
439 /// - `,`: reads 1 byte from the provided input stream, `self.input`
440 /// defaulting to [`std::io::stdin`]
441 /// if reading fails (e.g. there were no bytes to read (EOF) or other error), the current cell gets set back to `0`
442 /// - `[`: always should be paired with a `]`, acts as a "loop" in brainfuck
443 /// the code that is enclosed within a pair of `[ ]` gets looped over until the current cell != 0
444 /// - `]`: the closing bracket for a loop, paired with `[`
445 /// if the current cell != 0, jump back to corresponding `[`
446 ///
447 /// returns [`ExecutionInfo`]: a struct containing various information on the program's execution
448 /// such as the used memory array, the final pointer, instructions count etc.
449 ///
450 /// # Errors
451 /// - [`Error::MismatchedBrackets`]: the amount of `[` in the code does not equal the amount of `]`
452 /// - [`Error::IoError`]: Propogated from [`std::io::Error`] in the `.` operation
453 ///
454 #[allow(clippy::too_many_lines)]
455 pub fn execute(&mut self) -> Result<ExecutionInfo> {
456 let (opening, closing) = (
457 self.code.chars()
458 .filter(|c| *c == '[')
459 .count(),
460 self.code.chars()
461 .filter(|c| *c == ']')
462 .count()
463 );
464
465 if opening != closing {
466 return Err(Error::MismatchedBrackets {
467 opening, closing
468 });
469 }
470
471 let mut cells =
472 self.memory_size
473 .map_or_else(
474 || vec![0],
475 |mem_size| vec![0; mem_size],
476 );
477
478 self.instructions_ctn = 0;
479 let mut code_idx = 0usize;
480 let mut ptr = 0usize;
481 let time = self.bench_execution
482 .then(Instant::now);
483
484 while code_idx < self.code
485 .chars()
486 .count()
487 {
488 let mut incr_inst = true;
489
490 match self.code
491 .chars()
492 .nth(code_idx)
493 {
494 Some('+') =>
495 if cells[ptr] >= self.max_cell_value {
496 cells[ptr] = 0;
497 } else {
498 cells[ptr] += 1;
499 },
500 Some('-') =>
501 if cells[ptr] == 0 {
502 cells[ptr] = self.max_cell_value;
503 } else {
504 cells[ptr] -= 1;
505 },
506 Some('<') =>
507 if ptr == 0 {
508 ptr = cells.len() - 1;
509 } else {
510 ptr -= 1;
511 },
512 Some('>') => {
513 ptr += 1;
514 if let Some(mem_size) = self.memory_size {
515 if ptr >= mem_size {
516 ptr = 0;
517 }
518 } else if ptr >= cells.len() {
519 cells.push(0);
520 }
521 },
522 Some('.') =>
523 if let Some(chr) =
524 std::char::from_u32(cells[ptr])
525 {
526 if let Some(ref mut writer) =
527 self.output
528 {
529 let mut buf = vec![0; chr.len_utf8()];
530 chr.encode_utf8(&mut buf);
531
532 writer.write_all(&buf)?;
533 if self.flush_output {
534 writer.flush()?;
535 }
536 } else {
537 print!("{chr}");
538 if self.flush_output {
539 std::io::stdout()
540 .flush()?;
541 }
542 }
543 },
544 #[allow(clippy::option_if_let_else)]
545 Some(',') =>
546 cells[ptr] = if let Some(ref mut reader) =
547 self.input
548 {
549 let mut buffer = [0];
550 match reader
551 .read_exact(&mut buffer[0..1])
552 {
553 Ok(_) => u32::from(buffer[0]),
554 Err(_) => self.get_fallback_char(),
555 }
556 } else if self.prompt_stdin_once {
557 self.read_from_stdin_once()
558 } else {
559 self.read_from_stdin()
560 },
561 Some('[') =>
562 if cells[ptr] == 0 {
563 let mut loop_ = 1;
564 while loop_ > 0 {
565 code_idx += 1;
566 match self.code
567 .chars()
568 .nth(code_idx)
569 {
570 Some('[') => loop_ += 1,
571 Some(']') => loop_ -= 1,
572 _ => (),
573 }
574 }
575 },
576 Some(']') => {
577 let mut loop_ = 1;
578 while loop_ > 0 {
579 code_idx -= 1;
580 match self.code
581 .chars()
582 .nth(code_idx)
583 {
584 Some('[') => loop_ -= 1,
585 Some(']') => loop_ += 1,
586 _ => (),
587 }
588 }
589 code_idx -= 1;
590 },
591 _ => incr_inst = false,
592 }
593 code_idx += 1;
594
595 if incr_inst {
596 self.instructions_ctn += 1;
597 }
598
599 if let Some(cap) = self.instructions_limit {
600 if self.instructions_ctn > cap {
601 return Err(Error::MaxInstructionsExceeded(cap));
602 }
603 }
604 }
605 let mem_size = cells.len();
606
607 Ok(ExecutionInfo {
608 cells,
609 mem_size,
610 pointer: ptr,
611 code_len: code_idx,
612 instructions: self.instructions_count(),
613 time: time
614 .map(|t| t.elapsed()),
615 })
616 }
617}