Skip to main content

winnow/stream/
bstr.rs

1use core::num::NonZeroUsize;
2
3use crate::stream::AsBStr;
4use crate::stream::Checkpoint;
5use crate::stream::Compare;
6use crate::stream::CompareResult;
7use crate::stream::FindSlice;
8use crate::stream::Needed;
9use crate::stream::Offset;
10#[cfg(feature = "unstable-recover")]
11#[cfg(feature = "std")]
12use crate::stream::Recover;
13use crate::stream::SliceLen;
14use crate::stream::Stream;
15use crate::stream::StreamIsPartial;
16use crate::stream::UpdateSlice;
17use core::iter::{Cloned, Enumerate};
18use core::slice::Iter;
19use core::{cmp::Ordering, fmt, ops};
20
21/// Improved `Debug` experience for `&[u8]` UTF-8-ish streams
22#[allow(clippy::derived_hash_with_manual_eq)]
23#[derive(Hash)]
24#[repr(transparent)]
25pub struct BStr([u8]);
26
27impl BStr {
28    /// Make a stream out of a byte slice-like.
29    #[inline]
30    pub fn new<B: ?Sized + AsRef<[u8]>>(bytes: &B) -> &Self {
31        Self::from_bytes(bytes.as_ref())
32    }
33
34    #[inline]
35    fn from_bytes(slice: &[u8]) -> &Self {
36        unsafe { core::mem::transmute(slice) }
37    }
38
39    #[inline]
40    fn as_bytes(&self) -> &[u8] {
41        &self.0
42    }
43}
44
45impl SliceLen for &BStr {
46    #[inline(always)]
47    fn slice_len(&self) -> usize {
48        self.len()
49    }
50}
51
52impl<'i> Stream for &'i BStr {
53    type Token = u8;
54    type Slice = &'i [u8];
55
56    type IterOffsets = Enumerate<Cloned<Iter<'i, u8>>>;
57
58    type Checkpoint = Checkpoint<Self, Self>;
59
60    #[inline(always)]
61    fn iter_offsets(&self) -> Self::IterOffsets {
62        self.iter().cloned().enumerate()
63    }
64    #[inline(always)]
65    fn eof_offset(&self) -> usize {
66        self.len()
67    }
68
69    #[inline(always)]
70    fn next_token(&mut self) -> Option<Self::Token> {
71        if self.is_empty() {
72            None
73        } else {
74            let token = self[0];
75            *self = &self[1..];
76            Some(token)
77        }
78    }
79
80    #[inline(always)]
81    fn peek_token(&self) -> Option<Self::Token> {
82        if self.is_empty() {
83            None
84        } else {
85            Some(self[0])
86        }
87    }
88
89    #[inline(always)]
90    fn offset_for<P>(&self, predicate: P) -> Option<usize>
91    where
92        P: Fn(Self::Token) -> bool,
93    {
94        self.iter().position(|b| predicate(*b))
95    }
96    #[inline(always)]
97    fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
98        if let Some(needed) = tokens.checked_sub(self.len()).and_then(NonZeroUsize::new) {
99            Err(Needed::Size(needed))
100        } else {
101            Ok(tokens)
102        }
103    }
104    #[inline(always)]
105    fn next_slice(&mut self, offset: usize) -> Self::Slice {
106        let (slice, next) = self.0.split_at(offset);
107        *self = BStr::from_bytes(next);
108        slice
109    }
110    #[inline(always)]
111    unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
112        #[cfg(debug_assertions)]
113        self.peek_slice(offset);
114
115        // SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
116        let slice = unsafe { self.0.get_unchecked(..offset) };
117        // SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
118        let next = unsafe { self.0.get_unchecked(offset..) };
119        *self = BStr::from_bytes(next);
120        slice
121    }
122    #[inline(always)]
123    fn peek_slice(&self, offset: usize) -> Self::Slice {
124        &self[..offset]
125    }
126    #[inline(always)]
127    unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
128        #[cfg(debug_assertions)]
129        self.peek_slice(offset);
130
131        // SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
132        let slice = unsafe { self.0.get_unchecked(..offset) };
133        slice
134    }
135
136    #[inline(always)]
137    fn checkpoint(&self) -> Self::Checkpoint {
138        Checkpoint::<_, Self>::new(*self)
139    }
140    #[inline(always)]
141    fn reset(&mut self, checkpoint: &Self::Checkpoint) {
142        *self = checkpoint.inner;
143    }
144
145    fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
146        write!(f, "{self:#?}")
147    }
148}
149
150#[cfg(feature = "unstable-recover")]
151#[cfg(feature = "std")]
152impl<E> Recover<E> for &BStr {
153    #[inline(always)]
154    fn record_err(
155        &mut self,
156        _token_start: &Self::Checkpoint,
157        _err_start: &Self::Checkpoint,
158        err: E,
159    ) -> Result<(), E> {
160        Err(err)
161    }
162
163    /// Report whether the [`Stream`] can save off errors for recovery
164    #[inline(always)]
165    fn is_recovery_supported() -> bool {
166        false
167    }
168}
169
170impl StreamIsPartial for &BStr {
171    type PartialState = ();
172
173    #[inline]
174    fn complete(&mut self) -> Self::PartialState {
175        // Already complete
176    }
177
178    #[inline]
179    fn restore_partial(&mut self, _state: Self::PartialState) {}
180
181    #[inline(always)]
182    fn is_partial_supported() -> bool {
183        false
184    }
185}
186
187impl Offset for &BStr {
188    #[inline(always)]
189    fn offset_from(&self, start: &Self) -> usize {
190        self.as_bytes().offset_from(&start.as_bytes())
191    }
192}
193
194impl<'a> Offset<<&'a BStr as Stream>::Checkpoint> for &'a BStr {
195    #[inline(always)]
196    fn offset_from(&self, other: &<&'a BStr as Stream>::Checkpoint) -> usize {
197        self.checkpoint().offset_from(other)
198    }
199}
200
201impl AsBStr for &BStr {
202    #[inline(always)]
203    fn as_bstr(&self) -> &[u8] {
204        (*self).as_bytes()
205    }
206}
207
208impl<'a, T> Compare<T> for &'a BStr
209where
210    &'a [u8]: Compare<T>,
211{
212    #[inline(always)]
213    fn compare(&self, t: T) -> CompareResult {
214        let bytes = (*self).as_bytes();
215        bytes.compare(t)
216    }
217}
218
219impl<'i, S> FindSlice<S> for &'i BStr
220where
221    &'i [u8]: FindSlice<S>,
222{
223    #[inline(always)]
224    fn find_slice(&self, substr: S) -> Option<core::ops::Range<usize>> {
225        let bytes = (*self).as_bytes();
226        let offset = bytes.find_slice(substr);
227        offset
228    }
229}
230
231impl UpdateSlice for &BStr {
232    #[inline(always)]
233    fn update_slice(self, inner: Self::Slice) -> Self {
234        BStr::new(inner)
235    }
236}
237
238#[cfg(feature = "alloc")]
239impl fmt::Display for BStr {
240    #[inline]
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        alloc::string::String::from_utf8_lossy(self.as_bytes()).fmt(f)
243    }
244}
245
246impl fmt::Debug for BStr {
247    #[inline]
248    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249        if !f.alternate() {
250            write!(f, "\"")?;
251        }
252        for byte in self.as_bytes() {
253            let c = *byte as char;
254            write!(f, "{}", c.escape_debug())?;
255        }
256        if !f.alternate() {
257            write!(f, "\"")?;
258        }
259        Ok(())
260    }
261}
262
263impl ops::Deref for BStr {
264    type Target = [u8];
265
266    #[inline]
267    fn deref(&self) -> &[u8] {
268        self.as_bytes()
269    }
270}
271
272impl ops::Index<usize> for BStr {
273    type Output = u8;
274
275    #[inline]
276    fn index(&self, idx: usize) -> &u8 {
277        &self.as_bytes()[idx]
278    }
279}
280
281impl ops::Index<ops::RangeFull> for BStr {
282    type Output = BStr;
283
284    #[inline]
285    fn index(&self, _: ops::RangeFull) -> &BStr {
286        self
287    }
288}
289
290impl ops::Index<ops::Range<usize>> for BStr {
291    type Output = BStr;
292
293    #[inline]
294    fn index(&self, r: ops::Range<usize>) -> &BStr {
295        BStr::new(&self.as_bytes()[r.start..r.end])
296    }
297}
298
299impl ops::Index<ops::RangeInclusive<usize>> for BStr {
300    type Output = BStr;
301
302    #[inline]
303    fn index(&self, r: ops::RangeInclusive<usize>) -> &BStr {
304        BStr::new(&self.as_bytes()[*r.start()..=*r.end()])
305    }
306}
307
308impl ops::Index<ops::RangeFrom<usize>> for BStr {
309    type Output = BStr;
310
311    #[inline]
312    fn index(&self, r: ops::RangeFrom<usize>) -> &BStr {
313        BStr::new(&self.as_bytes()[r.start..])
314    }
315}
316
317impl ops::Index<ops::RangeTo<usize>> for BStr {
318    type Output = BStr;
319
320    #[inline]
321    fn index(&self, r: ops::RangeTo<usize>) -> &BStr {
322        BStr::new(&self.as_bytes()[..r.end])
323    }
324}
325
326impl ops::Index<ops::RangeToInclusive<usize>> for BStr {
327    type Output = BStr;
328
329    #[inline]
330    fn index(&self, r: ops::RangeToInclusive<usize>) -> &BStr {
331        BStr::new(&self.as_bytes()[..=r.end])
332    }
333}
334
335impl AsRef<[u8]> for BStr {
336    #[inline]
337    fn as_ref(&self) -> &[u8] {
338        self.as_bytes()
339    }
340}
341
342impl AsRef<BStr> for [u8] {
343    #[inline]
344    fn as_ref(&self) -> &BStr {
345        BStr::new(self)
346    }
347}
348
349impl AsRef<BStr> for str {
350    #[inline]
351    fn as_ref(&self) -> &BStr {
352        BStr::new(self)
353    }
354}
355
356#[cfg(feature = "alloc")]
357impl alloc::borrow::ToOwned for BStr {
358    type Owned = alloc::vec::Vec<u8>;
359
360    #[inline]
361    fn to_owned(&self) -> Self::Owned {
362        alloc::vec::Vec::from(self.as_bytes())
363    }
364}
365
366#[cfg(feature = "alloc")]
367impl core::borrow::Borrow<BStr> for alloc::vec::Vec<u8> {
368    #[inline]
369    fn borrow(&self) -> &BStr {
370        BStr::from_bytes(self.as_slice())
371    }
372}
373
374impl<'a> Default for &'a BStr {
375    fn default() -> &'a BStr {
376        BStr::new(b"")
377    }
378}
379
380impl<'a> From<&'a [u8]> for &'a BStr {
381    #[inline]
382    fn from(s: &'a [u8]) -> &'a BStr {
383        BStr::new(s)
384    }
385}
386
387impl<'a> From<&'a BStr> for &'a [u8] {
388    #[inline]
389    fn from(s: &'a BStr) -> &'a [u8] {
390        BStr::as_bytes(s)
391    }
392}
393
394impl<'a> From<&'a str> for &'a BStr {
395    #[inline]
396    fn from(s: &'a str) -> &'a BStr {
397        BStr::new(s.as_bytes())
398    }
399}
400
401impl Eq for BStr {}
402
403impl PartialEq<BStr> for BStr {
404    #[inline]
405    fn eq(&self, other: &BStr) -> bool {
406        self.as_bytes() == other.as_bytes()
407    }
408}
409
410impl_partial_eq!(BStr, [u8]);
411impl_partial_eq!(BStr, &'a [u8]);
412impl_partial_eq!(BStr, str);
413impl_partial_eq!(BStr, &'a str);
414
415impl PartialOrd for BStr {
416    #[inline]
417    fn partial_cmp(&self, other: &BStr) -> Option<Ordering> {
418        Some(self.cmp(other))
419    }
420}
421
422impl Ord for BStr {
423    #[inline]
424    fn cmp(&self, other: &BStr) -> Ordering {
425        Ord::cmp(self.as_bytes(), other.as_bytes())
426    }
427}
428
429impl_partial_ord!(BStr, [u8]);
430impl_partial_ord!(BStr, &'a [u8]);
431impl_partial_ord!(BStr, str);
432impl_partial_ord!(BStr, &'a str);
433
434#[cfg(test)]
435mod test {
436    use crate::stream::BStr;
437
438    #[test]
439    fn partial_eq_bstr_byte_slice() {
440        let input = b"foo".as_slice();
441        let actual = BStr::new(input);
442        assert!(actual == input);
443    }
444
445    #[test]
446    fn partial_eq_byte_slice_bstr() {
447        let input = b"foo".as_slice();
448        let actual = BStr::new(input);
449        assert!(input == actual);
450    }
451
452    #[test]
453    fn partial_eq_bstr_str() {
454        let input = "foo";
455        let actual = BStr::new(input);
456        assert!(actual == input);
457    }
458
459    #[test]
460    fn partial_eq_str_bstr() {
461        let input = "foo";
462        let actual = BStr::new(input);
463        assert!(input == actual);
464    }
465
466    #[test]
467    fn partial_ord_bstr_byte_slice() {
468        let input = b"foo".as_slice();
469        let actual = BStr::new(input);
470        assert!(actual.partial_cmp(input) == Some(core::cmp::Ordering::Equal));
471    }
472
473    #[test]
474    fn partial_ord_byte_slice_bstr() {
475        let input = b"foo".as_slice();
476        let actual = BStr::new(input);
477        assert!(input.partial_cmp(actual) == Some(core::cmp::Ordering::Equal));
478    }
479
480    #[test]
481    fn partial_ord_bstr_str() {
482        let input = "foo";
483        let actual = BStr::new(input);
484        assert!(actual.partial_cmp(input) == Some(core::cmp::Ordering::Equal));
485    }
486
487    #[test]
488    fn partial_ord_str_bstr() {
489        let input = "foo";
490        let actual = BStr::new(input);
491        assert!(input.partial_cmp(actual) == Some(core::cmp::Ordering::Equal));
492    }
493}
494
495#[cfg(all(test, feature = "std"))]
496mod display {
497    use crate::stream::BStr;
498
499    #[test]
500    fn clean() {
501        assert_eq!(&format!("{}", BStr::new(b"abc")), "abc");
502        assert_eq!(&format!("{}", BStr::new(b"\xf0\x28\x8c\xbc")), "�(��");
503    }
504}
505
506#[cfg(all(test, feature = "std"))]
507mod debug {
508    use crate::stream::BStr;
509    use crate::stream::Stream as _;
510    use snapbox::assert_data_eq;
511    use snapbox::str;
512
513    #[test]
514    fn test_debug() {
515        let input = BStr::new(b"abc");
516        let expected = str![[r#""abc""#]];
517        assert_data_eq!(&format!("{input:?}"), expected);
518
519        let input = BStr::new(b"\0\0\0 ftypisom\0\0\x02\0isomiso2avc1mp");
520        let expected = str![[r#""/0/0/0 ftypisom/0/0/u{2}/0isomiso2avc1mp""#]];
521        assert_data_eq!(&format!("{input:?}"), expected);
522    }
523
524    #[test]
525    fn test_pretty_debug() {
526        let input = BStr::new(b"abc");
527        let expected = str!["abc"];
528        assert_data_eq!(&format!("{input:#?}"), expected);
529    }
530
531    #[test]
532    fn test_trace() {
533        let input = BStr::new(b"abc");
534        let expected = str!["abc"];
535        assert_data_eq!(
536            crate::util::from_fn(|f| input.trace(f)).to_string(),
537            expected
538        );
539    }
540}