1#![doc = include_str!("../README.md")]
2#![no_std]
3#![warn(missing_docs)]
4#![warn(missing_debug_implementations)]
5
6extern crate alloc;
7
8use alloc::boxed::Box;
9use alloc::string::String;
10use alloc::vec;
11use alloc::vec::Vec;
12use core::fmt;
13use core::iter::FusedIterator;
14
15#[inline]
32unsafe fn utf8_str_unchecked(bytes: &[u8]) -> &str {
33 debug_assert!(core::str::from_utf8(bytes).is_ok());
34 unsafe { core::str::from_utf8_unchecked(bytes) }
37}
38
39#[inline]
42unsafe fn utf8_string_unchecked(bytes: Vec<u8>) -> String {
43 debug_assert!(core::str::from_utf8(&bytes).is_ok());
44 unsafe { String::from_utf8_unchecked(bytes) }
46}
47
48#[inline]
53fn utf8_char_len(b: u8) -> usize {
54 if b < 0xC0 {
57 1
58 } else if b < 0xE0 {
59 2
60 } else if b < 0xF0 {
61 3
62 } else {
63 4
64 }
65}
66
67#[derive(Clone)]
76pub struct CommandTrieBuilder<T> {
77 root: BuilderNode<T>,
78 len: usize,
79}
80
81#[derive(Clone)]
82struct BuilderNode<T> {
83 label: Box<[u8]>,
85 value: Option<T>,
86 children: Vec<BuilderNode<T>>,
88}
89
90impl<T> Default for CommandTrieBuilder<T> {
91 fn default() -> Self {
92 Self::new()
93 }
94}
95
96impl<T> CommandTrieBuilder<T> {
97 #[must_use]
99 pub fn new() -> Self {
100 Self {
101 root: BuilderNode {
102 label: Box::from(&[][..]),
103 value: None,
104 children: Vec::new(),
105 },
106 len: 0,
107 }
108 }
109
110 #[must_use]
112 pub fn len(&self) -> usize {
113 self.len
114 }
115
116 #[must_use]
118 pub fn is_empty(&self) -> bool {
119 self.len == 0
120 }
121
122 pub fn clear(&mut self) {
124 *self = Self::new();
125 }
126
127 pub fn insert(&mut self, key: &str, value: T) -> Option<T> {
132 let prev = self.root.insert(key.as_bytes(), value);
133 if prev.is_none() {
134 self.len += 1;
135 }
136 prev
137 }
138
139 pub fn remove(&mut self, key: &str) -> Option<T> {
143 let v = self.root.remove(key.as_bytes())?;
144 self.len -= 1;
145 Some(v)
146 }
147
148 #[must_use]
150 pub fn get(&self, key: &str) -> Option<&T> {
151 let mut node = &self.root;
152 let mut rem = key.as_bytes();
153 loop {
154 if rem.is_empty() {
155 return node.value.as_ref();
156 }
157 let child = node.find_child(rem)?;
158 if !rem.starts_with(&child.label) {
159 return None;
160 }
161 rem = &rem[child.label.len()..];
162 node = child;
163 }
164 }
165
166 #[must_use]
168 pub fn contains(&self, key: &str) -> bool {
169 self.get(key).is_some()
170 }
171
172 pub fn build(self) -> CommandTrie<T> {
187 let len = self.len;
188 let mut nodes: Vec<FrozenNode<T>> = Vec::new();
189 let mut labels: Vec<u8> = Vec::new();
190 let mut children: Vec<NodeId> = Vec::new();
191 let mut child_first_bytes: Vec<u8> = Vec::new();
192 build_visit(
193 self.root,
194 &mut nodes,
195 &mut labels,
196 &mut children,
197 &mut child_first_bytes,
198 );
199 CommandTrie {
200 nodes: nodes.into_boxed_slice(),
201 labels: labels.into_boxed_slice(),
202 children: children.into_boxed_slice(),
203 child_first_bytes: child_first_bytes.into_boxed_slice(),
204 len,
205 }
206 }
207}
208
209fn build_visit<T>(
212 node: BuilderNode<T>,
213 nodes: &mut Vec<FrozenNode<T>>,
214 labels: &mut Vec<u8>,
215 children: &mut Vec<NodeId>,
216 child_first_bytes: &mut Vec<u8>,
217) -> NodeId {
218 let id = u16_or_panic(nodes.len());
219 let label_start = u16_or_panic(labels.len());
220 let label_len = u16_or_panic(node.label.len());
221 labels.extend_from_slice(&node.label);
222
223 nodes.push(FrozenNode {
224 label_start,
225 label_len,
226 children_start: 0,
227 children_len: 0,
228 value: node.value,
229 });
230
231 let n_children = u16_or_panic(node.children.len());
235 let children_start = u16_or_panic(children.len());
236 for _ in 0..n_children {
237 children.push(0);
238 child_first_bytes.push(0);
239 }
240 for (i, child) in node.children.into_iter().enumerate() {
241 let first = child.label[0];
245 let slot = children_start as usize + i;
246 let child_id = build_visit(child, nodes, labels, children, child_first_bytes);
247 children[slot] = child_id;
248 child_first_bytes[slot] = first;
249 }
250
251 let n = &mut nodes[id as usize];
252 n.children_start = children_start;
253 n.children_len = n_children;
254 id
255}
256
257#[inline]
262fn u16_or_panic(n: usize) -> u16 {
263 u16::try_from(n).expect("command-trie size exceeds u16::MAX (see FrozenNode docs)")
264}
265
266impl<T> BuilderNode<T> {
267 fn find_child(&self, rem: &[u8]) -> Option<&BuilderNode<T>> {
273 let idx = self.child_index(rem).ok()?;
274 Some(&self.children[idx])
275 }
276
277 fn child_index(&self, rem: &[u8]) -> Result<usize, usize> {
292 let first = rem[0];
293 if first < 0x80 {
294 return self.children.binary_search_by_key(&first, |c| c.label[0]);
295 }
296 let needle_len = utf8_char_len(first).min(rem.len());
297 let needle = &rem[..needle_len];
298 self.children.binary_search_by(|c| {
299 let cn = utf8_char_len(c.label[0]).min(c.label.len());
300 c.label[..cn].cmp(needle)
301 })
302 }
303
304 fn insert(&mut self, rem: &[u8], value: T) -> Option<T> {
305 if rem.is_empty() {
306 return self.value.replace(value);
307 }
308 match self.child_index(rem) {
309 Err(at) => {
310 self.children.insert(
311 at,
312 BuilderNode {
313 label: Box::from(rem),
314 value: Some(value),
315 children: Vec::new(),
316 },
317 );
318 None
319 }
320 Ok(idx) => {
321 let child = &mut self.children[idx];
322 let common = lcp(&child.label, rem);
323 if common == child.label.len() {
324 return child.insert(&rem[common..], value);
325 }
326 let old_label = core::mem::replace(&mut child.label, Box::from(&rem[..common]));
329 let old_value = child.value.take();
330 let old_children = core::mem::take(&mut child.children);
331 let existing = BuilderNode {
332 label: Box::from(&old_label[common..]),
333 value: old_value,
334 children: old_children,
335 };
336 if common == rem.len() {
337 child.value = Some(value);
338 child.children = vec![existing];
339 } else {
340 let new_node = BuilderNode {
341 label: Box::from(&rem[common..]),
342 value: Some(value),
343 children: Vec::new(),
344 };
345 child.children = if existing.label[..].cmp(&new_node.label[..])
351 == core::cmp::Ordering::Less
352 {
353 vec![existing, new_node]
354 } else {
355 vec![new_node, existing]
356 };
357 }
358 None
359 }
360 }
361 }
362
363 fn remove(&mut self, rem: &[u8]) -> Option<T> {
364 if rem.is_empty() {
365 return self.value.take();
366 }
367 let idx = self.child_index(rem).ok()?;
368 if !rem.starts_with(&self.children[idx].label) {
369 return None;
370 }
371 let label_len = self.children[idx].label.len();
372 let removed = self.children[idx].remove(&rem[label_len..])?;
373
374 let child = &self.children[idx];
375 if child.value.is_none() {
376 if child.children.is_empty() {
377 self.children.remove(idx);
378 } else if child.children.len() == 1 {
379 let mut removed_child = self.children.remove(idx);
380 let mut grandchild = removed_child.children.pop().unwrap();
381 let mut merged =
382 Vec::with_capacity(removed_child.label.len() + grandchild.label.len());
383 merged.extend_from_slice(&removed_child.label);
384 merged.extend_from_slice(&grandchild.label);
385 grandchild.label = merged.into_boxed_slice();
386 self.children.insert(idx, grandchild);
387 }
388 }
389 Some(removed)
390 }
391}
392
393fn lcp(a: &[u8], b: &[u8]) -> usize {
400 let mut i = a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count();
401 while i > 0 && i < a.len() && (a[i] & 0xC0) == 0x80 {
406 i -= 1;
407 }
408 i
409}
410
411impl<K: AsRef<str>, T> FromIterator<(K, T)> for CommandTrieBuilder<T> {
412 fn from_iter<I: IntoIterator<Item = (K, T)>>(iter: I) -> Self {
414 let mut t = Self::new();
415 t.extend(iter);
416 t
417 }
418}
419
420impl<K: AsRef<str>, T> Extend<(K, T)> for CommandTrieBuilder<T> {
421 fn extend<I: IntoIterator<Item = (K, T)>>(&mut self, iter: I) {
423 for (k, v) in iter {
424 self.insert(k.as_ref(), v);
425 }
426 }
427}
428
429impl<T: fmt::Debug> fmt::Debug for CommandTrieBuilder<T> {
430 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431 f.debug_struct("CommandTrieBuilder")
432 .field("len", &self.len)
433 .field("root", &self.root)
434 .finish()
435 }
436}
437
438impl<T: fmt::Debug> fmt::Debug for BuilderNode<T> {
439 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440 f.debug_struct("BuilderNode")
441 .field("label", &unsafe { utf8_str_unchecked(&self.label) })
444 .field("value", &self.value)
445 .field("children", &self.children)
446 .finish()
447 }
448}
449
450type NodeId = u16;
455const ROOT: NodeId = 0;
456
457#[derive(Clone)]
464pub struct CommandTrie<T> {
465 nodes: Box<[FrozenNode<T>]>,
467 labels: Box<[u8]>,
469 children: Box<[NodeId]>,
472 child_first_bytes: Box<[u8]>,
476 len: usize,
477}
478
479#[derive(Clone)]
480struct FrozenNode<T> {
481 label_start: u16,
492 label_len: u16,
493 children_start: u16,
494 children_len: u16,
495 value: Option<T>,
496}
497
498impl<T> CommandTrie<T> {
499 #[must_use]
522 pub fn len(&self) -> usize {
523 self.len
524 }
525
526 #[must_use]
528 pub fn is_empty(&self) -> bool {
529 self.len == 0
530 }
531
532 #[inline]
533 fn label_of(&self, id: NodeId) -> &[u8] {
534 unsafe {
536 let n = self.nodes.get_unchecked(id as usize);
537 let start = n.label_start as usize;
538 let end = start + n.label_len as usize;
539 self.labels.get_unchecked(start..end)
540 }
541 }
542
543 #[inline]
544 fn children_of(&self, id: NodeId) -> &[NodeId] {
545 unsafe {
547 let n = self.nodes.get_unchecked(id as usize);
548 let start = n.children_start as usize;
549 let end = start + n.children_len as usize;
550 self.children.get_unchecked(start..end)
551 }
552 }
553
554 #[inline]
555 fn value_of(&self, id: NodeId) -> Option<&T> {
556 unsafe { self.nodes.get_unchecked(id as usize).value.as_ref() }
558 }
559
560 #[inline]
572 fn find_child(&self, parent: NodeId, rem: &[u8]) -> Option<NodeId> {
573 unsafe {
575 let n = self.nodes.get_unchecked(parent as usize);
576 let start = n.children_start as usize;
577 let end = start + n.children_len as usize;
578 let first = *rem.get_unchecked(0);
579 let slab = self.child_first_bytes.get_unchecked(start..end);
580 let idx = slab.binary_search(&first).ok()?;
583 if first < 0x80 {
588 return Some(*self.children.get_unchecked(start + idx));
589 }
590 self.find_child_multibyte(start, slab, idx, first, rem)
593 }
594 }
595
596 #[cold]
599 #[inline(never)]
600 fn find_child_multibyte(
601 &self,
602 start: usize,
603 slab: &[u8],
604 idx: usize,
605 first: u8,
606 rem: &[u8],
607 ) -> Option<NodeId> {
608 let clen = utf8_char_len(first);
609 debug_assert!(rem.len() >= clen);
614 unsafe {
618 let needle = rem.get_unchecked(..clen);
619 let mut lo = idx;
622 while lo > 0 && *slab.get_unchecked(lo - 1) == first {
623 lo -= 1;
624 }
625 let mut i = lo;
626 while i < slab.len() && *slab.get_unchecked(i) == first {
627 let child = *self.children.get_unchecked(start + i);
628 let lbl = self.label_of(child);
629 if lbl.len() >= clen && lbl.get_unchecked(..clen) == needle {
630 return Some(child);
631 }
632 i += 1;
633 }
634 None
635 }
636 }
637
638 #[must_use]
640 pub fn get(&self, key: &str) -> Option<&T> {
641 let mut node = ROOT;
642 let mut rem = key.as_bytes();
643 loop {
644 if rem.is_empty() {
645 return self.value_of(node);
646 }
647 let child = self.find_child(node, rem)?;
648 let lbl = self.label_of(child);
649 if !rem.starts_with(lbl) {
650 return None;
651 }
652 rem = &rem[lbl.len()..];
653 node = child;
654 }
655 }
656
657 #[must_use]
659 pub fn contains(&self, key: &str) -> bool {
660 self.get(key).is_some()
661 }
662
663 #[must_use]
668 pub fn longest_prefix_match<'a>(&self, input: &'a str) -> Option<(&'a str, &T)> {
669 let bytes = input.as_bytes();
670 let mut node = ROOT;
671 let mut consumed = 0usize;
672 let mut best: Option<(usize, &T)> = None;
675 loop {
676 if let Some(v) = self.value_of(node) {
677 best = Some((consumed, v));
678 }
679 let rem = &bytes[consumed..];
680 if rem.is_empty() {
681 break;
682 }
683 let Some(child) = self.find_child(node, rem) else {
684 break;
685 };
686 let lbl = self.label_of(child);
687 if !rem.starts_with(lbl) {
688 break;
689 }
690 consumed += lbl.len();
691 node = child;
692 }
693 best.map(|(n, v)| (&input[..n], v))
694 }
695
696 #[must_use]
698 pub fn contains_prefix(&self, prefix: &str) -> bool {
699 match self.descend_to_node(prefix.as_bytes()) {
700 Some(node) => self.value_of(node).is_some() || !self.children_of(node).is_empty(),
701 None => false,
702 }
703 }
704
705 fn descend_to_node(&self, mut rem: &[u8]) -> Option<NodeId> {
710 let mut node = ROOT;
711 while !rem.is_empty() {
712 let child = self.find_child(node, rem)?;
713 let lbl = self.label_of(child);
714 if rem.len() >= lbl.len() {
715 if !rem.starts_with(lbl) {
716 return None;
717 }
718 rem = &rem[lbl.len()..];
719 node = child;
720 } else {
721 if !lbl.starts_with(rem) {
722 return None;
723 }
724 node = child;
725 break;
726 }
727 }
728 Some(node)
729 }
730
731 fn descend_to_prefix(&self, mut rem: &[u8]) -> Option<(NodeId, Vec<u8>)> {
735 let mut node = ROOT;
736 let mut path: Vec<u8> = Vec::with_capacity(rem.len());
737 while !rem.is_empty() {
738 let child = self.find_child(node, rem)?;
739 let lbl = self.label_of(child);
740 if rem.len() >= lbl.len() {
741 if !rem.starts_with(lbl) {
742 return None;
743 }
744 path.extend_from_slice(lbl);
745 rem = &rem[lbl.len()..];
746 node = child;
747 } else {
748 if !lbl.starts_with(rem) {
749 return None;
750 }
751 path.extend_from_slice(lbl);
752 node = child;
753 break;
754 }
755 }
756 Some((node, path))
757 }
758
759 #[must_use]
764 pub fn iter(&self) -> Iter<'_, T> {
765 Iter::new(self, ROOT, Vec::new())
766 }
767
768 pub fn for_each(&self, mut f: impl FnMut(&str, &T)) {
771 let mut buf = Vec::new();
772 for_each_descendants(self, ROOT, &mut buf, &mut f);
773 }
774
775 #[must_use]
780 pub fn subtrie<'a>(&'a self, prefix: &str) -> Option<SubTrie<'a, T>> {
781 let (mut node, mut path) = self.descend_to_prefix(prefix.as_bytes())?;
782 if self.value_of(node).is_none() && self.children_of(node).is_empty() {
783 return None;
784 }
785 loop {
788 let kids = self.children_of(node);
789 if self.value_of(node).is_none() && kids.len() == 1 {
790 let child = kids[0];
791 path.extend_from_slice(self.label_of(child));
792 node = child;
793 } else {
794 break;
795 }
796 }
797 Some(SubTrie {
798 trie: self,
799 node,
800 query_len: prefix.len(),
801 common_prefix: path,
802 })
803 }
804
805 #[must_use]
810 pub fn completions<'a>(&'a self, prefix: &str) -> Vec<(String, &'a T)> {
811 match self.subtrie(prefix) {
812 Some(sub) => sub.into_iter().collect(),
813 None => Vec::new(),
814 }
815 }
816
817 #[must_use]
819 pub fn count_completions(&self, prefix: &str) -> usize {
820 match self.descend_to_node(prefix.as_bytes()) {
821 Some(node) => count_values(self, node),
822 None => 0,
823 }
824 }
825
826 #[must_use]
832 pub fn completion_prefix(&self, prefix: &str) -> Option<String> {
833 let mut rem = prefix.as_bytes();
834 let mut node = ROOT;
835 let mut buf: Vec<u8> = Vec::with_capacity(rem.len());
836 while !rem.is_empty() {
839 let child = self.find_child(node, rem)?;
840 let lbl = self.label_of(child);
841 if rem.len() >= lbl.len() {
842 if !rem.starts_with(lbl) {
843 return None;
844 }
845 buf.extend_from_slice(lbl);
846 rem = &rem[lbl.len()..];
847 node = child;
848 } else {
849 if !lbl.starts_with(rem) {
850 return None;
851 }
852 buf.extend_from_slice(lbl);
853 node = child;
854 break;
855 }
856 }
857 if self.value_of(node).is_none() && self.children_of(node).is_empty() {
858 return None;
859 }
860 while self.value_of(node).is_none() && self.children_of(node).len() == 1 {
862 let child = self.children_of(node)[0];
863 buf.extend_from_slice(self.label_of(child));
864 node = child;
865 }
866 Some(unsafe { utf8_string_unchecked(buf) })
868 }
869
870 pub fn for_each_completion(&self, prefix: &str, mut f: impl FnMut(&str, &T)) {
872 if let Some(sub) = self.subtrie(prefix) {
873 sub.for_each(&mut f);
874 }
875 }
876}
877
878impl<T: fmt::Debug> fmt::Debug for CommandTrie<T> {
879 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
880 f.debug_struct("CommandTrie")
883 .field("len", &self.len)
884 .field("nodes", &self.nodes.len())
885 .field("labels_bytes", &self.labels.len())
886 .field("children_edges", &self.children.len())
887 .finish_non_exhaustive()
888 }
889}
890
891impl<T> Default for CommandTrie<T> {
892 fn default() -> Self {
894 CommandTrieBuilder::new().build()
895 }
896}
897
898impl<'a, T> IntoIterator for &'a CommandTrie<T> {
899 type Item = (String, &'a T);
900 type IntoIter = Iter<'a, T>;
901 fn into_iter(self) -> Self::IntoIter {
902 self.iter()
903 }
904}
905
906#[derive(Clone)]
914pub struct SubTrie<'a, T> {
915 trie: &'a CommandTrie<T>,
916 node: NodeId,
918 query_len: usize,
921 common_prefix: Vec<u8>,
924}
925
926impl<'a, T> SubTrie<'a, T> {
927 #[must_use]
929 pub fn common_prefix(&self) -> &str {
930 unsafe { utf8_str_unchecked(&self.common_prefix) }
932 }
933
934 #[must_use]
941 pub fn extension(&self) -> &str {
942 unsafe { utf8_str_unchecked(&self.common_prefix[self.query_len..]) }
946 }
947
948 #[must_use]
951 pub fn is_unique(&self) -> bool {
952 self.trie.value_of(self.node).is_some() && self.trie.children_of(self.node).is_empty()
953 }
954
955 #[must_use]
959 pub fn value(&self) -> Option<&'a T> {
960 self.trie.value_of(self.node)
961 }
962
963 #[must_use]
967 pub fn unique_value(&self) -> Option<&'a T> {
968 if self.is_unique() {
969 self.value()
970 } else {
971 None
972 }
973 }
974
975 #[must_use]
977 pub fn len(&self) -> usize {
978 count_values(self.trie, self.node)
979 }
980
981 #[must_use]
983 pub fn is_empty(&self) -> bool {
984 false
985 }
986
987 #[must_use]
993 pub fn iter(&self) -> Iter<'a, T> {
994 Iter::new(self.trie, self.node, self.common_prefix.clone())
995 }
996
997 pub fn for_each(&self, mut f: impl FnMut(&str, &T)) {
999 let mut buf = self.common_prefix.clone();
1000 if let Some(v) = self.trie.value_of(self.node) {
1003 f(unsafe { utf8_str_unchecked(&buf) }, v);
1005 }
1006 for &child in self.trie.children_of(self.node) {
1007 for_each_descendants(self.trie, child, &mut buf, &mut f);
1008 }
1009 }
1010}
1011
1012impl<'a, T> IntoIterator for &SubTrie<'a, T> {
1013 type Item = (String, &'a T);
1014 type IntoIter = Iter<'a, T>;
1015 fn into_iter(self) -> Self::IntoIter {
1016 self.iter()
1017 }
1018}
1019
1020impl<'a, T> IntoIterator for SubTrie<'a, T> {
1021 type Item = (String, &'a T);
1022 type IntoIter = Iter<'a, T>;
1023 fn into_iter(self) -> Self::IntoIter {
1026 Iter::new(self.trie, self.node, self.common_prefix)
1027 }
1028}
1029
1030impl<T> fmt::Debug for SubTrie<'_, T> {
1031 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1032 f.debug_struct("SubTrie")
1033 .field("common_prefix", &self.common_prefix())
1034 .field("is_unique", &self.is_unique())
1035 .finish()
1036 }
1037}
1038
1039pub struct Iter<'a, T> {
1049 trie: &'a CommandTrie<T>,
1050 stack: Vec<Frame>,
1051 path: Vec<u8>,
1052 pending_root: Option<NodeId>,
1056 remaining: usize,
1058}
1059
1060enum Frame {
1061 Enter(NodeId),
1064 Exit(u16),
1067}
1068
1069impl<'a, T> Iter<'a, T> {
1070 fn new(trie: &'a CommandTrie<T>, root: NodeId, initial_path: Vec<u8>) -> Self {
1071 let mut stack = Vec::new();
1072 let kids = trie.children_of(root);
1074 for &child in kids.iter().rev() {
1075 stack.push(Frame::Enter(child));
1076 }
1077 let pending_root = if trie.value_of(root).is_some() {
1078 Some(root)
1079 } else {
1080 None
1081 };
1082 let remaining = count_values(trie, root);
1083 Self {
1084 trie,
1085 stack,
1086 path: initial_path,
1087 pending_root,
1088 remaining,
1089 }
1090 }
1091}
1092
1093impl<'a, T> Iterator for Iter<'a, T> {
1094 type Item = (String, &'a T);
1095
1096 fn next(&mut self) -> Option<Self::Item> {
1097 if let Some(id) = self.pending_root.take() {
1098 let v = self.trie.value_of(id).expect("pending_root has a value");
1099 self.remaining -= 1;
1100 return Some((unsafe { utf8_string_unchecked(self.path.clone()) }, v));
1103 }
1104 while let Some(frame) = self.stack.pop() {
1105 match frame {
1106 Frame::Exit(n) => {
1107 let new_len = self.path.len() - n as usize;
1108 self.path.truncate(new_len);
1109 }
1110 Frame::Enter(node) => {
1111 let lbl = self.trie.label_of(node);
1112 self.path.extend_from_slice(lbl);
1113 self.stack
1116 .push(Frame::Exit(u16::try_from(lbl.len()).unwrap()));
1117 for &child in self.trie.children_of(node).iter().rev() {
1118 self.stack.push(Frame::Enter(child));
1119 }
1120 if let Some(v) = self.trie.value_of(node) {
1121 self.remaining -= 1;
1122 return Some((unsafe { utf8_string_unchecked(self.path.clone()) }, v));
1124 }
1125 }
1126 }
1127 }
1128 None
1129 }
1130
1131 #[inline]
1132 fn size_hint(&self) -> (usize, Option<usize>) {
1133 (self.remaining, Some(self.remaining))
1134 }
1135}
1136
1137impl<T> ExactSizeIterator for Iter<'_, T> {
1138 #[inline]
1139 fn len(&self) -> usize {
1140 self.remaining
1141 }
1142}
1143
1144impl<T> FusedIterator for Iter<'_, T> {}
1145
1146impl<T> fmt::Debug for Iter<'_, T> {
1147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1148 f.debug_struct("Iter")
1149 .field("remaining_frames", &self.stack.len())
1150 .finish()
1151 }
1152}
1153
1154fn for_each_descendants<T>(
1159 trie: &CommandTrie<T>,
1160 node: NodeId,
1161 buf: &mut Vec<u8>,
1162 f: &mut impl FnMut(&str, &T),
1163) {
1164 let prev = buf.len();
1165 buf.extend_from_slice(trie.label_of(node));
1166 if let Some(v) = trie.value_of(node) {
1167 f(unsafe { utf8_str_unchecked(buf) }, v);
1169 }
1170 for &child in trie.children_of(node) {
1171 for_each_descendants(trie, child, buf, f);
1172 }
1173 buf.truncate(prev);
1174}
1175
1176fn count_values<T>(trie: &CommandTrie<T>, node: NodeId) -> usize {
1177 let mut n = usize::from(trie.value_of(node).is_some());
1178 for &child in trie.children_of(node) {
1179 n += count_values(trie, child);
1180 }
1181 n
1182}
1183
1184#[cfg(test)]
1189mod tests {
1190 use super::*;
1191 use alloc::format;
1192 use alloc::string::ToString;
1193
1194 fn build_from<'a, I: IntoIterator<Item = (&'a str, i32)>>(items: I) -> CommandTrie<i32> {
1196 let mut b = CommandTrieBuilder::new();
1197 for (k, v) in items {
1198 b.insert(k, v);
1199 }
1200 b.build()
1201 }
1202
1203 #[test]
1206 fn builder_insert_overwrite_remove() {
1207 let mut b = CommandTrieBuilder::new();
1208 assert_eq!(b.insert("commit", 1), None);
1209 assert_eq!(b.insert("commit", 2), Some(1));
1210 assert_eq!(b.get("commit"), Some(&2));
1211 assert!(b.contains("commit"));
1212 assert_eq!(b.remove("commit"), Some(2));
1213 assert_eq!(b.remove("commit"), None);
1214 assert!(b.is_empty());
1215 }
1216
1217 #[test]
1218 fn builder_remove_prunes_and_merges() {
1219 let mut b = CommandTrieBuilder::new();
1220 b.insert("command", 1);
1221 b.insert("commit", 2);
1222 b.insert("comm", 3);
1223 assert_eq!(b.remove("comm"), Some(3));
1224 assert_eq!(b.remove("commit"), Some(2));
1225 assert_eq!(b.get("command"), Some(&1));
1226 assert_eq!(b.len(), 1);
1227 }
1228
1229 #[test]
1230 fn builder_from_iter_and_extend() {
1231 let b: CommandTrieBuilder<i32> = [("a", 1), ("ab", 2), ("abc", 3)].into_iter().collect();
1232 assert_eq!(b.len(), 3);
1233 assert_eq!(b.get("ab"), Some(&2));
1234 }
1235
1236 #[test]
1237 fn builder_get_diverges_mid_edge() {
1238 let mut b = CommandTrieBuilder::new();
1241 b.insert("command", 1);
1242 assert_eq!(b.get("comx"), None);
1243 assert_eq!(b.get("c"), None);
1244 }
1245
1246 #[test]
1249 fn frozen_get() {
1250 let t = build_from([("commit", 1), ("command", 2)]);
1251 assert_eq!(t.get("commit"), Some(&1));
1252 assert_eq!(t.get("command"), Some(&2));
1253 assert_eq!(t.get("comm"), None);
1254 assert_eq!(t.get("commits"), None);
1255 assert_eq!(t.get(""), None);
1256 assert!(t.contains("commit"));
1257 assert!(!t.contains("comm"));
1258 }
1259
1260 #[test]
1261 fn frozen_query_accepts_non_ascii_keys() {
1262 let t = build_from([("commit", 1), ("command", 2), ("config", 3)]);
1267
1268 assert_eq!(t.get("café"), None);
1271 assert!(!t.contains("café"));
1272
1273 assert_eq!(t.get("commité"), None);
1275 assert_eq!(t.get("comméand"), None);
1276
1277 assert_eq!(t.get("🦀"), None);
1279 assert_eq!(t.get("π"), None);
1280
1281 assert!(!t.contains_prefix("café"));
1283 assert_eq!(t.count_completions("café"), 0);
1284 assert!(t.completions("café").is_empty());
1285 assert_eq!(t.completion_prefix("café"), None);
1286 assert!(t.subtrie("café").is_none());
1287 assert_eq!(t.longest_prefix_match("café"), None);
1288
1289 assert_eq!(t.longest_prefix_match("commit é"), Some(("commit", &1)),);
1293 }
1294
1295 #[test]
1296 fn frozen_empty_trie() {
1297 let t = CommandTrieBuilder::<i32>::new().build();
1298 assert_eq!(t.len(), 0);
1299 assert!(t.is_empty());
1300 assert_eq!(t.get(""), None);
1301 assert_eq!(t.get("anything"), None);
1302 assert!(!t.contains_prefix(""));
1303 assert!(t.subtrie("").is_none());
1304 assert_eq!(t.completion_prefix(""), None);
1305 assert_eq!(t.count_completions(""), 0);
1306 assert!(t.completions("").is_empty());
1307 assert_eq!(t.iter().count(), 0);
1308 }
1309
1310 #[test]
1311 fn frozen_longest_prefix_match() {
1312 let t = build_from([("git", 1), ("git-status", 2)]);
1313 assert_eq!(
1314 t.longest_prefix_match("git-status --short"),
1315 Some(("git-status", &2))
1316 );
1317 assert_eq!(t.longest_prefix_match("git foo"), Some(("git", &1)));
1318 assert_eq!(t.longest_prefix_match("git"), Some(("git", &1)));
1319 assert_eq!(t.longest_prefix_match("gi"), None);
1320 assert_eq!(t.longest_prefix_match("zzz"), None);
1321 assert_eq!(t.longest_prefix_match(""), None);
1322 let (matched, _) = t.longest_prefix_match("git-status xyz").unwrap();
1324 assert_eq!(matched.len(), 10);
1325 }
1326
1327 #[test]
1328 fn frozen_contains_prefix() {
1329 let t = build_from([("commit", 1)]);
1330 assert!(t.contains_prefix(""));
1331 assert!(t.contains_prefix("c"));
1332 assert!(t.contains_prefix("comm"));
1333 assert!(t.contains_prefix("commit"));
1334 assert!(!t.contains_prefix("commits"));
1335 assert!(!t.contains_prefix("d"));
1336 }
1337
1338 #[test]
1341 fn frozen_completions() {
1342 let t = build_from([("commit", 1), ("command", 2), ("config", 3), ("clone", 4)]);
1343 let mut got = t.completions("comm");
1344 got.sort_by(|a, b| a.0.cmp(&b.0));
1345 assert_eq!(
1346 got,
1347 vec![("command".to_string(), &2), ("commit".to_string(), &1)]
1348 );
1349 assert_eq!(t.completions("").len(), 4);
1350 assert!(t.completions("xyz").is_empty());
1351 }
1352
1353 #[test]
1354 fn frozen_completions_prefix_ends_mid_edge() {
1355 let t = build_from([("command", 1)]);
1356 let got = t.completions("co");
1357 assert_eq!(got, vec![("command".to_string(), &1)]);
1358 }
1359
1360 #[test]
1361 fn frozen_completion_prefix_extends_past_query() {
1362 let t = build_from([("command", 1), ("commit", 2)]);
1363 assert_eq!(t.completion_prefix("c").as_deref(), Some("comm"));
1364
1365 let t = build_from([("command", 1)]);
1366 assert_eq!(t.completion_prefix("").as_deref(), Some("command"));
1367 assert_eq!(t.completion_prefix("c").as_deref(), Some("command"));
1368 assert_eq!(t.completion_prefix("commits"), None);
1369 }
1370
1371 #[test]
1372 fn frozen_subtrie_views() {
1373 let t = build_from([("commit", 1), ("command", 2), ("config", 3)]);
1374 let sub = t.subtrie("comm").unwrap();
1375 assert_eq!(sub.common_prefix(), "comm");
1376 assert_eq!(sub.len(), 2);
1377 assert!(!sub.is_empty());
1378
1379 let mut via_iter: Vec<(String, i32)> = sub.iter().map(|(k, v)| (k, *v)).collect();
1380 via_iter.sort();
1381 let mut via_for_each: Vec<(String, i32)> = Vec::new();
1382 sub.for_each(|k, v| via_for_each.push((k.to_string(), *v)));
1383 via_for_each.sort();
1384 assert_eq!(via_iter, via_for_each);
1385 assert_eq!(
1386 via_iter,
1387 vec![("command".to_string(), 2), ("commit".to_string(), 1)]
1388 );
1389
1390 let owned: Vec<_> = sub.into_iter().collect();
1392 assert_eq!(owned.len(), 2);
1393 }
1394
1395 #[test]
1396 fn frozen_subtrie_on_exact_leaf() {
1397 let t = build_from([("commit", 1), ("command", 2)]);
1398 let sub = t.subtrie("commit").unwrap();
1399 assert_eq!(sub.common_prefix(), "commit");
1400 assert_eq!(sub.len(), 1);
1401 }
1402
1403 #[test]
1404 fn frozen_subtrie_extension_and_is_unique() {
1405 let t = build_from([("commit", 1), ("command", 2), ("config", 3), ("clone", 4)]);
1406
1407 let sub = t.subtrie("c").unwrap();
1409 assert_eq!(sub.common_prefix(), "c");
1410 assert_eq!(sub.extension(), "");
1411 assert!(!sub.is_unique());
1412
1413 let sub = t.subtrie("co").unwrap();
1415 assert_eq!(sub.common_prefix(), "co");
1416 assert_eq!(sub.extension(), "");
1417 assert!(!sub.is_unique());
1418
1419 let sub = t.subtrie("comma").unwrap();
1420 assert_eq!(sub.common_prefix(), "command");
1421 assert_eq!(sub.extension(), "nd");
1422 assert!(sub.is_unique());
1423
1424 let sub = t.subtrie("clone").unwrap();
1426 assert_eq!(sub.extension(), "");
1427 assert!(sub.is_unique());
1428
1429 let t2 = build_from([("git", 1), ("github", 2)]);
1431 let sub = t2.subtrie("gi").unwrap();
1432 assert_eq!(sub.common_prefix(), "git");
1433 assert_eq!(sub.extension(), "t");
1434 assert!(!sub.is_unique()); }
1436
1437 #[test]
1438 fn frozen_subtrie_value_and_unique_value() {
1439 let t = build_from([("commit", 1), ("command", 2), ("config", 3)]);
1440
1441 let sub = t.subtrie("com").unwrap();
1443 assert_eq!(sub.value(), None);
1444 assert_eq!(sub.unique_value(), None);
1445
1446 let sub = t.subtrie("commi").unwrap();
1448 assert_eq!(sub.common_prefix(), "commit");
1449 assert_eq!(sub.value(), Some(&1));
1450 assert_eq!(sub.unique_value(), Some(&1));
1451
1452 let t2 = build_from([("git", 10), ("github", 20)]);
1454 let sub = t2.subtrie("gi").unwrap();
1455 assert_eq!(sub.common_prefix(), "git");
1456 assert_eq!(sub.value(), Some(&10));
1457 assert_eq!(sub.unique_value(), None);
1458 }
1459
1460 #[test]
1461 fn frozen_iter_alphabetical() {
1462 let t = build_from([("commit", 1), ("command", 2), ("config", 3), ("clone", 4)]);
1463 let got: Vec<_> = t.iter().map(|(k, v)| (k, *v)).collect();
1464 assert_eq!(
1465 got,
1466 vec![
1467 ("clone".to_string(), 4),
1468 ("command".to_string(), 2),
1469 ("commit".to_string(), 1),
1470 ("config".to_string(), 3),
1471 ]
1472 );
1473
1474 let mut n = 0;
1476 for _ in &t {
1477 n += 1;
1478 }
1479 assert_eq!(n, 4);
1480 }
1481
1482 #[test]
1483 fn frozen_for_each_no_alloc() {
1484 let t = build_from([("a", 1), ("ab", 2), ("abc", 3)]);
1485 let mut got: Vec<(String, i32)> = Vec::new();
1486 t.for_each(|k, v| got.push((k.to_string(), *v)));
1487 got.sort();
1488 assert_eq!(
1489 got,
1490 vec![
1491 ("a".to_string(), 1),
1492 ("ab".to_string(), 2),
1493 ("abc".to_string(), 3),
1494 ]
1495 );
1496 }
1497
1498 #[test]
1499 fn frozen_count_and_for_each_completion() {
1500 let t = build_from([("commit", 1), ("command", 2), ("config", 3), ("clone", 4)]);
1501 assert_eq!(t.count_completions("c"), 4);
1502 assert_eq!(t.count_completions("comm"), 2);
1503 assert_eq!(t.count_completions("commit"), 1);
1504 assert_eq!(t.count_completions("z"), 0);
1505
1506 let mut got: Vec<String> = Vec::new();
1507 t.for_each_completion("comm", |k, _| got.push(k.to_string()));
1508 got.sort();
1509 assert_eq!(got, vec!["command".to_string(), "commit".to_string()]);
1510 }
1511
1512 #[test]
1515 fn build_packs_into_four_allocations() {
1516 let t = build_from([
1517 ("add", 0),
1518 ("alias", 1),
1519 ("branch", 2),
1520 ("checkout", 3),
1521 ("cherry-pick", 4),
1522 ("clean", 5),
1523 ("clone", 6),
1524 ("commit", 7),
1525 ("command", 8),
1526 ("config", 9),
1527 ]);
1528 for (i, k) in [
1530 "add",
1531 "alias",
1532 "branch",
1533 "checkout",
1534 "cherry-pick",
1535 "clean",
1536 "clone",
1537 "commit",
1538 "command",
1539 "config",
1540 ]
1541 .iter()
1542 .enumerate()
1543 {
1544 assert_eq!(t.get(k), Some(&(i as i32)));
1545 }
1546 assert_eq!(t.child_first_bytes.len(), t.children.len());
1549 for (i, &child) in t.children.iter().enumerate() {
1550 let lbl0 = t.label_of(child)[0];
1551 assert_eq!(t.child_first_bytes[i], lbl0);
1552 }
1553 for id in 0..t.nodes.len() as NodeId {
1555 let kids = t.children_of(id);
1556 for w in kids.windows(2) {
1557 let a = t.label_of(w[0])[0];
1558 let b = t.label_of(w[1])[0];
1559 assert!(a < b, "siblings not sorted at node {id}: {a} >= {b}");
1560 }
1561 }
1562 }
1563
1564 const _: fn() = || {
1567 fn assert_send_sync<X: Send + Sync>() {}
1568 assert_send_sync::<CommandTrieBuilder<i32>>();
1569 assert_send_sync::<CommandTrie<i32>>();
1570 assert_send_sync::<SubTrie<'static, i32>>();
1571 assert_send_sync::<Iter<'static, i32>>();
1572 };
1573
1574 #[test]
1577 fn builder_default_and_clear() {
1578 let mut b: CommandTrieBuilder<i32> = CommandTrieBuilder::default();
1579 assert!(b.is_empty());
1580 b.insert("a", 1);
1581 b.insert("ab", 2);
1582 assert_eq!(b.len(), 2);
1583 b.clear();
1584 assert!(b.is_empty());
1585 assert_eq!(b.get("a"), None);
1586 }
1587
1588 #[test]
1589 fn trie_default_is_empty() {
1590 let t: CommandTrie<i32> = CommandTrie::default();
1591 assert!(t.is_empty());
1592 assert_eq!(t.get("anything"), None);
1593 assert_eq!(t.iter().count(), 0);
1594 }
1595
1596 #[test]
1597 fn debug_impls_render() {
1598 let mut b = CommandTrieBuilder::new();
1599 b.insert("commit", 1);
1600 b.insert("command", 2);
1601 let s = format!("{b:?}");
1603 assert!(s.contains("CommandTrieBuilder"));
1604 assert!(s.contains("BuilderNode"));
1605
1606 let t = b.build();
1607 let s = format!("{t:?}");
1608 assert!(s.contains("CommandTrie"));
1609 assert!(s.contains("len"));
1610
1611 let sub = t.subtrie("comm").unwrap();
1612 let s = format!("{sub:?}");
1613 assert!(s.contains("SubTrie"));
1614 assert!(s.contains("comm"));
1615
1616 let it = t.iter();
1617 let s = format!("{it:?}");
1618 assert!(s.contains("Iter"));
1619 }
1620
1621 #[test]
1622 fn subtrie_ref_into_iter() {
1623 let t = build_from([("commit", 1), ("command", 2)]);
1624 let sub = t.subtrie("comm").unwrap();
1625 let from_ref: Vec<_> = (&sub).into_iter().collect();
1627 assert_eq!(from_ref.len(), 2);
1628 assert_eq!(sub.len(), 2);
1630 }
1631
1632 #[test]
1633 fn subtrie_for_each_emits_starting_node_value() {
1634 let t = build_from([("git", 10), ("github", 20)]);
1637 let sub = t.subtrie("git").unwrap();
1638 let mut got: Vec<(String, i32)> = Vec::new();
1639 sub.for_each(|k, v| got.push((k.to_string(), *v)));
1640 got.sort();
1641 assert_eq!(
1642 got,
1643 vec![("git".to_string(), 10), ("github".to_string(), 20)]
1644 );
1645 }
1646
1647 #[test]
1648 fn insert_32k_entries_no_panic() {
1649 const PREFIXES: &[&str] = &[
1660 "git-",
1661 "cargo-",
1662 "docker-",
1663 "kubectl-",
1664 "npm-",
1665 "pip-",
1666 "rustup-",
1667 "systemctl-",
1668 "journalctl-",
1669 "ip-",
1670 "nmcli-",
1671 "brew-",
1672 ];
1673 const STEMS: &[&str] = &[
1674 "list", "get", "set", "show", "describe", "create", "delete", "update", "apply",
1675 "watch", "rollout", "exec", "logs", "status", "info", "config", "scale", "patch",
1676 "expose", "annotate",
1677 ];
1678 const BUCKETS: u32 = 134;
1680 const N: u32 = PREFIXES.len() as u32 * STEMS.len() as u32 * BUCKETS;
1681 const _: () = assert!(N >= 32_000, "test corpus must hit the documented ~32k cap");
1682
1683 fn key(n: u32, buf: &mut String) {
1684 use core::fmt::Write;
1685 buf.clear();
1686 let p = (n as usize) % PREFIXES.len();
1687 let s = ((n as usize) / PREFIXES.len()) % STEMS.len();
1688 let bucket = (n as usize) / (PREFIXES.len() * STEMS.len());
1689 buf.push_str(PREFIXES[p]);
1690 buf.push_str(STEMS[s]);
1691 buf.push('-');
1692 write!(buf, "{bucket:03}").unwrap();
1693 }
1694
1695 let mut b: CommandTrieBuilder<u32> = CommandTrieBuilder::new();
1696 let mut buf = String::new();
1697 for i in 0..N {
1698 key(i, &mut buf);
1699 b.insert(&buf, i);
1700 }
1701 assert_eq!(b.len(), N as usize);
1702
1703 let t = b.build();
1704 assert_eq!(t.len(), N as usize);
1705
1706 for &i in &[0u32, 1, 11, 12, 239, 240, 1023, 12345, N / 2, N - 1] {
1708 key(i, &mut buf);
1709 assert_eq!(t.get(&buf), Some(&i), "lookup failed for {i}");
1710 }
1711 }
1712
1713 #[test]
1714 fn iter_is_fused() {
1715 fn assert_fused<I: FusedIterator>(_: &I) {}
1716 let t = CommandTrieBuilder::<i32>::new().build();
1717 let it = t.iter();
1718 assert_fused(&it);
1719 }
1720
1721 #[test]
1722 fn iter_is_exact_size() {
1723 fn assert_exact<I: ExactSizeIterator>(_: &I) {}
1724
1725 let t = build_from([("commit", 1), ("command", 2), ("config", 3), ("clone", 4)]);
1726
1727 let it = t.iter();
1728 assert_exact(&it);
1729 assert_eq!(it.len(), 4);
1730 assert_eq!(it.size_hint(), (4, Some(4)));
1731
1732 let mut it = t.iter();
1734 for expected in (0..4).rev() {
1735 assert!(it.next().is_some());
1736 assert_eq!(it.len(), expected);
1737 assert_eq!(it.size_hint(), (expected, Some(expected)));
1738 }
1739 assert!(it.next().is_none());
1740 assert_eq!(it.len(), 0);
1741
1742 let t2 = build_from([("git", 10), ("github", 20), ("gitlab", 30)]);
1745 let sub = t2.subtrie("git").unwrap();
1746 let it = sub.iter();
1747 assert_eq!(it.len(), 3);
1748 let collected: Vec<_> = sub.into_iter().collect();
1749 assert_eq!(collected.len(), 3);
1750
1751 let empty: CommandTrie<i32> = CommandTrieBuilder::new().build();
1753 assert_eq!(empty.iter().len(), 0);
1754 }
1755
1756 #[test]
1759 fn fuzz_against_btreemap() {
1760 use alloc::collections::BTreeMap;
1761
1762 let mut state: u64 = 0x_dead_beef_cafe_f00d;
1763 let mut rand = || {
1764 state = state
1765 .wrapping_mul(6364136223846793005)
1766 .wrapping_add(1442695040888963407);
1767 state
1768 };
1769
1770 let keys = [
1771 "", "a", "ab", "abc", "abd", "abcd", "abce", "b", "ba", "bar", "baz", "c", "co", "com",
1772 "comm", "command", "commit", "config", "x", "xy", "xyz",
1773 ];
1774 let probe_prefixes = [
1775 "", "a", "ab", "abc", "abz", "b", "c", "comm", "com", "x", "z",
1776 ];
1777
1778 let mut builder: CommandTrieBuilder<i32> = CommandTrieBuilder::new();
1779 let mut model: BTreeMap<String, i32> = BTreeMap::new();
1780
1781 for op in 0..500 {
1782 let r = rand();
1783 let key = keys[(r as usize) % keys.len()];
1784
1785 if (r >> 32) % 4 == 0 {
1786 let b_prev = builder.remove(key);
1787 let m_prev = model.remove(key);
1788 assert_eq!(b_prev, m_prev, "remove({key:?}) at op {op}");
1789 } else {
1790 let v = (r >> 8) as i32;
1791 let b_prev = builder.insert(key, v);
1792 let m_prev = model.insert(key.to_string(), v);
1793 assert_eq!(b_prev, m_prev, "insert({key:?}, {v}) at op {op}");
1794 }
1795
1796 assert_eq!(builder.len(), model.len(), "len at op {op}");
1797
1798 let trie = builder.clone().build();
1800 assert_eq!(trie.len(), model.len());
1801
1802 for k in &keys {
1803 assert_eq!(trie.get(k), model.get(*k), "get({k:?}) at op {op}");
1804 assert_eq!(trie.contains(k), model.contains_key(*k));
1805 }
1806
1807 for pfx in &probe_prefixes {
1808 let mut from_trie: Vec<String> =
1809 trie.completions(pfx).into_iter().map(|(k, _)| k).collect();
1810 from_trie.sort();
1811 let mut from_model: Vec<String> = model
1812 .keys()
1813 .filter(|k| k.starts_with(pfx))
1814 .cloned()
1815 .collect();
1816 from_model.sort();
1817 assert_eq!(from_trie, from_model, "completions({pfx:?}) at op {op}");
1818
1819 assert_eq!(
1820 trie.count_completions(pfx),
1821 from_model.len(),
1822 "count_completions({pfx:?}) at op {op}"
1823 );
1824
1825 if let Some(cp) = trie.completion_prefix(pfx) {
1826 assert!(cp.starts_with(pfx));
1827 for k in &from_model {
1828 assert!(k.starts_with(&cp));
1829 }
1830 } else {
1831 assert!(from_model.is_empty());
1832 }
1833 }
1834
1835 let from_trie: Vec<(String, i32)> = trie.iter().map(|(k, v)| (k, *v)).collect();
1836 let from_model: Vec<(String, i32)> =
1837 model.iter().map(|(k, v)| (k.clone(), *v)).collect();
1838 assert_eq!(from_trie, from_model, "iter at op {op}");
1839 }
1840 }
1841
1842 #[test]
1854 fn utf8_basic_insert_get() {
1855 let t = build_from([
1856 ("café", 1),
1857 ("über", 2),
1858 ("naïve", 3),
1859 ("naïveté", 4),
1860 ("🦀", 5),
1861 ("π", 6),
1862 ]);
1863 assert_eq!(t.get("café"), Some(&1));
1864 assert_eq!(t.get("über"), Some(&2));
1865 assert_eq!(t.get("naïve"), Some(&3));
1866 assert_eq!(t.get("naïveté"), Some(&4));
1867 assert_eq!(t.get("🦀"), Some(&5));
1868 assert_eq!(t.get("π"), Some(&6));
1869 assert_eq!(t.get("cafe"), None);
1870 assert_eq!(t.get("naïv"), None);
1871 }
1872
1873 #[test]
1874 fn utf8_shared_first_byte_siblings() {
1875 let t = build_from([("éa", 1), ("êb", 2), ("èc", 3), ("ad", 4)]);
1879 assert_eq!(t.get("éa"), Some(&1));
1880 assert_eq!(t.get("êb"), Some(&2));
1881 assert_eq!(t.get("èc"), Some(&3));
1882 assert_eq!(t.get("ad"), Some(&4));
1883 assert_eq!(t.get("éb"), None);
1884 assert_eq!(t.get("ê"), None);
1885 assert!(t.contains_prefix("é"));
1886 assert!(t.contains_prefix("ê"));
1887 assert!(!t.contains_prefix("ë"));
1888 }
1889
1890 #[test]
1891 fn utf8_split_at_shared_codepoint() {
1892 let t = build_from([("éa", 1), ("éb", 2)]);
1896 assert_eq!(t.get("éa"), Some(&1));
1897 assert_eq!(t.get("éb"), Some(&2));
1898 let sub = t.subtrie("é").expect("prefix 'é' should exist");
1899 assert_eq!(sub.common_prefix(), "é");
1900 assert_eq!(sub.len(), 2);
1901 }
1902
1903 #[test]
1904 fn utf8_sort_order_matches_btreemap() {
1905 use alloc::collections::BTreeMap;
1906 let pairs: Vec<(&str, i32)> = vec![
1907 ("apple", 1),
1908 ("café", 2),
1909 ("cab", 3),
1910 ("über", 4),
1911 ("ünder", 5),
1912 ("naïve", 6),
1913 ("naive", 7),
1914 ("🦀rust", 8),
1915 ("🦀", 9),
1916 ("π", 10),
1917 ("zoo", 11),
1918 ];
1919 let model: BTreeMap<&str, i32> = pairs.iter().copied().collect();
1920 let t = build_from(pairs.iter().copied());
1921 let from_trie: Vec<(String, i32)> = t.iter().map(|(k, v)| (k, *v)).collect();
1922 let from_model: Vec<(String, i32)> =
1923 model.iter().map(|(k, v)| (k.to_string(), *v)).collect();
1924 assert_eq!(from_trie, from_model);
1925 }
1926
1927 #[test]
1928 fn utf8_completion_prefix_extends_through_char() {
1929 let t = build_from([("naïveté", 1), ("zzz", 2)]);
1932 assert_eq!(t.completion_prefix("n").as_deref(), Some("naïveté"));
1933 assert_eq!(t.completion_prefix("naï").as_deref(), Some("naïveté"));
1937 }
1938
1939 #[test]
1940 fn utf8_longest_prefix_match() {
1941 let t = build_from([("café", 1), ("ca", 2)]);
1942 assert_eq!(t.longest_prefix_match("café au lait"), Some(("café", &1)));
1943 assert_eq!(t.longest_prefix_match("cab"), Some(("ca", &2)));
1944 assert_eq!(t.longest_prefix_match("caf"), Some(("ca", &2)));
1946 }
1947
1948 #[test]
1949 fn utf8_remove_and_reinsert() {
1950 let mut b = CommandTrieBuilder::new();
1951 b.insert("café", 1);
1952 b.insert("naïve", 2);
1953 assert_eq!(b.remove("café"), Some(1));
1954 assert_eq!(b.get("café"), None);
1955 assert_eq!(b.get("naïve"), Some(&2));
1956 b.insert("café", 11);
1957 let t = b.build();
1958 assert_eq!(t.get("café"), Some(&11));
1959 assert_eq!(t.get("naïve"), Some(&2));
1960 }
1961
1962 #[test]
1963 fn utf8_iter_roundtrip_emoji_heavy() {
1964 let keys = ["🦀", "🦀rust", "🦀🦀", "🔥", "🔥fire", "ascii"];
1965 let mut b = CommandTrieBuilder::new();
1966 for (i, k) in keys.iter().enumerate() {
1967 b.insert(k, i as i32);
1968 }
1969 let t = b.build();
1970 for (i, k) in keys.iter().enumerate() {
1971 assert_eq!(t.get(k), Some(&(i as i32)), "lookup {k}");
1972 }
1973 let collected: Vec<String> = t.iter().map(|(k, _)| k).collect();
1975 let mut expected: Vec<String> = keys.iter().map(|s| s.to_string()).collect();
1976 expected.sort();
1977 assert_eq!(collected, expected);
1978 }
1979
1980 #[test]
1981 fn utf8_three_byte_char_paths() {
1982 let mut b = CommandTrieBuilder::new();
1986 b.insert("中", 1);
1987 b.insert("中a", 2);
1988 b.insert("中b", 3);
1989 b.insert("間", 4);
1990 let t = b.build();
1991 assert_eq!(t.get("中"), Some(&1));
1992 assert_eq!(t.get("中a"), Some(&2));
1993 assert_eq!(t.get("中b"), Some(&3));
1994 assert_eq!(t.get("間"), Some(&4));
1995 assert_eq!(t.get("中c"), None);
1996 assert_eq!(t.longest_prefix_match("中ax"), Some(("中a", &2)),);
1997 }
1998
1999 #[test]
2000 fn utf8_lcp_backs_off_into_codepoint_boundary() {
2001 let mut b = CommandTrieBuilder::new();
2006 b.insert("Xé", 1);
2007 b.insert("Xè", 2);
2008 let t = b.build();
2009 assert_eq!(t.get("Xé"), Some(&1));
2010 assert_eq!(t.get("Xè"), Some(&2));
2011 let keys: Vec<String> = t.iter().map(|(k, _)| k).collect();
2015 assert_eq!(keys, vec!["Xè".to_string(), "Xé".to_string()]);
2016 }
2017}