bf_lib/
lib.rs

1//! # bf-lib
2//!
3//! `bf-lib` is small library to run brainfuck programs non-interactively
4//!
5//! The entry point is the [`Exec`] struct, stole the idea from [`subprocess`]
6//!
7//! [`subprocess`]: https://crates.io/crates/subprocess
8
9use std::{error, fmt, time, path::PathBuf};
10
11/// Possible errors encountered while running the program.
12#[derive(Debug)]
13pub enum Error {
14    Compile(String),
15    Runtime(RuntimeError),
16    Subprocess(subprocess::PopenError),
17    Syntax(usize),
18    Timeout,
19}
20
21impl error::Error for Error {}
22
23impl fmt::Display for Error {
24    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25        let pre = "Error, I didn't quite get that.\n";
26        match self {
27            Error::Compile(s) => write!(f, "{}rustc error: {}", pre, s),
28            Error::Runtime(e) => write!(f, "{}Runtime error: {}", pre, e),
29            Error::Subprocess(e) => write!(f, "{}rustc error: {}", pre, e),
30            Error::Syntax(p) => write!(f, "{}Unmatched bracket at {}.", pre, p),
31            Error::Timeout => write!(f, "{}Executable timed out.", pre),
32        }
33    }
34}
35
36/// Possible runtime errors encountered while running the program.
37#[derive(Debug, PartialEq)]
38pub enum RuntimeError {
39    OutOfMemoryBounds,
40    InputTooShort,
41    Signal,
42}
43
44impl fmt::Display for RuntimeError {
45    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46        match self {
47            RuntimeError::OutOfMemoryBounds => write!(f, "access memory out of bounds"),
48            RuntimeError::InputTooShort => write!(f, "input was not long enough"),
49            RuntimeError::Signal => write!(f, "executable was probably killed by a signal"),
50        }
51    }
52}
53
54/// Interface for running brainfuck code.
55///
56/// The [`prog`] method returns an instance with the default options (no timeout, input or
57/// temporary file path)
58///
59/// [`input`], [`timeout`] and [`tmpdir`] are used to change the default values, the program can
60/// then be run by calling [`run`], [`transpile`] or [`interpret`].
61///
62/// [`prog`]: struct.Exec.html#method.prog
63/// [`input`]: struct.Exec.html#method.input
64/// [`timeout`]: struct.Exec.html#method.timeout
65/// [`tmpdir`]: struct.Exec.html#method.tmpdir
66/// [`run`]: struct.Exec.html#method.run
67/// [`transpile`]: struct.Exec.html#method.transpile
68/// [`interpret`]: struct.Exec.html#method.interpret
69/// ```
70/// # use bf_lib::Exec;
71/// let prog = "++++++++++[>++++++++++>+++++++++++<<-]>++.>+..";
72/// let output = Exec::prog(prog).run().unwrap();
73///
74/// assert_eq!(String::from("foo"), output);
75/// ```
76pub struct Exec {
77    program: String,
78    input: Option<String>,
79    time: Option<time::Duration>,
80    tmp_path: Option<PathBuf>,
81}
82
83impl Exec {
84    /// Contructs a new `Exec`, configured to run `prog`.
85    /// By default it will be run without input, timeout or temporary file path (defaults to cwd).
86    pub fn prog(prog: &str) -> Exec {
87        Exec {
88            program: String::from(prog),
89            input: None,
90            time: None,
91            tmp_path: None,
92        }
93    }
94
95    /// Sets the input for the program.
96    pub fn input(self, input: Option<String>) -> Exec {
97        Exec {
98            input,
99            ..self
100        }
101    }
102
103    /// Sets the timeout for the program.
104    pub fn timeout(self, time: Option<time::Duration>) -> Exec {
105        Exec {
106            time,
107            ..self
108        }
109    }
110
111    /// Sets the temporary file path for the transpiler.
112    pub fn tmpdir(self, tmp_path: Option<PathBuf>) -> Exec {
113        Exec {
114            tmp_path,
115            ..self
116        }
117    }
118    
119    /// Wrapper for the [`transpile`] and [`interpret`] methods:
120    /// uses the faster transpiler when rustc is detected, falls back to interpreting the code.
121    ///
122    /// [`transpile`]: struct.Exec.html#method.interpret
123    /// [`interpret`]: struct.Exec.html#method.transpile
124    pub fn run(self) -> Result<String, Error> {
125        bf::run(&self.program, self.input, self.time, self.tmp_path)
126    }
127    
128    /// Runs the program with the interpreter, returning the output or an [`Error`].
129    pub fn interpret(self) -> Result<String, Error> {
130        bf::interpreter::run(&self.program, self.input, self.time)
131    }
132    
133    /// Runs the program with the transpiler, returning the output or an [`Error`].
134    ///
135    /// Needs read and write permission in the chosen temporary file folder.
136    pub fn transpile(self) -> Result<String, Error> {
137        bf::transpiler::run(&self.program, self.input, self.time, self.tmp_path)
138    }
139
140    /// Translated the program to rust code
141    pub fn translate(&self) -> Result<String, Error> {
142        bf::transpiler::translate(&self.program, self.input.clone())
143    }
144}
145
146/// Looks for unmatched brackets
147///
148/// ```
149/// let ok = "+[+]";
150/// let err = "[[]";
151/// bf_lib::check_brackets(ok).unwrap();
152/// bf_lib::check_brackets(err).unwrap_err();
153/// ```
154pub fn check_brackets(prog: &str) -> Result<(), Error> {
155    let mut open: Vec<usize> = Vec::new();
156    for (i, b) in prog.as_bytes().iter().enumerate() {
157        match b {
158            b'[' => open.push(i),
159            b']' => {
160                if let None = open.pop() {
161                    return Err(Error::Syntax(i));
162                };
163            }
164            _ => (),
165        }
166    }
167    if open.len() != 0 {
168        Err(Error::Syntax(open.pop().unwrap()))
169    } else {
170        Ok(())
171    }
172}
173
174/// Checks if the program will try to read user input.
175///
176/// ```
177/// let reads = ",[>+>+<<-]>.>.";
178/// let does_not_read = "foo. bar.";
179///
180/// assert_eq!(true, bf_lib::wants_input(reads));
181/// assert_eq!(false, bf_lib::wants_input(does_not_read));
182/// ```
183pub fn wants_input(program: &str) -> bool {
184    program.contains(",")
185}
186
187mod bf;
188
189#[cfg(test)]
190mod tests;