use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::vec::Vec;
use core::cell::RefCell;
pub struct Shape {
parent: Option<Rc<Shape>>,
key: Option<Box<str>>,
slot: u32,
len: u32,
transitions: RefCell<Vec<(Box<str>, Rc<Shape>)>>,
}
impl Shape {
#[must_use]
pub fn root() -> Rc<Shape> {
Rc::new(Shape {
parent: None,
key: None,
slot: 0,
len: 0,
transitions: RefCell::new(Vec::new()),
})
}
#[must_use]
pub fn len(&self) -> u32 {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn transition(self: &Rc<Self>, key: &str) -> Rc<Shape> {
if self.lookup(key).is_some() {
return Rc::clone(self);
}
if let Some((_, child)) = self
.transitions
.borrow()
.iter()
.find(|(k, _)| k.as_ref() == key)
{
return Rc::clone(child);
}
let child = Rc::new(Shape {
parent: Some(Rc::clone(self)),
key: Some(Box::from(key)),
slot: self.len,
len: self.len + 1,
transitions: RefCell::new(Vec::new()),
});
self.transitions
.borrow_mut()
.push((Box::from(key), Rc::clone(&child)));
child
}
#[must_use]
pub fn lookup(&self, key: &str) -> Option<u32> {
let mut node = self;
loop {
if let Some(k) = &node.key
&& k.as_ref() == key
{
return Some(node.slot);
}
node = node.parent.as_deref()?;
}
}
#[must_use]
pub fn contains(&self, key: &str) -> bool {
self.lookup(key).is_some()
}
#[must_use]
pub fn keys(&self) -> Vec<&str> {
let mut out: Vec<&str> = Vec::with_capacity(self.len as usize);
let mut node = self;
loop {
if let Some(k) = &node.key {
out.push(k.as_ref());
}
match node.parent.as_deref() {
Some(p) => node = p,
None => break,
}
}
out.reverse(); out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn root_is_empty() {
let root = Shape::root();
assert!(root.is_empty());
assert_eq!(root.len(), 0);
assert_eq!(root.lookup("x"), None);
assert!(root.keys().is_empty());
}
#[test]
fn transition_assigns_sequential_slots() {
let s = Shape::root();
let s = s.transition("x");
let s = s.transition("y");
let s = s.transition("z");
assert_eq!(s.len(), 3);
assert_eq!(s.lookup("x"), Some(0));
assert_eq!(s.lookup("y"), Some(1));
assert_eq!(s.lookup("z"), Some(2));
assert_eq!(s.lookup("w"), None);
assert_eq!(s.keys(), ["x", "y", "z"]);
}
#[test]
fn same_structure_shares_one_shape() {
let root = Shape::root();
let o1 = root.transition("a").transition("b");
let o2 = root.transition("a").transition("b");
assert!(Rc::ptr_eq(&o1, &o2));
}
#[test]
fn divergent_structure_forks_the_tree() {
let base = Shape::root().transition("a");
let with_b = base.transition("b");
let with_c = base.transition("c");
assert!(!Rc::ptr_eq(&with_b, &with_c));
assert_eq!(with_b.lookup("b"), Some(1));
assert_eq!(with_b.lookup("c"), None);
assert_eq!(with_c.lookup("c"), Some(1));
assert_eq!(with_b.lookup("a"), Some(0));
assert_eq!(with_c.lookup("a"), Some(0));
}
#[test]
fn re_adding_an_existing_key_is_a_no_op() {
let s = Shape::root().transition("x").transition("y");
let again = s.transition("x");
assert!(Rc::ptr_eq(&s, &again));
assert_eq!(again.len(), 2);
}
#[test]
fn property_order_differs_by_insertion_order() {
let xy = Shape::root().transition("x").transition("y");
let yx = Shape::root().transition("y").transition("x");
assert!(!Rc::ptr_eq(&xy, &yx));
assert_eq!(xy.lookup("x"), Some(0));
assert_eq!(yx.lookup("x"), Some(1));
assert_eq!(xy.keys(), ["x", "y"]);
assert_eq!(yx.keys(), ["y", "x"]);
}
}