[rlox] Implement Print Statements
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
use crate::gc::{GcHandle, Object};
|
use crate::gc::{GcHandle, Object, ObjectType};
|
||||||
use std::collections::LinkedList;
|
use std::collections::LinkedList;
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::fmt;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Op {
|
pub enum Op {
|
||||||
@@ -20,6 +20,8 @@ pub enum Op {
|
|||||||
Equal,
|
Equal,
|
||||||
Greater,
|
Greater,
|
||||||
Less,
|
Less,
|
||||||
|
|
||||||
|
Print,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
@@ -71,6 +73,28 @@ impl From<Object> 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 struct Chunk {
|
||||||
pub code: Vec<Op>,
|
pub code: Vec<Op>,
|
||||||
pub debug_info: Vec<usize>,
|
pub debug_info: Vec<usize>,
|
||||||
@@ -84,16 +108,21 @@ impl Chunk {
|
|||||||
code: Vec::new(),
|
code: Vec::new(),
|
||||||
debug_info: Vec::new(),
|
debug_info: Vec::new(),
|
||||||
constants: Vec::new(),
|
constants: Vec::new(),
|
||||||
allocations: LinkedList::new()
|
allocations: LinkedList::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with(code: Vec<Op>, debug_info: Vec<usize>, constants: Vec<Value>, allocations: LinkedList<GcHandle>) -> Self {
|
pub fn new_with(
|
||||||
|
code: Vec<Op>,
|
||||||
|
debug_info: Vec<usize>,
|
||||||
|
constants: Vec<Value>,
|
||||||
|
allocations: LinkedList<GcHandle>,
|
||||||
|
) -> Self {
|
||||||
Chunk {
|
Chunk {
|
||||||
code,
|
code,
|
||||||
debug_info,
|
debug_info,
|
||||||
constants,
|
constants,
|
||||||
allocations
|
allocations,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +208,7 @@ impl fmt::Debug for TraceInfo<'_> {
|
|||||||
.finish()?;
|
.finish()?;
|
||||||
write!(f, "")
|
write!(f, "")
|
||||||
}
|
}
|
||||||
_ => write!(f, "{:?}", op)
|
_ => write!(f, "{:?}", op),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,8 +217,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_value_equality() {
|
fn string_value_equality() {
|
||||||
use crate::gc::allocate_string;
|
|
||||||
use crate::bc::Value;
|
use crate::bc::Value;
|
||||||
|
use crate::gc::allocate_string;
|
||||||
|
|
||||||
let s1 = "bla5";
|
let s1 = "bla5";
|
||||||
let s2 = "bla6";
|
let s2 = "bla6";
|
||||||
|
|||||||
@@ -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)]
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
#[repr(usize)]
|
#[repr(usize)]
|
||||||
@@ -32,13 +35,20 @@ pub struct Object {
|
|||||||
ptr: *mut ObjectHeader,
|
ptr: *mut ObjectHeader,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object {
|
impl Display for Object {
|
||||||
pub fn get_otype(&self) -> ObjectType {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
unsafe {
|
match self.get_otype() {
|
||||||
(*self.ptr).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 {
|
impl fmt::Debug for Object {
|
||||||
@@ -46,14 +56,10 @@ impl fmt::Debug for Object {
|
|||||||
match self.get_otype() {
|
match self.get_otype() {
|
||||||
ObjectType::String => {
|
ObjectType::String => {
|
||||||
let string = self.ptr as *mut ObjStringHeader;
|
let string = self.ptr as *mut ObjStringHeader;
|
||||||
let data: &[u8] = ObjString::as_slice(string);
|
let data = ObjString::as_str(string);
|
||||||
write!(
|
|
||||||
f,
|
write!(f, "STR {} {:?}", data.len(), &data[..8.min(data.len())],)
|
||||||
"STR {} {:?}",
|
}
|
||||||
data.len(),
|
|
||||||
&data[..8.min(data.len())],
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,11 +84,11 @@ impl PartialEq for Object {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let slice = ObjString::as_slice(header);
|
let slice = ObjString::as_str(header);
|
||||||
let other_slice = ObjString::as_slice(other_header);
|
let other_slice = ObjString::as_str(other_header);
|
||||||
|
|
||||||
slice == other_slice
|
slice == other_slice
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,24 +107,29 @@ impl ObjString {
|
|||||||
Ok((layout.pad_to_align(), offset))
|
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 {
|
unsafe {
|
||||||
std::slice::from_raw_parts(
|
std::slice::from_raw_parts((ptr as *mut u8).offset(data_offset() as isize), (*ptr).len)
|
||||||
(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 (layout, offset) = ObjString::layout(length)?;
|
||||||
let allocation = alloc(layout);
|
let allocation = alloc(layout);
|
||||||
let data_ptr = allocation.offset(offset as isize);
|
let data_ptr = allocation.offset(offset as isize);
|
||||||
let header = allocation as *mut ObjStringHeader;
|
let header = allocation as *mut ObjStringHeader;
|
||||||
(*header).len = length;
|
(*header).len = length;
|
||||||
(*header).object_header.otype = ObjectType::String;
|
(*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);
|
let str = std::slice::from_raw_parts_mut(data_ptr, length);
|
||||||
Ok((GcHandle { object }, str))
|
Ok((GcHandle { object }, str))
|
||||||
}
|
}
|
||||||
@@ -132,11 +143,11 @@ pub unsafe fn allocate_string(content: &str) -> Result<GcHandle, LayoutError> {
|
|||||||
pub unsafe fn concat_string(a: Object, b: Object) -> Result<GcHandle, LayoutError> {
|
pub unsafe fn concat_string(a: Object, b: Object) -> Result<GcHandle, LayoutError> {
|
||||||
let a_head = a.ptr as *mut ObjStringHeader;
|
let a_head = a.ptr as *mut ObjStringHeader;
|
||||||
let b_head = b.ptr as *mut ObjStringHeader;
|
let b_head = b.ptr as *mut ObjStringHeader;
|
||||||
let a_data = ObjString::as_slice(a_head);
|
let a_data = ObjString::as_bytes(a_head);
|
||||||
let b_data = ObjString::as_slice(b_head);
|
let b_data = ObjString::as_bytes(b_head);
|
||||||
let new_len = a_data.len() + b_data.len();
|
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(a_data);
|
||||||
slice[a_data.len()..].copy_from_slice(b_data);
|
slice[a_data.len()..].copy_from_slice(b_data);
|
||||||
@@ -158,7 +169,7 @@ unsafe fn deallocate_object(object: Object) {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct GcHandle {
|
pub struct GcHandle {
|
||||||
object: Object
|
object: Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for GcHandle {
|
impl Drop for GcHandle {
|
||||||
|
|||||||
170
rlox/src/lc.rs
170
rlox/src/lc.rs
@@ -1,6 +1,6 @@
|
|||||||
use std::{collections::HashMap, convert::identity};
|
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::str::CharIndices;
|
use std::str::CharIndices;
|
||||||
|
use std::{collections::HashMap, convert::identity};
|
||||||
|
|
||||||
use crate::bc::{Chunk, Op};
|
use crate::bc::{Chunk, Op};
|
||||||
use crate::gc::allocate_string;
|
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::Plus),
|
||||||
';' => make_simple_token(self, TokenType::Semicolon),
|
';' => make_simple_token(self, TokenType::Semicolon),
|
||||||
'/' => match self.consume_if_eq('/') {
|
'/' => match self.consume_if_eq('/') {
|
||||||
Some(_) => { self.scan_comment(); self.next() },
|
Some(_) => {
|
||||||
|
self.scan_comment();
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
None => make_simple_token(self, TokenType::Slash),
|
None => make_simple_token(self, TokenType::Slash),
|
||||||
},
|
},
|
||||||
'*' => make_simple_token(self, TokenType::Star),
|
'*' => make_simple_token(self, TokenType::Star),
|
||||||
@@ -288,7 +291,14 @@ enum Precedence {
|
|||||||
Primary,
|
Primary,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ParseError<'src> {
|
||||||
|
InvalidNumber(Token<'src>)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<'src, T> = std::result::Result<T, ParseError<'src>>;
|
||||||
|
|
||||||
impl<'src> Parser<'src> {
|
impl<'src> Parser<'src> {
|
||||||
|
|
||||||
fn new(sc: Scanner<'src>) -> Self {
|
fn new(sc: Scanner<'src>) -> Self {
|
||||||
Parser {
|
Parser {
|
||||||
scanner: sc.into_iter().peekable(),
|
scanner: sc.into_iter().peekable(),
|
||||||
@@ -330,50 +340,50 @@ impl<'src> Parser<'src> {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
chunk.add_op(op, 0);
|
chunk.add_op(op, 0);
|
||||||
},
|
}
|
||||||
TokenType::Number => {
|
TokenType::Number => {
|
||||||
match token.span.parse::<f64>() {
|
match token.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"),
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
TokenType::String => {
|
TokenType::String => {
|
||||||
let without_quotes = &token.span[1..(token.span.len() - 1)];
|
let without_quotes = &token.span[1..(token.span.len() - 1)];
|
||||||
match self.intern_table.get(without_quotes) {
|
match self.intern_table.get(without_quotes) {
|
||||||
Some(&index) => {
|
Some(&index) => {
|
||||||
chunk.add_op(
|
chunk.add_op(Op::Constant { offset: index }, 0);
|
||||||
Op::Constant {
|
}
|
||||||
offset: index,
|
|
||||||
},
|
|
||||||
0
|
|
||||||
);
|
|
||||||
},
|
|
||||||
None => {
|
None => {
|
||||||
let object = unsafe { allocate_string(without_quotes) }.unwrap();
|
let object = unsafe { allocate_string(without_quotes) }.unwrap();
|
||||||
chunk.add_constant(object.get_object().into(), 0);
|
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);
|
chunk.allocations.push_front(object);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
TokenType::Nil | TokenType::True | TokenType::False => {
|
TokenType::Nil | TokenType::True | TokenType::False => {
|
||||||
let op = match token.ttype {
|
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);
|
||||||
},
|
}
|
||||||
TokenType::LeftParen => {
|
TokenType::LeftParen => {
|
||||||
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"),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
while let Some(op) = self.scanner.next_if(|token| {
|
while let Some(op) = self.scanner.next_if(|token| {
|
||||||
|
if token.ttype == TokenType::Semicolon {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let op_prec = Self::precedence(token.ttype);
|
let op_prec = Self::precedence(token.ttype);
|
||||||
if op_prec == min_prec {
|
if op_prec == min_prec {
|
||||||
match Self::associativity(min_prec) {
|
match Self::associativity(min_prec) {
|
||||||
@@ -404,17 +414,62 @@ impl<'src> Parser<'src> {
|
|||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expression(&mut self, chunk: &mut Chunk) {
|
pub fn expression(&mut self, chunk: &mut Chunk) {
|
||||||
self._expression(chunk, Precedence::None)
|
self._expression(chunk, Precedence::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn must_consume(&mut self, ttype: TokenType) -> Option<Token> {
|
||||||
|
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) {
|
pub fn compile(source: &str, chunk: &mut Chunk) {
|
||||||
let scanner = Scanner::new(source);
|
let scanner = Scanner::new(source);
|
||||||
let mut parser = Parser::new(scanner);
|
let mut parser = Parser::new(scanner);
|
||||||
parser.expression(chunk);
|
parser.compile(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -508,12 +563,10 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tokens,
|
tokens,
|
||||||
vec![
|
vec![Token {
|
||||||
Token {
|
ttype: TokenType::String,
|
||||||
ttype: TokenType::String,
|
span: &source[0..=12]
|
||||||
span: &source[0..=12]
|
}]
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(tokens[0].span, source);
|
assert_eq!(tokens[0].span, source);
|
||||||
@@ -527,6 +580,14 @@ mod tests {
|
|||||||
assert!(chunk.instr_eq(expected));
|
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]
|
#[test]
|
||||||
fn test_parser() {
|
fn test_parser() {
|
||||||
let source = "1 + 1 * (2 + 1)";
|
let source = "1 + 1 * (2 + 1)";
|
||||||
@@ -553,12 +614,7 @@ mod tests {
|
|||||||
fn parse_nil() {
|
fn parse_nil() {
|
||||||
let source = "nil + nil";
|
let source = "nil + nil";
|
||||||
use crate::bc::Op::*;
|
use crate::bc::Op::*;
|
||||||
let expected = Chunk::new_with(
|
let expected = Chunk::new_with(vec![Nil, Nil, Add], vec![], vec![], LinkedList::new());
|
||||||
vec![Nil, Nil, Add],
|
|
||||||
vec![],
|
|
||||||
vec![],
|
|
||||||
LinkedList::new(),
|
|
||||||
);
|
|
||||||
|
|
||||||
test_parse_expression(source, &expected);
|
test_parse_expression(source, &expected);
|
||||||
}
|
}
|
||||||
@@ -583,12 +639,9 @@ mod tests {
|
|||||||
use crate::bc::Op::*;
|
use crate::bc::Op::*;
|
||||||
let expected = Chunk::new_with(
|
let expected = Chunk::new_with(
|
||||||
vec![
|
vec![
|
||||||
False, Not, True, Not, True, Less, Not,
|
False, Not, True, Not, True, Less, Not, False, Greater, Not, True, Greater, False,
|
||||||
False, Greater, Not,
|
Less, Equal, True, Equal, Not,
|
||||||
True, Greater,
|
],
|
||||||
False, Less,
|
|
||||||
Equal,
|
|
||||||
True, Equal, Not],
|
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
LinkedList::new(),
|
LinkedList::new(),
|
||||||
@@ -609,4 +662,49 @@ mod tests {
|
|||||||
assert_eq!(chunk.constants.len(), 1);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use std::io;
|
|||||||
use bc::Chunk;
|
use bc::Chunk;
|
||||||
use vm::VM;
|
use vm::VM;
|
||||||
|
|
||||||
|
|
||||||
fn repl() {
|
fn repl() {
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ fn repl() {
|
|||||||
lc::compile(buffer.as_str(), &mut chunk);
|
lc::compile(buffer.as_str(), &mut chunk);
|
||||||
let mut vm = VM::new();
|
let mut vm = VM::new();
|
||||||
vm.set_trace(do_trace);
|
vm.set_trace(do_trace);
|
||||||
let result = vm.run(&chunk);
|
let result = vm.stdrun(&chunk);
|
||||||
println!("{:?}", result);
|
println!("{:?}", result);
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
}
|
}
|
||||||
@@ -49,26 +50,40 @@ fn main() {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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]
|
#[test]
|
||||||
fn test_compile_and_run_pi_math() {
|
fn test_compile_and_run_pi_math() {
|
||||||
let source = "-(3 * 7 * 11 * 17) / -(500 + 1000 - 250)";
|
let source = "-(3 * 7 * 11 * 17) / -(500 + 1000 - 250)";
|
||||||
let mut chunk = Chunk::new();
|
let mut chunk = Chunk::new();
|
||||||
compile(source, &mut chunk);
|
compile_expr(source, &mut chunk);
|
||||||
let mut vm = VM::new();
|
let mut vm = VM::new();
|
||||||
vm.run(&chunk).unwrap();
|
vm.stdrun(&chunk).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_concatenation() {
|
fn string_concatenation() {
|
||||||
let source = "\"hello\" + \" \" + \"world\"";
|
let source = "\"hello\" + \" \" + \"world\"";
|
||||||
let mut chunk = Chunk::new();
|
let mut chunk = Chunk::new();
|
||||||
compile(source, &mut chunk);
|
compile_expr(source, &mut chunk);
|
||||||
let mut vm = VM::new();
|
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_alloc = unsafe { allocate_string("hello world").unwrap() };
|
||||||
let target = Value::from(target_alloc.get_object());
|
let target = Value::from(target_alloc.get_object());
|
||||||
assert_eq!(result, target);
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::bc::{Chunk, Op, TraceInfo, Value};
|
use crate::bc::{Chunk, Op, TraceInfo, Value};
|
||||||
use crate::gc::{concat_string, GcHandle, ObjectType};
|
use crate::gc::{concat_string, GcHandle, ObjectType};
|
||||||
use std::collections::LinkedList;
|
use std::collections::LinkedList;
|
||||||
|
use std::io;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub struct VM {
|
pub struct VM {
|
||||||
@@ -56,7 +57,18 @@ impl VM {
|
|||||||
.ok_or(self.type_err("Number", top_of_stack))
|
.ok_or(self.type_err("Number", top_of_stack))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self, chunk: &Chunk) -> Result<Option<(Value, LinkedList<GcHandle>)>, VMError> {
|
pub fn stdrun(
|
||||||
|
&mut self,
|
||||||
|
chunk: &Chunk,
|
||||||
|
) -> Result<Option<(Value, LinkedList<GcHandle>)>, VMError> {
|
||||||
|
return self.run(chunk, &mut io::stdout());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<Output: io::Write>(
|
||||||
|
&mut self,
|
||||||
|
chunk: &Chunk,
|
||||||
|
output: &mut Output,
|
||||||
|
) -> Result<Option<(Value, LinkedList<GcHandle>)>, VMError> {
|
||||||
let mut allocations: LinkedList<GcHandle> = LinkedList::new();
|
let mut allocations: LinkedList<GcHandle> = LinkedList::new();
|
||||||
|
|
||||||
while self.pc < chunk.code.len() {
|
while self.pc < chunk.code.len() {
|
||||||
@@ -106,31 +118,28 @@ impl VM {
|
|||||||
let a = self.pop_num()?;
|
let a = self.pop_num()?;
|
||||||
self.push(Value::from(num + a));
|
self.push(Value::from(num + a));
|
||||||
}
|
}
|
||||||
Value::Obj(b) => {
|
Value::Obj(b) => match b.get_otype() {
|
||||||
match b.get_otype() {
|
ObjectType::String => {
|
||||||
ObjectType::String => {
|
let a = self.pop()?;
|
||||||
let a = self.pop()?;
|
match a {
|
||||||
match a {
|
Value::Obj(a) => match a.get_otype() {
|
||||||
Value::Obj(a) => {
|
ObjectType::String => {
|
||||||
match a.get_otype() {
|
let new_obj = unsafe { concat_string(a, b).unwrap() };
|
||||||
ObjectType::String => {
|
self.push(Value::from(new_obj.get_object()));
|
||||||
let new_obj = unsafe {
|
allocations.push_front(new_obj);
|
||||||
concat_string(a, b).unwrap()
|
Ok(())
|
||||||
};
|
|
||||||
self.push(Value::from(new_obj.get_object()));
|
|
||||||
allocations.push_front(new_obj);
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
Err(self.type_err("String", a))
|
|
||||||
}
|
}
|
||||||
}?
|
},
|
||||||
},
|
_ => 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 => {
|
Op::Subtract | Op::Multiply | Op::Divide => {
|
||||||
@@ -146,7 +155,7 @@ impl VM {
|
|||||||
}
|
}
|
||||||
Op::Greater | Op::Less => {
|
Op::Greater | Op::Less => {
|
||||||
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::Greater => a > b,
|
Op::Greater => a > b,
|
||||||
Op::Less => a < b,
|
Op::Less => a < b,
|
||||||
@@ -160,15 +169,21 @@ impl VM {
|
|||||||
let r = a == b;
|
let r = a == b;
|
||||||
self.push(r.into())
|
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() {
|
match self.stack.pop() {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some(result_value) => {
|
Some(result_value) => {
|
||||||
let escaping_allocs = allocations.into_iter().filter(
|
let escaping_allocs = allocations
|
||||||
|handle| Value::from(handle.get_object()) == result_value
|
.into_iter()
|
||||||
).collect();
|
.filter(|handle| Value::from(handle.get_object()) == result_value)
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(Some((result_value, escaping_allocs)))
|
Ok(Some((result_value, escaping_allocs)))
|
||||||
}
|
}
|
||||||
@@ -211,7 +226,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mut vm = VM::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, Value::from(3.1416));
|
assert_eq!(result, Value::from(3.1416));
|
||||||
assert!(vm.stack.is_empty());
|
assert!(vm.stack.is_empty());
|
||||||
@@ -220,16 +235,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nil_error() {
|
fn nil_error() {
|
||||||
let chunk = Chunk::new_with(
|
let chunk = Chunk::new_with(vec![Op::Nil, Op::Negate], vec![], vec![], LinkedList::new());
|
||||||
vec![Op::Nil, Op::Negate],
|
|
||||||
vec![],
|
|
||||||
vec![],
|
|
||||||
LinkedList::new(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut vm = VM::new();
|
let mut vm = VM::new();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vm.run(&chunk).unwrap_err(),
|
vm.stdrun(&chunk).unwrap_err(),
|
||||||
vm.type_err("Number", Value::Nil)
|
vm.type_err("Number", Value::Nil)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -243,7 +253,7 @@ mod tests {
|
|||||||
LinkedList::new(),
|
LinkedList::new(),
|
||||||
);
|
);
|
||||||
let mut vm = VM::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_eq!(result, true.into());
|
||||||
assert!(vm.stack.is_empty());
|
assert!(vm.stack.is_empty());
|
||||||
@@ -254,14 +264,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn not_nil_is_true() {
|
fn not_nil_is_true() {
|
||||||
let chunk = Chunk::new_with(
|
let chunk = Chunk::new_with(vec![Op::Nil, Op::Not], vec![], vec![], LinkedList::new());
|
||||||
vec![Op::Nil, Op::Not],
|
|
||||||
vec![],
|
|
||||||
vec![],
|
|
||||||
LinkedList::new(),
|
|
||||||
);
|
|
||||||
let mut vm = VM::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_eq!(result, true.into());
|
||||||
assert!(vm.stack.is_empty());
|
assert!(vm.stack.is_empty());
|
||||||
|
|||||||
Reference in New Issue
Block a user