zerotrie/cursor.rs
1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! Types for walking stepwise through a trie.
6//!
7//! For examples, see the `.cursor()` functions
8//! and the `Cursor` types in this module.
9
10use crate::reader;
11use crate::ZeroAsciiIgnoreCaseTrie;
12use crate::ZeroTrieSimpleAscii;
13
14use core::fmt;
15
16impl<Store> ZeroTrieSimpleAscii<Store>
17where
18 Store: AsRef<[u8]> + ?Sized,
19{
20 /// Gets a cursor into the current trie.
21 ///
22 /// Useful to query a trie with data that is not a slice.
23 ///
24 /// This is currently supported only on [`ZeroTrieSimpleAscii`]
25 /// and [`ZeroAsciiIgnoreCaseTrie`].
26 ///
27 /// # Examples
28 ///
29 /// Get a value out of a trie by [writing](fmt::Write) it to the cursor:
30 ///
31 /// ```
32 /// use core::fmt::Write;
33 /// use zerotrie::ZeroTrieSimpleAscii;
34 ///
35 /// // A trie with two values: "abc" and "abcdef"
36 /// let trie = ZeroTrieSimpleAscii::from_bytes(b"abc\x80def\x81");
37 ///
38 /// // Get out the value for "abc"
39 /// let mut cursor = trie.cursor();
40 /// write!(&mut cursor, "abc");
41 /// assert_eq!(cursor.take_value(), Some(0));
42 /// ```
43 ///
44 /// Find the longest prefix match:
45 ///
46 /// ```
47 /// use zerotrie::ZeroTrieSimpleAscii;
48 ///
49 /// // A trie with two values: "abc" and "abcdef"
50 /// let trie = ZeroTrieSimpleAscii::from_bytes(b"abc\x80def\x81");
51 ///
52 /// // Find the longest prefix of the string "abcdxy":
53 /// let query = b"abcdxy";
54 /// let mut longest_prefix = 0;
55 /// let mut cursor = trie.cursor();
56 /// for (i, b) in query.iter().enumerate() {
57 /// // Checking is_empty() is not required, but it is
58 /// // good for efficiency
59 /// if cursor.is_empty() {
60 /// break;
61 /// }
62 /// if cursor.take_value().is_some() {
63 /// longest_prefix = i;
64 /// }
65 /// cursor.step(*b);
66 /// }
67 ///
68 /// // The longest prefix is "abc" which is length 3:
69 /// assert_eq!(longest_prefix, 3);
70 /// ```
71 #[inline]
72 pub fn cursor(&self) -> ZeroTrieSimpleAsciiCursor<'_> {
73 ZeroTrieSimpleAsciiCursor {
74 trie: self.as_borrowed_slice(),
75 }
76 }
77
78 /// Queries the trie using a closure that writes to a cursor.
79 ///
80 /// Third-party string-like types can integrate with this API by returning a function
81 /// with the required signature.
82 ///
83 /// # Examples
84 ///
85 /// Using the `writeable` crate:
86 ///
87 /// ```
88 /// use writeable::Writeable;
89 /// use zerotrie::ZeroTrieSimpleAscii;
90 ///
91 /// // A trie with two values: "abc" and "abcdef"
92 /// let trie = ZeroTrieSimpleAscii::from_bytes(b"abc\x80def\x81");
93 ///
94 /// // Get out the value for "abc"
95 /// let needle = writeable::concat_writeable!("a", "bc");
96 /// assert_eq!(
97 /// trie.get_with_write_fn(|sink| needle.write_to(sink)),
98 /// Some(0)
99 /// );
100 /// ```
101 #[inline]
102 pub fn get_with_write_fn<'a>(
103 &'a self,
104 write_fn: impl for<'b> FnOnce(&'b mut ZeroTrieSimpleAsciiCursor<'a>) -> fmt::Result,
105 ) -> Option<usize> {
106 let mut cursor = self.cursor();
107 write_fn(&mut cursor).ok()?;
108 cursor.take_value()
109 }
110}
111
112impl<Store> ZeroAsciiIgnoreCaseTrie<Store>
113where
114 Store: AsRef<[u8]> + ?Sized,
115{
116 /// Gets a cursor into the current trie.
117 ///
118 /// Useful to query a trie with data that is not a slice.
119 ///
120 /// This is currently supported only on [`ZeroTrieSimpleAscii`]
121 /// and [`ZeroAsciiIgnoreCaseTrie`].
122 ///
123 /// # Examples
124 ///
125 /// Get a value out of a trie by [writing](fmt::Write) it to the cursor:
126 ///
127 /// ```
128 /// use core::fmt::Write;
129 /// use zerotrie::ZeroAsciiIgnoreCaseTrie;
130 ///
131 /// // A trie with two values: "aBc" and "aBcdEf"
132 /// let trie = ZeroAsciiIgnoreCaseTrie::from_bytes(b"aBc\x80dEf\x81");
133 ///
134 /// // Get out the value for "abc" (case-insensitive!)
135 /// let mut cursor = trie.cursor();
136 /// write!(&mut cursor, "abc");
137 /// assert_eq!(cursor.take_value(), Some(0));
138 /// ```
139 ///
140 /// For more examples, see [`ZeroTrieSimpleAscii::cursor`].
141 #[inline]
142 pub fn cursor(&self) -> ZeroAsciiIgnoreCaseTrieCursor<'_> {
143 ZeroAsciiIgnoreCaseTrieCursor {
144 trie: self.as_borrowed_slice(),
145 }
146 }
147
148 /// Queries the trie using a closure that writes to a cursor.
149 ///
150 /// Third-party string-like types can integrate with this API by returning a function
151 /// with the required signature.
152 ///
153 /// # Examples
154 ///
155 /// Using the `writeable` crate:
156 ///
157 /// ```
158 /// use writeable::Writeable;
159 /// use zerotrie::ZeroAsciiIgnoreCaseTrie;
160 ///
161 /// // A trie with two values: "aBc" and "aBcdEf"
162 /// let trie = ZeroAsciiIgnoreCaseTrie::from_bytes(b"aBc\x80dEf\x81");
163 ///
164 /// // Get out the value for "abc"
165 /// let needle = writeable::concat_writeable!("a", "bc");
166 /// assert_eq!(
167 /// trie.get_with_write_fn(|sink| needle.write_to(sink)),
168 /// Some(0)
169 /// );
170 /// ```
171 #[inline]
172 pub fn get_with_write_fn<'a>(
173 &'a self,
174 write_fn: impl for<'b> FnOnce(&'b mut ZeroAsciiIgnoreCaseTrieCursor<'a>) -> fmt::Result,
175 ) -> Option<usize> {
176 let mut cursor = self.cursor();
177 write_fn(&mut cursor).ok()?;
178 cursor.take_value()
179 }
180}
181
182impl<'a> ZeroTrieSimpleAscii<&'a [u8]> {
183 /// Same as [`ZeroTrieSimpleAscii::cursor()`] but moves self to avoid
184 /// having to doubly anchor the trie to the stack.
185 #[inline]
186 pub fn into_cursor(self) -> ZeroTrieSimpleAsciiCursor<'a> {
187 ZeroTrieSimpleAsciiCursor { trie: self }
188 }
189}
190
191impl<'a> ZeroAsciiIgnoreCaseTrie<&'a [u8]> {
192 /// Same as [`ZeroAsciiIgnoreCaseTrie::cursor()`] but moves self to avoid
193 /// having to doubly anchor the trie to the stack.
194 #[inline]
195 pub fn into_cursor(self) -> ZeroAsciiIgnoreCaseTrieCursor<'a> {
196 ZeroAsciiIgnoreCaseTrieCursor { trie: self }
197 }
198}
199
200/// A cursor into a [`ZeroTrieSimpleAscii`], useful for stepwise lookup.
201///
202/// For examples, see [`ZeroTrieSimpleAscii::cursor()`].
203// Clone but not Copy: <https://stackoverflow.com/q/32324251/1407170>
204#[derive(Debug, Clone)]
205pub struct ZeroTrieSimpleAsciiCursor<'a> {
206 trie: ZeroTrieSimpleAscii<&'a [u8]>,
207}
208
209/// A cursor into a [`ZeroAsciiIgnoreCaseTrie`], useful for stepwise lookup.
210///
211/// For examples, see [`ZeroAsciiIgnoreCaseTrie::cursor()`].
212// Clone but not Copy: <https://stackoverflow.com/q/32324251/1407170>
213#[derive(Debug, Clone)]
214pub struct ZeroAsciiIgnoreCaseTrieCursor<'a> {
215 trie: ZeroAsciiIgnoreCaseTrie<&'a [u8]>,
216}
217
218/// Information about a probed edge.
219#[derive(Debug, Copy, Clone, PartialEq, Eq)]
220#[non_exhaustive] // no need to destructure or construct this in userland
221pub struct AsciiProbeResult {
222 /// The character's byte value between this node and its parent.
223 pub byte: u8,
224 /// The number of siblings of this node, _including itself_.
225 pub total_siblings: u8,
226}
227
228impl<'a> ZeroTrieSimpleAsciiCursor<'a> {
229 /// Steps the cursor one character into the trie based on the character's byte value.
230 ///
231 /// # Examples
232 ///
233 /// Unrolled loop checking for string presence at every step:
234 ///
235 /// ```
236 /// use zerotrie::ZeroTrieSimpleAscii;
237 ///
238 /// // A trie with two values: "abc" and "abcdef"
239 /// let trie = ZeroTrieSimpleAscii::from_bytes(b"abc\x80def\x81");
240 ///
241 /// // Search the trie for the string "abcdxy"
242 /// let mut cursor = trie.cursor();
243 /// assert_eq!(cursor.take_value(), None); // ""
244 /// cursor.step(b'a');
245 /// assert_eq!(cursor.take_value(), None); // "a"
246 /// cursor.step(b'b');
247 /// assert_eq!(cursor.take_value(), None); // "ab"
248 /// cursor.step(b'c');
249 /// assert_eq!(cursor.take_value(), Some(0)); // "abc"
250 /// cursor.step(b'd');
251 /// assert_eq!(cursor.take_value(), None); // "abcd"
252 /// assert!(!cursor.is_empty());
253 /// cursor.step(b'x'); // no strings have the prefix "abcdx"
254 /// assert!(cursor.is_empty());
255 /// assert_eq!(cursor.take_value(), None); // "abcdx"
256 /// cursor.step(b'y');
257 /// assert_eq!(cursor.take_value(), None); // "abcdxy"
258 /// ```
259 ///
260 /// If the byte is not ASCII, the cursor will become empty:
261 ///
262 /// ```
263 /// use zerotrie::ZeroTrieSimpleAscii;
264 ///
265 /// // A trie with two values: "abc" and "abcdef"
266 /// let trie = ZeroTrieSimpleAscii::from_bytes(b"abc\x80def\x81");
267 ///
268 /// let mut cursor = trie.cursor();
269 /// assert_eq!(cursor.take_value(), None); // ""
270 /// cursor.step(b'a');
271 /// assert_eq!(cursor.take_value(), None); // "a"
272 /// cursor.step(b'b');
273 /// assert_eq!(cursor.take_value(), None); // "ab"
274 /// cursor.step(b'\xFF');
275 /// assert!(cursor.is_empty());
276 /// assert_eq!(cursor.take_value(), None);
277 /// ```
278 #[inline]
279 pub fn step(&mut self, byte: u8) {
280 reader::step_parameterized::<ZeroTrieSimpleAscii<[u8]>>(&mut self.trie.store, byte);
281 }
282
283 /// Takes the value at the current position.
284 ///
285 /// Calling this function on a new cursor is equivalent to calling `.get()`
286 /// with the empty string (except that it can only be called once).
287 ///
288 /// # Examples
289 ///
290 /// ```
291 /// use zerotrie::ZeroTrieSimpleAscii;
292 ///
293 /// // A trie with two values: "" and "abc"
294 /// let trie = ZeroTrieSimpleAscii::from_bytes(b"\x80abc\x81");
295 ///
296 /// assert_eq!(Some(0), trie.get(""));
297 /// let mut cursor = trie.cursor();
298 /// assert_eq!(Some(0), cursor.take_value());
299 /// assert_eq!(None, cursor.take_value());
300 /// ```
301 #[inline]
302 pub fn take_value(&mut self) -> Option<usize> {
303 reader::take_value(&mut self.trie.store)
304 }
305
306 /// Steps the cursor one character into the trie based on an edge index,
307 /// returning the corresponding character as a byte.
308 ///
309 /// This function is similar to [`Self::step()`], but it takes an index instead of a char.
310 /// This enables stepwise iteration over the contents of the trie.
311 ///
312 /// If there are multiple possibilities for the next byte, the `index` argument allows
313 /// visiting them in order. Since this function steps the cursor, the cursor must be
314 /// cloned (a cheap operation) in order to visit multiple children.
315 ///
316 /// # Examples
317 ///
318 /// Continually query index 0 to extract the first item from a trie:
319 ///
320 /// ```
321 /// use zerotrie::ZeroTrieSimpleAscii;
322 ///
323 /// let data: &[(String, usize)] = &[
324 /// ("ab".to_string(), 111),
325 /// ("abcxyz".to_string(), 22),
326 /// ("abde".to_string(), 333),
327 /// ("afg".to_string(), 44),
328 /// ];
329 ///
330 /// let trie: ZeroTrieSimpleAscii<Vec<u8>> =
331 /// data.iter().map(|(s, v)| (s.as_str(), *v)).collect();
332 ///
333 /// let mut cursor = trie.cursor();
334 /// let mut key = String::new();
335 /// let value = loop {
336 /// if let Some(value) = cursor.take_value() {
337 /// break value;
338 /// }
339 /// let probe_result = cursor.probe(0).unwrap();
340 /// key.push(char::from(probe_result.byte));
341 /// };
342 ///
343 /// assert_eq!(key, "ab");
344 /// assert_eq!(value, 111);
345 /// ```
346 ///
347 /// Stepwise iterate over all entries in the trie:
348 ///
349 /// ```
350 /// # use zerotrie::ZeroTrieSimpleAscii;
351 /// # let data: &[(String, usize)] = &[
352 /// # ("ab".to_string(), 111),
353 /// # ("abcxyz".to_string(), 22),
354 /// # ("abde".to_string(), 333),
355 /// # ("afg".to_string(), 44)
356 /// # ];
357 /// # let trie: ZeroTrieSimpleAscii<Vec<u8>> = data
358 /// # .iter()
359 /// # .map(|(s, v)| (s.as_str(), *v))
360 /// # .collect();
361 /// // (trie built as in previous example)
362 ///
363 /// // Initialize the iteration at the first child of the trie.
364 /// let mut stack = Vec::from([(trie.cursor(), 0, 0)]);
365 /// let mut key = Vec::new();
366 /// let mut results = Vec::new();
367 /// loop {
368 /// let Some((mut cursor, index, suffix_len)) = stack.pop() else {
369 /// // Nothing left in the trie.
370 /// break;
371 /// };
372 /// // Check to see if there is a value at the current node.
373 /// if let Some(value) = cursor.take_value() {
374 /// results.push((String::from_utf8(key.clone()).unwrap(), value));
375 /// }
376 /// // Now check for children of the current node.
377 /// let mut sub_cursor = cursor.clone();
378 /// if let Some(probe_result) = sub_cursor.probe(index) {
379 /// // Found a child. Add the current byte edge to the key.
380 /// key.push(probe_result.byte);
381 /// // Add the child to the stack, and also add back the current
382 /// // node if there are more siblings to visit.
383 /// if index + 1 < probe_result.total_siblings as usize {
384 /// stack.push((cursor, index + 1, suffix_len));
385 /// stack.push((sub_cursor, 0, 1));
386 /// } else {
387 /// stack.push((sub_cursor, 0, suffix_len + 1));
388 /// }
389 /// } else {
390 /// // No more children. Pop this node's bytes from the key.
391 /// for _ in 0..suffix_len {
392 /// key.pop();
393 /// }
394 /// }
395 /// }
396 ///
397 /// assert_eq!(&results, data);
398 /// ```
399 pub fn probe(&mut self, index: usize) -> Option<AsciiProbeResult> {
400 reader::probe_parameterized::<ZeroTrieSimpleAscii<[u8]>>(&mut self.trie.store, index)
401 }
402
403 /// Checks whether the cursor points to an empty trie.
404 ///
405 /// Use this to determine when to stop iterating.
406 #[inline]
407 pub fn is_empty(&self) -> bool {
408 self.trie.is_empty()
409 }
410
411 /// Returns a trie for all suffixes that begin with the previously stepped
412 /// bytes.
413 ///
414 /// # Examples
415 ///
416 /// ```
417 /// use zerotrie::ZeroTrieSimpleAscii;
418 ///
419 /// // A trie with two values: "abc" and "abcdef"
420 /// let trie = ZeroTrieSimpleAscii::from_bytes(b"abc\x80def\x81");
421 ///
422 /// // Consume the prefix "ab"
423 /// let mut cursor = trie.cursor();
424 /// cursor.step(b'a');
425 /// cursor.step(b'b');
426 /// let suffix_trie = cursor.into_suffix_trie();
427 ///
428 /// // The suffix trie contains the strings "c" and "cdef"
429 /// assert_eq!(suffix_trie.get("c"), Some(0));
430 /// assert_eq!(suffix_trie.get("cdef"), Some(1));
431 /// ```
432 pub fn into_suffix_trie(self) -> ZeroTrieSimpleAscii<&'a [u8]> {
433 self.trie
434 }
435}
436
437impl<'a> ZeroAsciiIgnoreCaseTrieCursor<'a> {
438 /// Steps the cursor one byte into the trie.
439 ///
440 /// Returns the byte if matched, which may be a different case than the input byte.
441 /// If this function returns `None`, any lookup loops can be terminated.
442 ///
443 /// # Examples
444 ///
445 /// Normalize the case of a value by stepping through an ignore-case trie:
446 ///
447 /// ```
448 /// use std::borrow::Cow;
449 /// use zerotrie::ZeroAsciiIgnoreCaseTrie;
450 ///
451 /// // A trie with two values: "aBc" and "aBcdEf"
452 /// let trie = ZeroAsciiIgnoreCaseTrie::from_bytes(b"aBc\x80dEf\x81");
453 ///
454 /// // Get out the value for "abc" and normalize the key string
455 /// let mut cursor = trie.cursor();
456 /// let mut key_str = Cow::Borrowed("abc".as_bytes());
457 /// let mut i = 0;
458 /// let value = loop {
459 /// let Some(&input_byte) = key_str.get(i) else {
460 /// break cursor.take_value();
461 /// };
462 /// let Some(matched_byte) = cursor.step(input_byte) else {
463 /// break None;
464 /// };
465 /// if matched_byte != input_byte {
466 /// key_str.to_mut()[i] = matched_byte;
467 /// }
468 /// i += 1;
469 /// };
470 ///
471 /// assert_eq!(value, Some(0));
472 /// assert_eq!(&*key_str, "aBc".as_bytes());
473 /// ```
474 ///
475 /// For more examples, see [`ZeroTrieSimpleAsciiCursor::step`].
476 #[inline]
477 pub fn step(&mut self, byte: u8) -> Option<u8> {
478 reader::step_parameterized::<ZeroAsciiIgnoreCaseTrie<[u8]>>(&mut self.trie.store, byte)
479 }
480
481 /// Takes the value at the current position.
482 ///
483 /// For more details, see [`ZeroTrieSimpleAsciiCursor::take_value`].
484 #[inline]
485 pub fn take_value(&mut self) -> Option<usize> {
486 reader::take_value(&mut self.trie.store)
487 }
488
489 /// Probes the next byte in the cursor.
490 ///
491 /// For more details, see [`ZeroTrieSimpleAsciiCursor::probe`].
492 pub fn probe(&mut self, index: usize) -> Option<AsciiProbeResult> {
493 reader::probe_parameterized::<ZeroAsciiIgnoreCaseTrie<[u8]>>(&mut self.trie.store, index)
494 }
495
496 /// Checks whether the cursor points to an empty trie.
497 ///
498 /// For more details, see [`ZeroTrieSimpleAsciiCursor::is_empty`].
499 #[inline]
500 pub fn is_empty(&self) -> bool {
501 self.trie.is_empty()
502 }
503
504 /// Returns a trie for all suffixes that begin with the previously stepped
505 /// bytes.
506 ///
507 /// # Examples
508 ///
509 /// ```
510 /// use zerotrie::ZeroAsciiIgnoreCaseTrie;
511 ///
512 /// // A trie with two values: "aBc" and "aBcdEf"
513 /// let trie = ZeroAsciiIgnoreCaseTrie::from_bytes(b"aBc\x80dEf\x81");
514 ///
515 /// // Consume the prefix "ab"
516 /// let mut cursor = trie.cursor();
517 /// cursor.step(b'a');
518 /// cursor.step(b'b');
519 /// let suffix_trie = cursor.into_suffix_trie();
520 ///
521 /// // The suffix trie contains the strings "c" and "cdef" (case-insensitive!)
522 /// assert_eq!(suffix_trie.get("c"), Some(0));
523 /// assert_eq!(suffix_trie.get("CDEF"), Some(1));
524 /// ```
525 pub fn into_suffix_trie(self) -> ZeroAsciiIgnoreCaseTrie<&'a [u8]> {
526 self.trie
527 }
528}
529
530impl fmt::Write for ZeroTrieSimpleAsciiCursor<'_> {
531 /// Steps the cursor through each ASCII byte of the string.
532 ///
533 /// If the string contains non-ASCII chars, an error is returned.
534 ///
535 /// # Examples
536 ///
537 /// ```
538 /// use core::fmt::Write;
539 /// use zerotrie::ZeroTrieSimpleAscii;
540 ///
541 /// // A trie with two values: "abc" and "abcdef"
542 /// let trie = ZeroTrieSimpleAscii::from_bytes(b"abc\x80def\x81");
543 ///
544 /// let mut cursor = trie.cursor();
545 /// cursor.write_str("abcdxy").expect("all ASCII");
546 /// cursor.write_str("🚂").expect_err("non-ASCII");
547 /// ```
548 fn write_str(&mut self, s: &str) -> fmt::Result {
549 for b in s.bytes() {
550 if !b.is_ascii() {
551 return Err(fmt::Error);
552 }
553 self.step(b);
554 }
555 Ok(())
556 }
557
558 /// Equivalent to [`ZeroTrieSimpleAsciiCursor::step()`], except returns
559 /// an error if the char is non-ASCII.
560 ///
561 /// # Examples
562 ///
563 /// ```
564 /// use core::fmt::Write;
565 /// use zerotrie::ZeroTrieSimpleAscii;
566 ///
567 /// // A trie with two values: "abc" and "abcdef"
568 /// let trie = ZeroTrieSimpleAscii::from_bytes(b"abc\x80def\x81");
569 ///
570 /// let mut cursor = trie.cursor();
571 /// cursor.write_char('a').expect("ASCII");
572 /// cursor.write_char('x').expect("ASCII");
573 /// cursor.write_char('🚂').expect_err("non-ASCII");
574 /// ```
575 fn write_char(&mut self, c: char) -> fmt::Result {
576 if !c.is_ascii() {
577 return Err(fmt::Error);
578 }
579 self.step(c as u8);
580 Ok(())
581 }
582}
583
584impl fmt::Write for ZeroAsciiIgnoreCaseTrieCursor<'_> {
585 /// Steps the cursor through each ASCII byte of the string.
586 ///
587 /// If the string contains non-ASCII chars, an error is returned.
588 fn write_str(&mut self, s: &str) -> fmt::Result {
589 for b in s.bytes() {
590 if !b.is_ascii() {
591 return Err(fmt::Error);
592 }
593 self.step(b);
594 }
595 Ok(())
596 }
597
598 /// Equivalent to [`ZeroAsciiIgnoreCaseTrieCursor::step()`], except returns
599 /// an error if the char is non-ASCII.
600 fn write_char(&mut self, c: char) -> fmt::Result {
601 if !c.is_ascii() {
602 return Err(fmt::Error);
603 }
604 self.step(c as u8);
605 Ok(())
606 }
607}