use smallvec::SmallVec;
use crate::objects::map::PropertyAttributes;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ShapeId(u32);
impl ShapeId {
#[inline]
pub fn index(self) -> u32 {
self.0
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Constness {
Const,
Mutable,
}
#[derive(Clone, Debug)]
pub struct ShapeDescriptor {
key: String,
field_index: u32,
attributes: PropertyAttributes,
constness: Constness,
}
impl ShapeDescriptor {
pub fn new(
key: impl Into<String>,
field_index: u32,
attributes: PropertyAttributes,
constness: Constness,
) -> Self {
Self {
key: key.into(),
field_index,
attributes,
constness,
}
}
#[inline]
pub fn key(&self) -> &str {
&self.key
}
#[inline]
pub fn field_index(&self) -> u32 {
self.field_index
}
#[inline]
pub fn attributes(&self) -> PropertyAttributes {
self.attributes
}
#[inline]
pub fn constness(&self) -> Constness {
self.constness
}
}
#[derive(Clone, Debug)]
pub struct ShapeTransition {
property_name: String,
attributes: PropertyAttributes,
target: ShapeId,
}
impl ShapeTransition {
#[inline]
pub fn property_name(&self) -> &str {
&self.property_name
}
#[inline]
pub fn attributes(&self) -> PropertyAttributes {
self.attributes
}
#[inline]
pub fn target(&self) -> ShapeId {
self.target
}
}
const TRANSITION_INLINE_CAP: usize = 4;
pub struct Shape {
id: ShapeId,
parent: Option<ShapeId>,
descriptors: Vec<ShapeDescriptor>,
property_count: u16,
instance_size: u16,
n_in_object_properties: u8,
transitions: SmallVec<[ShapeTransition; TRANSITION_INLINE_CAP]>,
}
impl Shape {
#[inline]
pub fn id(&self) -> ShapeId {
self.id
}
#[inline]
pub fn parent(&self) -> Option<ShapeId> {
self.parent
}
#[inline]
pub fn descriptors(&self) -> &[ShapeDescriptor] {
&self.descriptors
}
#[inline]
pub fn property_count(&self) -> u16 {
self.property_count
}
#[inline]
pub fn instance_size(&self) -> u16 {
self.instance_size
}
#[inline]
pub fn n_in_object_properties(&self) -> u8 {
self.n_in_object_properties
}
#[inline]
pub fn transitions(&self) -> &[ShapeTransition] {
&self.transitions
}
pub fn lookup(&self, key: &str) -> Option<&ShapeDescriptor> {
self.descriptors.iter().find(|d| d.key == key)
}
fn find_transition(&self, name: &str, attrs: PropertyAttributes) -> Option<ShapeId> {
self.transitions
.iter()
.find(|t| t.property_name == name && t.attributes == attrs)
.map(|t| t.target)
}
}
const DEFAULT_IN_OBJECT_SLOTS: u8 = 4;
pub struct ShapeTable {
shapes: Vec<Shape>,
}
impl ShapeTable {
pub fn new() -> Self {
let root = Shape {
id: ShapeId(0),
parent: None,
descriptors: Vec::new(),
property_count: 0,
instance_size: 0,
n_in_object_properties: DEFAULT_IN_OBJECT_SLOTS,
transitions: SmallVec::new(),
};
Self { shapes: vec![root] }
}
#[inline]
pub fn root(&self) -> ShapeId {
ShapeId(0)
}
#[inline]
pub fn get(&self, id: ShapeId) -> &Shape {
&self.shapes[id.0 as usize]
}
#[inline]
pub fn len(&self) -> usize {
self.shapes.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.shapes.len() <= 1
}
pub fn transition(
&mut self,
parent: ShapeId,
name: &str,
attrs: PropertyAttributes,
) -> ShapeId {
if let Some(existing) = self.shapes[parent.0 as usize].find_transition(name, attrs) {
return existing;
}
let child_id = ShapeId(self.shapes.len() as u32);
let parent_shape = &self.shapes[parent.0 as usize];
let field_index = parent_shape.property_count as u32;
let mut descriptors = parent_shape.descriptors.clone();
descriptors.push(ShapeDescriptor::new(
name,
field_index,
attrs,
Constness::Mutable,
));
let property_count = parent_shape.property_count + 1;
let n_in_object = parent_shape.n_in_object_properties;
let child = Shape {
id: child_id,
parent: Some(parent),
descriptors,
property_count,
instance_size: property_count,
n_in_object_properties: n_in_object,
transitions: SmallVec::new(),
};
self.shapes.push(child);
self.shapes[parent.0 as usize]
.transitions
.push(ShapeTransition {
property_name: name.to_string(),
attributes: attrs,
target: child_id,
});
child_id
}
#[inline]
pub fn lookup(&self, id: ShapeId, key: &str) -> Option<&ShapeDescriptor> {
self.get(id).lookup(key)
}
}
impl Default for ShapeTable {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shape_id_index() {
let id = ShapeId(42);
assert_eq!(id.index(), 42);
}
#[test]
fn test_shape_id_equality() {
assert_eq!(ShapeId(0), ShapeId(0));
assert_ne!(ShapeId(0), ShapeId(1));
}
#[test]
fn test_shape_descriptor_fields() {
let desc = ShapeDescriptor::new(
"x",
0,
PropertyAttributes::WRITABLE | PropertyAttributes::ENUMERABLE,
Constness::Mutable,
);
assert_eq!(desc.key(), "x");
assert_eq!(desc.field_index(), 0);
assert!(desc.attributes().contains(PropertyAttributes::WRITABLE));
assert!(desc.attributes().contains(PropertyAttributes::ENUMERABLE));
assert!(!desc.attributes().contains(PropertyAttributes::CONFIGURABLE));
assert_eq!(desc.constness(), Constness::Mutable);
}
#[test]
fn test_shape_descriptor_const() {
let desc = ShapeDescriptor::new("c", 3, PropertyAttributes::empty(), Constness::Const);
assert_eq!(desc.constness(), Constness::Const);
}
#[test]
fn test_table_new_has_root() {
let table = ShapeTable::new();
assert_eq!(table.len(), 1);
assert!(table.is_empty()); let root = table.get(table.root());
assert_eq!(root.property_count(), 0);
assert!(root.descriptors().is_empty());
assert!(root.parent().is_none());
assert!(root.transitions().is_empty());
}
#[test]
fn test_table_default_eq_new() {
let a = ShapeTable::new();
let b = ShapeTable::default();
assert_eq!(a.len(), b.len());
assert_eq!(a.root(), b.root());
}
#[test]
fn test_single_transition() {
let mut table = ShapeTable::new();
let root = table.root();
let attrs = PropertyAttributes::WRITABLE | PropertyAttributes::ENUMERABLE;
let child = table.transition(root, "x", attrs);
assert_ne!(child, root);
assert_eq!(table.len(), 2);
let shape = table.get(child);
assert_eq!(shape.property_count(), 1);
assert_eq!(shape.parent(), Some(root));
assert_eq!(shape.descriptors().len(), 1);
assert_eq!(shape.descriptors()[0].key(), "x");
assert_eq!(shape.descriptors()[0].field_index(), 0);
assert_eq!(shape.descriptors()[0].attributes(), attrs);
}
#[test]
fn test_transition_deduplication() {
let mut table = ShapeTable::new();
let root = table.root();
let attrs = PropertyAttributes::WRITABLE;
let a = table.transition(root, "x", attrs);
let b = table.transition(root, "x", attrs);
assert_eq!(a, b, "same (name, attrs) must deduplicate");
assert_eq!(table.len(), 2); }
#[test]
fn test_different_attrs_no_dedup() {
let mut table = ShapeTable::new();
let root = table.root();
let a = table.transition(root, "x", PropertyAttributes::WRITABLE);
let b = table.transition(root, "x", PropertyAttributes::ENUMERABLE);
assert_ne!(a, b, "different attrs must produce different shapes");
assert_eq!(table.len(), 3);
}
#[test]
fn test_different_names_no_dedup() {
let mut table = ShapeTable::new();
let root = table.root();
let attrs = PropertyAttributes::WRITABLE;
let a = table.transition(root, "x", attrs);
let b = table.transition(root, "y", attrs);
assert_ne!(a, b);
assert_eq!(table.len(), 3);
}
#[test]
fn test_transition_chain() {
let mut table = ShapeTable::new();
let root = table.root();
let attrs = PropertyAttributes::WRITABLE
| PropertyAttributes::ENUMERABLE
| PropertyAttributes::CONFIGURABLE;
let s1 = table.transition(root, "a", attrs);
let s2 = table.transition(s1, "b", attrs);
let s3 = table.transition(s2, "c", attrs);
assert_eq!(table.len(), 4);
let shape = table.get(s3);
assert_eq!(shape.property_count(), 3);
assert_eq!(shape.parent(), Some(s2));
let descs = shape.descriptors();
assert_eq!(descs.len(), 3);
assert_eq!(descs[0].key(), "a");
assert_eq!(descs[0].field_index(), 0);
assert_eq!(descs[1].key(), "b");
assert_eq!(descs[1].field_index(), 1);
assert_eq!(descs[2].key(), "c");
assert_eq!(descs[2].field_index(), 2);
}
#[test]
fn test_shared_prefix_shapes() {
let mut table = ShapeTable::new();
let root = table.root();
let attrs = PropertyAttributes::WRITABLE;
let sx = table.transition(root, "x", attrs);
let sxy = table.transition(sx, "y", attrs);
let sx2 = table.transition(root, "x", attrs);
let sxz = table.transition(sx2, "z", attrs);
assert_eq!(sx, sx2);
assert_ne!(sxy, sxz);
assert_eq!(table.get(root).transitions().len(), 1);
assert_eq!(table.get(sx).transitions().len(), 2);
}
#[test]
fn test_lookup_found() {
let mut table = ShapeTable::new();
let root = table.root();
let attrs = PropertyAttributes::WRITABLE | PropertyAttributes::ENUMERABLE;
let s1 = table.transition(root, "x", attrs);
let s2 = table.transition(s1, "y", attrs);
let desc = table.lookup(s2, "x").expect("x should exist");
assert_eq!(desc.field_index(), 0);
let desc = table.lookup(s2, "y").expect("y should exist");
assert_eq!(desc.field_index(), 1);
}
#[test]
fn test_lookup_not_found() {
let mut table = ShapeTable::new();
let root = table.root();
let s1 = table.transition(root, "x", PropertyAttributes::WRITABLE);
assert!(table.lookup(s1, "z").is_none());
}
#[test]
fn test_lookup_on_root_returns_none() {
let table = ShapeTable::new();
assert!(table.lookup(table.root(), "anything").is_none());
}
#[test]
fn test_shape_instance_size() {
let mut table = ShapeTable::new();
let root = table.root();
assert_eq!(table.get(root).instance_size(), 0);
let s1 = table.transition(root, "a", PropertyAttributes::WRITABLE);
assert_eq!(table.get(s1).instance_size(), 1);
let s2 = table.transition(s1, "b", PropertyAttributes::WRITABLE);
assert_eq!(table.get(s2).instance_size(), 2);
}
#[test]
fn test_shape_n_in_object_properties() {
let table = ShapeTable::new();
assert_eq!(
table.get(table.root()).n_in_object_properties(),
DEFAULT_IN_OBJECT_SLOTS
);
}
#[test]
fn test_shape_id_accessor() {
let table = ShapeTable::new();
let root = table.root();
assert_eq!(table.get(root).id(), root);
}
#[test]
fn test_transition_creates_mutable_descriptors() {
let mut table = ShapeTable::new();
let root = table.root();
let child = table.transition(root, "val", PropertyAttributes::WRITABLE);
let desc = &table.get(child).descriptors()[0];
assert_eq!(desc.constness(), Constness::Mutable);
}
#[test]
fn test_many_transitions_from_root() {
let mut table = ShapeTable::new();
let root = table.root();
let attrs = PropertyAttributes::WRITABLE;
let mut children = Vec::new();
for i in 0..20 {
let name = format!("prop{i}");
children.push(table.transition(root, &name, attrs));
}
let unique: std::collections::HashSet<_> = children.iter().collect();
assert_eq!(unique.len(), 20);
assert_eq!(table.get(root).transitions().len(), 20);
assert_eq!(table.len(), 21);
}
#[test]
fn test_shape_transition_accessors() {
let mut table = ShapeTable::new();
let root = table.root();
let attrs = PropertyAttributes::WRITABLE | PropertyAttributes::CONFIGURABLE;
let child = table.transition(root, "foo", attrs);
let t = &table.get(root).transitions()[0];
assert_eq!(t.property_name(), "foo");
assert_eq!(t.attributes(), attrs);
assert_eq!(t.target(), child);
}
}