[rlox] Implement basic linked-list allocation management
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
use crate::bc::Value::{Bool, Number};
|
use crate::gc::{GcHandle, Object};
|
||||||
|
use std::collections::LinkedList;
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -6,7 +7,7 @@ use std::fmt::Debug;
|
|||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Op {
|
pub enum Op {
|
||||||
Return,
|
Return,
|
||||||
Constant { offset: usize },
|
Constant { offset: u8 },
|
||||||
Nil,
|
Nil,
|
||||||
True,
|
True,
|
||||||
False,
|
False,
|
||||||
@@ -20,69 +21,46 @@ pub enum Op {
|
|||||||
Greater,
|
Greater,
|
||||||
Less,
|
Less,
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum Object {
|
|
||||||
String(String)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
Nil,
|
Nil,
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
Number(f64),
|
Number(f64),
|
||||||
Obj(Box<Object>)
|
Obj(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),
|
&Value::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),
|
&Value::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 {
|
||||||
fn from(value: f64) -> Self {
|
fn from(value: f64) -> Self {
|
||||||
Number(value)
|
Value::Number(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<bool> for Value {
|
impl From<bool> for Value {
|
||||||
fn from(value: bool) -> Self {
|
fn from(value: bool) -> Self {
|
||||||
Bool(value)
|
Value::Bool(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Value {
|
impl From<Object> for Value {
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: Object) -> Self {
|
||||||
Value::Obj(Box::from(Object::String(value.to_string())))
|
Value::Obj(value)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Value {
|
|
||||||
fn from(value: String) -> Self {
|
|
||||||
Value::Obj(Box::from(Object::String(value)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +68,7 @@ pub struct Chunk {
|
|||||||
pub code: Vec<Op>,
|
pub code: Vec<Op>,
|
||||||
pub debug_info: Vec<usize>,
|
pub debug_info: Vec<usize>,
|
||||||
pub constants: Vec<Value>,
|
pub constants: Vec<Value>,
|
||||||
|
pub allocations: LinkedList<GcHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chunk {
|
impl Chunk {
|
||||||
@@ -98,14 +77,16 @@ 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with(code: Vec<Op>, debug_info: Vec<usize>, constants: Vec<Value>) -> 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +105,7 @@ impl Chunk {
|
|||||||
self.constants.push(value);
|
self.constants.push(value);
|
||||||
self.add_op(
|
self.add_op(
|
||||||
Op::Constant {
|
Op::Constant {
|
||||||
offset: self.constants.len() - 1,
|
offset: self.constants.len() as u8 - 1,
|
||||||
},
|
},
|
||||||
line,
|
line,
|
||||||
)
|
)
|
||||||
@@ -187,7 +168,7 @@ impl fmt::Debug for TraceInfo<'_> {
|
|||||||
match op {
|
match op {
|
||||||
Op::Constant { offset } => {
|
Op::Constant { offset } => {
|
||||||
f.debug_struct("Constant")
|
f.debug_struct("Constant")
|
||||||
.field("val", &chunk.constants[offset])
|
.field("val", &chunk.constants[offset as usize])
|
||||||
.finish()?;
|
.finish()?;
|
||||||
write!(f, "")
|
write!(f, "")
|
||||||
}
|
}
|
||||||
@@ -197,18 +178,27 @@ impl fmt::Debug for TraceInfo<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod tests {
|
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;
|
||||||
|
|
||||||
let s1 = "bla5";
|
let s1 = "bla5";
|
||||||
let s2 = "bla6";
|
let s2 = "bla6";
|
||||||
|
|
||||||
let v1 = Value::from(s1);
|
unsafe {
|
||||||
let v2 = Value::from(s2);
|
let o1 = allocate_string(s1).unwrap();
|
||||||
let v3 = Value::from(s2);
|
let o2 = allocate_string(s2).unwrap();
|
||||||
|
let o3 = allocate_string(s2).unwrap();
|
||||||
|
let v1 = Value::from(o1.get_object());
|
||||||
|
let v2 = Value::from(o2.get_object());
|
||||||
|
let v3 = Value::from(o3.get_object());
|
||||||
|
let v4 = v2.clone();
|
||||||
|
|
||||||
assert_ne!(v1, v2);
|
assert_ne!(v1, v2);
|
||||||
assert_eq!(v2, v3);
|
assert_eq!(v2, v3);
|
||||||
|
assert_eq!(v2, v4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
174
rlox/src/gc.rs
Normal file
174
rlox/src/gc.rs
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
use std::{alloc::{alloc, dealloc, Layout, LayoutError}, fmt};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||||
|
#[repr(usize)]
|
||||||
|
pub enum ObjectType {
|
||||||
|
String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct ObjectHeader {
|
||||||
|
otype: ObjectType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct ObjStringHeader {
|
||||||
|
object_header: ObjectHeader,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct ObjString {
|
||||||
|
header: ObjStringHeader,
|
||||||
|
data: [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn data_offset() -> usize {
|
||||||
|
std::mem::size_of::<ObjStringHeader>()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Object {
|
||||||
|
ptr: *mut ObjectHeader,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object {
|
||||||
|
pub fn get_otype(&self) -> ObjectType {
|
||||||
|
unsafe {
|
||||||
|
(*self.ptr).otype
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Object {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
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())],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Object {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
if self.ptr == other.ptr {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if (*self.ptr).otype != (*other.ptr).otype {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
match (*self.ptr).otype {
|
||||||
|
ObjectType::String => {
|
||||||
|
let header = self.ptr as *mut ObjStringHeader;
|
||||||
|
let other_header = other.ptr as *mut ObjStringHeader;
|
||||||
|
|
||||||
|
if (*header).len != (*other_header).len {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let slice = ObjString::as_slice(header);
|
||||||
|
let other_slice = ObjString::as_slice(other_header);
|
||||||
|
|
||||||
|
slice == other_slice
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjString {
|
||||||
|
fn layout(length: usize) -> Result<(Layout, usize), LayoutError> {
|
||||||
|
let (layout, offset) = Layout::for_value(&ObjStringHeader {
|
||||||
|
object_header: ObjectHeader {
|
||||||
|
otype: ObjectType::String,
|
||||||
|
},
|
||||||
|
len: length,
|
||||||
|
})
|
||||||
|
.extend(Layout::array::<u8>(length)?)?;
|
||||||
|
|
||||||
|
Ok((layout.pad_to_align(), offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_slice<'a>(ptr: *mut ObjStringHeader) -> &'a [u8] {
|
||||||
|
unsafe {
|
||||||
|
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> {
|
||||||
|
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 str = std::slice::from_raw_parts_mut(data_ptr, length);
|
||||||
|
Ok((GcHandle { object }, str))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn allocate_string(content: &str) -> Result<GcHandle, LayoutError> {
|
||||||
|
let (gc_handle, slice) = allocate_string_obj(content.len())?;
|
||||||
|
slice.copy_from_slice(content.as_bytes());
|
||||||
|
Ok(gc_handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 new_len = a_data.len() + b_data.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);
|
||||||
|
|
||||||
|
Ok(gc_handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn deallocate_object(object: Object) {
|
||||||
|
match object.get_otype() {
|
||||||
|
ObjectType::String => {
|
||||||
|
let header = object.ptr as *mut ObjStringHeader;
|
||||||
|
dealloc(
|
||||||
|
object.ptr as *mut u8,
|
||||||
|
ObjString::layout((*header).len).unwrap().0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct GcHandle {
|
||||||
|
object: Object
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for GcHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { deallocate_object(self.object) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GcHandle {
|
||||||
|
pub fn get_object(&self) -> Object {
|
||||||
|
return self.object;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
use std::convert::identity;
|
use std::{collections::HashMap, convert::identity};
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::str::CharIndices;
|
use std::str::CharIndices;
|
||||||
|
|
||||||
use crate::bc::{Chunk, Op};
|
use crate::bc::{Chunk, Op};
|
||||||
|
use crate::gc::allocate_string;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
enum TokenType {
|
enum TokenType {
|
||||||
@@ -263,6 +264,7 @@ impl<'src> Iterator for Scanner<'src> {
|
|||||||
|
|
||||||
struct Parser<'src> {
|
struct Parser<'src> {
|
||||||
scanner: Peekable<Scanner<'src>>,
|
scanner: Peekable<Scanner<'src>>,
|
||||||
|
intern_table: HashMap<&'src str, u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Associativity {
|
enum Associativity {
|
||||||
@@ -290,6 +292,7 @@ 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(),
|
||||||
|
intern_table: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +339,21 @@ impl<'src> Parser<'src> {
|
|||||||
},
|
},
|
||||||
TokenType::String => {
|
TokenType::String => {
|
||||||
let without_quotes = &token.span[1..(token.span.len() - 1)];
|
let without_quotes = &token.span[1..(token.span.len() - 1)];
|
||||||
chunk.add_constant(without_quotes.into(), 0);
|
match self.intern_table.get(without_quotes) {
|
||||||
|
Some(&index) => {
|
||||||
|
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);
|
||||||
|
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 {
|
||||||
@@ -401,6 +418,8 @@ pub fn compile(source: &str, chunk: &mut Chunk) {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::collections::LinkedList;
|
||||||
|
|
||||||
use crate::bc::Value;
|
use crate::bc::Value;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -523,6 +542,7 @@ mod tests {
|
|||||||
],
|
],
|
||||||
vec![],
|
vec![],
|
||||||
vec![1., 1., 2., 1.].into_iter().map(Value::from).collect(),
|
vec![1., 1., 2., 1.].into_iter().map(Value::from).collect(),
|
||||||
|
LinkedList::new(),
|
||||||
);
|
);
|
||||||
|
|
||||||
test_parse_expression(source, &expected);
|
test_parse_expression(source, &expected);
|
||||||
@@ -536,6 +556,7 @@ mod tests {
|
|||||||
vec![Nil, Nil, Add],
|
vec![Nil, Nil, Add],
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
|
LinkedList::new(),
|
||||||
);
|
);
|
||||||
|
|
||||||
test_parse_expression(source, &expected);
|
test_parse_expression(source, &expected);
|
||||||
@@ -549,6 +570,7 @@ mod tests {
|
|||||||
vec![True, False, Multiply],
|
vec![True, False, Multiply],
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
|
LinkedList::new(),
|
||||||
);
|
);
|
||||||
|
|
||||||
test_parse_expression(source, &expected);
|
test_parse_expression(source, &expected);
|
||||||
@@ -568,6 +590,7 @@ mod tests {
|
|||||||
True, Equal, Not],
|
True, Equal, Not],
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
|
LinkedList::new(),
|
||||||
);
|
);
|
||||||
|
|
||||||
test_parse_expression(source, &expected);
|
test_parse_expression(source, &expected);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod bc;
|
mod bc;
|
||||||
mod lc;
|
mod lc;
|
||||||
mod vm;
|
mod vm;
|
||||||
|
mod gc;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io;
|
use std::io;
|
||||||
@@ -48,7 +49,7 @@ fn main() {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{bc::Chunk, lc::compile, vm::VM};
|
use crate::{bc::{Chunk, Value}, gc::allocate_string, lc::compile, vm::VM};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compile_and_run_pi_math() {
|
fn test_compile_and_run_pi_math() {
|
||||||
@@ -60,12 +61,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn string_handling() {
|
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(source, &mut chunk);
|
||||||
let mut vm = VM::new();
|
let mut vm = VM::new();
|
||||||
let v = vm.run(&chunk).unwrap();
|
let (result, _allocs) = vm.run(&chunk).unwrap().unwrap();
|
||||||
assert_eq!(v, Some("hello world".into()));
|
let target_alloc = unsafe { allocate_string("hello world").unwrap() };
|
||||||
|
let target = Value::from(target_alloc.get_object());
|
||||||
|
assert_eq!(result, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::bc::{Chunk, Op, TraceInfo, Value};
|
use crate::bc::{Chunk, Op, TraceInfo, Value};
|
||||||
use std::ops::Not;
|
use crate::gc::{concat_string, GcHandle, ObjectType};
|
||||||
|
use std::collections::LinkedList;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub struct VM {
|
pub struct VM {
|
||||||
@@ -55,7 +56,9 @@ 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>, VMError> {
|
pub fn run(&mut self, chunk: &Chunk) -> Result<Option<(Value, LinkedList<GcHandle>)>, VMError> {
|
||||||
|
let mut allocations: LinkedList<GcHandle> = LinkedList::new();
|
||||||
|
|
||||||
while self.pc < chunk.code.len() {
|
while self.pc < chunk.code.len() {
|
||||||
let instr = chunk.code[self.pc];
|
let instr = chunk.code[self.pc];
|
||||||
self.pc += 1;
|
self.pc += 1;
|
||||||
@@ -79,7 +82,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].clone()),
|
Op::Constant { offset } => self.push(chunk.constants[offset as usize].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,20 +106,29 @@ 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(ref _obj) => {
|
Value::Obj(b) => {
|
||||||
match b.as_str() {
|
match b.get_otype() {
|
||||||
None => Err(self.type_err("String", b)),
|
ObjectType::String => {
|
||||||
Some(str_b) => {
|
|
||||||
let a = self.pop()?;
|
let a = self.pop()?;
|
||||||
match a.as_str() {
|
match a {
|
||||||
Some(str_a) => {
|
Value::Obj(a) => {
|
||||||
self.push(Value::from(str_a.to_owned() + str_b));
|
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(())
|
Ok(())
|
||||||
},
|
},
|
||||||
None => 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))
|
||||||
};
|
};
|
||||||
@@ -151,17 +163,24 @@ impl VM {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(self
|
match self.stack.pop() {
|
||||||
.stack
|
None => Ok(None),
|
||||||
.is_empty()
|
Some(result_value) => {
|
||||||
.not()
|
let escaping_allocs = allocations.into_iter().filter(
|
||||||
.then_some(self.stack[self.stack.len() - 1].clone()))
|
|handle| Value::from(handle.get_object()) == result_value
|
||||||
|
).collect();
|
||||||
|
|
||||||
|
Ok(Some((result_value, escaping_allocs)))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Chunk, Op, Value, VM};
|
use std::collections::LinkedList;
|
||||||
|
|
||||||
|
use super::{Chunk, Op, VMError, Value, VM};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_arithmetic() {
|
fn simple_arithmetic() {
|
||||||
@@ -188,12 +207,15 @@ mod tests {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Value::from)
|
.map(Value::from)
|
||||||
.collect(),
|
.collect(),
|
||||||
|
LinkedList::new(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut vm = VM::new();
|
let mut vm = VM::new();
|
||||||
vm.run(&chunk).unwrap();
|
let (result, allocs) = vm.run(&chunk).unwrap().unwrap();
|
||||||
|
|
||||||
assert_eq!(vm.stack[0], Value::from(3.1416));
|
assert_eq!(result, Value::from(3.1416));
|
||||||
|
assert!(vm.stack.is_empty());
|
||||||
|
assert!(allocs.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -202,6 +224,7 @@ mod tests {
|
|||||||
vec![Op::Nil, Op::Negate],
|
vec![Op::Nil, Op::Negate],
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
|
LinkedList::new(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut vm = VM::new();
|
let mut vm = VM::new();
|
||||||
@@ -212,16 +235,21 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_booleans() {
|
fn simple_booleans() -> Result<(), VMError> {
|
||||||
let chunk = Chunk::new_with(
|
let chunk = Chunk::new_with(
|
||||||
vec![Op::False, Op::Not, Op::False, Op::Not, Op::Equal],
|
vec![Op::False, Op::Not, Op::False, Op::Not, Op::Equal],
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
|
LinkedList::new(),
|
||||||
);
|
);
|
||||||
let mut vm = VM::new();
|
let mut vm = VM::new();
|
||||||
vm.run(&chunk).unwrap();
|
let (result, allocs) = vm.run(&chunk)?.unwrap();
|
||||||
|
|
||||||
assert_eq!(vm.stack[0], true.into());
|
assert_eq!(result, true.into());
|
||||||
|
assert!(vm.stack.is_empty());
|
||||||
|
assert!(allocs.is_empty());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -230,10 +258,13 @@ mod tests {
|
|||||||
vec![Op::Nil, Op::Not],
|
vec![Op::Nil, Op::Not],
|
||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
|
LinkedList::new(),
|
||||||
);
|
);
|
||||||
let mut vm = VM::new();
|
let mut vm = VM::new();
|
||||||
vm.run(&chunk).unwrap();
|
let (result, allocs) = vm.run(&chunk).unwrap().unwrap();
|
||||||
|
|
||||||
assert_eq!(vm.stack[0], true.into());
|
assert_eq!(result, true.into());
|
||||||
|
assert!(vm.stack.is_empty());
|
||||||
|
assert!(allocs.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user