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, strings: Interner, // 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, } 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 { 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()); } } #[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 { let mut vm = VM { chunk, strings, ip: 0, stack: vec![], last_drop: None, }; vm.run() }