[rlox] Add strings
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
use crate::bc::Value::{Bool, Number};
|
use crate::bc::Value::{Bool, Number};
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::{Debug};
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Op {
|
pub enum Op {
|
||||||
Return,
|
Return,
|
||||||
@@ -19,28 +19,46 @@ pub enum Op {
|
|||||||
Greater,
|
Greater,
|
||||||
Less,
|
Less,
|
||||||
}
|
}
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Object {
|
||||||
|
String(String)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
Nil,
|
Nil,
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
Number(f64),
|
Number(f64),
|
||||||
|
Obj(Box<Object>)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
pub fn as_num(self) -> Option<f64> {
|
pub fn as_num(&self) -> Option<f64> {
|
||||||
match self {
|
match self {
|
||||||
Number(val) => Some(val),
|
&Number(val) => Some(val),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_bool(self) -> Option<bool> {
|
pub fn as_bool(&self) -> Option<bool> {
|
||||||
match self {
|
match self {
|
||||||
Bool(val) => Some(val),
|
&Bool(val) => Some(val),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
Value::Obj(obj) => {
|
||||||
|
match obj.as_ref() {
|
||||||
|
Object::String(string) => {
|
||||||
|
Some(string.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f64> for Value {
|
impl From<f64> for Value {
|
||||||
@@ -55,6 +73,18 @@ impl From<bool> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Value {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Value::Obj(Box::from(Object::String(value.to_string())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Value {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Value::Obj(Box::from(Object::String(value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Chunk {
|
pub struct Chunk {
|
||||||
pub code: Vec<Op>,
|
pub code: Vec<Op>,
|
||||||
pub debug_info: Vec<usize>,
|
pub debug_info: Vec<usize>,
|
||||||
@@ -164,3 +194,20 @@ impl fmt::Debug for TraceInfo<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod tests {
|
||||||
|
use crate::bc::{Object, Value};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_value_equality() {
|
||||||
|
let s1 = "bla5";
|
||||||
|
let s2 = "bla6";
|
||||||
|
|
||||||
|
let v1 = Value::from(s1);
|
||||||
|
let v2 = Value::from(s2);
|
||||||
|
let v3 = Value::from(s2);
|
||||||
|
|
||||||
|
assert_ne!(v1, v2);
|
||||||
|
assert_eq!(v2, v3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -318,47 +318,39 @@ impl<'src> Parser<'src> {
|
|||||||
fn _expression(&mut self, chunk: &mut Chunk, min_prec: Precedence) {
|
fn _expression(&mut self, chunk: &mut Chunk, min_prec: Precedence) {
|
||||||
match self.scanner.next() {
|
match self.scanner.next() {
|
||||||
None => panic!("Expected further tokens"),
|
None => panic!("Expected further tokens"),
|
||||||
Some(token) => match token {
|
Some(token) => match token.ttype {
|
||||||
Token {
|
TokenType::Minus | TokenType::Bang => {
|
||||||
ttype: ttype@(TokenType::Minus | TokenType::Bang),
|
|
||||||
span: _,
|
|
||||||
} => {
|
|
||||||
self._expression(chunk, Precedence::Unary);
|
self._expression(chunk, Precedence::Unary);
|
||||||
let op = match ttype {
|
let op = match token.ttype {
|
||||||
TokenType::Minus => Op::Negate,
|
TokenType::Minus => Op::Negate,
|
||||||
TokenType::Bang => Op::Not,
|
TokenType::Bang => Op::Not,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
chunk.add_op(op, 0);
|
chunk.add_op(op, 0);
|
||||||
}
|
},
|
||||||
Token {
|
TokenType::Number => {
|
||||||
ttype: TokenType::Number,
|
match token.span.parse::<f64>() {
|
||||||
span,
|
|
||||||
} => {
|
|
||||||
match span.parse::<f64>() {
|
|
||||||
Ok(c) => chunk.add_constant(c.into(), 0),
|
Ok(c) => chunk.add_constant(c.into(), 0),
|
||||||
_ => panic!("Could not parse number"),
|
_ => panic!("Could not parse number"),
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
Token {
|
TokenType::String => {
|
||||||
ttype: ttype@(TokenType::Nil | TokenType::True | TokenType::False),
|
let without_quotes = &token.span[1..(token.span.len() - 1)];
|
||||||
span: _,
|
chunk.add_constant(without_quotes.into(), 0);
|
||||||
} => {
|
},
|
||||||
let op = match ttype {
|
TokenType::Nil | TokenType::True | TokenType::False => {
|
||||||
|
let op = match token.ttype {
|
||||||
TokenType::Nil => Op::Nil,
|
TokenType::Nil => Op::Nil,
|
||||||
TokenType::True => Op::True,
|
TokenType::True => Op::True,
|
||||||
TokenType::False => Op::False,
|
TokenType::False => Op::False,
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
};
|
};
|
||||||
chunk.add_op(op, 0);
|
chunk.add_op(op, 0);
|
||||||
}
|
},
|
||||||
Token {
|
TokenType::LeftParen => {
|
||||||
ttype: TokenType::LeftParen,
|
|
||||||
span: _,
|
|
||||||
} => {
|
|
||||||
self._expression(chunk, Precedence::None);
|
self._expression(chunk, Precedence::None);
|
||||||
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"),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -463,6 +455,25 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_scan() {
|
||||||
|
let source = "\"hello world\"";
|
||||||
|
let scanner = Scanner::new(source);
|
||||||
|
let tokens: Vec<Token> = scanner.collect();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
tokens,
|
||||||
|
vec![
|
||||||
|
Token {
|
||||||
|
ttype: TokenType::String,
|
||||||
|
span: &source[0..=12]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(tokens[0].span, source);
|
||||||
|
}
|
||||||
|
|
||||||
fn test_parse_expression(source: &str, expected: &Chunk) {
|
fn test_parse_expression(source: &str, expected: &Chunk) {
|
||||||
let scanner = Scanner::new(source);
|
let scanner = Scanner::new(source);
|
||||||
let mut parser = Parser::new(scanner);
|
let mut parser = Parser::new(scanner);
|
||||||
|
|||||||
@@ -58,4 +58,14 @@ mod tests {
|
|||||||
let mut vm = VM::new();
|
let mut vm = VM::new();
|
||||||
vm.run(&chunk).unwrap();
|
vm.run(&chunk).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_handling() {
|
||||||
|
let source = "\"hello\" + \" \" + \"world\"";
|
||||||
|
let mut chunk = Chunk::new();
|
||||||
|
compile(source, &mut chunk);
|
||||||
|
let mut vm = VM::new();
|
||||||
|
let v = vm.run(&chunk).unwrap();
|
||||||
|
assert_eq!(v, Some("hello world".into()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::bc::{Chunk, Op, TraceInfo, Value};
|
use crate::bc::{Object, Chunk, Op, TraceInfo, Value};
|
||||||
use std::ops::Not;
|
use std::ops::Not;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -55,13 +55,6 @@ impl VM {
|
|||||||
.ok_or(self.type_err("Number", top_of_stack))
|
.ok_or(self.type_err("Number", top_of_stack))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop_bool(&mut self) -> Result<bool, VMError> {
|
|
||||||
let top_of_stack = self.pop()?;
|
|
||||||
top_of_stack
|
|
||||||
.as_bool()
|
|
||||||
.ok_or(self.type_err("Boolean", 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> {
|
||||||
while self.pc < chunk.code.len() {
|
while self.pc < chunk.code.len() {
|
||||||
let instr = chunk.code[self.pc];
|
let instr = chunk.code[self.pc];
|
||||||
@@ -86,7 +79,7 @@ impl VM {
|
|||||||
|
|
||||||
match instr {
|
match instr {
|
||||||
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].clone()),
|
||||||
Op::Nil => self.push(Value::Nil),
|
Op::Nil => self.push(Value::Nil),
|
||||||
Op::True => self.push(Value::Bool(true)),
|
Op::True => self.push(Value::Bool(true)),
|
||||||
Op::False => self.push(Value::Bool(false)),
|
Op::False => self.push(Value::Bool(false)),
|
||||||
@@ -103,11 +96,34 @@ impl VM {
|
|||||||
}?;
|
}?;
|
||||||
self.push(new_val.into());
|
self.push(new_val.into());
|
||||||
}
|
}
|
||||||
Op::Add | Op::Subtract | Op::Multiply | Op::Divide => {
|
Op::Add => {
|
||||||
|
let b = self.pop()?;
|
||||||
|
match b {
|
||||||
|
Value::Number(num) => {
|
||||||
|
let a = self.pop_num()?;
|
||||||
|
self.push(Value::from(num + a));
|
||||||
|
}
|
||||||
|
Value::Obj(ref obj) => {
|
||||||
|
match b.as_str() {
|
||||||
|
None => Err(self.type_err("String", b)),
|
||||||
|
Some(str_b) => {
|
||||||
|
let a = self.pop()?;
|
||||||
|
match a.as_str() {
|
||||||
|
Some(str_a) => {
|
||||||
|
Ok(self.push(Value::from(str_a.to_owned() + str_b)))
|
||||||
|
},
|
||||||
|
None => Err(self.type_err("String", a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?
|
||||||
|
}
|
||||||
|
_ => todo!()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Op::Subtract | Op::Multiply | Op::Divide => {
|
||||||
let b = self.pop_num()?;
|
let b = self.pop_num()?;
|
||||||
let a = self.pop_num()?;
|
let a = self.pop_num()?;
|
||||||
let r = match instr {
|
let r = match instr {
|
||||||
Op::Add => a + b,
|
|
||||||
Op::Subtract => a - b,
|
Op::Subtract => a - b,
|
||||||
Op::Multiply => a * b,
|
Op::Multiply => a * b,
|
||||||
Op::Divide => a / b,
|
Op::Divide => a / b,
|
||||||
@@ -115,17 +131,22 @@ impl VM {
|
|||||||
};
|
};
|
||||||
self.push(r.into())
|
self.push(r.into())
|
||||||
}
|
}
|
||||||
Op::Equal | Op::Greater | Op::Less => {
|
Op::Greater | Op::Less => {
|
||||||
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::Equal => a == b,
|
|
||||||
Op::Greater => a > b,
|
Op::Greater => a > b,
|
||||||
Op::Less => a < b,
|
Op::Less => a < b,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
self.push(r.into())
|
self.push(r.into())
|
||||||
}
|
}
|
||||||
|
Op::Equal => {
|
||||||
|
let b = self.pop()?;
|
||||||
|
let a = self.pop()?;
|
||||||
|
let r = a == b;
|
||||||
|
self.push(r.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +154,7 @@ impl VM {
|
|||||||
.stack
|
.stack
|
||||||
.is_empty()
|
.is_empty()
|
||||||
.not()
|
.not()
|
||||||
.then_some(self.stack[self.stack.len() - 1]))
|
.then_some(self.stack[self.stack.len() - 1].clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user