// TODO - remove this at some point. // I haven't gotten around to testing or working with this API yet and I don't // want it to clog the warnings yet. #![allow(dead_code)] use crate::syn::ast::SpExpr; use crate::{ syn::{span::Span, words::Scope}, vm::inst::Inst, }; use std::cell::RefCell; use std::collections::{BTreeMap, HashMap}; use std::fmt::{self, Display}; use std::rc::{Rc, Weak}; pub type Str = String; pub type Int = i64; pub type Float = f64; pub type VTable = HashMap; // ///////////////////////////////////////////////////////////////////////////// // Quote // ///////////////////////////////////////////////////////////////////////////// /// A handle to a quote pointing to an element in a `QuoteTable`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Quote(usize); impl Quote { pub fn index(&self) -> usize { self.0 } } /// A table of compiled quotes, their expression trees, and their spans. #[derive(Debug, Clone, Default)] pub struct QuoteTable { table: Vec<(Span, Scope, Vec, Vec)>, } impl QuoteTable { pub fn new() -> Self { Default::default() } pub fn insert( &mut self, span: Span, scope: Scope, quote: Vec, compiled: Vec, ) -> Quote { let next = Quote(self.table.len()); self.table.push((span, scope, quote, compiled)); next } pub fn get(&self, quote: Quote) -> &(Span, Scope, Vec, Vec) { &self.table[quote.0] } } // ///////////////////////////////////////////////////////////////////////////// // Value // ///////////////////////////////////////////////////////////////////////////// #[derive(Debug, Clone)] pub enum Value { Array(Vec), Float(Float), Int(Int), Str(Str), Quote(Quote), ObjPtr(ObjPtr), } impl Value { pub fn name(&self) -> &str { use Value::*; match self { Array(_) => "array", Float(_) => "float", Int(_) => "int", Str(_) => "str", Quote(_) => "quote", ObjPtr(_) => "object", } } } impl Display for Value { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { use Value::*; match self { Array(_) => write!(fmt, "[array]"), Float(f) => write!(fmt, "{}", f), Int(i) => write!(fmt, "{}", i), Str(s) => write!(fmt, "{}", s), Quote(q) => write!(fmt, "[quoted value #{}]", q.index()), ObjPtr(o) => write!(fmt, "[object #{}]", o.slot()), } } } // ///////////////////////////////////////////////////////////////////////////// // Obj // ///////////////////////////////////////////////////////////////////////////// #[derive(Debug, Clone)] pub struct ObjPtr { arena: Weak>, slot: usize, } impl ObjPtr { pub fn slot(&self) -> usize { self.slot } } #[derive(Debug, Default)] pub struct Obj { vtable: VTable, } impl Obj { pub fn new(vtable: VTable) -> Self { Self { vtable } } pub fn vtable(&self) -> &VTable { &self.vtable } pub fn vtable_mut(&mut self) -> &mut VTable { &mut self.vtable } } // ///////////////////////////////////////////////////////////////////////////// // Arena // ///////////////////////////////////////////////////////////////////////////// #[derive(Debug)] pub struct Arena { slots: Vec, slots_dirty: bool, objects: BTreeMap, max_size: Option, } impl Arena { pub fn new(max_size: Option) -> Self { Arena { slots: vec![SlotRange::Open(0)], slots_dirty: false, objects: Default::default(), max_size, } } /// Compress the slots in the arena. /// /// This will: /// 1. Sort all slots by their starting position, /// 2. Merge all slots where necessary. pub fn compress_slots(&mut self) { use SlotRange::*; self.slots.sort_by(|a, b| match (a, b) { (Range(s1, _), Range(s2, _)) => s1.cmp(s2), (Open(o1), Open(o2)) => o1.cmp(o2), (Range(_, _), Open(_)) => std::cmp::Ordering::Less, (Open(_), Range(_, _)) => std::cmp::Ordering::Greater, }); let mut slots: Vec = Vec::with_capacity(self.slots.len()); for slot in &self.slots { match slot { // Remove invalid slots Range(start, end) if start > end => continue, _ => {} } if let Some(last) = slots.last().copied() { if let Some(merged) = last.try_merge(slot) { slots.pop(); slots.push(merged); } else { slots.push(*slot); } } else { slots.push(*slot); } } self.slots = slots; // Make sure to indicate that slots available are not dirty. self.slots_dirty = false; } /// Creates a shared pointer for an Obj value, using the next available slot. /// /// If no slot is available, None is returned. pub fn alloc_obj(self_rc: &Rc>, obj: Obj) -> Option { use SlotRange::*; let mut self_mut = self_rc .try_borrow_mut() .expect("could not get arena mutably from Rc pointer"); // Compress if necessary if self_mut.slots_dirty { self_mut.compress_slots(); } if self_mut .max_size .map(|max_size| self_mut.objects.len() >= max_size) .unwrap_or(false) { // TODO : return err instead of option // Could not allocate a new object because of configuration return None; } // Get the next slot let slots = &mut self_mut.slots; let slot = match slots.first().copied().unwrap() { Range(start, end) => { if start == end { slots.remove(0); } else { slots[0] = Range(start + 1, end); } start } Open(index) => { slots[0] = Open(index + 1); index } }; let previous = self_mut.objects.insert(slot, obj); assert!( previous.is_none(), "slot {} was allocated but is already in use", slot ); Some(ObjPtr { arena: Rc::downgrade(self_rc), slot, }) } pub fn free_obj(&mut self, obj_ptr: ObjPtr) { // Compress if necessary if self.slots_dirty { self.compress_slots(); } let value = self.objects.remove(&obj_ptr.slot); assert!( value.is_some(), "attempted to free object that was not tracked (slot {})", obj_ptr.slot ); self.slots .push(SlotRange::Range(obj_ptr.slot, obj_ptr.slot)); self.slots_dirty = true; // not my problem } } // ///////////////////////////////////////////////////////////////////////////// // Slots // ///////////////////////////////////////////////////////////////////////////// #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SlotRange { /// A list of slots that are in a range, exclusive. /// /// If the range start and end are equal, then it is a single opening. Range(usize, usize), /// Everything from here and onward is open. Open(usize), } impl SlotRange { /// Try to merge this range with another range. /// /// If these ranges don't overlap one another, then None is returned. /// /// If a range is open, while the other is closed, the open range will take /// precedent over the closed range. /// /// If both ranges are open, the smaller range is given precedent. pub fn try_merge(&self, other: &SlotRange) -> Option { use SlotRange::*; match (self, other) { (Range(s1, e1), Range(s2, e2)) if (e1 + 1) >= *s2 => { let start = s1.min(s2); let end = e1.max(e2); Some(Range(*start, *end)) } (Range(s, e), Open(o)) | (Open(o), Range(s, e)) => { if s <= o && (e + 1) >= *o { Some(Open(*s)) } else if s >= o { Some(Open(*o)) } else { None } } (Open(o1), Open(o2)) => Some(Open(*o1.min(o2))), _ => None, } } } #[test] fn test_slot_range_merge() { use SlotRange::*; let tests = [ (Range(0, 4), Range(0, 5), Some(Range(0, 5))), (Range(1, 4), Range(0, 5), Some(Range(0, 5))), (Range(0, 4), Range(1, 5), Some(Range(0, 5))), // (Range(0, 5), Range(0, 3), Some(Range(0, 5))), (Range(0, 5), Range(1, 3), Some(Range(0, 5))), (Range(1, 5), Range(0, 4), Some(Range(0, 5))), // (Range(0, 4), Open(4), Some(Open(0))), (Range(1, 4), Open(4), Some(Open(1))), (Range(2, 4), Open(4), Some(Open(2))), // (Range(0, 3), Range(4, 4), Some(Range(0, 4))), (Range(0, 4), Range(6, 6), None), // (Range(0, 4), Range(2, 2), Some(Range(0, 4))), ]; for (a, b, expected) in tests { assert_eq!( a.try_merge(&b), expected, "expected merge of {:?} and {:?} to be {:?}", a, b, expected ); } } #[test] fn test_arena_compress_slots() { use SlotRange::*; let tests = [ (vec![Range(0, 4), Range(2, 6), Open(0)], vec![Open(0)]), (vec![Open(7), Range(0, 4), Range(2, 6)], vec![Open(0)]), ( vec![Open(8), Range(0, 4), Range(2, 6)], vec![Range(0, 6), Open(8)], ), (vec![Range(0, 4), Range(2, 6)], vec![Range(0, 6)]), (vec![Range(0, 4), Range(2, 6)], vec![Range(0, 6)]), ( vec![Range(0, 1), Range(2, 2), Range(3, 4)], vec![Range(0, 4)], ), ( vec![Range(0, 4), Range(3, 4), Range(2, 2)], vec![Range(0, 4)], ), ( vec![Range(3, 6), Range(0, 4), Range(2, 2)], vec![Range(0, 6)], ), (vec![Range(0, 6), Range(6, 6)], vec![Range(0, 6)]), (vec![Range(7, 6), Range(6, 6)], vec![Range(6, 6)]), ( vec![Range(0, 0), Range(1, 1), Range(2, 2)], vec![Range(0, 2)], ), ( vec![Range(0, 0), Range(1, 1), Range(2, 2), Open(3)], vec![Open(0)], ), ]; for (slots, expected) in tests { let mut arena = Arena { slots, slots_dirty: true, objects: Default::default(), max_size: None, }; arena.compress_slots(); assert_eq!(arena.slots, expected); } } #[test] fn test_arena_obj_lifetime() { let arena = Rc::new(RefCell::new(Arena::new(None))); let p1 = Arena::alloc_obj(&arena, Obj::default()).unwrap(); assert_eq!(arena.borrow().slots, vec![SlotRange::Open(1)]); let p2 = Arena::alloc_obj(&arena, Obj::default()).unwrap(); assert_eq!(arena.borrow().slots, vec![SlotRange::Open(2)]); let p3 = Arena::alloc_obj(&arena, Obj::default()).unwrap(); assert_eq!(arena.borrow().slots, vec![SlotRange::Open(3)]); { let mut arena_mut = arena.borrow_mut(); arena_mut.free_obj(p2); arena_mut.compress_slots(); assert_eq!( arena_mut.slots, vec![SlotRange::Range(1, 1), SlotRange::Open(3)] ); } let p4 = Arena::alloc_obj(&arena, Obj::default()).unwrap(); assert_eq!(arena.borrow().slots, vec![SlotRange::Open(3)]); { let mut arena_mut = arena.borrow_mut(); arena_mut.free_obj(p1); arena_mut.free_obj(p3); arena_mut.free_obj(p4); arena_mut.compress_slots(); assert_eq!(arena_mut.slots, vec![SlotRange::Open(0)],); } }