Files
sybil/src/object.rs

427 lines
12 KiB
Rust
Raw Normal View History

// 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<String, Value>;
// /////////////////////////////////////////////////////////////////////////////
// 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<SpExpr>, Vec<Inst>)>,
}
impl QuoteTable {
pub fn new() -> Self {
Default::default()
}
pub fn insert(
&mut self,
span: Span,
scope: Scope,
quote: Vec<SpExpr>,
compiled: Vec<Inst>,
) -> Quote {
let next = Quote(self.table.len());
self.table.push((span, scope, quote, compiled));
next
}
pub fn get(&self, quote: Quote) -> &(Span, Scope, Vec<SpExpr>, Vec<Inst>) {
&self.table[quote.0]
}
}
// /////////////////////////////////////////////////////////////////////////////
// Value
// /////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Clone)]
pub enum Value {
Array(Vec<Value>),
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<RefCell<Arena>>,
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<SlotRange>,
slots_dirty: bool,
objects: BTreeMap<usize, Obj>,
max_size: Option<usize>,
}
impl Arena {
pub fn new(max_size: Option<usize>) -> 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<SlotRange> = 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<RefCell<Self>>, obj: Obj) -> Option<ObjPtr> {
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<SlotRange> {
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)],);
}
}