use crate::dynamic_dawg_char::{DynamicDawgChar, DynamicDawgCharInner};
use crate::sync_compat::RwLock;
use crate::value::DictionaryValue;
use crate::zipper::{DictZipper, ValuedDictZipper};
use std::sync::Arc;
#[derive(Clone)]
pub struct DynamicDawgCharZipper<V: DictionaryValue = ()> {
inner: Arc<RwLock<DynamicDawgCharInner<V>>>,
node: usize,
path: Vec<char>,
}
impl<V: DictionaryValue> DynamicDawgCharZipper<V> {
pub fn new_from_dict(dict: &DynamicDawgChar<V>) -> Self {
DynamicDawgCharZipper {
inner: dict.inner.clone(),
node: 0, path: Vec::new(),
}
}
pub fn node(&self) -> usize {
self.node
}
}
impl<V: DictionaryValue> DictZipper for DynamicDawgCharZipper<V> {
type Unit = char;
fn is_final(&self) -> bool {
let inner = self.inner.read();
if self.node < inner.nodes.len() {
inner.nodes[self.node].is_final
} else {
false
}
}
fn descend(&self, label: Self::Unit) -> Option<Self> {
let inner = self.inner.read();
if self.node >= inner.nodes.len() {
return None;
}
for (edge_label, target_node) in &inner.nodes[self.node].edges {
if *edge_label == label {
let mut new_path = self.path.clone();
new_path.push(label);
return Some(DynamicDawgCharZipper {
inner: self.inner.clone(),
node: *target_node,
path: new_path,
});
}
}
None
}
fn path(&self) -> Vec<Self::Unit> {
self.path.clone()
}
fn children(&self) -> impl Iterator<Item = (Self::Unit, Self)> {
let edges: Vec<(char, usize)> = {
let inner = self.inner.read();
if self.node < inner.nodes.len() {
inner.nodes[self.node].edges.iter().copied().collect()
} else {
Vec::new()
}
};
let inner = self.inner.clone();
let base_path = self.path.clone();
edges.into_iter().map(move |(label, target)| {
let mut new_path = base_path.clone();
new_path.push(label);
(
label,
DynamicDawgCharZipper {
inner: inner.clone(),
node: target,
path: new_path,
},
)
})
}
}
impl<V: DictionaryValue> ValuedDictZipper for DynamicDawgCharZipper<V> {
type Value = V;
fn value(&self) -> Option<Self::Value> {
let inner = self.inner.read();
if self.node < inner.nodes.len() && inner.nodes[self.node].is_final {
inner.nodes[self.node].value.clone()
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_root_zipper_not_final() {
let dict: DynamicDawgChar<()> = DynamicDawgChar::new();
dict.insert("test");
let zipper = DynamicDawgCharZipper::new_from_dict(&dict);
assert!(!zipper.is_final());
assert_eq!(zipper.node(), 0);
}
#[test]
fn test_descend_nonexistent() {
let dict: DynamicDawgChar<()> = DynamicDawgChar::new();
dict.insert("test");
let zipper = DynamicDawgCharZipper::new_from_dict(&dict);
assert!(zipper.descend('x').is_none());
}
#[test]
fn test_descend_and_finality() {
let dict: DynamicDawgChar<()> = DynamicDawgChar::new();
dict.insert("cat");
dict.insert("catch");
let zipper = DynamicDawgCharZipper::new_from_dict(&dict);
let c = zipper.descend('c').expect("Should descend to 'c'");
assert!(!c.is_final());
let a = c.descend('a').expect("Should descend to 'a'");
assert!(!a.is_final());
let t = a.descend('t').expect("Should descend to 't'");
assert!(t.is_final(), "'cat' should be a final state");
let c2 = t.descend('c').expect("Should descend to 'c' from 'cat'");
let h = c2.descend('h').expect("Should descend to 'h'");
assert!(h.is_final(), "'catch' should be a final state");
}
#[test]
fn test_unicode_navigation() {
let dict: DynamicDawgChar<()> = DynamicDawgChar::new();
dict.insert("café");
dict.insert("naïve");
dict.insert("🎉");
let zipper = DynamicDawgCharZipper::new_from_dict(&dict);
let cafe_zipper = zipper
.descend('c')
.and_then(|z| z.descend('a'))
.and_then(|z| z.descend('f'))
.and_then(|z| z.descend('é'))
.expect("Should navigate to 'café'");
assert!(cafe_zipper.is_final());
let emoji_zipper = zipper.descend('🎉').expect("Should descend to emoji");
assert!(emoji_zipper.is_final());
}
#[test]
fn test_children_iteration() {
let dict: DynamicDawgChar<()> = DynamicDawgChar::new();
dict.insert("cat");
dict.insert("car");
dict.insert("dog");
let zipper = DynamicDawgCharZipper::new_from_dict(&dict);
let children: Vec<char> = zipper.children().map(|(label, _)| label).collect();
assert!(children.contains(&'c'));
assert!(children.contains(&'d'));
}
#[test]
fn test_valued_zipper() {
let dict: DynamicDawgChar<u32> = DynamicDawgChar::new();
dict.insert_with_value("cat", 1);
dict.insert_with_value("catch", 2);
let zipper = DynamicDawgCharZipper::new_from_dict(&dict);
let cat_zipper = zipper
.descend('c')
.and_then(|z| z.descend('a'))
.and_then(|z| z.descend('t'))
.expect("Should navigate to 'cat'");
assert_eq!(cat_zipper.value(), Some(1));
let catch_zipper = cat_zipper
.descend('c')
.and_then(|z| z.descend('h'))
.expect("Should navigate to 'catch'");
assert_eq!(catch_zipper.value(), Some(2));
}
#[test]
fn test_unicode_valued_zipper() {
let dict: DynamicDawgChar<String> = DynamicDawgChar::new();
dict.insert_with_value("café", "coffee".to_string());
dict.insert_with_value("naïve", "innocent".to_string());
let zipper = DynamicDawgCharZipper::new_from_dict(&dict);
let cafe_zipper = zipper
.descend('c')
.and_then(|z| z.descend('a'))
.and_then(|z| z.descend('f'))
.and_then(|z| z.descend('é'))
.expect("Should navigate to 'café'");
assert_eq!(cafe_zipper.value(), Some("coffee".to_string()));
}
#[test]
fn test_clone_independence() {
let dict: DynamicDawgChar<()> = DynamicDawgChar::new();
dict.insert("test");
let zipper1 = DynamicDawgCharZipper::new_from_dict(&dict);
let zipper2 = zipper1.clone();
let z1_t = zipper1.descend('t');
let z2_t = zipper2.descend('t');
assert!(z1_t.is_some());
assert!(z2_t.is_some());
assert_eq!(z1_t.unwrap().node(), z2_t.unwrap().node());
}
#[test]
fn test_empty_dictionary() {
let dict: DynamicDawgChar<()> = DynamicDawgChar::new();
let zipper = DynamicDawgCharZipper::new_from_dict(&dict);
assert!(!zipper.is_final());
assert_eq!(zipper.children().count(), 0);
}
#[test]
fn test_value_none_for_non_final() {
let dict: DynamicDawgChar<u32> = DynamicDawgChar::new();
dict.insert_with_value("cat", 42);
let zipper = DynamicDawgCharZipper::new_from_dict(&dict);
let c_zipper = zipper.descend('c').expect("Should descend to 'c'");
assert_eq!(
c_zipper.value(),
None,
"Non-final node should have no value"
);
}
#[test]
fn test_concurrent_access() {
use std::sync::Arc as StdArc;
use std::thread;
let dict = StdArc::new({
let d: DynamicDawgChar<()> = DynamicDawgChar::new();
d.insert("test");
d.insert("testing");
d
});
let handles: Vec<_> = (0..4)
.map(|_| {
let dict_clone = dict.clone();
thread::spawn(move || {
let zipper = DynamicDawgCharZipper::new_from_dict(&dict_clone);
zipper.descend('t').is_some()
})
})
.collect();
for handle in handles {
assert!(handle.join().unwrap());
}
}
#[test]
fn test_path_tracking() {
let dict: DynamicDawgChar<()> = DynamicDawgChar::new();
dict.insert("café");
let zipper = DynamicDawgCharZipper::new_from_dict(&dict);
let c = zipper.descend('c').unwrap();
assert_eq!(c.path(), vec!['c']);
let a = c.descend('a').unwrap();
assert_eq!(a.path(), vec!['c', 'a']);
let f = a.descend('f').unwrap();
assert_eq!(f.path(), vec!['c', 'a', 'f']);
let e = f.descend('é').unwrap();
assert_eq!(e.path(), vec!['c', 'a', 'f', 'é']);
}
}