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}