[rlox] Support Nil
This commit is contained in:
@@ -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, "")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user