[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::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<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 code: Vec<Op>,
|
||||
pub debug_info: Vec<usize>,
|
||||
@@ -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<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 {
|
||||
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";
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn allocate_string_obj<'a>(length: usize) -> Result<(GcHandle, &'a mut [u8]), LayoutError> {
|
||||
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> {
|
||||
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,8 +143,8 @@ pub unsafe fn allocate_string(content: &str) -> Result<GcHandle, LayoutError> {
|
||||
pub unsafe fn concat_string(a: Object, b: Object) -> Result<GcHandle, LayoutError> {
|
||||
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)?;
|
||||
@@ -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 {
|
||||
|
||||
166
rlox/src/lc.rs
166
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<T, ParseError<'src>>;
|
||||
|
||||
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::<f64>() {
|
||||
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<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) {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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();
|
||||
|
||||
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() {
|
||||
Value::Obj(b) => match b.get_otype() {
|
||||
ObjectType::String => {
|
||||
let a = self.pop()?;
|
||||
match a {
|
||||
Value::Obj(a) => {
|
||||
match a.get_otype() {
|
||||
Value::Obj(a) => match a.get_otype() {
|
||||
ObjectType::String => {
|
||||
let new_obj = unsafe {
|
||||
concat_string(a, b).unwrap()
|
||||
};
|
||||
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)),
|
||||
}?
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
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 => {
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user