Skip to main content

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(#[automatically_derived]
impl<'a> ::core::fmt::Debug for ZeroTrieSimpleAsciiCursor<'a> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f,
            "ZeroTrieSimpleAsciiCursor", "trie", &&self.trie)
    }
}Debug, #[automatically_derived]
impl<'a> ::core::clone::Clone for ZeroTrieSimpleAsciiCursor<'a> {
    #[inline]
    fn clone(&self) -> ZeroTrieSimpleAsciiCursor<'a> {
        ZeroTrieSimpleAsciiCursor {
            trie: ::core::clone::Clone::clone(&self.trie),
        }
    }
}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(#[automatically_derived]
impl<'a> ::core::fmt::Debug for ZeroAsciiIgnoreCaseTrieCursor<'a> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f,
            "ZeroAsciiIgnoreCaseTrieCursor", "trie", &&self.trie)
    }
}Debug, #[automatically_derived]
impl<'a> ::core::clone::Clone for ZeroAsciiIgnoreCaseTrieCursor<'a> {
    #[inline]
    fn clone(&self) -> ZeroAsciiIgnoreCaseTrieCursor<'a> {
        ZeroAsciiIgnoreCaseTrieCursor {
            trie: ::core::clone::Clone::clone(&self.trie),
        }
    }
}Clone)]
214pub struct ZeroAsciiIgnoreCaseTrieCursor<'a> {
215    trie: ZeroAsciiIgnoreCaseTrie<&'a [u8]>,
216}
217
218/// Information about a probed edge.
219#[derive(#[automatically_derived]
impl ::core::fmt::Debug for AsciiProbeResult {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "AsciiProbeResult", "byte", &self.byte, "total_siblings",
            &&self.total_siblings)
    }
}Debug, #[automatically_derived]
impl ::core::marker::Copy for AsciiProbeResult { }Copy, #[automatically_derived]
impl ::core::clone::Clone for AsciiProbeResult {
    #[inline]
    fn clone(&self) -> AsciiProbeResult {
        let _: ::core::clone::AssertParamIsClone<u8>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for AsciiProbeResult {
    #[inline]
    fn eq(&self, other: &AsciiProbeResult) -> bool {
        self.byte == other.byte && self.total_siblings == other.total_siblings
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for AsciiProbeResult {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<u8>;
    }
}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}