diff --git a/rlox/src/bc.rs b/rlox/src/bc.rs index 6b86012..f95916a 100644 --- a/rlox/src/bc.rs +++ b/rlox/src/bc.rs @@ -1,3 +1,4 @@ +use crate::bc::Value::Number; use std::convert::From; use std::fmt; @@ -12,20 +13,24 @@ pub enum Op { Divide, } -#[derive(Copy, Clone, PartialEq)] -pub struct Value { - pub val: f64, +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Value { + Nil, + Number(f64), } -impl fmt::Debug for Value { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{}", self.val) +impl Value { + pub fn as_num(self) -> Option { + match self { + Number(val) => Some(val), + _ => None, + } } } impl From for Value { fn from(value: f64) -> Self { - Value { val: value } + Number(value) } } @@ -48,7 +53,7 @@ impl Chunk { Chunk { code, debug_info, - constants + constants, } } @@ -65,13 +70,18 @@ impl Chunk { pub fn add_constant(&mut self, value: Value, line: usize) -> &mut Self { self.constants.push(value); - self.add_op(Op::Constant {offset: self.constants.len() - 1}, line) + self.add_op( + Op::Constant { + offset: self.constants.len() - 1, + }, + line, + ) } } pub struct NamedChunk { name: String, - chunk: Chunk + chunk: Chunk, } impl fmt::Debug for Chunk { @@ -128,7 +138,7 @@ impl fmt::Debug for TraceInfo<'_> { } Op::Constant { offset } => { f.debug_struct("Constant") - .field("val", &chunk.constants[offset].val) + .field("val", &chunk.constants[offset]) .finish()?; write!(f, "") } diff --git a/rlox/src/lc.rs b/rlox/src/lc.rs index df958a9..70dda3c 100644 --- a/rlox/src/lc.rs +++ b/rlox/src/lc.rs @@ -2,7 +2,7 @@ use std::convert::identity; use std::iter::Peekable; use std::str::CharIndices; -use crate::bc::{Chunk, Op}; +use crate::bc::{Chunk, Op, Value}; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] enum TokenType { @@ -348,6 +348,12 @@ impl<'src> Parser<'src> { _ => panic!("Could not parse number"), }; } + Token { + ttype: TokenType::Nil, + span: _, + } => { + chunk.add_constant(Value::Nil, 0); + } Token { ttype: TokenType::LeftParen, span: _, @@ -356,7 +362,7 @@ impl<'src> Parser<'src> { assert_eq!(self.scanner.next().unwrap().ttype, TokenType::RightParen) } _ => panic!("Expected '-' or number"), - } + }, }; while let Some(op) = self.scanner.next_if(|token| { @@ -475,7 +481,25 @@ mod tests { Add, ], vec![], - vec![1., 1., 2., 1.].into_iter().map(Value::from).collect() + vec![1., 1., 2., 1.].into_iter().map(Value::from).collect(), + ); + + assert!(chunk.instr_eq(&expected)); + } + + #[test] + fn parse_nil() { + let source = "nil + nil"; + let scanner = Scanner::new(source); + let mut parser = Parser::new(scanner); + let mut chunk = Chunk::new(); + parser.expression(&mut chunk); + + use crate::bc::Op::*; + let expected = Chunk::new_with( + vec![Constant { offset: 0 }, Constant { offset: 1 }, Add], + vec![], + vec![Value::Nil, Value::Nil], ); assert!(chunk.instr_eq(&expected)); diff --git a/rlox/src/vm.rs b/rlox/src/vm.rs index 754918b..ce72316 100644 --- a/rlox/src/vm.rs +++ b/rlox/src/vm.rs @@ -1,5 +1,6 @@ -use std::ops::Not; use crate::bc::{Chunk, Op, TraceInfo, Value}; +use std::ops::Not; +use std::rc::Rc; pub struct VM { pub trace: bool, @@ -7,10 +8,10 @@ pub struct VM { pc: usize, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum VMError { Compile, - Runtime(&'static str, usize), + Runtime(Rc, usize), } impl VM { @@ -23,7 +24,14 @@ impl VM { } fn runtime_err(&self, msg: &'static str) -> VMError { - VMError::Runtime(msg, self.pc) + VMError::Runtime(msg.into(), self.pc) + } + + fn type_err(&self, expected: &'static str, found: Value) -> VMError { + VMError::Runtime( + format!("Expected: {:}, Found: {:?}", expected, found).into(), + self.pc, + ) } fn push(&mut self, value: Value) { @@ -33,7 +41,14 @@ impl VM { fn pop(&mut self) -> Result { self.stack .pop() - .ok_or_else(|| self.runtime_err("Attempt to pop of empty stack.")) + .ok_or(self.runtime_err("Attempt to pop of empty stack.")) + } + + fn pop_num(&mut self) -> Result { + let top_of_stack = self.pop()?; + top_of_stack + .as_num() + .ok_or(self.type_err("Number", top_of_stack)) } pub fn run(&mut self, chunk: &Chunk) -> Result, VMError> { @@ -62,17 +77,17 @@ impl VM { Op::Return => print!("{:?}", self.pop()?), Op::Constant { offset } => self.push(chunk.constants[offset]), Op::Negate => { - let new_val = -self.pop()?.val; - self.push(Value::from(new_val)); + let new_val = -self.pop_num()?; + self.push(new_val.into()); } Op::Add | Op::Subtract | Op::Multiply | Op::Divide => { - let b = self.pop()?; - let a = self.pop()?; + let b = self.pop_num()?; + let a = self.pop_num()?; let r = match instr { - Op::Add => Ok(a.val + b.val), - Op::Subtract => Ok(a.val - b.val), - Op::Multiply => Ok(a.val * b.val), - Op::Divide => Ok(a.val / b.val), + Op::Add => Ok(a + b), + Op::Subtract => Ok(a - b), + Op::Multiply => Ok(a * b), + Op::Divide => Ok(a / b), _ => Err(self.runtime_err("Op not implemented")), }?; self.push(r.into()) @@ -80,13 +95,18 @@ impl VM { } } - Ok(self.stack.is_empty().not().then_some(self.stack[self.stack.len() - 1])) + Ok(self + .stack + .is_empty() + .not() + .then_some(self.stack[self.stack.len() - 1])) } } #[cfg(test)] mod tests { use super::{Chunk, Op, Value, VM}; + use crate::vm::VMError; #[test] fn simple_arithmetic() { @@ -120,4 +140,19 @@ mod tests { assert_eq!(vm.stack[0], Value::from(3.1416)); } + + #[test] + fn runtime_type_error() { + let chunk = Chunk::new_with( + vec![Op::Constant { offset: 0 }, Op::Negate], + vec![], + vec![Value::Nil], + ); + + let mut vm = VM::new(); + assert_eq!( + vm.run(&chunk).unwrap_err(), + vm.type_err("Number", Value::Nil) + ); + } }