1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! A virtual instruction.
//!
//! Instructions consist of an op code, a name, an arity and a function.
//!
//! ## Examples
//!
//! A simple `push` instruction, which takes a piece of data from the builder's
//! data space and places it onto the operand stack.
//!
//! ```
//! use stack_vm::{Instruction, Machine};
//!
//! fn push(machine: &mut Machine<u64>, args: &[usize]) {
//!     let arg = machine.get_data(args[0]).clone();
//!     machine.operand_push(arg);
//! }
//!
//! Instruction::new(0, "push", 1, push);
//! ```
//!
//! A `noop` instruction which does nothing.
//!
//! ```
//! use stack_vm::{Instruction, Machine};
//!
//! fn noop(_machine: &mut Machine<u64>, _args: &[usize]) {
//!     println!("noop");
//! }
//! ```
//!
//! A `jump` instruction, which takes the name of a label from the builder's data
//! and then jumps to it.
//!
//! Note that operand types have to implement `std::fmt::Debug`.
//!
//! ```
//! use std::fmt;
//! use stack_vm::{Instruction, Machine};
//!
//! #[derive(Debug)]
//! enum Operand { I(i64), S(String) }
//!
//! fn jump(machine: &mut Machine<Operand>, args: &[usize]) {
//!     let label = match machine.get_data(args[0]) {
//!         &Operand::S(ref str) => str.clone(),
//!         _ => panic!("Cannot jump to non-string label.")
//!     };
//!     machine.jump(&label);
//! }
//!
//! Instruction::new(1, "jump", 1, jump);
//! ```

use std::fmt;
use crate::machine::Machine;

/// Describes a single instruction which can be used to execute programs.
///
/// Contains:
/// * An op code - a unique integer to identify this instruction.
/// * A name for serialisation and debugging reasons.
/// * An arity - the number of arguments this instruction expects to receive.
/// * A function which is used to execute the instruction.
pub struct Instruction<T: fmt::Debug> {
    pub op_code: usize,
    pub name:    String,
    pub arity:   usize,
    pub fun:     InstructionFn<T>
}

/// The instruction function signature.
///
/// Each instruction is defined in terms of a function which takes a mutable
/// reference to a `Machine` and an array of `usize`.
///
/// Your instruction is able to manipulate the state of the machine as
/// required (by pushing operands to the stack, for example).
///
/// The `args` array contains indexes into the `Builder`'s data section. It's
/// up to your instruction to retrieve said data.
pub type InstructionFn<T> = fn(machine: &mut Machine<T>, args: &[usize]);

impl<T: fmt::Debug> fmt::Debug for Instruction<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Instruction {{ op_code: {}, name: {}, arity: {} }}", self.op_code, self.name, self.arity)
    }
}

impl<T: fmt::Debug> Instruction<T> {
    /// Create a new instruction.
    pub fn new(op_code: usize, name: &str, arity: usize, fun: InstructionFn<T>) -> Instruction<T> {
        Instruction {
            op_code,
            name: String::from(name),
            arity,
            fun
        }

    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[derive(Debug)]
    struct Operand(i64);

    fn noop(_machine: &mut Machine<Operand>, _args: &[usize]) {}

    #[test]
    fn new() {
        let operand = Instruction::new(13, "noop", 7, noop);
        assert_eq!(operand.op_code, 13);
        assert_eq!(operand.name, "noop".to_string());
        assert_eq!(operand.arity, 7);
    }
}