use super::chunk; use super::errors::*; use super::opcode::OpCode; use super::value::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, } 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, $op:tt ) => {{ let b = $vm.pop(); let a = $vm.pop(); with_type!($vm, b, Value::Number(num_b), { with_type!($vm, a, Value::Number(num_a), { $vm.push(Value::Number(num_a $op num_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 => return Ok(self.pop()), 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::OpNegate => { let v = self.pop(); with_type!( self, v, Value::Number(num), self.push(Value::Number(-num)) ); } OpCode::OpAdd => binary_op!(self, +), OpCode::OpSubtract => binary_op!(self, -), OpCode::OpMultiply => binary_op!(self, *), OpCode::OpDivide => binary_op!(self, /), } #[cfg(feature = "disassemble")] println!("=> {:?}", self.stack); } } } pub fn interpret(chunk: chunk::Chunk) -> LoxResult { let mut vm = VM { chunk, ip: 0, stack: vec![], }; vm.run() }