brainfoamkit_lib/
machine.rs

1// SPDX-FileCopyrightText: 2023 - 2024 Ali Sajid Imami
2//
3// SPDX-License-Identifier: Apache-2.0
4// SPDX-License-Identifier: MIT
5
6use crate::{
7    vm_reader::VMReader,
8    Byte,
9    Instruction,
10    Program,
11    VirtualMachineBuilder,
12};
13
14/// `VirtualMachine` is a struct representing a Virtual Machine capable of
15/// interpreting a `BrainFuck` program and tracking its state.
16///
17/// # Fields
18///
19/// * `tape`: A vector of `Byte` values representing the memory of the machine.
20///   Each `Byte` in the vector is a cell in the memory tape.
21/// * `program`: A `Program` instance representing the Brainfuck program that
22///   the machine is executing.
23/// * `memory_pointer`: A `usize` value representing the current position of the
24///   memory pointer. The memory pointer points to a given cell in the memory
25///   tape.
26/// * `program_counter`: A `usize` that represents which instruction of the
27///   `Program` is being executed right now.
28///
29/// # Example
30///
31/// ```
32/// use brainfoamkit_lib::{
33///     VMReader,
34///     VirtualMachine,
35/// };
36///
37/// let input_device = std::io::stdin();
38/// let machine = VirtualMachine::builder().input_device(input_device).build();
39/// ```
40#[allow(clippy::module_name_repetitions)]
41pub struct VirtualMachine<R>
42where
43    R: VMReader,
44{
45    tape:            Vec<Byte>,
46    program:         Program,
47    memory_pointer:  usize,
48    program_counter: usize,
49    input:           R,
50    //    output: W,
51}
52
53#[allow(dead_code)]
54#[allow(clippy::len_without_is_empty)]
55impl<R> VirtualMachine<R>
56where
57    R: VMReader,
58{
59    pub(crate) fn new(
60        tape_size: usize,
61        program: Program,
62        memory_pointer: usize,
63        program_counter: usize,
64        input: R,
65    ) -> Self {
66        // FIXME - Remove `memory_pointer` and `program_counter` from the constructor
67        // since they should always be set to 0 on initialization.
68
69        Self {
70            tape: vec![Byte::default(); tape_size],
71            program,
72            memory_pointer,
73            program_counter,
74            input,
75        }
76    }
77
78    /// Return the length of the "memory" or the `tape_size` of the
79    /// `VirtualMachine`.
80    ///
81    /// This method is an alias for the [`length`](#method.length) method.
82    ///
83    /// # Returns
84    ///
85    /// A `usize` value representing the length of the `VirtualMachine`.
86    ///
87    /// # Example
88    ///
89    /// ```
90    /// use brainfoamkit_lib::{
91    ///     VMReader,
92    ///     VirtualMachine,
93    /// };
94    ///
95    /// let input_device = std::io::stdin();
96    /// let machine = VirtualMachine::builder()
97    ///     .input_device(input_device)
98    ///     .tape_size(10)
99    ///     .build()
100    ///     .unwrap();
101    /// assert_eq!(machine.length(), 10);
102    /// ```
103    ///
104    /// # See Also
105    ///
106    /// * [`length`](#method.length)
107    /// * [`memory_pointer`](#method.memory_pointer)
108    /// * [`program_counter`](#method.program_counter)
109    #[must_use]
110    pub(crate) fn tape_size(&self) -> usize {
111        self.length()
112    }
113
114    /// Return the `Program` of the `VirtualMachine`.
115    ///
116    /// This method returns the `Program` of the `VirtualMachine`.
117    ///
118    /// # Returns
119    ///
120    /// A `Program` instance representing the `Program` of the `VirtualMachine`.
121    ///
122    /// # Example
123    ///
124    /// ```
125    /// use brainfoamkit_lib::{
126    ///     Program,
127    ///     VMReader,
128    ///     VirtualMachine,
129    /// };
130    ///
131    /// let input_device = std::io::stdin();
132    /// let machine = VirtualMachine::builder()
133    ///     .input_device(input_device)
134    ///     .build()
135    ///     .unwrap();
136    /// assert_eq!(machine.program(), Program::default());
137    /// ```
138    #[must_use]
139    pub fn program(&self) -> Program {
140        self.program.clone()
141    }
142
143    /// Create a new instance of `VirtualMachine` using `VirtualMachineBuilder`.
144    ///
145    /// This method provides a convenient way to create a new instance of
146    /// `VirtualMachine` using `VirtualMachineBuilder`. This method returns
147    /// a `VirtualMachineBuilder` instance that can be used to configure the
148    /// `VirtualMachine` before building it.
149    ///
150    /// # Returns
151    ///
152    /// A `VirtualMachineBuilder` instance that can be used to configure the
153    /// `VirtualMachine` before building it.
154    ///
155    /// # Example
156    ///
157    /// ```
158    /// use brainfoamkit_lib::{
159    ///     VMReader,
160    ///     VirtualMachine,
161    /// };
162    ///
163    /// let input_device = std::io::stdin();
164    ///
165    /// let machine = VirtualMachine::builder().input_device(input_device).build();
166    /// ```
167    ///
168    /// # See Also
169    ///
170    /// * [`VirtualMachineBuilder`](struct.VirtualMachineBuilder.html)
171    #[must_use]
172    pub const fn builder() -> VirtualMachineBuilder<R> {
173        VirtualMachineBuilder::<R>::new()
174    }
175
176    /// Returns the length of the `tape` inside the `VirtualMachine`.
177    ///
178    /// This method returns the length of the `tape` vector of the
179    /// `VirtualMachine`.
180    ///
181    /// # Returns
182    ///
183    /// A `usize` value representing the length of the `VirtualMachine`.
184    ///
185    /// # Example
186    ///
187    /// ```
188    /// use brainfoamkit_lib::{
189    ///     VMReader,
190    ///     VirtualMachine,
191    /// };
192    ///
193    /// let input_device = std::io::stdin();
194    /// let machine = VirtualMachine::builder()
195    ///     .input_device(input_device)
196    ///     .tape_size(10)
197    ///     .build()
198    ///     .unwrap();
199    /// assert_eq!(machine.length(), 10);
200    /// ```
201    #[must_use]
202    pub fn length(&self) -> usize {
203        self.tape.len()
204    }
205
206    /// Returns the current position of the memory pointer.
207    ///
208    /// This method returns the current position of the memory pointer in the
209    /// `VirtualMachine`.
210    ///
211    /// # Returns
212    ///
213    /// A `usize` value representing the current position of the memory pointer.
214    ///
215    /// # Example
216    ///
217    /// ```
218    /// use brainfoamkit_lib::{
219    ///     VMReader,
220    ///     VirtualMachine,
221    /// };
222    ///
223    /// let input_device = std::io::stdin();
224    /// let machine = VirtualMachine::builder()
225    ///     .input_device(input_device)
226    ///     .build()
227    ///     .unwrap();
228    /// assert_eq!(machine.memory_pointer(), 0);
229    /// ```
230    #[must_use]
231    pub const fn memory_pointer(&self) -> usize {
232        self.memory_pointer
233    }
234
235    /// Returns the current position of the program counter.
236    ///
237    /// This method returns the current position of the program counter in the
238    /// `VirtualMachine`.
239    ///
240    /// # Returns
241    ///
242    /// A `usize` value representing the current position of the program
243    /// counter.
244    ///
245    /// # Example
246    ///
247    /// ```
248    /// use brainfoamkit_lib::{
249    ///     VMReader,
250    ///     VirtualMachine,
251    /// };
252    ///
253    /// let input_device = std::io::stdin();
254    /// let machine = VirtualMachine::builder()
255    ///     .input_device(input_device)
256    ///     .build()
257    ///     .unwrap();
258    /// assert_eq!(machine.program_counter(), 0);
259    /// ```
260    #[must_use]
261    pub const fn program_counter(&self) -> usize {
262        self.program_counter
263    }
264
265    /// returns the current input device of the `VirtualMachine`.
266    ///
267    /// This method returns the current input device of the `VirtualMachine`.
268    /// This allows for testing and type checking of the input device.
269    ///
270    /// # Returns
271    ///
272    /// A reference to the current input device of the
273    /// `VirtualMachine`.
274    ///
275    /// # Example
276    ///
277    /// ```
278    /// use brainfoamkit_lib::{
279    ///     MockReader,
280    ///     VMReader,
281    ///     VirtualMachine,
282    /// };
283    ///
284    /// let input_device = MockReader {
285    ///     data: std::io::Cursor::new("A".as_bytes().to_vec()),
286    /// };
287    /// let mut machine = VirtualMachine::builder()
288    ///     .input_device(input_device)
289    ///     .build()
290    ///     .unwrap();
291    ///
292    /// assert_eq!(machine.input_device().read().unwrap(), 65);
293    /// ```
294    ///
295    /// # See Also
296    ///
297    /// * [`VMReader`](trait.VMReader.html)
298    /// * [`VirtualMachineBuilder`](struct.VirtualMachineBuilder.html)
299    #[must_use]
300    pub fn input_device(&mut self) -> &mut R {
301        &mut self.input
302    }
303
304    /// Returns the current instruction of the `VirtualMachine`.
305    ///
306    /// This method returns the instruction at the current position of the
307    /// program counter in the program. If the program counter is out of
308    /// bounds of the program, this method returns `None`.
309    ///
310    /// # Returns
311    ///
312    /// An `Option` that contains the current instruction if the program counter
313    /// is within the bounds of the program, or `None` if the program
314    /// counter is out of bounds.
315    ///
316    /// # Example
317    ///
318    /// ```
319    /// use brainfoamkit_lib::{
320    ///     Instruction,
321    ///     Program,
322    ///     VMReader,
323    ///     VirtualMachine,
324    /// };
325    ///
326    /// let program = Program::from(vec![
327    ///     Instruction::IncrementPointer,
328    ///     Instruction::IncrementValue,
329    /// ]);
330    /// let input_device = std::io::stdin();
331    /// let mut machine = VirtualMachine::builder()
332    ///     .input_device(input_device)
333    ///     .program(program)
334    ///     .build()
335    ///     .unwrap();
336    /// assert_eq!(
337    ///     machine.get_instruction(),
338    ///     Some(Instruction::IncrementPointer)
339    /// );
340    /// machine.execute_instruction();
341    /// assert_eq!(machine.get_instruction(), Some(Instruction::IncrementValue));
342    /// machine.execute_instruction();
343    /// assert_eq!(machine.get_instruction(), None);
344    /// ```
345    #[must_use]
346    pub fn get_instruction(&self) -> Option<Instruction> {
347        self.program.get_instruction(self.program_counter)
348    }
349
350    /// Executes the current instruction of the `VirtualMachine`.
351    ///
352    /// This method executes the instruction at the current position of the
353    /// memory pointer in the program. If the memory pointer is out of bounds of
354    /// the program, this method does nothing.
355    ///
356    /// # Example
357    ///
358    /// ```
359    /// use brainfoamkit_lib::{
360    ///     Instruction,
361    ///     Program,
362    ///     VMReader,
363    ///     VirtualMachine,
364    /// };
365    ///
366    /// let program = Program::from(vec![
367    ///     Instruction::IncrementPointer,
368    ///     Instruction::IncrementValue,
369    /// ]);
370    /// let input_device = std::io::stdin();
371    /// let mut machine = VirtualMachine::builder()
372    ///     .input_device(input_device)
373    ///     .program(program)
374    ///     .build()
375    ///     .unwrap();
376    /// assert_eq!(machine.memory_pointer(), 0);
377    /// machine.execute_instruction();
378    /// assert_eq!(machine.memory_pointer(), 1);
379    /// machine.execute_instruction();
380    /// assert_eq!(machine.memory_pointer(), 1);
381    /// ```
382    pub fn execute_instruction(&mut self) {
383        let current_instruction = self.get_instruction().unwrap_or(Instruction::NoOp);
384        match current_instruction {
385            Instruction::IncrementPointer => self.increment_pointer(),
386            Instruction::DecrementPointer => self.decrement_pointer(),
387            Instruction::IncrementValue => self.increment_value(),
388            Instruction::DecrementValue => self.decrement_value(),
389            Instruction::OutputValue => self.output_value(),
390            Instruction::InputValue => self.input_value(),
391            Instruction::JumpForward => self.jump_forward(),
392            Instruction::JumpBackward => self.jump_backward(),
393            Instruction::NoOp => {}
394        }
395        self.program_counter += 1;
396    }
397
398    fn increment_pointer(&mut self) {
399        let next = self.memory_pointer.checked_add(1);
400        if let Some(next) = next {
401            self.memory_pointer = next;
402        } else {
403            self.memory_pointer = 0;
404        }
405    }
406
407    fn decrement_pointer(&mut self) {
408        let next = self.memory_pointer.checked_sub(1);
409        if let Some(next) = next {
410            self.memory_pointer = next;
411        } else {
412            self.memory_pointer = self.tape.len() - 1;
413        }
414    }
415
416    fn increment_value(&mut self) {
417        self.tape[self.memory_pointer].increment();
418    }
419
420    fn decrement_value(&mut self) {
421        self.tape[self.memory_pointer].decrement();
422    }
423
424    fn output_value(&self) {
425        todo!("Implement output_value")
426    }
427
428    fn input_value(&mut self) {
429        let input = self.input.read();
430        if let Ok(input) = input {
431            self.tape[self.memory_pointer] = Byte::from(input);
432        }
433    }
434
435    fn jump_forward(&self) {
436        todo!("Implement jump_forward")
437    }
438
439    fn jump_backward(&self) {
440        todo!("Implement jump_backward")
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use std::io::Cursor;
447
448    use super::*;
449    use crate::vm_reader::MockReader;
450
451    #[test]
452    fn test_machine_get_instruction() {
453        let instructions = vec![
454            Instruction::IncrementPointer,
455            Instruction::DecrementPointer,
456            Instruction::IncrementValue,
457            Instruction::DecrementValue,
458            Instruction::OutputValue,
459            Instruction::InputValue,
460            Instruction::JumpForward,
461            Instruction::JumpBackward,
462            Instruction::NoOp,
463        ];
464        let program = Program::from(instructions);
465        let input_device = MockReader {
466            data: Cursor::new("A".as_bytes().to_vec()),
467        };
468        let machine = VirtualMachine::builder()
469            .input_device(input_device)
470            .program(program)
471            .build()
472            .unwrap();
473        assert_eq!(
474            machine.get_instruction(),
475            Some(Instruction::IncrementPointer)
476        );
477    }
478
479    #[test]
480    fn test_machine_execute_instruction() {
481        let input_device = MockReader {
482            data: Cursor::new("A".as_bytes().to_vec()),
483        };
484        let program = Program::from(vec![
485            Instruction::IncrementPointer,
486            Instruction::IncrementValue,
487            Instruction::DecrementValue,
488            Instruction::DecrementPointer,
489        ]);
490        let mut machine = VirtualMachine::builder()
491            .input_device(input_device)
492            .program(program)
493            .build()
494            .unwrap();
495
496        machine.execute_instruction();
497        assert_eq!(
498            machine.memory_pointer(),
499            1,
500            "Memory pointer should be incremented"
501        );
502        assert_eq!(
503            machine.program_counter(),
504            1,
505            "Program counter should be incremented"
506        );
507
508        machine.execute_instruction();
509        assert_eq!(
510            machine.tape[1],
511            Byte::from(0b0000_0001),
512            "Value at memory pointer should be incremented"
513        );
514        assert_eq!(
515            machine.memory_pointer(),
516            1,
517            "Memory pointer should not be changed"
518        );
519        assert_eq!(
520            machine.program_counter(),
521            2,
522            "Program counter should be incremented"
523        );
524
525        machine.execute_instruction();
526        assert_eq!(
527            machine.tape[1],
528            Byte::from(0),
529            "Value at memory pointer should be decremented"
530        );
531        assert_eq!(
532            machine.memory_pointer(),
533            1,
534            "Memory pointer should not be decremented"
535        );
536        assert_eq!(
537            machine.program_counter(),
538            3,
539            "Program counter should be incremented"
540        );
541
542        machine.execute_instruction();
543        assert_eq!(
544            machine.memory_pointer(),
545            0,
546            "Memory pointer should be decremented"
547        );
548        assert_eq!(
549            machine.program_counter(),
550            4,
551            "Program counter should be incremented"
552        );
553    }
554
555    #[test]
556    fn test_memory_pointer() {
557        let input_device = MockReader {
558            data: Cursor::new("A".as_bytes().to_vec()),
559        };
560        let machine = VirtualMachine::builder()
561            .input_device(input_device)
562            .build()
563            .unwrap();
564        assert_eq!(
565            machine.memory_pointer(),
566            0,
567            "Memory pointer should be initialized to 0"
568        );
569    }
570
571    #[test]
572    fn test_program_counter() {
573        let input_device = MockReader {
574            data: Cursor::new("A".as_bytes().to_vec()),
575        };
576        let machine = VirtualMachine::builder()
577            .input_device(input_device)
578            .build()
579            .unwrap();
580        assert_eq!(
581            machine.program_counter(),
582            0,
583            "Program counter should be initialized to 0"
584        );
585    }
586
587    #[test]
588    fn test_increment_pointer() {
589        let input_device = MockReader {
590            data: Cursor::new("A".as_bytes().to_vec()),
591        };
592        let mut machine = VirtualMachine::builder()
593            .input_device(input_device)
594            .build()
595            .unwrap();
596        machine.increment_pointer();
597        assert_eq!(
598            machine.memory_pointer(),
599            1,
600            "Memory pointer should be incremented"
601        );
602    }
603
604    #[test]
605    fn test_decrement_pointer() {
606        let input_device = MockReader {
607            data: Cursor::new("A".as_bytes().to_vec()),
608        };
609        let mut machine = VirtualMachine::builder()
610            .input_device(input_device)
611            .tape_size(100)
612            .build()
613            .unwrap();
614        machine.decrement_pointer();
615        assert_eq!(
616            machine.memory_pointer(),
617            99,
618            "Memory pointer should be decremented"
619        );
620    }
621
622    #[test]
623    fn test_increment_value() {
624        let input_device = MockReader {
625            data: Cursor::new("A".as_bytes().to_vec()),
626        };
627        let mut machine = VirtualMachine::builder()
628            .input_device(input_device)
629            .build()
630            .unwrap();
631        let increment_result = Byte::from(1);
632
633        machine.increment_value();
634        assert_eq!(
635            machine.tape[0], increment_result,
636            "Value at memory pointer should be incremented"
637        );
638    }
639
640    #[test]
641    fn test_decrement_value() {
642        let input_device = MockReader {
643            data: Cursor::new("A".as_bytes().to_vec()),
644        };
645        let mut machine = VirtualMachine::builder()
646            .input_device(input_device)
647            .build()
648            .unwrap();
649        machine.tape[0] = Byte::from(1);
650        machine.decrement_value();
651        assert_eq!(
652            machine.tape[0],
653            Byte::from(0),
654            "Value at memory pointer should be decremented"
655        );
656    }
657
658    #[test]
659    #[should_panic(expected = "not yet implemented")]
660    fn test_output_value() {
661        let input_device = MockReader {
662            data: Cursor::new("A".as_bytes().to_vec()),
663        };
664        let machine = VirtualMachine::builder()
665            .input_device(input_device)
666            .build()
667            .unwrap();
668        machine.output_value();
669    }
670
671    #[test]
672    fn test_valid_input_value() {
673        let data = vec![65]; // A's ASCII value is 65
674        let input_device = MockReader {
675            data: Cursor::new(data),
676        };
677        let mut machine = VirtualMachine::builder()
678            .input_device(input_device)
679            .build()
680            .unwrap();
681
682        machine.input_value();
683
684        assert_eq!(
685            machine.tape[0],
686            Byte::from(65),
687            "Value at memory pointer should be set to the input value"
688        );
689    }
690
691    #[test]
692    fn test_invalid_input_value() {
693        let data = vec![129]; // 129 is not a valid ASCII value
694        let input_device = MockReader {
695            data: Cursor::new(data),
696        };
697        let mut machine = VirtualMachine::builder()
698            .input_device(input_device)
699            .build()
700            .unwrap();
701
702        machine.input_value();
703
704        assert_eq!(
705            machine.tape[0],
706            Byte::from(0),
707            "Value at memory pointer should not be set to the input value"
708        );
709    }
710
711    #[test]
712    #[should_panic(expected = "not yet implemented")]
713    fn test_jump_forward() {
714        let input_device = MockReader {
715            data: Cursor::new("A".as_bytes().to_vec()),
716        };
717        let machine = VirtualMachine::builder()
718            .input_device(input_device)
719            .build()
720            .unwrap();
721        machine.jump_forward();
722    }
723
724    #[test]
725    #[should_panic(expected = "not yet implemented")]
726    fn test_jump_backward() {
727        let input_device = MockReader {
728            data: Cursor::new("A".as_bytes().to_vec()),
729        };
730        let machine = VirtualMachine::builder()
731            .input_device(input_device)
732            .build()
733            .unwrap();
734        machine.jump_backward();
735    }
736}