[rlox] Support Nil

This commit is contained in:
ctsk
2023-10-08 13:38:30 +02:00
parent 957463bf2f
commit 824ab73dcf
3 changed files with 97 additions and 28 deletions

View File

@@ -1,3 +1,4 @@
use crate::bc::Value::Number;
use std::convert::From; use std::convert::From;
use std::fmt; use std::fmt;
@@ -12,20 +13,24 @@ pub enum Op {
Divide, Divide,
} }
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub struct Value { pub enum Value {
pub val: f64, Nil,
Number(f64),
} }
impl fmt::Debug for Value { impl Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { pub fn as_num(self) -> Option<f64> {
write!(f, "{}", self.val) match self {
Number(val) => Some(val),
_ => None,
}
} }
} }
impl From<f64> for Value { impl From<f64> for Value {
fn from(value: f64) -> Self { fn from(value: f64) -> Self {
Value { val: value } Number(value)
} }
} }
@@ -48,7 +53,7 @@ impl Chunk {
Chunk { Chunk {
code, code,
debug_info, debug_info,
constants constants,
} }
} }
@@ -65,13 +70,18 @@ impl Chunk {
pub fn add_constant(&mut self, value: Value, line: usize) -> &mut Self { pub fn add_constant(&mut self, value: Value, line: usize) -> &mut Self {
self.constants.push(value); 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 { pub struct NamedChunk {
name: String, name: String,
chunk: Chunk chunk: Chunk,
} }
impl fmt::Debug for Chunk { impl fmt::Debug for Chunk {
@@ -128,7 +138,7 @@ impl fmt::Debug for TraceInfo<'_> {
} }
Op::Constant { offset } => { Op::Constant { offset } => {
f.debug_struct("Constant") f.debug_struct("Constant")
.field("val", &chunk.constants[offset].val) .field("val", &chunk.constants[offset])
.finish()?; .finish()?;
write!(f, "") write!(f, "")
} }

View File

@@ -2,7 +2,7 @@ use std::convert::identity;
use std::iter::Peekable; use std::iter::Peekable;
use std::str::CharIndices; use std::str::CharIndices;
use crate::bc::{Chunk, Op}; use crate::bc::{Chunk, Op, Value};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum TokenType { enum TokenType {
@@ -348,6 +348,12 @@ impl<'src> Parser<'src> {
_ => panic!("Could not parse number"), _ => panic!("Could not parse number"),
}; };
} }
Token {
ttype: TokenType::Nil,
span: _,
} => {
chunk.add_constant(Value::Nil, 0);
}
Token { Token {
ttype: TokenType::LeftParen, ttype: TokenType::LeftParen,
span: _, span: _,
@@ -356,7 +362,7 @@ impl<'src> Parser<'src> {
assert_eq!(self.scanner.next().unwrap().ttype, TokenType::RightParen) assert_eq!(self.scanner.next().unwrap().ttype, TokenType::RightParen)
} }
_ => panic!("Expected '-' or number"), _ => panic!("Expected '-' or number"),
} },
}; };
while let Some(op) = self.scanner.next_if(|token| { while let Some(op) = self.scanner.next_if(|token| {
@@ -475,7 +481,25 @@ mod tests {
Add, Add,
], ],
vec![], 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)); assert!(chunk.instr_eq(&expected));

View File

@@ -1,5 +1,6 @@
use std::ops::Not;
use crate::bc::{Chunk, Op, TraceInfo, Value}; use crate::bc::{Chunk, Op, TraceInfo, Value};
use std::ops::Not;
use std::rc::Rc;
pub struct VM { pub struct VM {
pub trace: bool, pub trace: bool,
@@ -7,10 +8,10 @@ pub struct VM {
pc: usize, pc: usize,
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub enum VMError { pub enum VMError {
Compile, Compile,
Runtime(&'static str, usize), Runtime(Rc<str>, usize),
} }
impl VM { impl VM {
@@ -23,7 +24,14 @@ impl VM {
} }
fn runtime_err(&self, msg: &'static str) -> VMError { 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) { fn push(&mut self, value: Value) {
@@ -33,7 +41,14 @@ impl VM {
fn pop(&mut self) -> Result<Value, VMError> { fn pop(&mut self) -> Result<Value, VMError> {
self.stack self.stack
.pop() .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<f64, VMError> {
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<Option<Value>, VMError> { pub fn run(&mut self, chunk: &Chunk) -> Result<Option<Value>, VMError> {
@@ -62,17 +77,17 @@ impl VM {
Op::Return => print!("{:?}", self.pop()?), Op::Return => print!("{:?}", self.pop()?),
Op::Constant { offset } => self.push(chunk.constants[offset]), Op::Constant { offset } => self.push(chunk.constants[offset]),
Op::Negate => { Op::Negate => {
let new_val = -self.pop()?.val; let new_val = -self.pop_num()?;
self.push(Value::from(new_val)); self.push(new_val.into());
} }
Op::Add | Op::Subtract | Op::Multiply | Op::Divide => { Op::Add | Op::Subtract | Op::Multiply | Op::Divide => {
let b = self.pop()?; let b = self.pop_num()?;
let a = self.pop()?; let a = self.pop_num()?;
let r = match instr { let r = match instr {
Op::Add => Ok(a.val + b.val), Op::Add => Ok(a + b),
Op::Subtract => Ok(a.val - b.val), Op::Subtract => Ok(a - b),
Op::Multiply => Ok(a.val * b.val), Op::Multiply => Ok(a * b),
Op::Divide => Ok(a.val / b.val), Op::Divide => Ok(a / b),
_ => Err(self.runtime_err("Op not implemented")), _ => Err(self.runtime_err("Op not implemented")),
}?; }?;
self.push(r.into()) 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)] #[cfg(test)]
mod tests { mod tests {
use super::{Chunk, Op, Value, VM}; use super::{Chunk, Op, Value, VM};
use crate::vm::VMError;
#[test] #[test]
fn simple_arithmetic() { fn simple_arithmetic() {
@@ -120,4 +140,19 @@ mod tests {
assert_eq!(vm.stack[0], Value::from(3.1416)); 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)
);
}
} }