[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::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<f64> {
match self {
Number(val) => Some(val),
_ => None,
}
}
}
impl From<f64> 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, "")
}

View File

@@ -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));

View File

@@ -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<str>, 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<Value, VMError> {
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<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> {
@@ -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)
);
}
}