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;