use std::collections::HashMap;
use super::chunk;
use super::errors::*;
use super::interner::Interner;
use super::opcode::OpCode;
use super::value::{LoxString, Value};
pub struct VM {
chunk: chunk::Chunk,
// TODO(tazjin): Accessing array elements constantly is not ideal,
// lets see if something clever can be done with iterators.
ip: usize,
stack: Vec<Value>,
strings: Interner,
globals: HashMap<LoxString, Value>,
// Operations that consume values from the stack without pushing
// anything leave their last value in this slot, which makes it
// possible to return values from interpreters that ran code which
// ended with a statement.
last_drop: Option<Value>,
}
impl VM {
fn push(&mut self, value: Value) {
self.stack.push(value)
}
fn pop(&mut self) -> Value {
self.stack.pop().expect("fatal error: stack empty!")
}
}
macro_rules! with_type {
( $self:ident, $val:ident, $type:pat, $body:expr ) => {
match $val {
$type => $body,
_ => {
return Err(Error {
line: $self.chunk.get_line($self.ip - 1),
kind: ErrorKind::TypeError(format!(
"Expected type {}, but found value: {:?}",
stringify!($type),
$val,
)),
})
}
}
};
}
macro_rules! binary_op {
( $vm:ident, $type:tt, $op:tt ) => {
binary_op!($vm, $type, $type, $op)
};
( $vm:ident, $in_type:tt, $out_type:tt, $op:tt ) => {{
let b = $vm.pop();
let a = $vm.pop();
with_type!($vm, b, Value::$in_type(val_b), {
with_type!($vm, a, Value::$in_type(val_a), {
$vm.push(Value::$out_type(val_a $op val_b))
})
})
}};
}
impl VM {
fn run(&mut self) -> LoxResult<Value> {
loop {
let op = &self.chunk.code[self.ip];
#[cfg(feature = "disassemble")]
chunk::disassemble_instruction(&self.chunk, self.ip);
self.ip += 1;
match op {
OpCode::OpReturn => {
if !self.stack.is_empty() {
let val = self.pop();
return Ok(self.return_value(val));
} else if self.last_drop.is_some() {
let val = self.last_drop.take().unwrap();
return Ok(self.return_value(val));
} else {
return Ok(Value::Nil);
}
}
OpCode::OpConstant(idx) => {
let c = self.chunk.constant(*idx).clone();
self.push(c);
}
OpCode::OpNil => self.push(Value::Nil),
OpCode::OpTrue => self.push(Value::Bool(true)),
OpCode::OpFalse => self.push(Value::Bool(false)),
OpCode::OpNot => {
let v = self.pop();
self.push(Value::Bool(v.is_falsey()));
}
OpCode::OpEqual => {
let b = self.pop();
let a = self.pop();
self.push(Value::Bool(a == b));
}
OpCode::OpLess => binary_op!(self, Number, Bool, <),
OpCode::OpGreater => binary_op!(self, Number, Bool, >),
OpCode::OpNegate => {
let v = self.pop();
with_type!(self, v, Value::Number(num), self.push(Value::Number(-num)));
}
OpCode::OpSubtract => binary_op!(self, Number, -),
OpCode::OpMultiply => binary_op!(self, Number, *),
OpCode::OpDivide => binary_op!(self, Number, /),
OpCode::OpAdd => {
let b = self.pop();
let a = self.pop();
match (a, b) {
(Value::String(s_a), Value::String(s_b)) => {
let mut new_s = self.resolve_str(&s_a).to_string();
new_s.push_str(self.resolve_str(&s_b));
self.push(Value::String(new_s.into()));
}
(Value::Number(n_a), Value::Number(n_b)) => {
self.push(Value::Number(n_a + n_b))
}
_ => {
return Err(Error {
line: self.chunk.get_line(self.ip - 1),
kind: ErrorKind::TypeError(
"'+' operator only works on strings and numbers".into(),
),
})
}
}
}
OpCode::OpPrint => {
let val = self.pop();
println!("{}", self.print_value(val));
}
OpCode::OpPop => {
self.last_drop = Some(self.pop());
}
OpCode::OpDefineGlobal(name_idx) => {
let name = self.chunk.constant(*name_idx);
with_type!(self, name, Value::String(name), {
let name = name.clone();
let val = self.pop();
self.globals.insert(name, val);
});
}
OpCode::OpGetGlobal(name_idx) => {
let name = self.chunk.constant(*name_idx);
with_type!(self, name, Value::String(name), {
let val = match self.globals.get(name) {
None => unimplemented!("variable not found error"),
Some(val) => val.clone(),
};
self.push(val)
});
}
OpCode::OpSetGlobal(name_idx) => {
let name = self.chunk.constant(*name_idx).clone();
let new_val = self.pop();
with_type!(self, name, Value::String(name), {
match self.globals.get_mut(&name) {
None => unimplemented!("variable not found error"),
Some(val) => {
*val = new_val;
}
}
});
}
OpCode::OpGetLocal(local_idx) => {
let value = self.stack[local_idx.0].clone();
self.push(value);
}
OpCode::OpSetLocal(local_idx) => {
debug_assert!(
self.stack.len() > local_idx.0,
"stack is not currently large enough for local"
);
self.stack[local_idx.0] = self.stack.last().unwrap().clone();
}
OpCode::OpJumpPlaceholder(_) => {
panic!("unpatched jump detected - this is a fatal compiler error!");
}
OpCode::OpJump(offset) => {
self.ip += offset.0;
}
OpCode::OpJumpIfFalse(offset) => {
if self
.stack
.last()
.expect("condition should leave a value on the stack")
.is_falsey()
{
self.ip += offset.0;
}
}
}
#[cfg(feature = "disassemble")]
println!("=> {:?}", self.stack);
}
}
// For some types of values (e.g. interned strings), returns
// should no longer include any references into the interpreter.
fn return_value(&self, val: Value) -> Value {
match val {
Value::String(string @ LoxString::Interned(_)) => {
Value::String(self.resolve_str(&string).to_string().into())
}
_ => val,
}
}
fn resolve_str<'a>(&'a self, string: &'a LoxString) -> &'a str {
match string {
LoxString::Heap(s) => s.as_str(),
LoxString::Interned(id) => self.strings.lookup(*id),
}
}
fn print_value(&self, val: Value) -> String {
match val {
Value::String(LoxString::Heap(s)) => s,
Value::String(LoxString::Interned(id)) => self.strings.lookup(id).into(),
_ => format!("{:?}", val),
}
}
}
pub fn interpret(strings: Interner, chunk: chunk::Chunk) -> LoxResult<Value> {
let mut vm = VM {
chunk,
strings,
globals: HashMap::new(),
ip: 0,
stack: vec![],
last_drop: None,
};
vm.run()
}