stack_vm/
instruction.rs

1//! A virtual instruction.
2//!
3//! Instructions consist of an op code, a name, an arity and a function.
4//!
5//! ## Examples
6//!
7//! A simple `push` instruction, which takes a piece of data from the builder's
8//! data space and places it onto the operand stack.
9//!
10//! ```
11//! use stack_vm::{Instruction, Machine};
12//!
13//! fn push(machine: &mut Machine<u64>, args: &[usize]) {
14//!     let arg = machine.get_data(args[0]).clone();
15//!     machine.operand_push(arg);
16//! }
17//!
18//! Instruction::new(0, "push", 1, push);
19//! ```
20//!
21//! A `noop` instruction which does nothing.
22//!
23//! ```
24//! use stack_vm::{Instruction, Machine};
25//!
26//! fn noop(_machine: &mut Machine<u64>, _args: &[usize]) {
27//!     println!("noop");
28//! }
29//! ```
30//!
31//! A `jump` instruction, which takes the name of a label from the builder's data
32//! and then jumps to it.
33//!
34//! Note that operand types have to implement `std::fmt::Debug`.
35//!
36//! ```
37//! use std::fmt;
38//! use stack_vm::{Instruction, Machine};
39//!
40//! #[derive(Debug)]
41//! enum Operand { I(i64), S(String) }
42//!
43//! fn jump(machine: &mut Machine<Operand>, args: &[usize]) {
44//!     let label = match machine.get_data(args[0]) {
45//!         &Operand::S(ref str) => str.clone(),
46//!         _ => panic!("Cannot jump to non-string label.")
47//!     };
48//!     machine.jump(&label);
49//! }
50//!
51//! Instruction::new(1, "jump", 1, jump);
52//! ```
53
54use std::fmt;
55use crate::machine::Machine;
56
57/// Describes a single instruction which can be used to execute programs.
58///
59/// Contains:
60/// * An op code - a unique integer to identify this instruction.
61/// * A name for serialisation and debugging reasons.
62/// * An arity - the number of arguments this instruction expects to receive.
63/// * A function which is used to execute the instruction.
64pub struct Instruction<T: fmt::Debug> {
65    pub op_code: usize,
66    pub name:    String,
67    pub arity:   usize,
68    pub fun:     InstructionFn<T>
69}
70
71/// The instruction function signature.
72///
73/// Each instruction is defined in terms of a function which takes a mutable
74/// reference to a `Machine` and an array of `usize`.
75///
76/// Your instruction is able to manipulate the state of the machine as
77/// required (by pushing operands to the stack, for example).
78///
79/// The `args` array contains indexes into the `Builder`'s data section. It's
80/// up to your instruction to retrieve said data.
81pub type InstructionFn<T> = fn(machine: &mut Machine<T>, args: &[usize]);
82
83impl<T: fmt::Debug> fmt::Debug for Instruction<T> {
84    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85        write!(f, "Instruction {{ op_code: {}, name: {}, arity: {} }}", self.op_code, self.name, self.arity)
86    }
87}
88
89impl<T: fmt::Debug> Instruction<T> {
90    /// Create a new instruction.
91    pub fn new(op_code: usize, name: &str, arity: usize, fun: InstructionFn<T>) -> Instruction<T> {
92        Instruction {
93            op_code,
94            name: String::from(name),
95            arity,
96            fun
97        }
98
99    }
100}
101
102#[cfg(test)]
103mod test {
104    use super::*;
105
106    #[derive(Debug)]
107    struct Operand(i64);
108
109    fn noop(_machine: &mut Machine<Operand>, _args: &[usize]) {}
110
111    #[test]
112    fn new() {
113        let operand = Instruction::new(13, "noop", 7, noop);
114        assert_eq!(operand.op_code, 13);
115        assert_eq!(operand.name, "noop".to_string());
116        assert_eq!(operand.arity, 7);
117    }
118}