brainfoamkit_lib/
machine_builder.rs

1// SPDX-FileCopyrightText: 2023 - 2024 Ali Sajid Imami
2//
3// SPDX-License-Identifier: Apache-2.0
4// SPDX-License-Identifier: MIT
5
6use anyhow::Result;
7
8use crate::{
9    vm_reader::VMReader,
10    Program,
11    VirtualMachine,
12};
13
14/// `VirtualMachineBuilder` is a builder for the `VirtualMachine` struct.
15///
16/// This builder allows you to set the `program` and `tape_size` for a
17/// `VirtualMachine` before building it. Both `program` and `tape_size` are
18/// optional. If they're not provided, the `VirtualMachine` will be initialized
19/// with default values.
20///
21/// # Examples
22///
23/// ```
24/// use brainfoamkit_lib::{
25///     Program,
26///     VMReader,
27///     VirtualMachineBuilder,
28/// };
29/// let program = Program::default();
30/// let input_device = std::io::stdin();
31/// let vm = VirtualMachineBuilder::new()
32///     .program(program)
33///     .tape_size(1024)
34///     .input_device(input_device)
35///     .build()
36///     .unwrap();
37/// ```
38#[derive(Default)]
39#[allow(clippy::module_name_repetitions)]
40pub struct VirtualMachineBuilder<R>
41where
42    R: VMReader,
43{
44    /// The program that the `VirtualMachine` will execute. If not provided,
45    /// the `VirtualMachine` will be initialized with a default program.
46    program: Option<Program>,
47
48    /// The size of the tape for the `VirtualMachine`. If not provided,
49    /// the `VirtualMachine` will be initialized with a default tape size.
50    tape_size: Option<usize>,
51
52    /// The input device for the `VirtualMachine`. If not provided,
53    /// the `VirtualMachine` will be initialized with a STDIN as the input
54    /// device.
55    input_device: Option<R>,
56}
57
58impl<R> VirtualMachineBuilder<R>
59where
60    R: VMReader,
61{
62    /// Creates a new `VirtualMachineBuilder` with empty values.
63    ///
64    /// This function returns a new `VirtualMachineBuilder` with `program` and
65    /// `tape_size` set to `None`. These values can be set later using the
66    /// builder's methods.
67    ///
68    /// # Returns
69    ///
70    /// A new `VirtualMachineBuilder` struct with empty values.
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use std::io::Stdin;
76    ///
77    /// use brainfoamkit_lib::{
78    ///     VMReader,
79    ///     VirtualMachineBuilder,
80    /// };
81    ///
82    /// let builder = VirtualMachineBuilder::<Stdin>::new();
83    /// ```
84    #[must_use]
85    pub const fn new() -> Self {
86        Self {
87            program:      None,
88            tape_size:    None,
89            input_device: None,
90        }
91    }
92
93    /// Set the program to be run by the virtual machine.
94    ///
95    /// # Arguments
96    ///
97    /// * `program` - The program to be run by the virtual machine.
98    ///
99    /// # Returns
100    ///
101    /// * Builder by value with the program set.
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use brainfoamkit_lib::{
107    ///     Program,
108    ///     VMReader,
109    ///     VirtualMachineBuilder,
110    /// };
111    ///
112    /// let input_device = std::io::stdin();
113    /// let program = Program::from("++++++[>++++++++++<-]>+++++.");
114    /// let vm = VirtualMachineBuilder::new()
115    ///     .input_device(input_device)
116    ///     .program(program)
117    ///     .build()
118    ///     .unwrap();
119    ///
120    /// assert_eq!(vm.program(), Program::from("++++++[>++++++++++<-]>+++++."));
121    /// ```
122    #[must_use]
123    pub fn program(mut self, program: Program) -> Self {
124        self.program = Some(program);
125        self
126    }
127
128    /// Set the size of the tape to be used by the virtual machine.
129    /// The default size is 30,000.
130    ///
131    /// # Arguments
132    ///
133    /// * `tape_size` - The size of the tape to be used by the virtual machine.
134    ///
135    /// # Returns
136    ///
137    /// * Builder by value with the tape size set.
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// use brainfoamkit_lib::{
143    ///     VMReader,
144    ///     VirtualMachineBuilder,
145    /// };
146    ///
147    /// let input_device = std::io::stdin();
148    /// let vm = VirtualMachineBuilder::new()
149    ///     .input_device(input_device)
150    ///     .tape_size(100)
151    ///     .build()
152    ///     .unwrap();
153    ///
154    /// assert_eq!(vm.length(), 100);
155    /// ```
156    #[must_use]
157    pub const fn tape_size(mut self, tape_size: usize) -> Self {
158        self.tape_size = Some(tape_size);
159        self
160    }
161
162    /// Set the input device to be used by the virtual machine.
163    ///
164    /// The default input device is `stdin`.
165    ///
166    /// # Arguments
167    ///
168    /// * `input_device` - The input device to be used by the virtual machine.
169    ///
170    /// # Returns
171    ///
172    /// * Builder by value with the input device set.
173    ///
174    /// # Examples
175    ///
176    /// ```
177    /// use brainfoamkit_lib::{
178    ///     VMReader,
179    ///     VirtualMachineBuilder,
180    /// };
181    ///
182    /// let input_device = std::io::stdin();
183    ///
184    /// let mut vm = VirtualMachineBuilder::new()
185    ///     .input_device(input_device)
186    ///     .build()
187    ///     .unwrap();
188    ///
189    /// assert_eq!(
190    ///     vm.input_device().get_vmreader_type(),
191    ///     brainfoamkit_lib::VMReaderType::Stdin
192    /// );
193    /// ```
194    #[must_use]
195    pub fn input_device(mut self, input_device: R) -> Self {
196        self.input_device = Some(input_device);
197        self
198    }
199
200    /// Build the virtual machine.
201    ///
202    /// # Returns
203    ///
204    /// * A `Result` containing either a `VirtualMachine` or an `Error`.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// use brainfoamkit_lib::{
210    ///     Program,
211    ///     VMReader,
212    ///     VirtualMachineBuilder,
213    /// };
214    ///
215    /// let input_device = std::io::stdin();
216    /// let program = Program::from("++++++[>++++++++++<-]>+++++.");
217    /// let vm = VirtualMachineBuilder::new()
218    ///     .program(program)
219    ///     .tape_size(100)
220    ///     .input_device(input_device)
221    ///     .build();
222    /// ```
223    ///
224    /// # Errors
225    ///
226    /// * If the input device is not set, this function will return an error.
227    pub fn build(self) -> Result<VirtualMachine<R>> {
228        let program = self.program.unwrap_or_default();
229        let tape_size = self.tape_size.unwrap_or(30000);
230        let Some(input_device) = self.input_device else {
231            return Err(anyhow::anyhow!("Input device not set."));
232        };
233
234        Ok(VirtualMachine::new(tape_size, program, 0, 0, input_device))
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241    use crate::vm_reader::{
242        MockReader,
243        VMReaderType,
244    };
245
246    #[test]
247    fn test_program() {
248        let program = Program::from("++++++[>++++++++++<-]>+++++.");
249        let input_device = MockReader {
250            data: std::io::Cursor::new("A".as_bytes().to_vec()),
251        };
252        let vm = VirtualMachine::builder()
253            .input_device(input_device)
254            .program(program)
255            .build()
256            .unwrap();
257        assert_eq!(vm.program(), Program::from("++++++[>++++++++++<-]>+++++."));
258    }
259
260    #[test]
261    fn test_tape_size() {
262        let input_device = MockReader {
263            data: std::io::Cursor::new("A".as_bytes().to_vec()),
264        };
265        let vm = VirtualMachine::builder()
266            .input_device(input_device)
267            .tape_size(100)
268            .build()
269            .unwrap();
270        assert_eq!(vm.tape_size(), 100);
271    }
272
273    #[test]
274    fn test_input_device() {
275        let input_device = MockReader {
276            data: std::io::Cursor::new("A".as_bytes().to_vec()),
277        };
278        let mut vm = VirtualMachine::builder()
279            .input_device(input_device)
280            .build()
281            .unwrap();
282        assert_eq!(vm.input_device().get_vmreader_type(), VMReaderType::Mock);
283    }
284
285    #[test]
286    fn test_build() {
287        let program = Program::from("++++++[>++++++++++<-]>+++++.");
288        let input_device = MockReader {
289            data: std::io::Cursor::new("A".as_bytes().to_vec()),
290        };
291        let vm = VirtualMachine::builder()
292            .input_device(input_device)
293            .tape_size(100)
294            .program(program)
295            .build()
296            .unwrap();
297        assert_eq!(vm.program(), Program::from("++++++[>++++++++++<-]>+++++."));
298        assert_eq!(vm.tape_size(), 100);
299    }
300
301    #[test]
302    fn test_default() {
303        let input_device = MockReader {
304            data: std::io::Cursor::new("A".as_bytes().to_vec()),
305        };
306        let vm = VirtualMachine::builder()
307            .input_device(input_device)
308            .build()
309            .unwrap();
310        assert_eq!(vm.program(), Program::default());
311        assert_eq!(vm.tape_size(), 30000);
312    }
313}