use crate::nanbox::NanBox;
use crate::shape::Shape;
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::rc::Rc;
use alloc::string::ToString;
use alloc::vec::Vec;
fn array_index(k: &str) -> Option<u32> {
k.parse::<u32>()
.ok()
.filter(|n| *n < u32::MAX && n.to_string() == k)
}
enum ObjectData {
Shaped {
shape: Rc<Shape>,
slots: Vec<NanBox>,
},
Dict {
order: Vec<Box<str>>,
map: BTreeMap<Box<str>, NanBox>,
},
}
pub struct Object {
data: ObjectData,
dict_shape: Option<Rc<Shape>>,
accessors: Vec<(alloc::boxed::Box<str>, NanBox, NanBox)>,
hidden: Vec<alloc::boxed::Box<str>>,
readonly: Vec<alloc::boxed::Box<str>>,
non_configurable: Vec<alloc::boxed::Box<str>>,
frozen: bool,
extensible: bool,
sealed: bool,
class_tag: Option<u32>,
proto: Option<crate::heap::Handle>,
}
impl Object {
#[must_use]
pub fn new(root: Rc<Shape>) -> Self {
Self {
data: ObjectData::Shaped {
shape: root,
slots: Vec::new(),
},
dict_shape: None,
accessors: Vec::new(),
hidden: Vec::new(),
readonly: Vec::new(),
non_configurable: Vec::new(),
frozen: false,
extensible: true,
sealed: false,
class_tag: None,
proto: None,
}
}
#[must_use]
pub fn proto(&self) -> Option<crate::heap::Handle> {
self.proto
}
pub fn set_proto(&mut self, proto: Option<crate::heap::Handle>) {
self.proto = proto;
}
pub fn set_class_tag(&mut self, class_id: u32) {
self.class_tag = Some(class_id);
}
#[must_use]
pub fn class_tag(&self) -> Option<u32> {
self.class_tag
}
pub fn define_accessor(&mut self, name: &str, getter: NanBox, setter: NanBox) {
if let Some(a) = self
.accessors
.iter_mut()
.find(|(k, _, _)| k.as_ref() == name)
{
if !matches!(getter.unpack(), crate::nanbox::Unpacked::Undefined) {
a.1 = getter;
}
if !matches!(setter.unpack(), crate::nanbox::Unpacked::Undefined) {
a.2 = setter;
}
} else {
self.accessors
.push((alloc::boxed::Box::from(name), getter, setter));
}
}
#[must_use]
pub fn accessor_keys(&self) -> Vec<&str> {
self.accessors.iter().map(|(k, _, _)| k.as_ref()).collect()
}
#[must_use]
pub fn accessor(&self, name: &str) -> Option<(NanBox, NanBox)> {
self.accessors
.iter()
.find(|(k, _, _)| k.as_ref() == name)
.map(|(_, g, s)| (*g, *s))
}
#[must_use]
pub fn shape(&self) -> &Rc<Shape> {
match &self.data {
ObjectData::Shaped { shape, .. } => shape,
ObjectData::Dict { .. } => self
.dict_shape
.as_ref()
.expect("dictionary objects carry a sentinel shape"),
}
}
#[must_use]
pub fn len(&self) -> u32 {
match &self.data {
ObjectData::Shaped { shape, .. } => shape.len(),
ObjectData::Dict { order, .. } => order.len() as u32,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
match &self.data {
ObjectData::Shaped { shape, .. } => shape.is_empty(),
ObjectData::Dict { order, .. } => order.is_empty(),
}
}
#[must_use]
pub fn get(&self, key: &str) -> Option<NanBox> {
match &self.data {
ObjectData::Shaped { shape, slots } => {
let slot = shape.lookup(key)?;
slots.get(slot as usize).copied()
}
ObjectData::Dict { map, .. } => map.get(key).copied(),
}
}
#[must_use]
pub fn contains(&self, key: &str) -> bool {
match &self.data {
ObjectData::Shaped { shape, .. } => shape.contains(key),
ObjectData::Dict { map, .. } => map.contains_key(key),
}
}
pub fn set(&mut self, key: &str, value: NanBox) {
if self.frozen || self.is_readonly(key) {
return;
}
match &mut self.data {
ObjectData::Shaped { shape, slots } => {
if let Some(slot) = shape.lookup(key) {
slots[slot as usize] = value;
} else if self.extensible {
*shape = shape.transition(key);
slots.push(value);
}
}
ObjectData::Dict { order, map } => {
if let Some(v) = map.get_mut(key) {
*v = value;
} else if self.extensible {
order.push(Box::from(key));
map.insert(Box::from(key), value);
}
}
}
}
pub fn maybe_convert_to_dict(&mut self, key: &str, threshold: usize) {
if let ObjectData::Shaped { shape, .. } = &self.data
&& self.extensible
&& !self.frozen
&& !self.is_readonly(key)
&& shape.lookup(key).is_none()
&& shape.len() as usize >= threshold
{
self.convert_to_dict();
}
}
fn convert_to_dict(&mut self) {
let ObjectData::Shaped { shape, slots } = &self.data else {
return;
};
let keys = shape.keys();
let mut order: Vec<Box<str>> = Vec::with_capacity(keys.len());
let mut map: BTreeMap<Box<str>, NanBox> = BTreeMap::new();
for k in keys {
let slot = shape.lookup(k).expect("shape key resolves");
let v = slots[slot as usize];
order.push(Box::from(k));
map.insert(Box::from(k), v);
}
self.dict_shape = Some(Shape::root());
self.data = ObjectData::Dict { order, map };
}
#[must_use]
pub fn keys(&self) -> Vec<&str> {
match &self.data {
ObjectData::Shaped { shape, .. } => shape.keys(),
ObjectData::Dict { order, .. } => order.iter().map(Box::as_ref).collect(),
}
}
#[must_use]
pub fn all_keys(&self) -> Vec<&str> {
let mut keys = self.keys();
keys.extend(self.accessors.iter().map(|(k, _, _)| k.as_ref()));
keys
}
#[must_use]
pub fn ordered_keys(&self) -> Vec<&str> {
let keys = self.keys();
let mut ints: Vec<&str> = keys
.iter()
.copied()
.filter(|k| array_index(k).is_some())
.collect();
ints.sort_by_key(|k| array_index(k).unwrap());
let strs = keys.iter().copied().filter(|k| array_index(k).is_none());
let acc = self.accessors.iter().map(|(k, _, _)| k.as_ref());
ints.into_iter().chain(strs).chain(acc).collect()
}
#[must_use]
pub fn enumerable_keys(&self) -> Vec<&str> {
let keys: Vec<&str> = self
.keys()
.into_iter()
.filter(|k| !self.is_hidden(k))
.collect();
let mut ints: Vec<&str> = keys
.iter()
.copied()
.filter(|k| array_index(k).is_some())
.collect();
ints.sort_by_key(|k| array_index(k).unwrap());
let strs = keys.into_iter().filter(|k| array_index(k).is_none());
let acc = self
.accessors
.iter()
.map(|(k, _, _)| k.as_ref())
.filter(|k| !self.is_hidden(k));
ints.into_iter().chain(strs).chain(acc).collect()
}
pub fn set_hidden(&mut self, key: &str) {
if !self.is_hidden(key) {
self.hidden.push(alloc::boxed::Box::from(key));
}
}
#[must_use]
pub fn is_hidden(&self, key: &str) -> bool {
self.hidden.iter().any(|k| k.as_ref() == key)
}
pub fn set_readonly(&mut self, key: &str) {
if !self.is_readonly(key) {
self.readonly.push(alloc::boxed::Box::from(key));
}
}
#[must_use]
pub fn is_readonly(&self, key: &str) -> bool {
self.readonly.iter().any(|k| k.as_ref() == key)
}
pub fn clear_readonly(&mut self, key: &str) {
self.readonly.retain(|k| k.as_ref() != key);
}
pub fn set_non_configurable(&mut self, key: &str) {
if !self.is_non_configurable(key) {
self.non_configurable.push(alloc::boxed::Box::from(key));
}
}
#[must_use]
pub fn is_non_configurable(&self, key: &str) -> bool {
self.non_configurable.iter().any(|k| k.as_ref() == key)
}
#[must_use]
pub fn has_own_key(&self, key: &str) -> bool {
self.contains(key) || self.accessors.iter().any(|(k, _, _)| k.as_ref() == key)
}
pub fn freeze(&mut self) {
self.frozen = true;
self.sealed = true;
self.extensible = false;
}
pub fn prevent_extensions(&mut self) {
self.extensible = false;
}
pub fn seal(&mut self) {
self.sealed = true;
self.extensible = false;
}
#[must_use]
pub fn is_extensible(&self) -> bool {
self.extensible
}
#[must_use]
pub fn is_sealed(&self) -> bool {
self.sealed || self.frozen
}
#[must_use]
pub fn is_frozen(&self) -> bool {
self.frozen
}
pub fn clear_accessor(&mut self, key: &str) {
self.accessors.retain(|(k, _, _)| k.as_ref() != key);
}
pub fn delete(&mut self, root: Rc<Shape>, key: &str) -> bool {
let had_accessor = self.accessors.iter().any(|(k, _, _)| k.as_ref() == key);
self.accessors.retain(|(k, _, _)| k.as_ref() != key);
match &mut self.data {
ObjectData::Shaped { shape, slots } => {
if !shape.contains(key) {
return had_accessor;
}
let kept: Vec<(alloc::string::String, NanBox)> = shape
.keys()
.into_iter()
.filter(|k| *k != key)
.map(|k| {
let slot = shape.lookup(k).expect("shape key resolves");
(alloc::string::String::from(k), slots[slot as usize])
})
.collect();
let mut new_shape = root;
let mut new_slots = Vec::with_capacity(kept.len());
for (k, v) in kept {
new_shape = new_shape.transition(&k);
new_slots.push(v);
}
*shape = new_shape;
*slots = new_slots;
true
}
ObjectData::Dict { order, map } => {
if map.remove(key).is_none() {
return had_accessor;
}
order.retain(|k| k.as_ref() != key);
true
}
}
}
pub fn relocate_handles(
&mut self,
forward: &dyn Fn(crate::heap::Handle) -> crate::heap::Handle,
) {
let fwd = |v: &mut NanBox| {
if let Some(raw) = v.as_handle() {
*v = NanBox::handle(forward(crate::heap::Handle::from_raw(raw)).to_raw());
}
};
match &mut self.data {
ObjectData::Shaped { slots, .. } => {
for slot in slots {
fwd(slot);
}
}
ObjectData::Dict { map, .. } => {
for v in map.values_mut() {
fwd(v);
}
}
}
for (_, g, s) in &mut self.accessors {
fwd(g);
fwd(s);
}
if let Some(p) = self.proto {
self.proto = Some(forward(p));
}
}
pub fn trace_handles(&self, mut visit: impl FnMut(crate::heap::Handle)) {
let trace_value = |v: &NanBox, visit: &mut dyn FnMut(crate::heap::Handle)| {
if let Some(raw) = v.as_handle() {
visit(crate::heap::Handle::from_raw(raw));
}
};
match &self.data {
ObjectData::Shaped { slots, .. } => {
for slot in slots {
trace_value(slot, &mut visit);
}
}
ObjectData::Dict { map, .. } => {
for v in map.values() {
trace_value(v, &mut visit);
}
}
}
for (_, g, s) in &self.accessors {
for v in [g, s] {
if let Some(raw) = v.as_handle() {
visit(crate::heap::Handle::from_raw(raw));
}
}
}
if let Some(p) = self.proto {
visit(p);
}
}
}
impl crate::gc::Trace for Object {
fn trace(&self, visit: &mut dyn FnMut(crate::heap::Handle)) {
self.trace_handles(visit);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::heap::Heap;
use crate::nanbox::Unpacked;
fn n(x: f64) -> NanBox {
NanBox::number(x)
}
#[test]
fn set_get_and_update() {
let mut o = Object::new(Shape::root());
assert!(o.is_empty());
o.set("x", n(1.0));
o.set("y", n(2.0));
assert_eq!(o.len(), 2);
assert_eq!(o.get("x").unwrap().unpack(), Unpacked::Number(1.0));
assert_eq!(o.get("y").unwrap().unpack(), Unpacked::Number(2.0));
assert_eq!(o.get("z"), None);
let shape_before = Rc::clone(o.shape());
o.set("x", n(9.0));
assert!(Rc::ptr_eq(o.shape(), &shape_before));
assert_eq!(o.get("x").unwrap().unpack(), Unpacked::Number(9.0));
assert_eq!(o.len(), 2);
assert_eq!(o.keys(), ["x", "y"]);
}
#[test]
fn same_structure_objects_share_a_shape() {
let root = Shape::root();
let mut a = Object::new(Rc::clone(&root));
let mut b = Object::new(Rc::clone(&root));
a.set("p", n(1.0));
a.set("q", n(2.0));
b.set("p", n(10.0));
b.set("q", n(20.0));
assert!(Rc::ptr_eq(a.shape(), b.shape()));
assert_eq!(a.get("p").unwrap().unpack(), Unpacked::Number(1.0));
assert_eq!(b.get("p").unwrap().unpack(), Unpacked::Number(10.0));
}
#[test]
fn mixed_value_kinds_in_slots() {
let mut o = Object::new(Shape::root());
o.set("a", NanBox::number(3.5));
o.set("b", NanBox::boolean(true));
o.set("c", NanBox::null());
o.set("d", NanBox::handle(42));
assert_eq!(o.get("a").unwrap().unpack(), Unpacked::Number(3.5));
assert_eq!(o.get("b").unwrap().unpack(), Unpacked::Bool(true));
assert_eq!(o.get("c").unwrap().unpack(), Unpacked::Null);
assert_eq!(o.get("d").unwrap().unpack(), Unpacked::Handle(42));
}
#[test]
fn objects_live_in_the_heap_and_reference_each_other() {
let root = Shape::root();
let mut heap: Heap<Object> = Heap::new();
let mut child = Object::new(Rc::clone(&root));
child.set("value", n(7.0));
let child_handle = heap.alloc(child);
let mut parent = Object::new(Rc::clone(&root));
parent.set("child", NanBox::handle(child_handle.to_raw()));
let parent_handle = heap.alloc(parent);
let parent_ref = heap.get(parent_handle).unwrap();
let raw = parent_ref.get("child").unwrap().as_handle().unwrap();
let resolved = crate::heap::Handle::from_raw(raw);
let child_ref = heap.get(resolved).unwrap();
assert_eq!(
child_ref.get("value").unwrap().unpack(),
Unpacked::Number(7.0)
);
}
fn add(o: &mut Object, key: &str, value: NanBox, threshold: usize) {
o.maybe_convert_to_dict(key, threshold);
o.set(key, value);
}
#[test]
fn converts_to_dictionary_past_threshold_and_preserves_semantics() {
let mut o = Object::new(Shape::root());
let threshold = 4;
for i in 0..4 {
add(&mut o, &alloc::format!("k{i}"), n(f64::from(i)), threshold);
}
assert_eq!(o.shape().lookup("k0"), Some(0));
assert_eq!(o.len(), 4);
add(&mut o, "k4", n(4.0), threshold);
assert_eq!(o.len(), 5);
assert_eq!(o.shape().lookup("k0"), None);
assert_eq!(o.shape().lookup("k4"), None);
for i in 5..300 {
add(&mut o, &alloc::format!("k{i}"), n(f64::from(i)), threshold);
}
assert_eq!(o.len(), 300);
assert_eq!(o.get("k0").unwrap().unpack(), Unpacked::Number(0.0));
assert_eq!(o.get("k3").unwrap().unpack(), Unpacked::Number(3.0));
assert_eq!(o.get("k150").unwrap().unpack(), Unpacked::Number(150.0));
assert_eq!(o.get("k299").unwrap().unpack(), Unpacked::Number(299.0));
assert_eq!(o.get("nope"), None);
let keys = o.keys();
assert_eq!(keys.len(), 300);
assert_eq!(keys[0], "k0");
assert_eq!(keys[299], "k299");
assert!(o.contains("k200"));
assert!(o.has_own_key("k200"));
add(&mut o, "k150", n(-1.0), threshold);
assert_eq!(o.len(), 300, "updating an existing key does not grow");
assert_eq!(o.get("k150").unwrap().unpack(), Unpacked::Number(-1.0));
assert!(o.delete(Shape::root(), "k0"));
assert_eq!(o.get("k0"), None);
assert_eq!(o.len(), 299);
assert_eq!(o.keys()[0], "k1");
assert!(!o.delete(Shape::root(), "k0"));
}
#[test]
fn dictionary_ordered_keys_put_integer_indices_first() {
let mut o = Object::new(Shape::root());
let threshold = 0; add(&mut o, "b", n(1.0), threshold);
add(&mut o, "2", n(1.0), threshold);
add(&mut o, "1", n(1.0), threshold);
add(&mut o, "a", n(1.0), threshold);
assert_eq!(o.shape().lookup("b"), None);
assert_eq!(o.ordered_keys(), ["1", "2", "b", "a"]);
assert_eq!(o.enumerable_keys(), ["1", "2", "b", "a"]);
assert_eq!(o.keys(), ["b", "2", "1", "a"]);
}
#[test]
fn dictionary_traces_handle_values() {
let mut o = Object::new(Shape::root());
let threshold = 0;
add(&mut o, "h1", NanBox::handle(7), threshold);
add(&mut o, "n", n(1.0), threshold);
add(&mut o, "h2", NanBox::handle(9), threshold);
let mut seen: Vec<u64> = Vec::new();
o.trace_handles(|h| seen.push(h.to_raw()));
seen.sort_unstable();
assert_eq!(seen, [7, 9]);
}
}