[rlox] Add strings

This commit is contained in:
ctsk
2023-10-16 20:40:53 +02:00
parent 90e70c4cd2
commit 584536c059
4 changed files with 135 additions and 46 deletions

View File

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

View File

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

View File

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

View File

@@ -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()))
} }
} }