vtashkov_bf/
lib.rs

1//! Brainfuck interpreter
2//!
3//! # Examples
4//!
5//! This will output "Hello World!\n" in the output vector:
6//!
7//! ```
8//! use std::io::Cursor;
9//! use std::str;
10
11//! use vtashkov_bf::Interpreter;
12
13//! let source_code = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.";
14//! let mut input = Cursor::new(vec![]);
15//! let mut output = vec![];
16//! let mut interpreter = Interpreter::new(&mut input, &mut output, 30000);
17//! interpreter.execute(&source_code);
18//! assert_eq!("Hello World!\n", str::from_utf8(output.as_slice()).unwrap());
19//! ```
20//!
21
22/// The Brainfuck interpreter
23mod interpreter;
24
25/// Memory cells for the interpreter (memory tape)
26mod memory;
27
28use std::{
29    fs,
30    io::{self, Read, Write},
31};
32
33use clap::Parser;
34
35// re-exports
36pub use interpreter::Interpreter;
37
38/// Command-line arguments for the interpreter
39/// input_file - the path to the file to be interpreted
40/// memory_size - the number of the cells in the memory, defaults to 30 000
41#[derive(Parser, Debug)]
42#[command(author, version, about)]
43pub struct Args {
44    /// Path to the file to be interpreted
45    #[arg()]
46    input_file: String,
47
48    /// Number of the cells in the memory, defaults to 30 000
49    #[arg(short, long, default_value_t = 30000)]
50    memory_size: usize,
51}
52
53/// Runs the interpreter using the arguments passed - file to read the source from and memory size
54pub fn run_cmd(args: Args, input: &mut impl Read, output: &mut impl Write) -> Result<(), String> {
55    let source_code = read_file_contents(&args.input_file)?;
56    let mut interpreter = Interpreter::new(input, output, args.memory_size);
57    interpreter.execute(&source_code);
58    Ok(())
59}
60
61fn read_file_contents(input_file_path: &str) -> Result<String, String> {
62    fs::read_to_string(input_file_path).map_err(|error| match error.kind() {
63        io::ErrorKind::NotFound => format!("no such file: '{input_file_path}'"),
64        _ => error.to_string(),
65    })
66}
67
68#[cfg(test)]
69mod tests {
70    use io::Cursor;
71    use std::str;
72
73    use super::*;
74
75    #[test]
76    fn run_cmd_can_be_invoked() {
77        let args = Args {
78            input_file: String::from(""),
79            memory_size: 1,
80        };
81        let mut input = Cursor::new(vec![]);
82        let mut output = vec![];
83        let _ = run_cmd(args, &mut input, &mut output);
84    }
85
86    #[test]
87    fn run_cmd_with_wrong_input_file_returns_no_such_file() {
88        let invalid_file_name = "./examples/invalid.bf";
89        let args = Args {
90            input_file: String::from(invalid_file_name),
91            memory_size: 1,
92        };
93        let mut input = Cursor::new(vec![]);
94        let mut output = vec![];
95        let result = run_cmd(args, &mut input, &mut output);
96        assert!(result.is_err());
97        let expected_error_message = format!("no such file: '{}'", invalid_file_name);
98        assert_eq!(expected_error_message, result.err().unwrap())
99    }
100
101    #[test]
102    fn run_cmd_can_execute_hello_world() {
103        let args = Args {
104            input_file: String::from("./examples/hello_world.bf"),
105            memory_size: 30000,
106        };
107        let mut input = Cursor::new(vec![]);
108        let mut output = vec![];
109        let result = run_cmd(args, &mut input, &mut output);
110        assert!(result.is_ok());
111        assert_eq!("Hello World!\n", str::from_utf8(output.as_slice()).unwrap());
112    }
113}