extendable_vm 0.4.0

Library that simplifies implementation of stack VMs
Documentation
use crate::Chunk;

/// Location in code
///
/// `chunk_id` is the id of the chunk where the code is located
/// and `instruction_pointer` is the index of the next byte which is to be read.
///
#[derive(Clone)]
pub struct InstructionPointer {
    pub chunk_id: usize,
    pub instruction_pointer: usize,
}

impl InstructionPointer {
    pub fn new(chunk_id: usize) -> InstructionPointer {
        InstructionPointer {
            instruction_pointer: 0,
            chunk_id,
        }
    }

    pub fn read_and_advance<Value>(&mut self, chunk: &Chunk<Value>) -> Option<u8> {
        if self.instruction_pointer < chunk.code.len() {
            let instruction = chunk.code[self.instruction_pointer];
            self.instruction_pointer += 1;
            Some(instruction)
        } else {
            None
        }
    }

    pub fn jump_forward(&mut self, offset: usize) {
        self.instruction_pointer += offset
    }

    pub fn jump_backward(&mut self, offset: usize) {
        if self.instruction_pointer < offset {
            panic!("Jumped too far backward: ip={}", self.instruction_pointer);
        } else {
            self.instruction_pointer -= offset;
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{Chunk, InstructionPointer};

    #[test]
    fn should_iterate_over_all_instructions() {
        let mut expected_code: Vec<u8> = vec![];
        for i in 1..100 {
            expected_code.push(i);
        }

        let chunk = Chunk {
            constants: vec![0],
            code: expected_code.clone(),
        };

        let mut pointer = InstructionPointer::new(0);
        let mut actual_code: Vec<u8> = vec![];

        while let Some(byte) = pointer.read_and_advance(&chunk) {
            actual_code.push(byte)
        }
        assert_eq!(expected_code, actual_code)
    }

    #[test]
    fn should_jump_backward() {
        let code: Vec<u8> = vec![0, 1, 2, 3, 4, 5];

        let chunk = Chunk {
            constants: vec![0],
            code,
        };

        let expected_code: Vec<u8> = vec![0, 1, 2, 3, 4, 5, 2, 3, 4, 5];

        let mut pointer = InstructionPointer::new(0);
        let mut actual_code: Vec<u8> = vec![];

        while let Some(byte) = pointer.read_and_advance(&chunk) {
            actual_code.push(byte)
        }
        pointer.jump_backward(4);
        while let Some(byte) = pointer.read_and_advance(&chunk) {
            actual_code.push(byte)
        }

        assert_eq!(expected_code, actual_code)
    }

    #[test]
    fn should_jump_forward() {
        let chunk = Chunk {
            constants: vec![0],
            code: vec![0, 1, 2, 3, 4, 5],
        };

        let mut pointer = InstructionPointer::new(0);
        pointer.jump_forward(5);

        assert_eq!(5, pointer.read_and_advance(&chunk).unwrap());
    }

    #[test]
    #[should_panic]
    fn should_panic_if_jumps_too_far_backward() {
        let mut pointer = InstructionPointer::new(0);
        pointer.jump_backward(10);
    }
}