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}