1use alloc::vec::Vec;
13
14use crate::error::ZipError;
15
16#[derive(Debug, Clone)]
17pub struct Dictionary {
18 arena: Vec<u8>,
19 offsets: Vec<u32>,
20 max_entries: usize,
21}
22
23impl Dictionary {
24 #[must_use]
28 pub const fn new(max_entries: usize) -> Self {
29 Self {
30 arena: Vec::new(),
31 offsets: Vec::new(),
32 max_entries,
33 }
34 }
35
36 #[allow(clippy::cast_possible_truncation)]
44 pub fn add(&mut self, phrase: &[u8]) -> Result<u32, ZipError> {
45 if let Some(pos) = (0..self.offsets.len()).position(|i| self.get_entry(i) == phrase) {
46 return Ok(pos as u32);
47 }
48 if self.max_entries == 0 {
49 return Err(ZipError::DictionaryFull);
50 }
51 if self.offsets.len() >= self.max_entries {
52 let first_end = if self.offsets.len() > 1 {
53 self.offsets[1] as usize
54 } else {
55 self.arena.len()
56 };
57 self.arena.drain(..first_end);
58 self.offsets.remove(0);
59 for off in &mut self.offsets {
60 *off -= first_end as u32;
61 }
62 }
63 if self.arena.len().saturating_add(phrase.len()) > u32::MAX as usize {
64 return Err(ZipError::DictionaryFull);
65 }
66 self.offsets.push(self.arena.len() as u32);
67 self.arena.extend_from_slice(phrase);
68 Ok((self.offsets.len() - 1) as u32)
69 }
70
71 #[must_use]
72 pub fn lookup(&self, idx: u32) -> Option<&[u8]> {
73 let i = idx as usize;
74 if i >= self.offsets.len() {
75 return None;
76 }
77 Some(self.get_entry(i))
78 }
79
80 #[inline]
81 fn get_entry(&self, i: usize) -> &[u8] {
82 let start = self.offsets[i] as usize;
83 let end = if i + 1 < self.offsets.len() {
84 self.offsets[i + 1] as usize
85 } else {
86 self.arena.len()
87 };
88 &self.arena[start..end]
89 }
90
91 #[must_use]
92 pub const fn len(&self) -> usize {
93 self.offsets.len()
94 }
95
96 #[must_use]
97 pub const fn is_empty(&self) -> bool {
98 self.offsets.is_empty()
99 }
100}
101
102#[cfg(test)]
103#[allow(
104 clippy::doc_markdown,
105 clippy::unreadable_literal,
106 clippy::cast_possible_truncation,
107 clippy::cast_sign_loss,
108 clippy::cast_precision_loss,
109 clippy::cast_possible_wrap,
110 clippy::uninlined_format_args
111)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn dictionary_basic() {
117 let mut dict = Dictionary::new(100);
118 let idx1 = dict.add(b"hello").unwrap();
119 let idx2 = dict.add(b"world").unwrap();
120 assert_eq!(dict.lookup(idx1), Some(b"hello".as_slice()));
121 assert_eq!(dict.lookup(idx2), Some(b"world".as_slice()));
122 }
123
124 #[test]
125 fn dictionary_dedup() {
126 let mut dict = Dictionary::new(100);
127 let idx1 = dict.add(b"hello").unwrap();
128 let idx2 = dict.add(b"hello").unwrap();
129 assert_eq!(idx1, idx2);
130 assert_eq!(dict.len(), 1);
131 }
132
133 #[test]
134 fn dictionary_eviction() {
135 let mut dict = Dictionary::new(3);
136 dict.add(b"a").unwrap();
137 dict.add(b"b").unwrap();
138 dict.add(b"c").unwrap();
139 dict.add(b"d").unwrap();
140 assert_eq!(dict.len(), 3);
141 }
142
143 #[test]
149 fn dictionary_initial_empty() {
150 let dict = Dictionary::new(10);
151 assert!(dict.is_empty());
152 assert_eq!(dict.len(), 0);
153 }
154
155 #[test]
157 fn dictionary_not_empty_after_add() {
158 let mut dict = Dictionary::new(10);
159 dict.add(b"abc").unwrap();
160 assert!(!dict.is_empty());
161 assert_eq!(dict.len(), 1);
162 }
163
164 #[test]
166 fn dictionary_lookup_out_of_range() {
167 let dict = Dictionary::new(10);
168 assert_eq!(dict.lookup(0), None);
169 assert_eq!(dict.lookup(100), None);
170 assert_eq!(dict.lookup(u32::MAX), None);
171 }
172
173 #[test]
175 fn dictionary_add_empty_phrase() {
176 let mut dict = Dictionary::new(10);
177 let idx = dict.add(b"").unwrap();
178 assert_eq!(dict.lookup(idx), Some(b"".as_slice()));
179 }
180
181 #[test]
183 fn dictionary_max_one() {
184 let mut dict = Dictionary::new(1);
185 dict.add(b"first").unwrap();
186 assert_eq!(dict.len(), 1);
187 dict.add(b"second").unwrap();
188 assert_eq!(dict.len(), 1);
190 assert_eq!(dict.lookup(0), Some(b"second".as_slice()));
191 }
192
193 #[test]
195 fn dictionary_re_add_after_eviction() {
196 let mut dict = Dictionary::new(2);
197 dict.add(b"A").unwrap();
198 dict.add(b"B").unwrap();
199 dict.add(b"C").unwrap();
201 let idx = dict.add(b"A").unwrap();
203 assert_eq!(dict.lookup(idx), Some(b"A".as_slice()));
204 }
205
206 #[test]
208 fn dictionary_dedup_after_eviction() {
209 let mut dict = Dictionary::new(3);
210 dict.add(b"x").unwrap();
211 dict.add(b"y").unwrap();
212 dict.add(b"z").unwrap();
213 dict.add(b"w").unwrap();
215 let idx = dict.add(b"y").unwrap();
217 assert_eq!(dict.lookup(idx), Some(b"y".as_slice()));
218 assert_eq!(dict.len(), 3);
219 }
220
221 #[test]
223 fn dictionary_many_entries() {
224 let mut dict = Dictionary::new(1000);
225 for i in 0u32..500 {
226 let phrase = alloc::format!("entry_{i}");
227 dict.add(phrase.as_bytes()).unwrap();
228 }
229 assert_eq!(dict.len(), 500);
230 }
231
232 #[test]
234 fn dictionary_clone() {
235 let mut dict = Dictionary::new(10);
236 dict.add(b"test").unwrap();
237 let dict2 = dict.clone();
238 assert_eq!(dict2.len(), 1);
239 assert_eq!(dict2.lookup(0), Some(b"test".as_slice()));
240 }
241
242 #[test]
244 fn dictionary_debug() {
245 let dict = Dictionary::new(5);
246 let s = alloc::format!("{:?}", dict);
247 assert!(s.contains("Dictionary"));
248 }
249
250 #[test]
252 fn dictionary_binary_phrase() {
253 let mut dict = Dictionary::new(10);
254 let binary = &[0x00, 0xFF, 0x80, 0x7F];
255 let idx = dict.add(binary).unwrap();
256 assert_eq!(dict.lookup(idx), Some(binary.as_slice()));
257 }
258
259 #[test]
261 fn dictionary_dedup_multiple_times() {
262 let mut dict = Dictionary::new(100);
263 for _ in 0..10 {
264 dict.add(b"same").unwrap();
265 }
266 assert_eq!(dict.len(), 1);
267 }
268
269 #[test]
271 fn dictionary_eviction_removes_first() {
272 let mut dict = Dictionary::new(2);
273 dict.add(b"alpha").unwrap();
274 dict.add(b"beta").unwrap();
275 dict.add(b"gamma").unwrap();
277 assert_eq!(dict.len(), 2);
278 assert_eq!(dict.lookup(0), Some(b"beta".as_slice()));
281 assert_eq!(dict.lookup(1), Some(b"gamma".as_slice()));
282 }
283}