brainfoamkit_lib/vm_reader.rs
1// SPDX-FileCopyrightText: 2023 - 2024 Ali Sajid Imami
2//
3// SPDX-License-Identifier: Apache-2.0
4// SPDX-License-Identifier: MIT
5
6use std::{
7 fs::File,
8 io::{
9 Cursor,
10 Read,
11 Stdin,
12 },
13};
14
15use anyhow::{
16 anyhow,
17 Result,
18};
19
20/// Allowable types of `VMReader`
21///
22/// This enum is used to determine the type of `VMReader` that is being used.
23///
24/// The currently supported types are:
25///
26/// * Stdin - The standard input device as implemented by the [std::io::Stdin struct](https://doc.rust-lang.org/std/io/struct.Stdin.html)
27/// * File - A file as implemented by the [std::fs::File struct](https://doc.rust-lang.org/std/fs/struct.File.html)
28/// * Mock - A mock reader as implemented by the [`MockReader`
29/// struct](struct.MockReader.html)
30/// * Unknown - The default type of `VMReader`
31///
32/// # Examples
33///
34/// ```
35/// use brainfoamkit_lib::{
36/// VMReader,
37/// VMReaderType,
38/// };
39/// use tempfile::NamedTempFile;
40///
41/// let stdin = std::io::stdin();
42/// let temp_file = NamedTempFile::new().unwrap();
43/// let file = temp_file.reopen().unwrap();
44/// let mock = brainfoamkit_lib::MockReader {
45/// data: std::io::Cursor::new("A".as_bytes().to_vec()),
46/// };
47///
48/// assert_eq!(stdin.get_vmreader_type(), VMReaderType::Stdin);
49/// assert_eq!(file.get_vmreader_type(), VMReaderType::File);
50/// assert_eq!(mock.get_vmreader_type(), VMReaderType::Mock);
51/// ```
52///
53/// # See Also
54///
55/// * [`VMReader`](trait.VMReader.html)
56/// * [`MockReader`](struct.MockReader.html)
57/// * [Stdin](https://doc.rust-lang.org/std/io/struct.Stdin.html)
58/// * [File](https://doc.rust-lang.org/std/fs/struct.File.html)
59#[derive(Debug, PartialEq, Eq)]
60pub enum VMReaderType {
61 /// The standard input device as implemented by the [std::io::Stdin struct](https://doc.rust-lang.org/std/io/struct.Stdin.html)
62 Stdin,
63 /// A file as implemented by the [std::fs::File struct](https://doc.rust-lang.org/std/fs/struct.File.html)
64 File,
65 /// A mock reader as implemented by the [`MockReader`
66 /// struct](struct.MockReader.html)
67 Mock,
68 /// The default type of `VMReader`
69 Unknown,
70}
71/// The `VMReader` trait
72///
73/// This trait is used to implement a `Reader` for the `VirtualMachine`. This
74/// allows us to abstract over several different types of `Reader`s, including
75/// `StdIn` and `File`. This trait is also implemented for the `MockReader`
76/// struct, which is used for testing.
77///
78/// This is a restricted trait, meaning that it will only be implemented for
79/// specific types. This is done to ensure that the `VMReader` is only
80/// implemented for types that are valid for the `VirtualMachine`. The valid
81/// types for `VMReader` are listed in the
82/// [`VMReaderType`](enum.VMReaderType.html) enum.
83///
84/// # Examples
85///
86/// ```
87/// use brainfoamkit_lib::{
88/// VMReader,
89/// VMReaderType,
90/// };
91/// use tempfile::NamedTempFile;
92///
93/// let stdin = std::io::stdin();
94/// let temp_file = NamedTempFile::new().unwrap();
95/// let file = temp_file.reopen().unwrap();
96/// let mock = brainfoamkit_lib::MockReader {
97/// data: std::io::Cursor::new("A".as_bytes().to_vec()),
98/// };
99///
100/// assert_eq!(
101/// stdin.get_vmreader_type(),
102/// brainfoamkit_lib::VMReaderType::Stdin
103/// );
104/// assert_eq!(
105/// file.get_vmreader_type(),
106/// brainfoamkit_lib::VMReaderType::File
107/// );
108/// assert_eq!(
109/// mock.get_vmreader_type(),
110/// brainfoamkit_lib::VMReaderType::Mock
111/// );
112/// ```
113///
114/// # See Also
115///
116/// * [`VMReaderType`](enum.VMReaderType.html)
117/// * [`MockReader`](struct.MockReader.html)
118/// * [Stdin](https://doc.rust-lang.org/std/io/struct.Stdin.html)
119/// * [File](https://doc.rust-lang.org/std/fs/struct.File.html)
120pub trait VMReader {
121 /// Read a single byte from the reader
122 ///
123 /// This function reads a single byte from the reader and returns it as a
124 /// `u8` for use by the `VirtualMachine`.
125 ///
126 /// # Errors
127 ///
128 /// This function will return an error if the byte read from the reader is
129 /// not within the ASCII range.
130 fn read(&mut self) -> Result<u8> {
131 Ok(0)
132 }
133
134 /// Get the type of the reader
135 ///
136 /// This function returns the type of the reader as a `VMReaderType` enum.
137 ///
138 /// The currently supported types are:
139 ///
140 /// * Stdin - The standard input device as implemented by the [std::io::Stdin struct](https://doc.rust-lang.org/std/io/struct.Stdin.html)
141 /// * File - A file as implemented by the [std::fs::File struct](https://doc.rust-lang.org/std/fs/struct.File.html)
142 /// * Mock - A mock reader as implemented by the [`MockReader`
143 /// struct](struct.MockReader.html)
144 /// * Unknown - The default type of `VMReader`
145 ///
146 /// The default type of `VMReader` is `Unknown`, and is used when the type
147 /// of the reader is not set.
148 fn get_vmreader_type(&self) -> VMReaderType {
149 VMReaderType::Unknown
150 }
151}
152
153/// The `MockReader` struct
154///
155/// This struct is used to implement a mock `Reader` for the `VirtualMachine`.
156/// This allows for us to test the `VirtualMachine` without having to use
157/// `Stdin` or `File` as the `Reader`.
158///
159/// This struct is used for testing purposes only, and should not be used in
160/// production code.
161///
162/// # Examples
163///
164/// ```
165/// use brainfoamkit_lib::VMReader;
166///
167/// let mut mock = brainfoamkit_lib::MockReader {
168/// data: std::io::Cursor::new("A".as_bytes().to_vec()),
169/// };
170///
171/// assert_eq!(mock.read().unwrap(), 65);
172/// ```
173///
174/// # See Also
175///
176/// * [`VMReader`](trait.VMReader.html)
177/// * [`VMReaderType`](enum.VMReaderType.html)
178/// * [Stdin](https://doc.rust-lang.org/std/io/struct.Stdin.html)
179/// * [File](https://doc.rust-lang.org/std/fs/struct.File.html)
180#[derive(Debug, Default)]
181pub struct MockReader {
182 pub data: Cursor<Vec<u8>>,
183}
184
185/// The implementation of the `VMReader` trait for the `MockReader` struct
186impl VMReader for MockReader {
187 /// Read a single byte from the mock reader
188 ///
189 /// This function reads a single byte from the mock reader and returns it as
190 /// a `u8` for use by the `VirtualMachine`. The mock reader is
191 /// implemented using a `Cursor<Vec<u8>>`, which is used to store the data
192 /// that is read from the mock reader.
193 ///
194 /// # Errors
195 ///
196 /// This function will return an error if the byte read from the mock reader
197 /// is not within the ASCII range.
198 ///
199 /// # Examples
200 ///
201 /// ```
202 /// use brainfoamkit_lib::VMReader;
203 ///
204 /// let mut mock = brainfoamkit_lib::MockReader {
205 /// data: std::io::Cursor::new("A".as_bytes().to_vec()),
206 /// };
207 ///
208 /// assert_eq!(mock.read().unwrap(), 65);
209 /// assert_eq!(
210 /// mock.get_vmreader_type(),
211 /// brainfoamkit_lib::VMReaderType::Mock
212 /// );
213 /// ```
214 fn read(&mut self) -> Result<u8> {
215 let mut buffer = [0u8; 1];
216 self.data.read_exact(&mut buffer)?;
217
218 if buffer[0] <= 128 {
219 Ok(buffer[0])
220 } else {
221 Err(anyhow!("Byte is not within the ASCII range"))
222 }
223 }
224
225 fn get_vmreader_type(&self) -> VMReaderType {
226 VMReaderType::Mock
227 }
228}
229
230/// The implementation of the `VMReader` trait for the `Stdin` struct
231impl VMReader for Stdin {
232 /// Read a single byte from STDIN
233 ///
234 /// This function reads a single byte from STDIN and returns it as a `u8`
235 /// for use by the `VirtualMachine`.
236 ///
237 /// # Errors
238 ///
239 /// This function will return an error if the byte read from STDIN is not
240 /// within the ASCII range.
241 fn read(&mut self) -> Result<u8> {
242 let mut buffer = [0u8; 1];
243 self.read_exact(&mut buffer)?;
244
245 if buffer[0] <= 128 {
246 Ok(buffer[0])
247 } else {
248 Err(anyhow!("Byte is not within the ASCII range"))
249 }
250 }
251
252 fn get_vmreader_type(&self) -> VMReaderType {
253 VMReaderType::Stdin
254 }
255}
256
257/// The implementation of the `VMReader` trait for the `File` struct
258impl VMReader for File {
259 /// Read a single byte from a file
260 ///
261 /// This function reads a single byte from a file and returns it as a `u8`
262 /// for use by the `VirtualMachine`.
263 ///
264 /// # Errors
265 ///
266 /// This function will return an error if the byte read from the file is not
267 /// within the ASCII range.
268 fn read(&mut self) -> Result<u8> {
269 let mut buffer = [0u8; 1];
270 self.read_exact(&mut buffer)?;
271
272 if buffer[0] <= 128 {
273 Ok(buffer[0])
274 } else {
275 Err(anyhow!("Byte is not within the ASCII range"))
276 }
277 }
278
279 fn get_vmreader_type(&self) -> VMReaderType {
280 VMReaderType::File
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use std::io::{
287 Cursor,
288 Write,
289 };
290
291 use tempfile::NamedTempFile;
292
293 use super::*;
294
295 struct DefaultReader;
296
297 impl VMReader for DefaultReader {}
298
299 #[test]
300 fn test_default_trait() {
301 let mut reader = DefaultReader;
302 let read_value = reader.read().unwrap();
303 assert_eq!(read_value, 0);
304 assert_eq!(reader.get_vmreader_type(), VMReaderType::Unknown);
305 }
306
307 #[test]
308 fn test_read_from_stdin() {
309 let mut stdin = Cursor::new("A".as_bytes());
310 let mut buffer = [0u8; 1];
311 stdin.read_exact(&mut buffer).unwrap();
312 assert_eq!(buffer[0], 65);
313 }
314
315 #[test]
316 fn test_read_from_file() {
317 let mut temp_file = NamedTempFile::new().unwrap();
318 temp_file.write_all("A".as_bytes()).unwrap();
319
320 let mut file = temp_file.reopen().unwrap();
321 let read_value = VMReader::read(&mut file).unwrap();
322 assert_eq!(read_value, 65);
323
324 temp_file.close().unwrap();
325 }
326
327 #[test]
328 fn test_read_from_mock() {
329 let mut mock = MockReader {
330 data: Cursor::new("A".as_bytes().to_vec()),
331 };
332 let read_value = mock.read().unwrap();
333 assert_eq!(read_value, 65);
334 }
335
336 #[test]
337 fn test_get_vmreader_type() {
338 let stdin = std::io::stdin();
339 let temp_file = NamedTempFile::new().unwrap();
340 let file = temp_file.reopen().unwrap();
341 let mock = MockReader {
342 data: Cursor::new("A".as_bytes().to_vec()),
343 };
344 let default = DefaultReader;
345
346 assert_eq!(stdin.get_vmreader_type(), VMReaderType::Stdin);
347 assert_eq!(file.get_vmreader_type(), VMReaderType::File);
348 assert_eq!(mock.get_vmreader_type(), VMReaderType::Mock);
349 assert_eq!(default.get_vmreader_type(), VMReaderType::Unknown);
350
351 temp_file.close().unwrap();
352 }
353}