stack_vm/
machine.rs

1//! The machine that makes the magic happen.
2//!
3//! Pour all your ingredients into `Machine` and make it dance.
4
5use crate::code::Code;
6use crate::frame::Frame;
7use crate::instruction_table::InstructionTable;
8use crate::stack::Stack;
9use crate::table::Table;
10use std::fmt;
11
12/// `Machine` contains all the information needed to run your program.
13///
14/// * A `Code`, used describe the source instructions and data to execute.
15/// * An instruction pointer, which points to the currently-executing
16///   instruciton.
17/// * A `Table` of constants, which you can use in your instructions if needed.
18/// * A `Stack` of `Frame` used to keep track of calls being executed.
19/// * A `Stack` of `T` which is used as the main operand stack.
20pub struct Machine<'a, T: 'a + fmt::Debug> {
21    pub code: Code<T>,
22    pub instruction_table: &'a InstructionTable<T>,
23    pub ip: usize,
24    pub constants: &'a Table<Item = T>,
25    pub call_stack: Stack<Frame<T>>,
26    pub operand_stack: Stack<T>,
27}
28
29impl<'a, T: 'a + fmt::Debug> Machine<'a, T> {
30    /// Returns a new `Machine` ready to execute instructions.
31    ///
32    /// The machine is initialised by passing in your `Code` which contains
33    /// all the code and data of your program, and a `Table` of constants`.
34    pub fn new(
35        code: Code<T>,
36        constants: &'a Table<Item = T>,
37        instruction_table: &'a InstructionTable<T>,
38    ) -> Machine<'a, T> {
39        let frame: Frame<T> = Frame::new(code.code.len());
40        let mut call_stack = Stack::new();
41        call_stack.push(frame);
42
43        Machine {
44            code,
45            instruction_table,
46            ip: 0,
47            constants,
48            call_stack,
49            operand_stack: Stack::new(),
50        }
51    }
52
53    /// Run the machine.
54    ///
55    /// Kick off the process of running the program.
56    ///
57    /// Steps through the instructions in your program executing them
58    /// one-by-one.  Each instruction function is executed, much like a
59    /// callback.
60    ///
61    /// Stops when either the last instruction is executed or when the
62    /// last frame is removed from the call stack.
63    pub fn run(&mut self) {
64        loop {
65            if self.ip == self.code.code.len() {
66                break;
67            }
68
69            let op_code = self.next_code();
70            let arity = self.next_code();
71
72            let instr = self
73                .instruction_table
74                .by_op_code(op_code)
75                .unwrap_or_else(|| panic!("Unable to find instruction with op code {}", op_code));
76
77            let mut args: Vec<usize> = vec![];
78
79            for _i in 0..arity {
80                args.push(self.next_code());
81            }
82
83            let fun = instr.fun;
84            fun(self, args.as_slice());
85        }
86    }
87
88    /// Retrieve the next instruction of the program and increment
89    /// the instruction pointer.
90    #[inline]
91    fn next_code(&mut self) -> usize {
92        let code = self.code.code[self.ip];
93        self.ip += 1;
94        code
95    }
96
97    /// Look up a local variable in the current call frame.
98    ///
99    /// Note that the variable may not be set in the current frame but it's up
100    /// to your instruction to figure out how to deal with this situation.
101    pub fn get_local(&self, name: &str) -> Option<&T> {
102        self.call_stack.peek().get_local(name)
103    }
104
105    /// Look for a local variable in all call frames.
106    ///
107    /// The machine will look in each frame in the call stack starting at the
108    /// top and moving down until it locates the local variable in question
109    /// or runs out of stack frames.
110    pub fn get_local_deep(&self, name: &str) -> Option<&T> {
111        for frame in self.call_stack.as_slice().iter().rev() {
112            let local = frame.get_local(name);
113            if local.is_some() {
114                return local;
115            }
116        }
117        None
118    }
119
120    /// Set a local variable in the current call frame.
121    ///
122    /// Places a value in the frame's local variable table.
123    pub fn set_local(&mut self, name: &str, value: T) {
124        self.call_stack.peek_mut().set_local(name, value)
125    }
126
127    /// Push an operand onto the operand stack.
128    pub fn operand_push(&mut self, value: T) {
129        self.operand_stack.push(value);
130    }
131
132    /// Pop an operand off the operand stack.
133    pub fn operand_pop(&mut self) -> T {
134        self.operand_stack.pop()
135    }
136
137    /// Retrieve a reference to a `T` stored in the Code's data section.
138    pub fn get_data(&self, idx: usize) -> &T {
139        self.code
140            .data
141            .get(idx)
142            .unwrap_or_else(|| panic!("Constant data is not present at index {}.", idx))
143    }
144
145    /// Perform a jump to a named label.
146    ///
147    /// This method performs the following actions:
148    /// * Retrieve the instruction pointer for a given label from the Code.
149    /// * Set the machine's instruction pointer to the new location.
150    ///
151    /// This method will panic the thread if the label does not exist.
152    pub fn jump(&mut self, label: &str) {
153        self.ip = self
154            .code
155            .get_label_ip(label)
156            .unwrap_or_else(|| panic!("Attempted to jump to unknown label {}", label));
157    }
158
159    /// Performs a call to a named label.
160    ///
161    /// This method is very similar to `jump` except that it records it's
162    /// current instruction pointer and saves it in the call stack.
163    ///
164    /// This method performs the following actions:
165    /// * Create a new frame with it's return address set to the current
166    ///   instruction pointer.
167    /// * Jump to the named label using `jump`.
168    ///
169    /// This method specifically does not transfer operands to call arguments.
170    pub fn call(&mut self, label: &str) {
171        self.call_stack.push(Frame::new(self.ip));
172        self.jump(label);
173    }
174
175    /// Performs a return.
176    ///
177    /// This method pops the top frame off the call stack and moves the
178    /// instruction pointer back to the frame's return address.
179    /// It's up to you to push your return value onto the operand stack (if
180    /// your language has such return semantics).
181    ///
182    /// The last call frame contains a return address at the end of the source
183    /// code, so the machine will stop executing at the beginning of the next
184    /// iteration.
185    ///
186    /// If you call `ret` too many times then the machine will panic when it
187    /// attempts to pop the last frame off the stack.
188    pub fn ret(&mut self) {
189        let frame = self.call_stack.pop();
190        self.ip = frame.return_address;
191    }
192}
193
194#[cfg(test)]
195mod test {
196    use super::*;
197    use crate::builder::Builder;
198    use crate::instruction::Instruction;
199    use crate::instruction_table::InstructionTable;
200    use crate::write_many_table::WriteManyTable;
201
202    fn push(machine: &mut Machine<usize>, args: &[usize]) {
203        let arg = &machine.code.data[args[0]];
204        machine.operand_stack.push(*arg);
205    }
206
207    fn add(machine: &mut Machine<usize>, _args: &[usize]) {
208        let rhs = machine.operand_pop();
209        let lhs = machine.operand_pop();
210        machine.operand_stack.push(lhs + rhs);
211    }
212
213    fn instruction_table() -> InstructionTable<usize> {
214        let mut it = InstructionTable::new();
215        it.insert(Instruction::new(1, "push", 1, push));
216        it.insert(Instruction::new(2, "add", 0, add));
217        it
218    }
219
220    #[test]
221    fn new() {
222        let it = instruction_table();
223        let builder: Builder<usize> = Builder::new(&it);
224        let constants: WriteManyTable<usize> = WriteManyTable::new();
225        let machine = Machine::new(Code::from(builder), &constants, &it);
226        assert_eq!(machine.ip, 0);
227        assert!(!machine.call_stack.is_empty());
228        assert!(machine.operand_stack.is_empty());
229    }
230
231    #[test]
232    fn run() {
233        let it = instruction_table();
234        let mut builder: Builder<usize> = Builder::new(&it);
235        builder.push("push", vec![2]);
236        builder.push("push", vec![3]);
237        builder.push("add", vec![]);
238        let constants: WriteManyTable<usize> = WriteManyTable::new();
239        let mut machine = Machine::new(Code::from(builder), &constants, &it);
240        machine.run();
241        let result = machine.operand_stack.pop();
242        assert_eq!(result, 5);
243    }
244
245    #[test]
246    fn get_local() {
247        let it = instruction_table();
248        let builder: Builder<usize> = Builder::new(&it);
249        let constants: WriteManyTable<usize> = WriteManyTable::new();
250        let mut machine = Machine::new(Code::from(builder), &constants, &it);
251        assert!(machine.get_local("example").is_none());
252        machine.set_local("example", 13);
253        assert!(machine.get_local("example").is_some());
254    }
255
256    #[test]
257    fn get_local_deep() {
258        let it = instruction_table();
259        let mut builder: Builder<usize> = Builder::new(&it);
260        builder.label("next");
261
262        let constants: WriteManyTable<usize> = WriteManyTable::new();
263        let mut machine = Machine::new(Code::from(builder), &constants, &it);
264        machine.set_local("outer", 13);
265        assert_eq!(*machine.get_local_deep("outer").unwrap(), 13);
266        machine.call("next");
267        machine.set_local("outer", 14);
268        machine.set_local("inner", 15);
269        assert_eq!(*machine.get_local_deep("outer").unwrap(), 14);
270        assert_eq!(*machine.get_local_deep("inner").unwrap(), 15);
271        machine.ret();
272        assert_eq!(*machine.get_local_deep("outer").unwrap(), 13);
273        assert!(machine.get_local_deep("inner").is_none());
274    }
275
276    #[test]
277    fn set_local() {
278        let it = instruction_table();
279        let builder: Builder<usize> = Builder::new(&it);
280        let constants: WriteManyTable<usize> = WriteManyTable::new();
281        let mut machine = Machine::new(Code::from(builder), &constants, &it);
282        assert!(machine.get_local("example").is_none());
283        machine.set_local("example", 13);
284        assert_eq!(*machine.get_local("example").unwrap(), 13);
285    }
286}