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}