use std::fmt;
use stack::Stack;
use frame::Frame;
use table::Table;
use builder::Builder;
use code::Code;
use instruction_table::InstructionTable;
pub struct Machine<'a, T: 'a + fmt::Debug> {
pub code: Code<T>,
pub instruction_table: &'a InstructionTable<T>,
pub ip: usize,
pub constants: &'a Table<Item = T>,
pub call_stack: Stack<Frame<T>>,
pub operand_stack: Stack<T>,
}
impl<'a, T: 'a + fmt::Debug> Machine<'a, T> {
pub fn from_builder(builder: Builder<'a, T>, constants: &'a Table<Item = T>) -> Machine<'a, T> {
let instruction_table = builder.instruction_table.clone();
let code = Code::from_builder(builder);
Machine::from_code(code, constants, instruction_table)
}
pub fn from_code(code: Code<T>, constants: &'a Table<Item = T>, instruction_table: &'a InstructionTable<T>) -> Machine<'a, T> {
let frame: Frame<T> = Frame::new(code.code.len());
let mut call_stack = Stack::new();
call_stack.push(frame);
Machine {
code: code,
instruction_table: instruction_table,
ip: 0,
constants: constants,
call_stack: call_stack,
operand_stack: Stack::new()
}
}
pub fn run(mut machine: Machine<'a, T>) -> Machine<'a, T> {
loop {
if machine.ip == machine.code.code.len() { break; }
let op_code = machine.code.code[machine.ip];
let arity = machine.code.code[machine.ip + 1];
machine.ip = machine.ip + 2;
let instr = machine
.instruction_table
.by_op_code(op_code)
.expect(&format!("Unable to find instruction with op code {}", op_code));
let mut args: Vec<usize> = vec![];
for _i in 0..arity {
args.push(machine.code.code[machine.ip]);
machine.ip = machine.ip + 1;
}
let fun = instr.fun;
fun(&mut machine, args.as_slice());
}
machine
}
pub fn get_local(&self, name: &str) -> Option<&T> {
self.call_stack
.peek()
.get_local(name)
}
pub fn get_local_deep(&self, name: &str) -> Option<&T> {
for frame in self.call_stack.as_slice().iter().rev() {
let local = frame.get_local(name);
if local.is_some() { return local; }
}
None
}
pub fn set_local(&mut self, name: &str, value: T) {
self.call_stack
.peek_mut()
.set_local(name, value)
}
pub fn operand_push(&mut self, value: T) {
self.operand_stack
.push(value);
}
pub fn operand_pop(&mut self) -> T {
self.operand_stack
.pop()
}
pub fn get_data(&self, idx: usize) -> &T {
self.code
.data
.get(idx)
.expect(&format!("Constant data is not present at index {}.", idx))
}
pub fn jump(&mut self, label: &str) {
let new_ip = self.code
.get_label_ip(label)
.expect(&format!("Attempt to jump to unknown label {}", label));
let old_ip = self.ip;
self.call_stack.push(Frame::new(old_ip));
self.ip = new_ip;
}
pub fn ret(&mut self) {
let frame = self.call_stack.pop();
self.ip = frame.return_address;
}
}
#[cfg(test)]
mod test {
use super::*;
use write_many_table::WriteManyTable;
use instruction::Instruction;
use instruction_table::InstructionTable;
fn push(machine: &mut Machine<usize>, args: &[usize]) {
let arg = machine.code.data.get(args[0]).unwrap();
machine.operand_stack.push(*arg);
}
fn add(machine: &mut Machine<usize>, _args: &[usize]) {
let rhs = machine.operand_pop();
let lhs = machine.operand_pop();
machine.operand_stack.push(lhs + rhs);
}
fn instruction_table() -> InstructionTable<usize> {
let mut it = InstructionTable::new();
it.insert(Instruction::new(1, "push", 1, push));
it.insert(Instruction::new(2, "add", 0, add));
it
}
#[test]
fn new() {
let it = instruction_table();
let builder: Builder<usize> = Builder::new(&it);
let constants: WriteManyTable<usize> = WriteManyTable::new();
let machine = Machine::from_builder(builder, &constants);
assert_eq!(machine.ip, 0);
assert!(!machine.call_stack.is_empty());
assert!(machine.operand_stack.is_empty());
}
#[test]
fn run() {
let it = instruction_table();
let mut builder: Builder<usize> = Builder::new(&it);
builder.push("push", vec![2]);
builder.push("push", vec![3]);
builder.push("add", vec![]);
let constants: WriteManyTable<usize> = WriteManyTable::new();
let machine = Machine::from_builder(builder, &constants);
let mut machine = Machine::run(machine);
let result = machine.operand_stack.pop();
assert_eq!(result, 5);
}
#[test]
fn get_local() {
let it = instruction_table();
let builder: Builder<usize> = Builder::new(&it);
let constants: WriteManyTable<usize> = WriteManyTable::new();
let mut machine = Machine::from_builder(builder, &constants);
assert!(machine.get_local("example").is_none());
machine.set_local("example", 13);
assert!(machine.get_local("example").is_some());
}
#[test]
fn get_local_deep() {
let it = instruction_table();
let mut builder: Builder<usize> = Builder::new(&it);
builder.label("next");
let constants: WriteManyTable<usize> = WriteManyTable::new();
let mut machine = Machine::from_builder(builder, &constants);
machine.set_local("outer", 13);
assert_eq!(*machine.get_local_deep("outer").unwrap(), 13);
machine.jump("next");
machine.set_local("outer", 14);
machine.set_local("inner", 15);
assert_eq!(*machine.get_local_deep("outer").unwrap(), 14);
assert_eq!(*machine.get_local_deep("inner").unwrap(), 15);
machine.ret();
assert_eq!(*machine.get_local_deep("outer").unwrap(), 13);
assert!(machine.get_local_deep("inner").is_none());
}
#[test]
fn set_local() {
let it = instruction_table();
let builder: Builder<usize> = Builder::new(&it);
let constants: WriteManyTable<usize> = WriteManyTable::new();
let mut machine = Machine::from_builder(builder, &constants);
assert!(machine.get_local("example").is_none());
machine.set_local("example", 13);
assert_eq!(*machine.get_local("example").unwrap(), 13);
}
}