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}