// SPDX-License-Identifier: MPL-2.0 use alloc::{ collections::{vec_deque, BTreeMap, VecDeque}, string::{String, ToString}, }; use core::{fmt::Display, iter::zip, ops::Deref}; pub type PathElement = String; pub type KtestPathIter<'a> = vec_deque::Iter<'a, PathElement>; #[derive(Debug)] pub struct KtestPath { path: VecDeque, } impl From<&str> for KtestPath { fn from(s: &str) -> Self { let mut path = VecDeque::new(); for module in s.split("::") { path.push_back(module.to_string()); } Self { path } } } impl KtestPath { pub fn new() -> Self { Self { path: VecDeque::new(), } } pub fn from(s: &str) -> Self { Self { path: s.split("::").map(PathElement::from).collect(), } } pub fn push_back(&mut self, s: &str) { self.path.push_back(PathElement::from(s)); } pub fn pop_back(&mut self) -> Option { self.path.pop_back() } pub fn push_front(&mut self, s: &str) { self.path.push_front(PathElement::from(s)) } pub fn pop_front(&mut self) -> Option { self.path.pop_front() } pub fn len(&self) -> usize { self.path.len() } pub fn is_empty(&self) -> bool { self.path.is_empty() } pub fn starts_with(&self, other: &Self) -> bool { if self.path.len() < other.path.len() { return false; } for (e1, e2) in zip(self.path.iter(), other.path.iter()) { if e1 != e2 { return false; } } true } pub fn ends_with(&self, other: &Self) -> bool { if self.path.len() < other.path.len() { return false; } for (e1, e2) in zip(self.path.iter().rev(), other.path.iter().rev()) { if e1 != e2 { return false; } } true } pub fn iter(&self) -> KtestPathIter { self.path.iter() } } impl Default for KtestPath { fn default() -> Self { Self::new() } } impl Display for KtestPath { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { let mut first = true; for e in self.path.iter() { if first { first = false; } else { write!(f, "::")?; } write!(f, "{}", e)?; } Ok(()) } } #[cfg(test)] mod path_test { use super::*; #[test] fn test_ktest_path() { let mut path = KtestPath::new(); path.push_back("a"); path.push_back("b"); path.push_back("c"); assert_eq!(path.to_string(), "a::b::c"); assert_eq!(path.pop_back(), Some("c".to_string())); assert_eq!(path.pop_back(), Some("b".to_string())); assert_eq!(path.pop_back(), Some("a".to_string())); assert_eq!(path.pop_back(), None); } #[test] fn test_ktest_path_starts_with() { let mut path = KtestPath::new(); path.push_back("a"); path.push_back("b"); path.push_back("c"); assert!(path.starts_with(&KtestPath::from("a"))); assert!(path.starts_with(&KtestPath::from("a::b"))); assert!(path.starts_with(&KtestPath::from("a::b::c"))); assert!(!path.starts_with(&KtestPath::from("a::b::c::d"))); assert!(!path.starts_with(&KtestPath::from("a::b::d"))); assert!(!path.starts_with(&KtestPath::from("a::d"))); assert!(!path.starts_with(&KtestPath::from("d"))); } #[test] fn test_ktest_path_ends_with() { let mut path = KtestPath::new(); path.push_back("a"); path.push_back("b"); path.push_back("c"); assert!(path.ends_with(&KtestPath::from("c"))); assert!(path.ends_with(&KtestPath::from("b::c"))); assert!(path.ends_with(&KtestPath::from("a::b::c"))); assert!(!path.ends_with(&KtestPath::from("d::a::b::c"))); assert!(!path.ends_with(&KtestPath::from("a::b::d"))); assert!(!path.ends_with(&KtestPath::from("a::d"))); assert!(!path.ends_with(&KtestPath::from("d"))); } } #[derive(Debug)] pub struct SuffixTrie { children: BTreeMap, is_end: bool, } impl SuffixTrie { pub fn new() -> Self { Self { children: BTreeMap::new(), is_end: false, } } pub fn from_paths>(paths: I) -> Self { let mut t = Self::new(); for i in paths { t.insert(i.iter()); } t } pub fn insert(&mut self, path: I) where I: DoubleEndedIterator, P: Deref, { let mut cur = self; for e in path.into_iter().rev() { cur = cur.children.entry(e.clone()).or_default(); } cur.is_end = true; } /// Find if there is a perfect match in this suffix trie. pub fn matches(&self, path: I) -> bool where I: DoubleEndedIterator, P: Deref, { let mut cur = self; for e in path.into_iter().rev() { if let Some(next) = cur.children.get(&*e) { cur = next; } else { return false; } } cur.is_end } /// Find if any suffix of the path exists in the suffix trie. pub fn contains(&self, path: I) -> bool where I: DoubleEndedIterator, P: Deref, { let mut cur = self; for e in path.into_iter().rev() { if let Some(next) = cur.children.get(&*e) { cur = next; if cur.is_end { return true; } } else { return false; } } false } } impl Default for SuffixTrie { fn default() -> Self { Self::new() } } #[cfg(test)] mod suffix_trie_test { use super::*; static TEST_PATHS: &[&str] = &[ "a::b::c::d", "a::b::c::e", "a::b::d::e", "a::b::f::g", "h::i::j::k", "l::m::n", "m::n", ]; #[test] fn test_contains() { let trie = SuffixTrie::from_paths(TEST_PATHS.iter().map(|&s| KtestPath::from(s))); assert!(trie.contains(KtestPath::from("e::f::g::a::b::c::d").iter())); assert!(trie.contains(KtestPath::from("e::f::g::a::b::f::g").iter())); assert!(trie.contains(KtestPath::from("h::i::j::l::m::n").iter())); assert!(trie.contains(KtestPath::from("l::m::n").iter())); assert!(!trie.contains(KtestPath::from("a::b::c").iter())); assert!(!trie.contains(KtestPath::from("b::c::d").iter())); assert!(!trie.contains(KtestPath::from("a::b::f").iter())); assert!(!trie.contains(KtestPath::from("i::j").iter())); assert!(!trie.contains(KtestPath::from("h::i::j::l::n").iter())); assert!(!trie.contains(KtestPath::from("n").iter())); } #[test] fn test_matches() { let trie = SuffixTrie::from_paths(TEST_PATHS.iter().map(|&s| KtestPath::from(s))); assert!(trie.matches(KtestPath::from("a::b::c::d").iter())); assert!(trie.matches(KtestPath::from("a::b::c::e").iter())); assert!(trie.matches(KtestPath::from("l::m::n").iter())); assert!(trie.matches(KtestPath::from("m::n").iter())); assert!(!trie.matches(KtestPath::from("a::b::d").iter())); assert!(!trie.matches(KtestPath::from("b::c::e").iter())); assert!(!trie.matches(KtestPath::from("l::n::h::k::j::i").iter())); assert!(!trie.matches(KtestPath::from("n").iter())); } }