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}