diff --git a/rlox/src/bc.rs b/rlox/src/bc.rs index 0e2f5db..be22379 100644 --- a/rlox/src/bc.rs +++ b/rlox/src/bc.rs @@ -1,8 +1,8 @@ -use crate::gc::{GcHandle, Object}; +use crate::gc::{GcHandle, Object, ObjectType}; use std::collections::LinkedList; use std::convert::From; -use std::fmt; use std::fmt::Debug; +use std::fmt::{self, Display}; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Op { @@ -20,6 +20,8 @@ pub enum Op { Equal, Greater, Less, + + Print, } #[derive(Clone, Debug, PartialEq)] @@ -71,6 +73,28 @@ impl From for Value { } } +impl Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Value::Nil => write!(f, "nil"), + Value::Bool(true) => write!(f, "true"), + Value::Bool(false) => write!(f, "false"), + Value::Number(number) => { + let stringified = number.to_string(); + match stringified.strip_suffix(".0") { + Some(integer) => write!(f, "{}", integer), + None => write!(f, "{}", stringified), + } + } + Value::Obj(object) => match object.get_otype() { + ObjectType::String => { + write!(f, "{}", object) + } + }, + } + } +} + pub struct Chunk { pub code: Vec, pub debug_info: Vec, @@ -84,16 +108,21 @@ impl Chunk { code: Vec::new(), debug_info: Vec::new(), constants: Vec::new(), - allocations: LinkedList::new() + allocations: LinkedList::new(), } } - pub fn new_with(code: Vec, debug_info: Vec, constants: Vec, allocations: LinkedList) -> Self { + pub fn new_with( + code: Vec, + debug_info: Vec, + constants: Vec, + allocations: LinkedList, + ) -> Self { Chunk { code, debug_info, constants, - allocations + allocations, } } @@ -179,7 +208,7 @@ impl fmt::Debug for TraceInfo<'_> { .finish()?; write!(f, "") } - _ => write!(f, "{:?}", op) + _ => write!(f, "{:?}", op), } } } @@ -188,8 +217,8 @@ mod tests { #[test] fn string_value_equality() { - use crate::gc::allocate_string; use crate::bc::Value; + use crate::gc::allocate_string; let s1 = "bla5"; let s2 = "bla6"; diff --git a/rlox/src/gc.rs b/rlox/src/gc.rs index 4486a96..cf28f55 100644 --- a/rlox/src/gc.rs +++ b/rlox/src/gc.rs @@ -1,4 +1,7 @@ -use std::{alloc::{alloc, dealloc, Layout, LayoutError}, fmt}; +use std::{ + alloc::{alloc, dealloc, Layout, LayoutError}, + fmt::{self, Display}, +}; #[derive(PartialEq, Eq, Clone, Copy)] #[repr(usize)] @@ -32,13 +35,20 @@ pub struct Object { ptr: *mut ObjectHeader, } -impl Object { - pub fn get_otype(&self) -> ObjectType { - unsafe { - (*self.ptr).otype +impl Display for Object { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.get_otype() { + ObjectType::String => { + write!(f, "{}", ObjString::as_str(self.ptr as *mut ObjStringHeader)) + } } } +} +impl Object { + pub fn get_otype(&self) -> ObjectType { + unsafe { (*self.ptr).otype } + } } impl fmt::Debug for Object { @@ -46,14 +56,10 @@ impl fmt::Debug for Object { match self.get_otype() { ObjectType::String => { let string = self.ptr as *mut ObjStringHeader; - let data: &[u8] = ObjString::as_slice(string); - write!( - f, - "STR {} {:?}", - data.len(), - &data[..8.min(data.len())], - ) - }, + let data = ObjString::as_str(string); + + write!(f, "STR {} {:?}", data.len(), &data[..8.min(data.len())],) + } } } } @@ -78,11 +84,11 @@ impl PartialEq for Object { return false; } - let slice = ObjString::as_slice(header); - let other_slice = ObjString::as_slice(other_header); + let slice = ObjString::as_str(header); + let other_slice = ObjString::as_str(other_header); slice == other_slice - }, + } } } } @@ -101,24 +107,29 @@ impl ObjString { Ok((layout.pad_to_align(), offset)) } - fn as_slice<'a>(ptr: *mut ObjStringHeader) -> &'a [u8] { + fn as_bytes<'a>(ptr: *const ObjStringHeader) -> &'a [u8] { unsafe { - std::slice::from_raw_parts( - (ptr as *mut u8).offset(data_offset() as isize), - (*ptr).len - ) + std::slice::from_raw_parts((ptr as *mut u8).offset(data_offset() as isize), (*ptr).len) } } + + fn as_str<'a>(ptr: *const ObjStringHeader) -> &'a str { + unsafe { std::str::from_utf8_unchecked(ObjString::as_bytes(ptr)) } + } } -pub unsafe fn allocate_string_obj<'a>(length: usize) -> Result<(GcHandle, &'a mut [u8]), LayoutError> { +pub unsafe fn allocate_string_obj<'a>( + length: usize, +) -> Result<(GcHandle, &'a mut [u8]), LayoutError> { let (layout, offset) = ObjString::layout(length)?; let allocation = alloc(layout); let data_ptr = allocation.offset(offset as isize); let header = allocation as *mut ObjStringHeader; (*header).len = length; (*header).object_header.otype = ObjectType::String; - let object = Object { ptr: header as *mut ObjectHeader }; + let object = Object { + ptr: header as *mut ObjectHeader, + }; let str = std::slice::from_raw_parts_mut(data_ptr, length); Ok((GcHandle { object }, str)) } @@ -132,11 +143,11 @@ pub unsafe fn allocate_string(content: &str) -> Result { pub unsafe fn concat_string(a: Object, b: Object) -> Result { let a_head = a.ptr as *mut ObjStringHeader; let b_head = b.ptr as *mut ObjStringHeader; - let a_data = ObjString::as_slice(a_head); - let b_data = ObjString::as_slice(b_head); + let a_data = ObjString::as_bytes(a_head); + let b_data = ObjString::as_bytes(b_head); let new_len = a_data.len() + b_data.len(); - let (gc_handle, slice) = allocate_string_obj(new_len)?; + let (gc_handle, slice) = allocate_string_obj(new_len)?; slice[..a_data.len()].copy_from_slice(a_data); slice[a_data.len()..].copy_from_slice(b_data); @@ -158,7 +169,7 @@ unsafe fn deallocate_object(object: Object) { #[derive(Debug, Clone, PartialEq)] pub struct GcHandle { - object: Object + object: Object, } impl Drop for GcHandle { diff --git a/rlox/src/lc.rs b/rlox/src/lc.rs index 2734f6e..85ed9f0 100644 --- a/rlox/src/lc.rs +++ b/rlox/src/lc.rs @@ -1,6 +1,6 @@ -use std::{collections::HashMap, convert::identity}; use std::iter::Peekable; use std::str::CharIndices; +use std::{collections::HashMap, convert::identity}; use crate::bc::{Chunk, Op}; use crate::gc::allocate_string; @@ -234,7 +234,10 @@ impl<'src> Iterator for Scanner<'src> { '+' => make_simple_token(self, TokenType::Plus), ';' => make_simple_token(self, TokenType::Semicolon), '/' => match self.consume_if_eq('/') { - Some(_) => { self.scan_comment(); self.next() }, + Some(_) => { + self.scan_comment(); + self.next() + } None => make_simple_token(self, TokenType::Slash), }, '*' => make_simple_token(self, TokenType::Star), @@ -288,7 +291,14 @@ enum Precedence { Primary, } +enum ParseError<'src> { + InvalidNumber(Token<'src>) +} + +type Result<'src, T> = std::result::Result>; + impl<'src> Parser<'src> { + fn new(sc: Scanner<'src>) -> Self { Parser { scanner: sc.into_iter().peekable(), @@ -330,50 +340,50 @@ impl<'src> Parser<'src> { _ => unreachable!(), }; chunk.add_op(op, 0); - }, + } TokenType::Number => { match token.span.parse::() { Ok(c) => chunk.add_constant(c.into(), 0), _ => panic!("Could not parse number"), }; - }, + } TokenType::String => { let without_quotes = &token.span[1..(token.span.len() - 1)]; match self.intern_table.get(without_quotes) { Some(&index) => { - chunk.add_op( - Op::Constant { - offset: index, - }, - 0 - ); - }, + chunk.add_op(Op::Constant { offset: index }, 0); + } None => { let object = unsafe { allocate_string(without_quotes) }.unwrap(); chunk.add_constant(object.get_object().into(), 0); - self.intern_table.insert(without_quotes, chunk.constants.len() as u8 - 1); + self.intern_table + .insert(without_quotes, chunk.constants.len() as u8 - 1); chunk.allocations.push_front(object); - }, + } }; - }, + } TokenType::Nil | TokenType::True | TokenType::False => { let op = match token.ttype { TokenType::Nil => Op::Nil, TokenType::True => Op::True, TokenType::False => Op::False, - _ => unreachable!() + _ => unreachable!(), }; chunk.add_op(op, 0); - }, + } TokenType::LeftParen => { self._expression(chunk, Precedence::None); assert_eq!(self.scanner.next().unwrap().ttype, TokenType::RightParen) - }, + } _ => panic!("Expected '-' or number"), }, }; while let Some(op) = self.scanner.next_if(|token| { + if token.ttype == TokenType::Semicolon { + return false; + } + let op_prec = Self::precedence(token.ttype); if op_prec == min_prec { match Self::associativity(min_prec) { @@ -404,17 +414,62 @@ impl<'src> Parser<'src> { _ => todo!(), }; } + } pub fn expression(&mut self, chunk: &mut Chunk) { self._expression(chunk, Precedence::None) } + + pub fn must_consume(&mut self, ttype: TokenType) -> Option { + self.scanner.next_if( + |tok| tok.ttype == ttype + ).or_else( + || panic!() + ) + } + + pub fn print_statement(&mut self, chunk: &mut Chunk) { + self.must_consume(TokenType::Print).unwrap(); + self.expression(chunk); + chunk.add_op(Op::Print, 0); + self.must_consume(TokenType::Semicolon).unwrap(); + } + + pub fn expr_statement(&mut self, chunk: &mut Chunk) { + self.expression(chunk); + self.must_consume(TokenType::Semicolon).unwrap(); + } + + pub fn statement(&mut self, chunk: &mut Chunk) { + match self.scanner.peek().unwrap().ttype { + TokenType::Print => self.print_statement(chunk), + _ => self.expr_statement(chunk), + } + } + + pub fn declaration(&mut self, chunk: &mut Chunk) { + self.statement(chunk); + } + + pub fn compile(&mut self, chunk: &mut Chunk) { + while let Some(_) = self.scanner.peek() { + self.declaration(chunk); + } + } +} + +#[cfg(test)] +pub fn compile_expr(source: &str, chunk: &mut Chunk) { + let scanner = Scanner::new(source); + let mut parser = Parser::new(scanner); + parser.expression(chunk); } pub fn compile(source: &str, chunk: &mut Chunk) { let scanner = Scanner::new(source); let mut parser = Parser::new(scanner); - parser.expression(chunk); + parser.compile(chunk); } #[cfg(test)] @@ -508,12 +563,10 @@ mod tests { assert_eq!( tokens, - vec![ - Token { - ttype: TokenType::String, - span: &source[0..=12] - } - ] + vec![Token { + ttype: TokenType::String, + span: &source[0..=12] + }] ); assert_eq!(tokens[0].span, source); @@ -527,6 +580,14 @@ mod tests { assert!(chunk.instr_eq(expected)); } + fn test_parse_program(source: &str, expected: &Chunk) { + let scanner = Scanner::new(source); + let mut parser = Parser::new(scanner); + let mut chunk = Chunk::new(); + parser.compile(&mut chunk); + assert!(chunk.instr_eq(expected)); + } + #[test] fn test_parser() { let source = "1 + 1 * (2 + 1)"; @@ -553,12 +614,7 @@ mod tests { fn parse_nil() { let source = "nil + nil"; use crate::bc::Op::*; - let expected = Chunk::new_with( - vec![Nil, Nil, Add], - vec![], - vec![], - LinkedList::new(), - ); + let expected = Chunk::new_with(vec![Nil, Nil, Add], vec![], vec![], LinkedList::new()); test_parse_expression(source, &expected); } @@ -583,12 +639,9 @@ mod tests { use crate::bc::Op::*; let expected = Chunk::new_with( vec![ - False, Not, True, Not, True, Less, Not, - False, Greater, Not, - True, Greater, - False, Less, - Equal, - True, Equal, Not], + False, Not, True, Not, True, Less, Not, False, Greater, Not, True, Greater, False, + Less, Equal, True, Equal, Not, + ], vec![], vec![], LinkedList::new(), @@ -609,4 +662,49 @@ mod tests { assert_eq!(chunk.constants.len(), 1); } + #[test] + fn basic_print_statement() { + let source = "print 1 + 1;"; + use crate::bc::Op::*; + let expected = Chunk::new_with( + vec![Constant { offset: 0 }, Constant { offset: 1 }, Add, Print], + vec![], + vec![Value::from(1.0), Value::from(1.0)], + LinkedList::new(), + ); + + test_parse_program(source, &expected); + } + + #[test] + fn basic_print_string_statement() { + let source = "print \"string\";"; + let allocation = unsafe { allocate_string("string").unwrap() }; + let object = allocation.get_object(); + let mut allocations = LinkedList::new(); + allocations.push_front(allocation); + use crate::bc::Op::*; + let expected = Chunk::new_with( + vec![Constant { offset: 0 }, Print], + vec![], + vec![Value::from(object)], + allocations, + ); + + test_parse_program(source, &expected); + } + + #[test] + fn basic_expr_statement() { + let source = "1 / 1;"; + use crate::bc::Op::*; + let expected = Chunk::new_with( + vec![Constant { offset: 0 }, Constant { offset: 1 }, Divide], + vec![], + vec![Value::from(1.0), Value::from(1.0)], + LinkedList::new(), + ); + + test_parse_program(source, &expected); + } } diff --git a/rlox/src/main.rs b/rlox/src/main.rs index 5501882..ff3ae22 100644 --- a/rlox/src/main.rs +++ b/rlox/src/main.rs @@ -9,6 +9,7 @@ use std::io; use bc::Chunk; use vm::VM; + fn repl() { let mut buffer = String::new(); @@ -21,7 +22,7 @@ fn repl() { lc::compile(buffer.as_str(), &mut chunk); let mut vm = VM::new(); vm.set_trace(do_trace); - let result = vm.run(&chunk); + let result = vm.stdrun(&chunk); println!("{:?}", result); buffer.clear(); } @@ -49,26 +50,40 @@ fn main() { #[cfg(test)] mod tests { - use crate::{bc::{Chunk, Value}, gc::allocate_string, lc::compile, vm::VM}; + use std::io::BufWriter; + + use crate::{bc::{Chunk, Value}, gc::allocate_string, lc::{compile, compile_expr}, vm::VM}; #[test] fn test_compile_and_run_pi_math() { let source = "-(3 * 7 * 11 * 17) / -(500 + 1000 - 250)"; let mut chunk = Chunk::new(); - compile(source, &mut chunk); + compile_expr(source, &mut chunk); let mut vm = VM::new(); - vm.run(&chunk).unwrap(); + vm.stdrun(&chunk).unwrap(); } #[test] fn string_concatenation() { let source = "\"hello\" + \" \" + \"world\""; let mut chunk = Chunk::new(); - compile(source, &mut chunk); + compile_expr(source, &mut chunk); let mut vm = VM::new(); - let (result, _allocs) = vm.run(&chunk).unwrap().unwrap(); + let (result, _allocs) = vm.stdrun(&chunk).unwrap().unwrap(); let target_alloc = unsafe { allocate_string("hello world").unwrap() }; let target = Value::from(target_alloc.get_object()); assert_eq!(result, target); } + + #[test] + fn print_hello_world() { + let source = "print \"hello\" + \" \" + \"world\";"; + let mut chunk = Chunk::new(); + let mut vm = VM::new(); + compile(source, &mut chunk); + let mut buf = BufWriter::new(Vec::new()); + vm.run(&chunk, &mut buf).unwrap(); + let stdoutput = String::from_utf8(buf.into_inner().unwrap()).unwrap(); + assert_eq!(stdoutput, "hello world\n"); + } } diff --git a/rlox/src/vm.rs b/rlox/src/vm.rs index f2aa083..4b2e72f 100644 --- a/rlox/src/vm.rs +++ b/rlox/src/vm.rs @@ -1,6 +1,7 @@ use crate::bc::{Chunk, Op, TraceInfo, Value}; use crate::gc::{concat_string, GcHandle, ObjectType}; use std::collections::LinkedList; +use std::io; use std::rc::Rc; pub struct VM { @@ -56,7 +57,18 @@ impl VM { .ok_or(self.type_err("Number", top_of_stack)) } - pub fn run(&mut self, chunk: &Chunk) -> Result)>, VMError> { + pub fn stdrun( + &mut self, + chunk: &Chunk, + ) -> Result)>, VMError> { + return self.run(chunk, &mut io::stdout()); + } + + pub fn run( + &mut self, + chunk: &Chunk, + output: &mut Output, + ) -> Result)>, VMError> { let mut allocations: LinkedList = LinkedList::new(); while self.pc < chunk.code.len() { @@ -106,31 +118,28 @@ impl VM { let a = self.pop_num()?; self.push(Value::from(num + a)); } - Value::Obj(b) => { - match b.get_otype() { - ObjectType::String => { - let a = self.pop()?; - match a { - Value::Obj(a) => { - match a.get_otype() { - ObjectType::String => { - let new_obj = unsafe { - concat_string(a, b).unwrap() - }; - self.push(Value::from(new_obj.get_object())); - allocations.push_front(new_obj); - Ok(()) - }, - } - }, - _ => { - Err(self.type_err("String", a)) + Value::Obj(b) => match b.get_otype() { + ObjectType::String => { + let a = self.pop()?; + match a { + Value::Obj(a) => match a.get_otype() { + ObjectType::String => { + let new_obj = unsafe { concat_string(a, b).unwrap() }; + self.push(Value::from(new_obj.get_object())); + allocations.push_front(new_obj); + Ok(()) } - }? - }, + }, + _ => Err(self.type_err("String", a)), + }? } + }, + _ => { + return Err(VMError::Runtime( + "Operands of + need to be numbers or strings".into(), + self.pc, + )) } - _ => return Err(VMError::Runtime("Operands of + need to be numbers or strings".into(), self.pc)) }; } Op::Subtract | Op::Multiply | Op::Divide => { @@ -146,7 +155,7 @@ impl VM { } Op::Greater | Op::Less => { let b = self.pop_num()?; - let a = self.pop_num()?; + let a = self.pop_num()?; let r = match instr { Op::Greater => a > b, Op::Less => a < b, @@ -160,15 +169,21 @@ impl VM { let r = a == b; self.push(r.into()) } + Op::Print => { + let value = self.pop()?; + writeln!(output, "{}", value) + .map_err(|_| VMError::Runtime("Failed to print".into(), self.pc))? + } } } match self.stack.pop() { None => Ok(None), Some(result_value) => { - let escaping_allocs = allocations.into_iter().filter( - |handle| Value::from(handle.get_object()) == result_value - ).collect(); + let escaping_allocs = allocations + .into_iter() + .filter(|handle| Value::from(handle.get_object()) == result_value) + .collect(); Ok(Some((result_value, escaping_allocs))) } @@ -211,7 +226,7 @@ mod tests { ); let mut vm = VM::new(); - let (result, allocs) = vm.run(&chunk).unwrap().unwrap(); + let (result, allocs) = vm.stdrun(&chunk).unwrap().unwrap(); assert_eq!(result, Value::from(3.1416)); assert!(vm.stack.is_empty()); @@ -220,16 +235,11 @@ mod tests { #[test] fn nil_error() { - let chunk = Chunk::new_with( - vec![Op::Nil, Op::Negate], - vec![], - vec![], - LinkedList::new(), - ); + let chunk = Chunk::new_with(vec![Op::Nil, Op::Negate], vec![], vec![], LinkedList::new()); let mut vm = VM::new(); assert_eq!( - vm.run(&chunk).unwrap_err(), + vm.stdrun(&chunk).unwrap_err(), vm.type_err("Number", Value::Nil) ); } @@ -243,7 +253,7 @@ mod tests { LinkedList::new(), ); let mut vm = VM::new(); - let (result, allocs) = vm.run(&chunk)?.unwrap(); + let (result, allocs) = vm.stdrun(&chunk)?.unwrap(); assert_eq!(result, true.into()); assert!(vm.stack.is_empty()); @@ -254,14 +264,9 @@ mod tests { #[test] fn not_nil_is_true() { - let chunk = Chunk::new_with( - vec![Op::Nil, Op::Not], - vec![], - vec![], - LinkedList::new(), - ); + let chunk = Chunk::new_with(vec![Op::Nil, Op::Not], vec![], vec![], LinkedList::new()); let mut vm = VM::new(); - let (result, allocs) = vm.run(&chunk).unwrap().unwrap(); + let (result, allocs) = vm.stdrun(&chunk).unwrap().unwrap(); assert_eq!(result, true.into()); assert!(vm.stack.is_empty());