zerotrie/builder/nonconst/
builder.rs1use core::cmp::Ordering;
6
7use super::super::branch_meta::BranchMeta;
8use super::store::NonConstLengthsStack;
9use super::store::TrieBuilderStore;
10use crate::builder::slice_indices::ByteSliceWithIndices;
11use crate::byte_phf::PerfectByteHashMapCacheOwned;
12use crate::error::ZeroTrieBuildError;
13use crate::options::*;
14use crate::varint;
15use alloc::vec::Vec;
16
17pub(crate) struct ZeroTrieBuilder<S> {
19 data: S,
20 phf_cache: PerfectByteHashMapCacheOwned,
21 options: ZeroTrieBuilderOptions,
22}
23
24impl<S: TrieBuilderStore> ZeroTrieBuilder<S> {
25 pub fn to_bytes(&self) -> Vec<u8> {
27 self.data.atbs_to_bytes()
28 }
29
30 fn prepend_ascii(&mut self, ascii: u8) -> Result<usize, ZeroTrieBuildError> {
35 if ascii <= 127 {
36 self.data.atbs_push_front(ascii);
37 Ok(1)
38 } else if matches!(self.options.ascii_mode, AsciiMode::BinarySpans) {
39 if let Some(old_front) = self.data.atbs_pop_front() {
40 let old_byte_len = self.data.atbs_len() + 1;
41 if old_front & 0b11100000 == 0b10100000 {
42 #[expect(clippy::unwrap_used)]
45 let old_span_size =
46 varint::try_read_varint_meta3_from_tstore(old_front, &mut self.data)
47 .unwrap();
48 self.data.atbs_push_front(ascii);
49 let varint_array = varint::write_varint_meta3(old_span_size + 1);
50 self.data.atbs_extend_front(varint_array.as_slice());
51 self.data.atbs_bitor_assign(0, 0b10100000);
52 let new_byte_len = self.data.atbs_len();
53 return Ok(new_byte_len - old_byte_len);
54 } else {
55 self.data.atbs_push_front(old_front);
56 }
57 }
58 self.data.atbs_push_front(ascii);
60 self.data.atbs_push_front(0b10100001);
61 Ok(2)
62 } else {
63 Err(ZeroTrieBuildError::NonAsciiError)
64 }
65 }
66
67 #[must_use]
70 fn prepend_value(&mut self, value: usize) -> usize {
71 let varint_array = varint::write_varint_meta3(value);
72 self.data.atbs_extend_front(varint_array.as_slice());
73 self.data.atbs_bitor_assign(0, 0b10000000);
74 varint_array.len()
75 }
76
77 #[must_use]
80 fn prepend_branch(&mut self, value: usize) -> usize {
81 let varint_array = varint::write_varint_meta2(value);
82 self.data.atbs_extend_front(varint_array.as_slice());
83 self.data.atbs_bitor_assign(0, 0b11000000);
84 varint_array.len()
85 }
86
87 #[must_use]
90 fn prepend_slice(&mut self, s: &[u8]) -> usize {
91 self.data.atbs_extend_front(s);
92 s.len()
93 }
94
95 pub fn from_bytes_iter<K: AsRef<[u8]>, I: IntoIterator<Item = (K, usize)>>(
97 iter: I,
98 options: ZeroTrieBuilderOptions,
99 ) -> Result<Self, ZeroTrieBuildError> {
100 let items = Vec::<(K, usize)>::from_iter(iter);
101 let mut items = items
102 .iter()
103 .map(|(k, v)| (k.as_ref(), *v))
104 .collect::<Vec<(&[u8], usize)>>();
105 items.sort_by(|a, b| cmp_keys_values(options, *a, *b));
106 let ascii_str_slice = items.as_slice();
107 let byte_str_slice = ByteSliceWithIndices::from_byte_slice(ascii_str_slice);
108 Self::from_sorted_tuple_slice_impl(byte_str_slice, options)
109 }
110
111 pub fn from_sorted_tuple_slice(
118 items: ByteSliceWithIndices,
119 options: ZeroTrieBuilderOptions,
120 ) -> Result<Self, ZeroTrieBuildError> {
121 if matches!(options.case_sensitivity, CaseSensitivity::IgnoreCase) {
122 let mut items_vec = items.to_vec_u8();
124 items_vec.sort_by(|a, b| cmp_keys_values(options, *a, *b));
125 Self::from_sorted_tuple_slice_impl(
126 ByteSliceWithIndices::from_byte_slice(&items_vec),
127 options,
128 )
129 } else {
130 Self::from_sorted_tuple_slice_impl(items, options)
131 }
132 }
133
134 fn from_sorted_tuple_slice_impl(
136 items: ByteSliceWithIndices,
137 options: ZeroTrieBuilderOptions,
138 ) -> Result<Self, ZeroTrieBuildError> {
139 #[allow(clippy::indexing_slicing)] let mut i = 0;
141 while i + 1 < items.len() {
142 let ab0 = items.get_or_panic(i);
143 let ab1 = items.get_or_panic(i + 1);
144 debug_assert!(cmp_keys_values(options, (ab0.0, ab0.1), (ab1.0, ab1.1)).is_lt());
145 i += 1;
146 }
147
148 let mut result = Self {
149 data: S::atbs_new_empty(),
150 phf_cache: PerfectByteHashMapCacheOwned::new_empty(),
151 options,
152 };
153 let total_size = result.create(items)?;
154 debug_assert!(total_size == result.data.atbs_len());
155 Ok(result)
156 }
157
158 #[expect(clippy::unwrap_used, clippy::indexing_slicing)] fn create(&mut self, all_items: ByteSliceWithIndices) -> Result<usize, ZeroTrieBuildError> {
161 let mut prefix_len = match all_items.last() {
162 Some(x) => x.0.len(),
163 None => return Ok(0),
165 };
166 let mut lengths_stack = NonConstLengthsStack::new();
168 let mut i = all_items.len() - 1;
169 let mut j = all_items.len();
170 let mut current_len = 0;
171 loop {
173 let item_i = all_items.get_or_panic(i);
174 let item_j = all_items.get_or_panic(j - 1);
175 debug_assert!(crate::builder::slice_indices::prefix_eq_or_panic(
176 item_i.0, item_j.0, prefix_len
177 ));
178 if item_i.0.len() == prefix_len {
180 let len = self.prepend_value(item_i.1);
181 current_len += len;
182 }
183 if prefix_len == 0 {
184 break;
186 }
187 prefix_len -= 1;
189 let mut new_i = i;
190 let mut new_j = j;
191 let mut ascii_i = item_i.0[prefix_len];
192 let mut ascii_j = item_j.0[prefix_len];
193 debug_assert_eq!(ascii_i, ascii_j);
194 let key_ascii = ascii_i;
195 loop {
196 if new_i == 0 {
197 break;
198 }
199 let candidate = all_items.get_or_panic(new_i - 1).0;
200 if candidate.len() < prefix_len {
201 break;
203 }
204 if crate::builder::slice_indices::prefix_eq_or_panic(
205 item_i.0, candidate, prefix_len,
206 ) {
207 new_i -= 1;
208 } else {
209 break;
210 }
211 if candidate.len() == prefix_len {
212 break;
214 }
215 let candidate = candidate[prefix_len];
216 if candidate != ascii_i {
217 ascii_i = candidate;
218 }
219 }
220 loop {
221 if new_j == all_items.len() {
222 break;
223 }
224 let candidate = all_items.get_or_panic(new_j).0;
225 if candidate.len() < prefix_len {
226 break;
228 }
229 if crate::builder::slice_indices::prefix_eq_or_panic(
230 item_j.0, candidate, prefix_len,
231 ) {
232 new_j += 1;
233 } else {
234 break;
235 }
236 if candidate.len() == prefix_len {
237 unreachable!("A shorter string should be earlier in the sequence");
238 }
239 let candidate = candidate[prefix_len];
240 if candidate != ascii_j {
241 ascii_j = candidate;
242 }
243 }
244 if ascii_i == key_ascii && ascii_j == key_ascii {
247 let len = self.prepend_ascii(key_ascii)?;
248 current_len += len;
249 if matches!(self.options.case_sensitivity, CaseSensitivity::IgnoreCase)
250 && i == new_i + 2
251 {
252 return Err(ZeroTrieBuildError::MixedCase);
254 }
255 debug_assert!(
256 i == new_i || i == new_i + 1,
257 "only the exact prefix string can be picked up at this level: {key_ascii}"
258 );
259 i = new_i;
260 debug_assert_eq!(j, new_j);
261 continue;
262 }
263 if ascii_j == key_ascii {
265 lengths_stack.push(BranchMeta {
267 ascii: key_ascii,
268 cumulative_length: current_len,
269 local_length: current_len,
270 count: 1,
271 });
272 } else {
273 let BranchMeta {
275 cumulative_length,
276 count,
277 ..
278 } = lengths_stack.peek_or_panic();
279 lengths_stack.push(BranchMeta {
280 ascii: key_ascii,
281 cumulative_length: cumulative_length + current_len,
282 local_length: current_len,
283 count: count + 1,
284 });
285 }
286 if ascii_i != key_ascii {
287 j = i;
290 i -= 1;
291 prefix_len = all_items.get_or_panic(i).0.len();
292 current_len = 0;
293 continue;
294 }
295 let (total_length, total_count) = {
298 let BranchMeta {
299 cumulative_length,
300 count,
301 ..
302 } = lengths_stack.peek_or_panic();
303 (cumulative_length, count)
304 };
305 let mut branch_metas = lengths_stack.pop_many_or_panic(total_count);
306 let original_keys = branch_metas.map_to_ascii_bytes();
307 if matches!(self.options.case_sensitivity, CaseSensitivity::IgnoreCase) {
308 let mut seen_ascii_alpha = [false; 26];
310 for c in original_keys.as_const_slice().as_slice() {
311 if c.is_ascii_alphabetic() {
312 let i = (c.to_ascii_lowercase() - b'a') as usize;
313 #[allow(clippy::indexing_slicing)] if seen_ascii_alpha[i] {
315 return Err(ZeroTrieBuildError::MixedCase);
316 } else {
317 seen_ascii_alpha[i] = true;
318 }
319 }
320 }
321 }
322 let use_phf = matches!(self.options.phf_mode, PhfMode::UsePhf);
323 let opt_phf_vec = if total_count > 15 && use_phf {
324 let phf_vec = self
325 .phf_cache
326 .try_get_or_insert(original_keys.as_const_slice().as_slice().to_vec())?;
327 loop {
330 let mut l = total_count - 1;
331 let mut changes = 0;
332 let mut start = 0;
333 while l > 0 {
334 let a = *branch_metas.as_const_slice().get_or_panic(l);
335 let b = *branch_metas.as_const_slice().get_or_panic(l - 1);
336 let a_idx = phf_vec.keys().iter().position(|x| x == &a.ascii).unwrap();
337 let b_idx = phf_vec.keys().iter().position(|x| x == &b.ascii).unwrap();
338 if a_idx > b_idx {
339 self.data.atbs_swap_ranges(
342 start,
343 start + a.local_length,
344 start + a.local_length + b.local_length,
345 );
346 branch_metas.swap_or_panic(l - 1, l);
347 start += b.local_length;
348 changes += 1;
349 } else {
351 start += a.local_length;
352 }
353 l -= 1;
354 }
355 if changes == 0 {
356 break;
357 }
358 }
359 Some(phf_vec)
360 } else {
361 None
362 };
363 current_len = total_length;
365 let w = (usize::BITS as usize - (total_length.leading_zeros() as usize) - 1) / 8;
366 if w > 3 && matches!(self.options.capacity_mode, CapacityMode::Normal) {
367 return Err(ZeroTrieBuildError::CapacityExceeded);
368 }
369 let mut k = 0;
370 while k <= w {
371 self.data.atbs_prepend_n_zeros(total_count - 1);
372 current_len += total_count - 1;
373 let mut l = 0;
374 let mut length_to_write = 0;
375 while l < total_count {
376 let BranchMeta { local_length, .. } = *branch_metas
377 .as_const_slice()
378 .get_or_panic(total_count - l - 1);
379 let mut adjusted_length = length_to_write;
380 let mut m = 0;
381 while m < k {
382 adjusted_length >>= 8;
383 m += 1;
384 }
385 if l > 0 {
386 self.data.atbs_bitor_assign(l - 1, adjusted_length as u8);
387 }
388 l += 1;
389 length_to_write += local_length;
390 }
391 k += 1;
392 }
393 assert!(0 < total_count && total_count <= 256);
395 let branch_value = (w << 8) + (total_count & 0xff);
396 if let Some(phf_vec) = opt_phf_vec {
397 self.data.atbs_extend_front(phf_vec.as_bytes());
398 let phf_len = phf_vec.as_bytes().len();
399 let branch_len = self.prepend_branch(branch_value);
400 current_len += phf_len + branch_len;
401 } else {
402 let search_len = self.prepend_slice(original_keys.as_slice());
403 let branch_len = self.prepend_branch(branch_value);
404 current_len += search_len + branch_len;
405 }
406 i = new_i;
407 j = new_j;
408 }
409 assert!(lengths_stack.is_empty());
410 Ok(current_len)
411 }
412}
413
414fn cmp_keys_values(
415 options: ZeroTrieBuilderOptions,
416 a: (&[u8], usize),
417 b: (&[u8], usize),
418) -> Ordering {
419 if matches!(options.case_sensitivity, CaseSensitivity::Sensitive) {
420 a.0.cmp(b.0)
421 } else {
422 let a_iter = a.0.iter().map(|x| x.to_ascii_lowercase());
423 let b_iter = b.0.iter().map(|x| x.to_ascii_lowercase());
424 Iterator::cmp(a_iter, b_iter)
425 }
426 .then_with(|| a.1.cmp(&b.1))
427}