1#[cfg(not(feature = "std"))]
11use alloc::string::{String, ToString};
12#[cfg(not(feature = "std"))]
13use alloc::vec::Vec;
14
15use crate::collections::HashMap;
16
17#[derive(Debug, Clone, Default)]
24pub struct SynonymRegistry {
25 groups: Vec<Vec<String>>,
26 index: HashMap<String, usize>,
29}
30
31impl SynonymRegistry {
32 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub fn register_group(&mut self, words: &[&str]) {
39 if words.is_empty() {
40 return;
41 }
42 let group_idx = self.groups.len();
43 let group: Vec<String> = words.iter().map(|w| w.to_string()).collect();
44 for w in &group {
47 self.index.insert(w.to_lowercase(), group_idx);
48 }
49 self.groups.push(group);
50 }
51
52 pub fn synonyms_for(&self, word: &str) -> Option<&[String]> {
57 let key = word.to_lowercase();
58 self.index.get(&key).map(|&i| self.groups[i].as_slice())
59 }
60
61 pub fn is_empty(&self) -> bool {
62 self.groups.is_empty()
63 }
64
65 pub fn len(&self) -> usize {
66 self.groups.len()
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn lookup_finds_registered_word() {
76 let mut r = SynonymRegistry::new();
77 r.register_group(&["class", "type"]);
78 let group = r.synonyms_for("class").unwrap();
79 assert_eq!(group, &["class".to_string(), "type".to_string()]);
80 }
81
82 #[test]
83 fn lookup_is_case_insensitive() {
84 let mut r = SynonymRegistry::new();
85 r.register_group(&["class", "type"]);
86 assert!(r.synonyms_for("Class").is_some());
87 assert!(r.synonyms_for("TYPE").is_some());
88 }
89
90 #[test]
91 fn unregistered_word_has_no_group() {
92 let r = SynonymRegistry::new();
93 assert!(r.synonyms_for("class").is_none());
94 }
95
96 #[test]
97 fn empty_group_is_ignored() {
98 let mut r = SynonymRegistry::new();
99 r.register_group(&[]);
100 assert!(r.is_empty());
101 }
102}