use crate::{
vm_reader::VMReader,
Byte,
Instruction,
Program,
VirtualMachineBuilder,
};
#[allow(clippy::module_name_repetitions)]
pub struct VirtualMachine<R>
where
R: VMReader,
{
tape: Vec<Byte>,
program: Program,
memory_pointer: usize,
program_counter: usize,
input: R,
}
#[allow(dead_code)]
#[allow(clippy::len_without_is_empty)]
impl<R> VirtualMachine<R>
where
R: VMReader,
{
pub(crate) fn new(
tape_size: usize,
program: Program,
memory_pointer: usize,
program_counter: usize,
input: R,
) -> Self {
Self {
tape: vec![Byte::default(); tape_size],
program,
memory_pointer,
program_counter,
input,
}
}
#[must_use]
pub(crate) fn tape_size(&self) -> usize {
self.length()
}
#[must_use]
pub fn program(&self) -> Program {
self.program.clone()
}
#[must_use]
pub const fn builder() -> VirtualMachineBuilder<R> {
VirtualMachineBuilder::<R>::new()
}
#[must_use]
pub fn length(&self) -> usize {
self.tape.len()
}
#[must_use]
pub const fn memory_pointer(&self) -> usize {
self.memory_pointer
}
#[must_use]
pub const fn program_counter(&self) -> usize {
self.program_counter
}
#[must_use]
pub fn input_device(&mut self) -> &mut R {
&mut self.input
}
#[must_use]
pub fn get_instruction(&self) -> Option<Instruction> {
self.program.get_instruction(self.program_counter)
}
pub fn execute_instruction(&mut self) {
let current_instruction = self.get_instruction().unwrap_or(Instruction::NoOp);
match current_instruction {
Instruction::IncrementPointer => self.increment_pointer(),
Instruction::DecrementPointer => self.decrement_pointer(),
Instruction::IncrementValue => self.increment_value(),
Instruction::DecrementValue => self.decrement_value(),
Instruction::OutputValue => self.output_value(),
Instruction::InputValue => self.input_value(),
Instruction::JumpForward => self.jump_forward(),
Instruction::JumpBackward => self.jump_backward(),
Instruction::NoOp => {}
}
self.program_counter += 1;
}
fn increment_pointer(&mut self) {
let next = self.memory_pointer.checked_add(1);
if let Some(next) = next {
self.memory_pointer = next;
} else {
self.memory_pointer = 0;
}
}
fn decrement_pointer(&mut self) {
let next = self.memory_pointer.checked_sub(1);
if let Some(next) = next {
self.memory_pointer = next;
} else {
self.memory_pointer = self.tape.len() - 1;
}
}
fn increment_value(&mut self) {
self.tape[self.memory_pointer].increment();
}
fn decrement_value(&mut self) {
self.tape[self.memory_pointer].decrement();
}
fn output_value(&mut self) {
todo!("Implement output_value")
}
fn input_value(&mut self) {
let input = self.input.read();
if let Ok(input) = input {
self.tape[self.memory_pointer] = Byte::from(input);
}
}
fn jump_forward(&mut self) {
todo!("Implement jump_forward")
}
fn jump_backward(&mut self) {
todo!("Implement jump_backward")
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
use crate::vm_reader::MockReader;
#[test]
fn test_machine_get_instruction() {
let instructions = vec![
Instruction::IncrementPointer,
Instruction::DecrementPointer,
Instruction::IncrementValue,
Instruction::DecrementValue,
Instruction::OutputValue,
Instruction::InputValue,
Instruction::JumpForward,
Instruction::JumpBackward,
Instruction::NoOp,
];
let program = Program::from(instructions);
let input_device = MockReader {
data: Cursor::new("A".as_bytes().to_vec()),
};
let machine = VirtualMachine::builder()
.input_device(input_device)
.program(program)
.build()
.unwrap();
assert_eq!(
machine.get_instruction(),
Some(Instruction::IncrementPointer)
);
}
#[test]
fn test_machine_execute_instruction() {
let input_device = MockReader {
data: Cursor::new("A".as_bytes().to_vec()),
};
let program = Program::from(vec![
Instruction::IncrementPointer,
Instruction::IncrementValue,
Instruction::DecrementValue,
Instruction::DecrementPointer,
]);
let mut machine = VirtualMachine::builder()
.input_device(input_device)
.program(program)
.build()
.unwrap();
machine.execute_instruction();
assert_eq!(
machine.memory_pointer(),
1,
"Memory pointer should be incremented"
);
assert_eq!(
machine.program_counter(),
1,
"Program counter should be incremented"
);
machine.execute_instruction();
assert_eq!(
machine.tape[1],
Byte::from(0b0000_0001),
"Value at memory pointer should be incremented"
);
assert_eq!(
machine.memory_pointer(),
1,
"Memory pointer should not be changed"
);
assert_eq!(
machine.program_counter(),
2,
"Program counter should be incremented"
);
machine.execute_instruction();
assert_eq!(
machine.tape[1],
Byte::from(0),
"Value at memory pointer should be decremented"
);
assert_eq!(
machine.memory_pointer(),
1,
"Memory pointer should not be decremented"
);
assert_eq!(
machine.program_counter(),
3,
"Program counter should be incremented"
);
machine.execute_instruction();
assert_eq!(
machine.memory_pointer(),
0,
"Memory pointer should be decremented"
);
assert_eq!(
machine.program_counter(),
4,
"Program counter should be incremented"
);
}
#[test]
fn test_memory_pointer() {
let input_device = MockReader {
data: Cursor::new("A".as_bytes().to_vec()),
};
let machine = VirtualMachine::builder()
.input_device(input_device)
.build()
.unwrap();
assert_eq!(
machine.memory_pointer(),
0,
"Memory pointer should be initialized to 0"
);
}
#[test]
fn test_program_counter() {
let input_device = MockReader {
data: Cursor::new("A".as_bytes().to_vec()),
};
let machine = VirtualMachine::builder()
.input_device(input_device)
.build()
.unwrap();
assert_eq!(
machine.program_counter(),
0,
"Program counter should be initialized to 0"
);
}
#[test]
fn test_increment_pointer() {
let input_device = MockReader {
data: Cursor::new("A".as_bytes().to_vec()),
};
let mut machine = VirtualMachine::builder()
.input_device(input_device)
.build()
.unwrap();
machine.increment_pointer();
assert_eq!(
machine.memory_pointer(),
1,
"Memory pointer should be incremented"
);
}
#[test]
fn test_decrement_pointer() {
let input_device = MockReader {
data: Cursor::new("A".as_bytes().to_vec()),
};
let mut machine = VirtualMachine::builder()
.input_device(input_device)
.tape_size(100)
.build()
.unwrap();
machine.decrement_pointer();
assert_eq!(
machine.memory_pointer(),
99,
"Memory pointer should be decremented"
);
}
#[test]
fn test_increment_value() {
let input_device = MockReader {
data: Cursor::new("A".as_bytes().to_vec()),
};
let mut machine = VirtualMachine::builder()
.input_device(input_device)
.build()
.unwrap();
let increment_result = Byte::from(1);
machine.increment_value();
assert_eq!(
machine.tape[0], increment_result,
"Value at memory pointer should be incremented"
);
}
#[test]
fn test_decrement_value() {
let input_device = MockReader {
data: Cursor::new("A".as_bytes().to_vec()),
};
let mut machine = VirtualMachine::builder()
.input_device(input_device)
.build()
.unwrap();
machine.tape[0] = Byte::from(1);
machine.decrement_value();
assert_eq!(
machine.tape[0],
Byte::from(0),
"Value at memory pointer should be decremented"
);
}
#[test]
#[should_panic(expected = "not yet implemented")]
fn test_output_value() {
let input_device = MockReader {
data: Cursor::new("A".as_bytes().to_vec()),
};
let mut machine = VirtualMachine::builder()
.input_device(input_device)
.build()
.unwrap();
machine.output_value();
}
#[test]
fn test_valid_input_value() {
let data = vec![65]; let input_device = MockReader {
data: Cursor::new(data),
};
let mut machine = VirtualMachine::builder()
.input_device(input_device)
.build()
.unwrap();
machine.input_value();
assert_eq!(
machine.tape[0],
Byte::from(65),
"Value at memory pointer should be set to the input value"
);
}
#[test]
fn test_invalid_input_value() {
let data = vec![129]; let input_device = MockReader {
data: Cursor::new(data),
};
let mut machine = VirtualMachine::builder()
.input_device(input_device)
.build()
.unwrap();
machine.input_value();
assert_eq!(
machine.tape[0],
Byte::from(0),
"Value at memory pointer should not be set to the input value"
);
}
#[test]
#[should_panic(expected = "not yet implemented")]
fn test_jump_forward() {
let input_device = MockReader {
data: Cursor::new("A".as_bytes().to_vec()),
};
let mut machine = VirtualMachine::builder()
.input_device(input_device)
.build()
.unwrap();
machine.jump_forward();
}
#[test]
#[should_panic(expected = "not yet implemented")]
fn test_jump_backward() {
let input_device = MockReader {
data: Cursor::new("A".as_bytes().to_vec()),
};
let mut machine = VirtualMachine::builder()
.input_device(input_device)
.build()
.unwrap();
machine.jump_backward();
}
}