feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+540
View File
@@ -0,0 +1,540 @@
use core::num::NonZeroUsize;
use crate::stream::AsBStr;
use crate::stream::Checkpoint;
use crate::stream::Compare;
use crate::stream::CompareResult;
use crate::stream::FindSlice;
use crate::stream::Needed;
use crate::stream::Offset;
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
use crate::stream::Recover;
use crate::stream::SliceLen;
use crate::stream::Stream;
use crate::stream::StreamIsPartial;
use crate::stream::UpdateSlice;
use core::iter::{Cloned, Enumerate};
use core::slice::Iter;
use core::{cmp::Ordering, fmt, ops};
/// Improved `Debug` experience for `&[u8]` UTF-8-ish streams
#[allow(clippy::derived_hash_with_manual_eq)]
#[derive(Hash)]
#[repr(transparent)]
pub struct BStr([u8]);
impl BStr {
/// Make a stream out of a byte slice-like.
#[inline]
pub fn new<B: ?Sized + AsRef<[u8]>>(bytes: &B) -> &Self {
Self::from_bytes(bytes.as_ref())
}
#[inline]
fn from_bytes(slice: &[u8]) -> &Self {
unsafe { core::mem::transmute(slice) }
}
#[inline]
fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl SliceLen for &BStr {
#[inline(always)]
fn slice_len(&self) -> usize {
self.len()
}
}
impl<'i> Stream for &'i BStr {
type Token = u8;
type Slice = &'i [u8];
type IterOffsets = Enumerate<Cloned<Iter<'i, u8>>>;
type Checkpoint = Checkpoint<Self, Self>;
#[inline(always)]
fn iter_offsets(&self) -> Self::IterOffsets {
self.iter().cloned().enumerate()
}
#[inline(always)]
fn eof_offset(&self) -> usize {
self.len()
}
#[inline(always)]
fn next_token(&mut self) -> Option<Self::Token> {
if self.is_empty() {
None
} else {
let token = self[0];
*self = &self[1..];
Some(token)
}
}
#[inline(always)]
fn peek_token(&self) -> Option<Self::Token> {
if self.is_empty() {
None
} else {
Some(self[0])
}
}
#[inline(always)]
fn offset_for<P>(&self, predicate: P) -> Option<usize>
where
P: Fn(Self::Token) -> bool,
{
self.iter().position(|b| predicate(*b))
}
#[inline(always)]
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
if let Some(needed) = tokens.checked_sub(self.len()).and_then(NonZeroUsize::new) {
Err(Needed::Size(needed))
} else {
Ok(tokens)
}
}
#[inline(always)]
fn next_slice(&mut self, offset: usize) -> Self::Slice {
let (slice, next) = self.0.split_at(offset);
*self = BStr::from_bytes(next);
slice
}
#[inline(always)]
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
#[cfg(debug_assertions)]
self.peek_slice(offset);
// SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
let slice = unsafe { self.0.get_unchecked(..offset) };
// SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
let next = unsafe { self.0.get_unchecked(offset..) };
*self = BStr::from_bytes(next);
slice
}
#[inline(always)]
fn peek_slice(&self, offset: usize) -> Self::Slice {
&self[..offset]
}
#[inline(always)]
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
#[cfg(debug_assertions)]
self.peek_slice(offset);
// SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
let slice = unsafe { self.0.get_unchecked(..offset) };
slice
}
#[inline(always)]
fn checkpoint(&self) -> Self::Checkpoint {
Checkpoint::<_, Self>::new(*self)
}
#[inline(always)]
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
*self = checkpoint.inner;
}
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:#?}")
}
}
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
impl<E> Recover<E> for &BStr {
#[inline(always)]
fn record_err(
&mut self,
_token_start: &Self::Checkpoint,
_err_start: &Self::Checkpoint,
err: E,
) -> Result<(), E> {
Err(err)
}
/// Report whether the [`Stream`] can save off errors for recovery
#[inline(always)]
fn is_recovery_supported() -> bool {
false
}
}
impl StreamIsPartial for &BStr {
type PartialState = ();
#[inline]
fn complete(&mut self) -> Self::PartialState {
// Already complete
}
#[inline]
fn restore_partial(&mut self, _state: Self::PartialState) {}
#[inline(always)]
fn is_partial_supported() -> bool {
false
}
}
impl Offset for &BStr {
#[inline(always)]
fn offset_from(&self, start: &Self) -> usize {
self.as_bytes().offset_from(&start.as_bytes())
}
}
impl<'a> Offset<<&'a BStr as Stream>::Checkpoint> for &'a BStr {
#[inline(always)]
fn offset_from(&self, other: &<&'a BStr as Stream>::Checkpoint) -> usize {
self.checkpoint().offset_from(other)
}
}
impl AsBStr for &BStr {
#[inline(always)]
fn as_bstr(&self) -> &[u8] {
(*self).as_bytes()
}
}
impl<'a, T> Compare<T> for &'a BStr
where
&'a [u8]: Compare<T>,
{
#[inline(always)]
fn compare(&self, t: T) -> CompareResult {
let bytes = (*self).as_bytes();
bytes.compare(t)
}
}
impl<'i, S> FindSlice<S> for &'i BStr
where
&'i [u8]: FindSlice<S>,
{
#[inline(always)]
fn find_slice(&self, substr: S) -> Option<core::ops::Range<usize>> {
let bytes = (*self).as_bytes();
let offset = bytes.find_slice(substr);
offset
}
}
impl UpdateSlice for &BStr {
#[inline(always)]
fn update_slice(self, inner: Self::Slice) -> Self {
BStr::new(inner)
}
}
#[cfg(feature = "alloc")]
impl fmt::Display for BStr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
alloc::string::String::from_utf8_lossy(self.as_bytes()).fmt(f)
}
}
impl fmt::Debug for BStr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !f.alternate() {
write!(f, "\"")?;
}
for byte in self.as_bytes() {
let c = *byte as char;
write!(f, "{}", c.escape_debug())?;
}
if !f.alternate() {
write!(f, "\"")?;
}
Ok(())
}
}
impl ops::Deref for BStr {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
self.as_bytes()
}
}
impl ops::Index<usize> for BStr {
type Output = u8;
#[inline]
fn index(&self, idx: usize) -> &u8 {
&self.as_bytes()[idx]
}
}
impl ops::Index<ops::RangeFull> for BStr {
type Output = BStr;
#[inline]
fn index(&self, _: ops::RangeFull) -> &BStr {
self
}
}
impl ops::Index<ops::Range<usize>> for BStr {
type Output = BStr;
#[inline]
fn index(&self, r: ops::Range<usize>) -> &BStr {
BStr::new(&self.as_bytes()[r.start..r.end])
}
}
impl ops::Index<ops::RangeInclusive<usize>> for BStr {
type Output = BStr;
#[inline]
fn index(&self, r: ops::RangeInclusive<usize>) -> &BStr {
BStr::new(&self.as_bytes()[*r.start()..=*r.end()])
}
}
impl ops::Index<ops::RangeFrom<usize>> for BStr {
type Output = BStr;
#[inline]
fn index(&self, r: ops::RangeFrom<usize>) -> &BStr {
BStr::new(&self.as_bytes()[r.start..])
}
}
impl ops::Index<ops::RangeTo<usize>> for BStr {
type Output = BStr;
#[inline]
fn index(&self, r: ops::RangeTo<usize>) -> &BStr {
BStr::new(&self.as_bytes()[..r.end])
}
}
impl ops::Index<ops::RangeToInclusive<usize>> for BStr {
type Output = BStr;
#[inline]
fn index(&self, r: ops::RangeToInclusive<usize>) -> &BStr {
BStr::new(&self.as_bytes()[..=r.end])
}
}
impl AsRef<[u8]> for BStr {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl AsRef<BStr> for [u8] {
#[inline]
fn as_ref(&self) -> &BStr {
BStr::new(self)
}
}
impl AsRef<BStr> for str {
#[inline]
fn as_ref(&self) -> &BStr {
BStr::new(self)
}
}
#[cfg(feature = "alloc")]
impl alloc::borrow::ToOwned for BStr {
type Owned = alloc::vec::Vec<u8>;
#[inline]
fn to_owned(&self) -> Self::Owned {
alloc::vec::Vec::from(self.as_bytes())
}
}
#[cfg(feature = "alloc")]
impl core::borrow::Borrow<BStr> for alloc::vec::Vec<u8> {
#[inline]
fn borrow(&self) -> &BStr {
BStr::from_bytes(self.as_slice())
}
}
impl<'a> Default for &'a BStr {
fn default() -> &'a BStr {
BStr::new(b"")
}
}
impl<'a> From<&'a [u8]> for &'a BStr {
#[inline]
fn from(s: &'a [u8]) -> &'a BStr {
BStr::new(s)
}
}
impl<'a> From<&'a BStr> for &'a [u8] {
#[inline]
fn from(s: &'a BStr) -> &'a [u8] {
BStr::as_bytes(s)
}
}
impl<'a> From<&'a str> for &'a BStr {
#[inline]
fn from(s: &'a str) -> &'a BStr {
BStr::new(s.as_bytes())
}
}
impl Eq for BStr {}
impl PartialEq<BStr> for BStr {
#[inline]
fn eq(&self, other: &BStr) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl_partial_eq!(BStr, [u8]);
impl_partial_eq!(BStr, &'a [u8]);
impl_partial_eq!(BStr, str);
impl_partial_eq!(BStr, &'a str);
impl PartialOrd for BStr {
#[inline]
fn partial_cmp(&self, other: &BStr) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for BStr {
#[inline]
fn cmp(&self, other: &BStr) -> Ordering {
Ord::cmp(self.as_bytes(), other.as_bytes())
}
}
impl_partial_ord!(BStr, [u8]);
impl_partial_ord!(BStr, &'a [u8]);
impl_partial_ord!(BStr, str);
impl_partial_ord!(BStr, &'a str);
#[cfg(test)]
mod test {
use crate::stream::BStr;
#[test]
fn partial_eq_bstr_byte_slice() {
let input = b"foo".as_slice();
let actual = BStr::new(input);
assert!(actual == input);
}
#[test]
fn partial_eq_byte_slice_bstr() {
let input = b"foo".as_slice();
let actual = BStr::new(input);
assert!(input == actual);
}
#[test]
fn partial_eq_bstr_str() {
let input = "foo";
let actual = BStr::new(input);
assert!(actual == input);
}
#[test]
fn partial_eq_str_bstr() {
let input = "foo";
let actual = BStr::new(input);
assert!(input == actual);
}
#[test]
fn partial_ord_bstr_byte_slice() {
let input = b"foo".as_slice();
let actual = BStr::new(input);
assert!(actual.partial_cmp(input) == Some(core::cmp::Ordering::Equal));
}
#[test]
fn partial_ord_byte_slice_bstr() {
let input = b"foo".as_slice();
let actual = BStr::new(input);
assert!(input.partial_cmp(actual) == Some(core::cmp::Ordering::Equal));
}
#[test]
fn partial_ord_bstr_str() {
let input = "foo";
let actual = BStr::new(input);
assert!(actual.partial_cmp(input) == Some(core::cmp::Ordering::Equal));
}
#[test]
fn partial_ord_str_bstr() {
let input = "foo";
let actual = BStr::new(input);
assert!(input.partial_cmp(actual) == Some(core::cmp::Ordering::Equal));
}
}
#[cfg(all(test, feature = "std"))]
mod display {
use crate::stream::BStr;
#[test]
fn clean() {
assert_eq!(&format!("{}", BStr::new(b"abc")), "abc");
assert_eq!(&format!("{}", BStr::new(b"\xf0\x28\x8c\xbc")), "�(��");
}
}
#[cfg(all(test, feature = "std"))]
mod debug {
use crate::stream::BStr;
use crate::stream::Stream as _;
use snapbox::assert_data_eq;
use snapbox::str;
#[test]
fn test_debug() {
let input = BStr::new(b"abc");
let expected = str![[r#""abc""#]];
assert_data_eq!(&format!("{input:?}"), expected);
let input = BStr::new(b"\0\0\0 ftypisom\0\0\x02\0isomiso2avc1mp");
let expected = str![[r#""/0/0/0 ftypisom/0/0/u{2}/0isomiso2avc1mp""#]];
assert_data_eq!(&format!("{input:?}"), expected);
}
#[test]
fn test_pretty_debug() {
let input = BStr::new(b"abc");
let expected = str!["abc"];
assert_data_eq!(&format!("{input:#?}"), expected);
}
#[test]
fn test_trace() {
let input = BStr::new(b"abc");
let expected = str!["abc"];
assert_data_eq!(
crate::util::from_fn(|f| input.trace(f)).to_string(),
expected
);
}
}
+562
View File
@@ -0,0 +1,562 @@
use core::num::NonZeroUsize;
use crate::stream::AsBytes;
use crate::stream::Checkpoint;
use crate::stream::Compare;
use crate::stream::CompareResult;
use crate::stream::FindSlice;
use crate::stream::Needed;
use crate::stream::Offset;
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
use crate::stream::Recover;
use crate::stream::SliceLen;
use crate::stream::Stream;
use crate::stream::StreamIsPartial;
use crate::stream::UpdateSlice;
use core::iter::{Cloned, Enumerate};
use core::slice::Iter;
use core::{cmp::Ordering, fmt, ops};
/// Improved `Debug` experience for `&[u8]` byte streams
#[allow(clippy::derived_hash_with_manual_eq)]
#[derive(Hash)]
#[repr(transparent)]
pub struct Bytes([u8]);
impl Bytes {
/// Make a stream out of a byte slice-like.
#[inline]
pub fn new<B: ?Sized + AsRef<[u8]>>(bytes: &B) -> &Self {
Self::from_bytes(bytes.as_ref())
}
#[inline]
fn from_bytes(slice: &[u8]) -> &Self {
unsafe { core::mem::transmute(slice) }
}
#[inline]
fn as_bytes(&self) -> &[u8] {
&self.0
}
}
impl SliceLen for &Bytes {
#[inline(always)]
fn slice_len(&self) -> usize {
self.len()
}
}
impl<'i> Stream for &'i Bytes {
type Token = u8;
type Slice = &'i [u8];
type IterOffsets = Enumerate<Cloned<Iter<'i, u8>>>;
type Checkpoint = Checkpoint<Self, Self>;
#[inline(always)]
fn iter_offsets(&self) -> Self::IterOffsets {
self.iter().cloned().enumerate()
}
#[inline(always)]
fn eof_offset(&self) -> usize {
self.len()
}
#[inline(always)]
fn next_token(&mut self) -> Option<Self::Token> {
if self.is_empty() {
None
} else {
let token = self[0];
*self = &self[1..];
Some(token)
}
}
#[inline(always)]
fn peek_token(&self) -> Option<Self::Token> {
if self.is_empty() {
None
} else {
Some(self[0])
}
}
#[inline(always)]
fn offset_for<P>(&self, predicate: P) -> Option<usize>
where
P: Fn(Self::Token) -> bool,
{
self.iter().position(|b| predicate(*b))
}
#[inline(always)]
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
if let Some(needed) = tokens.checked_sub(self.len()).and_then(NonZeroUsize::new) {
Err(Needed::Size(needed))
} else {
Ok(tokens)
}
}
#[inline(always)]
fn next_slice(&mut self, offset: usize) -> Self::Slice {
let (slice, next) = self.0.split_at(offset);
*self = Bytes::from_bytes(next);
slice
}
#[inline(always)]
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
#[cfg(debug_assertions)]
self.peek_slice(offset);
// SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
let slice = unsafe { self.0.get_unchecked(..offset) };
// SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
let next = unsafe { self.0.get_unchecked(offset..) };
*self = Bytes::from_bytes(next);
slice
}
#[inline(always)]
fn peek_slice(&self, offset: usize) -> Self::Slice {
&self[..offset]
}
#[inline(always)]
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
#[cfg(debug_assertions)]
self.peek_slice(offset);
// SAFETY: `Stream::next_slice_unchecked` requires `offset` to be in bounds
let slice = unsafe { self.0.get_unchecked(..offset) };
slice
}
#[inline(always)]
fn checkpoint(&self) -> Self::Checkpoint {
Checkpoint::<_, Self>::new(*self)
}
#[inline(always)]
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
*self = checkpoint.inner;
}
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:#?}")
}
}
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
impl<E> Recover<E> for &Bytes {
#[inline(always)]
fn record_err(
&mut self,
_token_start: &Self::Checkpoint,
_err_start: &Self::Checkpoint,
err: E,
) -> Result<(), E> {
Err(err)
}
/// Report whether the [`Stream`] can save off errors for recovery
#[inline(always)]
fn is_recovery_supported() -> bool {
false
}
}
impl StreamIsPartial for &Bytes {
type PartialState = ();
#[inline]
fn complete(&mut self) -> Self::PartialState {
// Already complete
}
#[inline]
fn restore_partial(&mut self, _state: Self::PartialState) {}
#[inline(always)]
fn is_partial_supported() -> bool {
false
}
}
impl Offset for &Bytes {
#[inline(always)]
fn offset_from(&self, start: &Self) -> usize {
self.as_bytes().offset_from(&start.as_bytes())
}
}
impl<'a> Offset<<&'a Bytes as Stream>::Checkpoint> for &'a Bytes {
#[inline(always)]
fn offset_from(&self, other: &<&'a Bytes as Stream>::Checkpoint) -> usize {
self.checkpoint().offset_from(other)
}
}
impl AsBytes for &Bytes {
#[inline(always)]
fn as_bytes(&self) -> &[u8] {
(*self).as_bytes()
}
}
impl<'a, T> Compare<T> for &'a Bytes
where
&'a [u8]: Compare<T>,
{
#[inline(always)]
fn compare(&self, t: T) -> CompareResult {
let bytes = (*self).as_bytes();
bytes.compare(t)
}
}
impl<'i, S> FindSlice<S> for &'i Bytes
where
&'i [u8]: FindSlice<S>,
{
#[inline(always)]
fn find_slice(&self, substr: S) -> Option<core::ops::Range<usize>> {
let bytes = (*self).as_bytes();
let offset = bytes.find_slice(substr);
offset
}
}
impl UpdateSlice for &Bytes {
#[inline(always)]
fn update_slice(self, inner: Self::Slice) -> Self {
Bytes::new(inner)
}
}
impl fmt::Display for Bytes {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<Self as fmt::UpperHex>::fmt(self, f)
}
}
impl fmt::Debug for Bytes {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<Self as fmt::UpperHex>::fmt(self, f)
}
}
impl fmt::LowerHex for Bytes {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.as_bytes() {
write!(f, "{byte:0>2x}")?;
}
Ok(())
}
}
impl fmt::UpperHex for Bytes {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, byte) in self.as_bytes().iter().enumerate() {
if 0 < i {
let absolute = (self.as_bytes().as_ptr() as usize) + i;
if f.alternate() && absolute != 0 && absolute % 4 == 0 {
write!(f, "_")?;
}
}
write!(f, "{byte:0>2X}")?;
}
Ok(())
}
}
impl ops::Deref for Bytes {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
self.as_bytes()
}
}
impl ops::Index<usize> for Bytes {
type Output = u8;
#[inline]
fn index(&self, idx: usize) -> &u8 {
&self.as_bytes()[idx]
}
}
impl ops::Index<ops::RangeFull> for Bytes {
type Output = Bytes;
#[inline]
fn index(&self, _: ops::RangeFull) -> &Bytes {
self
}
}
impl ops::Index<ops::Range<usize>> for Bytes {
type Output = Bytes;
#[inline]
fn index(&self, r: ops::Range<usize>) -> &Bytes {
Bytes::new(&self.as_bytes()[r.start..r.end])
}
}
impl ops::Index<ops::RangeInclusive<usize>> for Bytes {
type Output = Bytes;
#[inline]
fn index(&self, r: ops::RangeInclusive<usize>) -> &Bytes {
Bytes::new(&self.as_bytes()[*r.start()..=*r.end()])
}
}
impl ops::Index<ops::RangeFrom<usize>> for Bytes {
type Output = Bytes;
#[inline]
fn index(&self, r: ops::RangeFrom<usize>) -> &Bytes {
Bytes::new(&self.as_bytes()[r.start..])
}
}
impl ops::Index<ops::RangeTo<usize>> for Bytes {
type Output = Bytes;
#[inline]
fn index(&self, r: ops::RangeTo<usize>) -> &Bytes {
Bytes::new(&self.as_bytes()[..r.end])
}
}
impl ops::Index<ops::RangeToInclusive<usize>> for Bytes {
type Output = Bytes;
#[inline]
fn index(&self, r: ops::RangeToInclusive<usize>) -> &Bytes {
Bytes::new(&self.as_bytes()[..=r.end])
}
}
impl AsRef<[u8]> for Bytes {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl AsRef<Bytes> for [u8] {
#[inline]
fn as_ref(&self) -> &Bytes {
Bytes::new(self)
}
}
impl AsRef<Bytes> for str {
#[inline]
fn as_ref(&self) -> &Bytes {
Bytes::new(self)
}
}
#[cfg(feature = "alloc")]
impl alloc::borrow::ToOwned for Bytes {
type Owned = alloc::vec::Vec<u8>;
#[inline]
fn to_owned(&self) -> Self::Owned {
alloc::vec::Vec::from(self.as_bytes())
}
}
#[cfg(feature = "alloc")]
impl core::borrow::Borrow<Bytes> for alloc::vec::Vec<u8> {
#[inline]
fn borrow(&self) -> &Bytes {
Bytes::from_bytes(self.as_slice())
}
}
impl<'a> Default for &'a Bytes {
fn default() -> &'a Bytes {
Bytes::new(b"")
}
}
impl<'a> From<&'a [u8]> for &'a Bytes {
#[inline]
fn from(s: &'a [u8]) -> &'a Bytes {
Bytes::new(s)
}
}
impl<'a> From<&'a Bytes> for &'a [u8] {
#[inline]
fn from(s: &'a Bytes) -> &'a [u8] {
Bytes::as_bytes(s)
}
}
impl<'a> From<&'a str> for &'a Bytes {
#[inline]
fn from(s: &'a str) -> &'a Bytes {
Bytes::new(s.as_bytes())
}
}
impl Eq for Bytes {}
impl PartialEq<Bytes> for Bytes {
#[inline]
fn eq(&self, other: &Bytes) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl_partial_eq!(Bytes, [u8]);
impl_partial_eq!(Bytes, &'a [u8]);
impl_partial_eq!(Bytes, str);
impl_partial_eq!(Bytes, &'a str);
impl PartialOrd for Bytes {
#[inline]
fn partial_cmp(&self, other: &Bytes) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Bytes {
#[inline]
fn cmp(&self, other: &Bytes) -> Ordering {
Ord::cmp(self.as_bytes(), other.as_bytes())
}
}
impl_partial_ord!(Bytes, [u8]);
impl_partial_ord!(Bytes, &'a [u8]);
impl_partial_ord!(Bytes, str);
impl_partial_ord!(Bytes, &'a str);
#[cfg(test)]
mod test {
use crate::stream::Bytes;
#[test]
fn partial_eq_bytes_byte_slice() {
let input = b"foo".as_slice();
let actual = Bytes::new(input);
assert!(actual == input);
}
#[test]
fn partial_eq_byte_slice_bytes() {
let input = b"foo".as_slice();
let actual = Bytes::new(input);
assert!(input == actual);
}
#[test]
fn partial_eq_bytes_str() {
let input = "foo";
let actual = Bytes::new(input);
assert!(actual == input);
}
#[test]
fn partial_eq_str_bytes() {
let input = "foo";
let actual = Bytes::new(input);
assert!(input == actual);
}
#[test]
fn partial_ord_bytes_byte_slice() {
let input = b"foo".as_slice();
let actual = Bytes::new(input);
assert!(actual.partial_cmp(input) == Some(core::cmp::Ordering::Equal));
}
#[test]
fn partial_ord_byte_slice_bytes() {
let input = b"foo".as_slice();
let actual = Bytes::new(input);
assert!(input.partial_cmp(actual) == Some(core::cmp::Ordering::Equal));
}
#[test]
fn partial_ord_bytes_str() {
let input = "foo";
let actual = Bytes::new(input);
assert!(actual.partial_cmp(input) == Some(core::cmp::Ordering::Equal));
}
#[test]
fn partial_ord_str_bytes() {
let input = "foo";
let actual = Bytes::new(input);
assert!(input.partial_cmp(actual) == Some(core::cmp::Ordering::Equal));
}
}
#[cfg(all(test, feature = "std"))]
mod display {
use crate::stream::Bytes;
#[test]
fn clean() {
assert_eq!(&format!("{}", Bytes::new(b"abc")), "616263");
assert_eq!(&format!("{}", Bytes::new(b"\xf0\x28\x8c\xbc")), "F0288CBC");
}
}
#[cfg(all(test, feature = "std"))]
mod debug {
use crate::stream::Bytes;
use crate::stream::Stream as _;
use snapbox::assert_data_eq;
use snapbox::str;
#[test]
fn test_debug() {
let input = Bytes::new(b"\0\0\0 ftypisom\0\0\x02\0isomiso2avc1mp");
let expected = str!["000000206674797069736F6D0000020069736F6D69736F32617663316D70"];
assert_data_eq!(&format!("{input:?}"), expected);
}
#[test]
fn test_pretty_debug() {
let input = Bytes::new(b"\0\0\0 ftypisom\0\0\x02\0isomiso2avc1mp");
let expected = str!["000000206674797069736F6D0000020069736F6D69736F32617663316D70"];
assert_data_eq!(&format!("{input:#?}").replace('_', ""), expected);
}
#[test]
fn test_trace() {
let input = Bytes::new(b"\0\0\0 ftypisom\0\0\x02\0isomiso2avc1mp");
let expected = str!["000000206674797069736F6D0000020069736F6D69736F32617663316D70"];
assert_data_eq!(
crate::util::from_fn(|f| input.trace(f))
.to_string()
.replace('_', ""),
expected
);
}
#[test]
fn test_sliced() {
// Output can change from run-to-run
let total = Bytes::new(b"12345678901234567890");
let _ = format!("{total:#?}");
let _ = format!("{:#?}", &total[1..]);
let _ = format!("{:#?}", &total[10..]);
}
}
+320
View File
@@ -0,0 +1,320 @@
use crate::stream::AsBStr;
use crate::stream::AsBytes;
use crate::stream::Checkpoint;
use crate::stream::Compare;
use crate::stream::CompareResult;
use crate::stream::FindSlice;
use crate::stream::Location;
use crate::stream::Needed;
use crate::stream::Offset;
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
use crate::stream::Recover;
use crate::stream::SliceLen;
use crate::stream::Stream;
use crate::stream::StreamIsPartial;
use crate::stream::UpdateSlice;
/// Allow collecting the span of a parsed token within a slice
///
/// Converting byte offsets to line or column numbers is left up to the user, as computing column
/// numbers requires domain knowledge (are columns byte-based, codepoint-based, or grapheme-based?)
/// and O(n) iteration over the input to determine codepoint and line boundaries.
///
/// [The `line-span` crate](https://docs.rs/line-span/latest/line_span/) can help with converting
/// byte offsets to line numbers.
///
/// See [`Parser::span`][crate::Parser::span] and [`Parser::with_span`][crate::Parser::with_span] for more details
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
#[doc(alias = "LocatingSliceSpan")]
#[doc(alias = "Located")]
pub struct LocatingSlice<I> {
initial: I,
input: I,
}
impl<I> LocatingSlice<I>
where
I: Clone + Offset,
{
/// Wrap another Stream with span tracking
pub fn new(input: I) -> Self {
let initial = input.clone();
Self { initial, input }
}
#[inline]
fn previous_token_end(&self) -> usize {
// Assumptions:
// - Index offsets is sufficient
// - Tokens are continuous
self.input.offset_from(&self.initial)
}
#[inline]
fn current_token_start(&self) -> usize {
// Assumptions:
// - Index offsets is sufficient
self.input.offset_from(&self.initial)
}
}
impl<I> LocatingSlice<I>
where
I: Clone + Stream + Offset,
{
/// Reset the stream to the start
///
/// This is useful for formats that encode a graph with addresses relative to the start of the
/// input.
#[doc(alias = "fseek")]
#[inline]
pub fn reset_to_start(&mut self) {
let start = self.initial.checkpoint();
self.input.reset(&start);
}
}
impl<I> AsRef<I> for LocatingSlice<I> {
#[inline(always)]
fn as_ref(&self) -> &I {
&self.input
}
}
impl<I: core::fmt::Debug> core::fmt::Debug for LocatingSlice<I> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.input.fmt(f)
}
}
impl<I> core::ops::Deref for LocatingSlice<I> {
type Target = I;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.input
}
}
impl<I: core::fmt::Display> core::fmt::Display for LocatingSlice<I> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.input.fmt(f)
}
}
impl<I> SliceLen for LocatingSlice<I>
where
I: SliceLen,
{
#[inline(always)]
fn slice_len(&self) -> usize {
self.input.slice_len()
}
}
impl<I: Stream> Stream for LocatingSlice<I> {
type Token = <I as Stream>::Token;
type Slice = <I as Stream>::Slice;
type IterOffsets = <I as Stream>::IterOffsets;
type Checkpoint = Checkpoint<I::Checkpoint, Self>;
#[inline(always)]
fn iter_offsets(&self) -> Self::IterOffsets {
self.input.iter_offsets()
}
#[inline(always)]
fn eof_offset(&self) -> usize {
self.input.eof_offset()
}
#[inline(always)]
fn next_token(&mut self) -> Option<Self::Token> {
self.input.next_token()
}
#[inline(always)]
fn peek_token(&self) -> Option<Self::Token> {
self.input.peek_token()
}
#[inline(always)]
fn offset_for<P>(&self, predicate: P) -> Option<usize>
where
P: Fn(Self::Token) -> bool,
{
self.input.offset_for(predicate)
}
#[inline(always)]
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
self.input.offset_at(tokens)
}
#[inline(always)]
fn next_slice(&mut self, offset: usize) -> Self::Slice {
self.input.next_slice(offset)
}
#[inline(always)]
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
// SAFETY: Passing up invariants
unsafe { self.input.next_slice_unchecked(offset) }
}
#[inline(always)]
fn peek_slice(&self, offset: usize) -> Self::Slice {
self.input.peek_slice(offset)
}
#[inline(always)]
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
// SAFETY: Passing up invariants
unsafe { self.input.peek_slice_unchecked(offset) }
}
#[inline(always)]
fn checkpoint(&self) -> Self::Checkpoint {
Checkpoint::<_, Self>::new(self.input.checkpoint())
}
#[inline(always)]
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
self.input.reset(&checkpoint.inner);
}
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.input.trace(f)
}
}
impl<I> Location for LocatingSlice<I>
where
I: Clone + Offset,
{
#[inline(always)]
fn previous_token_end(&self) -> usize {
self.previous_token_end()
}
#[inline(always)]
fn current_token_start(&self) -> usize {
self.current_token_start()
}
}
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
impl<I, E> Recover<E> for LocatingSlice<I>
where
I: Recover<E>,
I: Stream,
{
#[inline(always)]
fn record_err(
&mut self,
_token_start: &Self::Checkpoint,
_err_start: &Self::Checkpoint,
err: E,
) -> Result<(), E> {
Err(err)
}
/// Report whether the [`Stream`] can save off errors for recovery
#[inline(always)]
fn is_recovery_supported() -> bool {
false
}
}
impl<I> StreamIsPartial for LocatingSlice<I>
where
I: StreamIsPartial,
{
type PartialState = I::PartialState;
#[inline]
fn complete(&mut self) -> Self::PartialState {
self.input.complete()
}
#[inline]
fn restore_partial(&mut self, state: Self::PartialState) {
self.input.restore_partial(state);
}
#[inline(always)]
fn is_partial_supported() -> bool {
I::is_partial_supported()
}
#[inline(always)]
fn is_partial(&self) -> bool {
self.input.is_partial()
}
}
impl<I> Offset for LocatingSlice<I>
where
I: Stream,
{
#[inline(always)]
fn offset_from(&self, other: &Self) -> usize {
self.offset_from(&other.checkpoint())
}
}
impl<I> Offset<<LocatingSlice<I> as Stream>::Checkpoint> for LocatingSlice<I>
where
I: Stream,
{
#[inline(always)]
fn offset_from(&self, other: &<LocatingSlice<I> as Stream>::Checkpoint) -> usize {
self.checkpoint().offset_from(other)
}
}
impl<I> AsBytes for LocatingSlice<I>
where
I: AsBytes,
{
#[inline(always)]
fn as_bytes(&self) -> &[u8] {
self.input.as_bytes()
}
}
impl<I> AsBStr for LocatingSlice<I>
where
I: AsBStr,
{
#[inline(always)]
fn as_bstr(&self) -> &[u8] {
self.input.as_bstr()
}
}
impl<I, U> Compare<U> for LocatingSlice<I>
where
I: Compare<U>,
{
#[inline(always)]
fn compare(&self, other: U) -> CompareResult {
self.input.compare(other)
}
}
impl<I, T> FindSlice<T> for LocatingSlice<I>
where
I: FindSlice<T>,
{
#[inline(always)]
fn find_slice(&self, substr: T) -> Option<core::ops::Range<usize>> {
self.input.find_slice(substr)
}
}
impl<I> UpdateSlice for LocatingSlice<I>
where
I: UpdateSlice,
{
#[inline(always)]
fn update_slice(mut self, inner: Self::Slice) -> Self {
self.input = I::update_slice(self.input, inner);
self
}
}
+2011
View File
File diff suppressed because it is too large. Load diff
+355
View File
@@ -0,0 +1,355 @@
use crate::stream::AsBStr;
use crate::stream::AsBytes;
use crate::stream::Checkpoint;
use crate::stream::Compare;
use crate::stream::CompareResult;
use crate::stream::FindSlice;
use crate::stream::Location;
use crate::stream::Needed;
use crate::stream::Offset;
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
use crate::stream::Recover;
use crate::stream::SliceLen;
use crate::stream::Stream;
use crate::stream::StreamIsPartial;
use crate::stream::UpdateSlice;
/// Mark the input as a partial buffer for streaming input.
///
/// Complete input means that we already have all of the data. This will be the common case with
/// small files that can be read entirely to memory.
///
/// In contrast, streaming input assumes that we might not have all of the data.
/// This can happen with some network protocol or large file parsers, where the
/// input buffer can be full and need to be resized or refilled.
/// - [`ErrMode::Incomplete`][crate::error::ErrMode::Incomplete] will report how much more data is needed.
/// - [`Parser::complete_err`][crate::Parser::complete_err] transform
/// [`ErrMode::Incomplete`][crate::error::ErrMode::Incomplete] to
/// [`ErrMode::Backtrack`][crate::error::ErrMode::Backtrack]
///
/// See also [`StreamIsPartial`] to tell whether the input supports complete or partial parsing.
///
/// See also [Special Topics: Parsing Partial Input][crate::_topic::partial].
///
/// # Example
///
/// Here is how it works in practice:
///
/// ```rust
/// # #[cfg(feature = "ascii")] {
/// # use winnow::{Result, error::ErrMode, error::Needed, error::ContextError, token, ascii, stream::Partial};
/// # use winnow::prelude::*;
///
/// fn take_partial<'s>(i: &mut Partial<&'s [u8]>) -> ModalResult<&'s [u8], ContextError> {
/// token::take(4u8).parse_next(i)
/// }
///
/// fn take_complete<'s>(i: &mut &'s [u8]) -> ModalResult<&'s [u8], ContextError> {
/// token::take(4u8).parse_next(i)
/// }
///
/// // both parsers will take 4 bytes as expected
/// assert_eq!(take_partial.parse_peek(Partial::new(&b"abcde"[..])), Ok((Partial::new(&b"e"[..]), &b"abcd"[..])));
/// assert_eq!(take_complete.parse_peek(&b"abcde"[..]), Ok((&b"e"[..], &b"abcd"[..])));
///
/// // if the input is smaller than 4 bytes, the partial parser
/// // will return `Incomplete` to indicate that we need more data
/// assert_eq!(take_partial.parse_peek(Partial::new(&b"abc"[..])), Err(ErrMode::Incomplete(Needed::new(1))));
///
/// // but the complete parser will return an error
/// assert!(take_complete.parse_peek(&b"abc"[..]).is_err());
///
/// // the alpha0 function takes 0 or more alphabetic characters
/// fn alpha0_partial<'s>(i: &mut Partial<&'s str>) -> ModalResult<&'s str, ContextError> {
/// ascii::alpha0.parse_next(i)
/// }
///
/// fn alpha0_complete<'s>(i: &mut &'s str) -> ModalResult<&'s str, ContextError> {
/// ascii::alpha0.parse_next(i)
/// }
///
/// // if there's a clear limit to the taken characters, both parsers work the same way
/// assert_eq!(alpha0_partial.parse_peek(Partial::new("abcd;")), Ok((Partial::new(";"), "abcd")));
/// assert_eq!(alpha0_complete.parse_peek("abcd;"), Ok((";", "abcd")));
///
/// // but when there's no limit, the partial version returns `Incomplete`, because it cannot
/// // know if more input data should be taken. The whole input could be "abcd;", or
/// // "abcde;"
/// assert_eq!(alpha0_partial.parse_peek(Partial::new("abcd")), Err(ErrMode::Incomplete(Needed::new(1))));
///
/// // while the complete version knows that all of the data is there
/// assert_eq!(alpha0_complete.parse_peek("abcd"), Ok(("", "abcd")));
/// # }
/// ```
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Partial<I> {
input: I,
partial: bool,
}
impl<I> Partial<I>
where
I: StreamIsPartial,
{
/// Create a partial input
#[inline]
pub fn new(input: I) -> Self {
debug_assert!(
!I::is_partial_supported(),
"`Partial` can only wrap complete sources"
);
let partial = true;
Self { input, partial }
}
/// Extract the original [`Stream`]
#[inline(always)]
pub fn into_inner(self) -> I {
self.input
}
}
impl<I> Default for Partial<I>
where
I: Default + StreamIsPartial,
{
#[inline]
fn default() -> Self {
Self::new(I::default())
}
}
impl<I> core::ops::Deref for Partial<I> {
type Target = I;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.input
}
}
impl<I: core::fmt::Display> core::fmt::Display for Partial<I> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.input.fmt(f)
}
}
impl<I> SliceLen for Partial<I>
where
I: SliceLen,
{
#[inline(always)]
fn slice_len(&self) -> usize {
self.input.slice_len()
}
}
impl<I: Stream> Stream for Partial<I> {
type Token = <I as Stream>::Token;
type Slice = <I as Stream>::Slice;
type IterOffsets = <I as Stream>::IterOffsets;
type Checkpoint = Checkpoint<I::Checkpoint, Self>;
#[inline(always)]
fn iter_offsets(&self) -> Self::IterOffsets {
self.input.iter_offsets()
}
#[inline(always)]
fn eof_offset(&self) -> usize {
self.input.eof_offset()
}
#[inline(always)]
fn next_token(&mut self) -> Option<Self::Token> {
self.input.next_token()
}
#[inline(always)]
fn peek_token(&self) -> Option<Self::Token> {
self.input.peek_token()
}
#[inline(always)]
fn offset_for<P>(&self, predicate: P) -> Option<usize>
where
P: Fn(Self::Token) -> bool,
{
self.input.offset_for(predicate)
}
#[inline(always)]
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
self.input.offset_at(tokens)
}
#[inline(always)]
fn next_slice(&mut self, offset: usize) -> Self::Slice {
self.input.next_slice(offset)
}
#[inline(always)]
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
// SAFETY: Passing up invariants
unsafe { self.input.next_slice_unchecked(offset) }
}
#[inline(always)]
fn peek_slice(&self, offset: usize) -> Self::Slice {
self.input.peek_slice(offset)
}
#[inline(always)]
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
// SAFETY: Passing up invariants
unsafe { self.input.peek_slice_unchecked(offset) }
}
#[inline(always)]
fn checkpoint(&self) -> Self::Checkpoint {
Checkpoint::<_, Self>::new(self.input.checkpoint())
}
#[inline(always)]
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
self.input.reset(&checkpoint.inner);
}
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.input.trace(f)
}
}
impl<I> Location for Partial<I>
where
I: Location,
{
#[inline(always)]
fn previous_token_end(&self) -> usize {
self.input.previous_token_end()
}
#[inline(always)]
fn current_token_start(&self) -> usize {
self.input.current_token_start()
}
}
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
impl<I, E> Recover<E> for Partial<I>
where
I: Recover<E>,
I: Stream,
{
#[inline(always)]
fn record_err(
&mut self,
_token_start: &Self::Checkpoint,
_err_start: &Self::Checkpoint,
err: E,
) -> Result<(), E> {
Err(err)
}
/// Report whether the [`Stream`] can save off errors for recovery
#[inline(always)]
fn is_recovery_supported() -> bool {
false
}
}
impl<I> StreamIsPartial for Partial<I>
where
I: StreamIsPartial,
{
type PartialState = bool;
#[inline]
fn complete(&mut self) -> Self::PartialState {
core::mem::replace(&mut self.partial, false)
}
#[inline]
fn restore_partial(&mut self, state: Self::PartialState) {
self.partial = state;
}
#[inline(always)]
fn is_partial_supported() -> bool {
true
}
#[inline(always)]
fn is_partial(&self) -> bool {
self.partial
}
}
impl<I> Offset for Partial<I>
where
I: Stream,
{
#[inline(always)]
fn offset_from(&self, start: &Self) -> usize {
self.offset_from(&start.checkpoint())
}
}
impl<I> Offset<<Partial<I> as Stream>::Checkpoint> for Partial<I>
where
I: Stream,
{
#[inline(always)]
fn offset_from(&self, other: &<Partial<I> as Stream>::Checkpoint) -> usize {
self.checkpoint().offset_from(other)
}
}
impl<I> AsBytes for Partial<I>
where
I: AsBytes,
{
#[inline(always)]
fn as_bytes(&self) -> &[u8] {
self.input.as_bytes()
}
}
impl<I> AsBStr for Partial<I>
where
I: AsBStr,
{
#[inline(always)]
fn as_bstr(&self) -> &[u8] {
self.input.as_bstr()
}
}
impl<I, T> Compare<T> for Partial<I>
where
I: Compare<T>,
{
#[inline(always)]
fn compare(&self, t: T) -> CompareResult {
self.input.compare(t)
}
}
impl<I, T> FindSlice<T> for Partial<I>
where
I: FindSlice<T>,
{
#[inline(always)]
fn find_slice(&self, substr: T) -> Option<core::ops::Range<usize>> {
self.input.find_slice(substr)
}
}
impl<I> UpdateSlice for Partial<I>
where
I: UpdateSlice,
{
#[inline(always)]
fn update_slice(self, inner: Self::Slice) -> Self {
Partial {
input: I::update_slice(self.input, inner),
partial: self.partial,
}
}
}
+143
View File
@@ -0,0 +1,143 @@
/// A range bounded inclusively for counting parses performed
///
/// This is flexible in what can be converted to a [Range]:
/// ```rust
/// # #[cfg(all(feature = "std", feature = "parser"))] {
/// # use winnow::prelude::*;
/// # use winnow::token::any;
/// # use winnow::combinator::repeat;
/// # fn inner(input: &mut &str) -> ModalResult<char> {
/// # any.parse_next(input)
/// # }
/// # let mut input = "0123456789012345678901234567890123456789";
/// # let input = &mut input;
/// let parser: Vec<_> = repeat(5, inner).parse_next(input).unwrap();
/// # let mut input = "0123456789012345678901234567890123456789";
/// # let input = &mut input;
/// let parser: Vec<_> = repeat(.., inner).parse_next(input).unwrap();
/// # let mut input = "0123456789012345678901234567890123456789";
/// # let input = &mut input;
/// let parser: Vec<_> = repeat(1.., inner).parse_next(input).unwrap();
/// # let mut input = "0123456789012345678901234567890123456789";
/// # let input = &mut input;
/// let parser: Vec<_> = repeat(5..8, inner).parse_next(input).unwrap();
/// # let mut input = "0123456789012345678901234567890123456789";
/// # let input = &mut input;
/// let parser: Vec<_> = repeat(5..=8, inner).parse_next(input).unwrap();
/// # }
/// ```
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct Range {
pub(crate) start_inclusive: usize,
pub(crate) end_inclusive: Option<usize>,
}
impl Range {
#[inline(always)]
fn raw(start_inclusive: usize, end_inclusive: Option<usize>) -> Self {
Self {
start_inclusive,
end_inclusive,
}
}
}
impl core::ops::RangeBounds<usize> for Range {
#[inline(always)]
fn start_bound(&self) -> core::ops::Bound<&usize> {
core::ops::Bound::Included(&self.start_inclusive)
}
#[inline(always)]
fn end_bound(&self) -> core::ops::Bound<&usize> {
if let Some(end_inclusive) = &self.end_inclusive {
core::ops::Bound::Included(end_inclusive)
} else {
core::ops::Bound::Unbounded
}
}
}
impl From<usize> for Range {
#[inline(always)]
fn from(fixed: usize) -> Self {
(fixed..=fixed).into()
}
}
impl From<core::ops::Range<usize>> for Range {
#[inline(always)]
fn from(range: core::ops::Range<usize>) -> Self {
let start_inclusive = range.start;
let end_inclusive = Some(range.end.saturating_sub(1));
Self::raw(start_inclusive, end_inclusive)
}
}
impl From<core::ops::RangeFull> for Range {
#[inline(always)]
fn from(_: core::ops::RangeFull) -> Self {
let start_inclusive = 0;
let end_inclusive = None;
Self::raw(start_inclusive, end_inclusive)
}
}
impl From<core::ops::RangeFrom<usize>> for Range {
#[inline(always)]
fn from(range: core::ops::RangeFrom<usize>) -> Self {
let start_inclusive = range.start;
let end_inclusive = None;
Self::raw(start_inclusive, end_inclusive)
}
}
impl From<core::ops::RangeTo<usize>> for Range {
#[inline(always)]
fn from(range: core::ops::RangeTo<usize>) -> Self {
let start_inclusive = 0;
let end_inclusive = Some(range.end.saturating_sub(1));
Self::raw(start_inclusive, end_inclusive)
}
}
impl From<core::ops::RangeInclusive<usize>> for Range {
#[inline(always)]
fn from(range: core::ops::RangeInclusive<usize>) -> Self {
let start_inclusive = *range.start();
let end_inclusive = Some(*range.end());
Self::raw(start_inclusive, end_inclusive)
}
}
impl From<core::ops::RangeToInclusive<usize>> for Range {
#[inline(always)]
fn from(range: core::ops::RangeToInclusive<usize>) -> Self {
let start_inclusive = 0;
let end_inclusive = Some(range.end);
Self::raw(start_inclusive, end_inclusive)
}
}
impl core::fmt::Display for Range {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.start_inclusive.fmt(f)?;
match self.end_inclusive {
Some(e) if e == self.start_inclusive => {}
Some(e) => {
"..=".fmt(f)?;
e.fmt(f)?;
}
None => {
"..".fmt(f)?;
}
}
Ok(())
}
}
impl core::fmt::Debug for Range {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self}")
}
}
+343
View File
@@ -0,0 +1,343 @@
use crate::error::FromRecoverableError;
use crate::error::Needed;
use crate::stream::AsBStr;
use crate::stream::AsBytes;
use crate::stream::Checkpoint;
use crate::stream::Compare;
use crate::stream::CompareResult;
use crate::stream::FindSlice;
use crate::stream::Location;
use crate::stream::Offset;
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
use crate::stream::Recover;
use crate::stream::SliceLen;
use crate::stream::Stream;
use crate::stream::StreamIsPartial;
use crate::stream::UpdateSlice;
/// Allow recovering from parse errors, capturing them as the parser continues
///
/// Generally, this will be used indirectly via
/// [`RecoverableParser::recoverable_parse`][crate::RecoverableParser::recoverable_parse].
#[derive(Clone, Debug)]
pub struct Recoverable<I, E>
where
I: Stream,
{
input: I,
errors: Vec<E>,
is_recoverable: bool,
}
impl<I, E> Default for Recoverable<I, E>
where
I: Default + Stream,
{
#[inline]
fn default() -> Self {
Self::new(I::default())
}
}
impl<I, E> Recoverable<I, E>
where
I: Stream,
{
/// Track recoverable errors with the stream
#[inline]
pub fn new(input: I) -> Self {
Self {
input,
errors: Default::default(),
is_recoverable: true,
}
}
/// Act as a normal stream
#[inline]
pub fn unrecoverable(input: I) -> Self {
Self {
input,
errors: Default::default(),
is_recoverable: false,
}
}
/// Access the current input and errors
#[inline]
pub fn into_parts(self) -> (I, Vec<E>) {
(self.input, self.errors)
}
}
impl<I, E> AsRef<I> for Recoverable<I, E>
where
I: Stream,
{
#[inline(always)]
fn as_ref(&self) -> &I {
&self.input
}
}
impl<I, E> core::ops::Deref for Recoverable<I, E>
where
I: Stream,
{
type Target = I;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.input
}
}
impl<I: core::fmt::Display, E> core::fmt::Display for Recoverable<I, E>
where
I: Stream,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.input, f)
}
}
impl<I, E> SliceLen for Recoverable<I, E>
where
I: SliceLen,
I: Stream,
{
#[inline(always)]
fn slice_len(&self) -> usize {
self.input.slice_len()
}
}
impl<I, E: core::fmt::Debug> Stream for Recoverable<I, E>
where
I: Stream,
{
type Token = <I as Stream>::Token;
type Slice = <I as Stream>::Slice;
type IterOffsets = <I as Stream>::IterOffsets;
type Checkpoint = Checkpoint<I::Checkpoint, Self>;
#[inline(always)]
fn iter_offsets(&self) -> Self::IterOffsets {
self.input.iter_offsets()
}
#[inline(always)]
fn eof_offset(&self) -> usize {
self.input.eof_offset()
}
#[inline(always)]
fn next_token(&mut self) -> Option<Self::Token> {
self.input.next_token()
}
#[inline(always)]
fn peek_token(&self) -> Option<Self::Token> {
self.input.peek_token()
}
#[inline(always)]
fn offset_for<P>(&self, predicate: P) -> Option<usize>
where
P: Fn(Self::Token) -> bool,
{
self.input.offset_for(predicate)
}
#[inline(always)]
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
self.input.offset_at(tokens)
}
#[inline(always)]
fn next_slice(&mut self, offset: usize) -> Self::Slice {
self.input.next_slice(offset)
}
#[inline(always)]
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
// SAFETY: Passing up invariants
unsafe { self.input.next_slice_unchecked(offset) }
}
#[inline(always)]
fn peek_slice(&self, offset: usize) -> Self::Slice {
self.input.peek_slice(offset)
}
#[inline(always)]
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
// SAFETY: Passing up invariants
unsafe { self.input.peek_slice_unchecked(offset) }
}
#[inline(always)]
fn checkpoint(&self) -> Self::Checkpoint {
Checkpoint::<_, Self>::new(self.input.checkpoint())
}
#[inline(always)]
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
self.input.reset(&checkpoint.inner);
}
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.input.trace(f)
}
}
impl<I, E> Location for Recoverable<I, E>
where
I: Location,
I: Stream,
{
#[inline(always)]
fn previous_token_end(&self) -> usize {
self.input.previous_token_end()
}
#[inline(always)]
fn current_token_start(&self) -> usize {
self.input.current_token_start()
}
}
impl<I, E, R> Recover<E> for Recoverable<I, R>
where
I: Stream,
R: FromRecoverableError<Self, E>,
R: core::fmt::Debug,
E: crate::error::ParserError<Self>,
{
fn record_err(
&mut self,
token_start: &Self::Checkpoint,
err_start: &Self::Checkpoint,
err: E,
) -> Result<(), E> {
if self.is_recoverable {
if err.is_incomplete() {
Err(err)
} else {
self.errors
.push(R::from_recoverable_error(token_start, err_start, self, err));
Ok(())
}
} else {
Err(err)
}
}
/// Report whether the [`Stream`] can save off errors for recovery
#[inline(always)]
fn is_recovery_supported() -> bool {
true
}
}
impl<I, E> StreamIsPartial for Recoverable<I, E>
where
I: StreamIsPartial,
I: Stream,
{
type PartialState = I::PartialState;
#[inline]
fn complete(&mut self) -> Self::PartialState {
self.input.complete()
}
#[inline]
fn restore_partial(&mut self, state: Self::PartialState) {
self.input.restore_partial(state);
}
#[inline(always)]
fn is_partial_supported() -> bool {
I::is_partial_supported()
}
#[inline(always)]
fn is_partial(&self) -> bool {
self.input.is_partial()
}
}
impl<I, E> Offset for Recoverable<I, E>
where
I: Stream,
E: core::fmt::Debug,
{
#[inline(always)]
fn offset_from(&self, other: &Self) -> usize {
self.offset_from(&other.checkpoint())
}
}
impl<I, E> Offset<<Recoverable<I, E> as Stream>::Checkpoint> for Recoverable<I, E>
where
I: Stream,
E: core::fmt::Debug,
{
#[inline(always)]
fn offset_from(&self, other: &<Recoverable<I, E> as Stream>::Checkpoint) -> usize {
self.checkpoint().offset_from(other)
}
}
impl<I, E> AsBytes for Recoverable<I, E>
where
I: Stream,
I: AsBytes,
{
#[inline(always)]
fn as_bytes(&self) -> &[u8] {
self.input.as_bytes()
}
}
impl<I, E> AsBStr for Recoverable<I, E>
where
I: Stream,
I: AsBStr,
{
#[inline(always)]
fn as_bstr(&self) -> &[u8] {
self.input.as_bstr()
}
}
impl<I, E, U> Compare<U> for Recoverable<I, E>
where
I: Stream,
I: Compare<U>,
{
#[inline(always)]
fn compare(&self, other: U) -> CompareResult {
self.input.compare(other)
}
}
impl<I, E, T> FindSlice<T> for Recoverable<I, E>
where
I: Stream,
I: FindSlice<T>,
{
#[inline(always)]
fn find_slice(&self, substr: T) -> Option<core::ops::Range<usize>> {
self.input.find_slice(substr)
}
}
impl<I, E> UpdateSlice for Recoverable<I, E>
where
I: Stream,
I: UpdateSlice,
E: core::fmt::Debug,
{
#[inline(always)]
fn update_slice(mut self, inner: Self::Slice) -> Self {
self.input = I::update_slice(self.input, inner);
self
}
}
+308
View File
@@ -0,0 +1,308 @@
use crate::stream::AsBStr;
use crate::stream::AsBytes;
use crate::stream::Checkpoint;
use crate::stream::Compare;
use crate::stream::CompareResult;
use crate::stream::FindSlice;
use crate::stream::Location;
use crate::stream::Needed;
use crate::stream::Offset;
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
use crate::stream::Recover;
use crate::stream::SliceLen;
use crate::stream::Stream;
use crate::stream::StreamIsPartial;
use crate::stream::UpdateSlice;
/// Thread global state through your parsers
///
/// Use cases
/// - Recursion checks
/// - Error recovery
/// - Debugging
///
/// # Example
///
/// ```
/// # #[cfg(feature = "ascii")] {
/// # use std::cell::Cell;
/// # use winnow::prelude::*;
/// # use winnow::stream::Stateful;
/// # use winnow::ascii::alpha1;
/// # type Error = ();
///
/// #[derive(Debug)]
/// struct State<'s>(&'s mut u32);
///
/// impl<'s> State<'s> {
/// fn count(&mut self) {
/// *self.0 += 1;
/// }
/// }
///
/// type Stream<'is> = Stateful<&'is str, State<'is>>;
///
/// fn word<'s>(i: &mut Stream<'s>) -> ModalResult<&'s str> {
/// i.state.count();
/// alpha1.parse_next(i)
/// }
///
/// let data = "Hello";
/// let mut state = 0;
/// let input = Stream { input: data, state: State(&mut state) };
/// let output = word.parse(input).unwrap();
/// assert_eq!(state, 1);
/// # }
/// ```
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[doc(alias = "LocatingSliceSpan")]
pub struct Stateful<I, S> {
/// Inner input being wrapped in state
pub input: I,
/// User-provided state
pub state: S,
}
impl<I, S> AsRef<I> for Stateful<I, S> {
#[inline(always)]
fn as_ref(&self) -> &I {
&self.input
}
}
impl<I, S> core::ops::Deref for Stateful<I, S> {
type Target = I;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl<I: core::fmt::Display, S> core::fmt::Display for Stateful<I, S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.input.fmt(f)
}
}
impl<I, S> SliceLen for Stateful<I, S>
where
I: SliceLen,
{
#[inline(always)]
fn slice_len(&self) -> usize {
self.input.slice_len()
}
}
impl<I: Stream, S: core::fmt::Debug> Stream for Stateful<I, S> {
type Token = <I as Stream>::Token;
type Slice = <I as Stream>::Slice;
type IterOffsets = <I as Stream>::IterOffsets;
type Checkpoint = Checkpoint<I::Checkpoint, Self>;
#[inline(always)]
fn iter_offsets(&self) -> Self::IterOffsets {
self.input.iter_offsets()
}
#[inline(always)]
fn eof_offset(&self) -> usize {
self.input.eof_offset()
}
#[inline(always)]
fn next_token(&mut self) -> Option<Self::Token> {
self.input.next_token()
}
#[inline(always)]
fn peek_token(&self) -> Option<Self::Token> {
self.input.peek_token()
}
#[inline(always)]
fn offset_for<P>(&self, predicate: P) -> Option<usize>
where
P: Fn(Self::Token) -> bool,
{
self.input.offset_for(predicate)
}
#[inline(always)]
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
self.input.offset_at(tokens)
}
#[inline(always)]
fn next_slice(&mut self, offset: usize) -> Self::Slice {
self.input.next_slice(offset)
}
#[inline(always)]
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
// SAFETY: Passing up invariants
unsafe { self.input.next_slice_unchecked(offset) }
}
#[inline(always)]
fn peek_slice(&self, offset: usize) -> Self::Slice {
self.input.peek_slice(offset)
}
#[inline(always)]
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
// SAFETY: Passing up invariants
unsafe { self.input.peek_slice_unchecked(offset) }
}
#[inline(always)]
fn checkpoint(&self) -> Self::Checkpoint {
Checkpoint::<_, Self>::new(self.input.checkpoint())
}
#[inline(always)]
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
self.input.reset(&checkpoint.inner);
}
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.input.trace(f)
}
}
impl<I, S> Location for Stateful<I, S>
where
I: Location,
{
#[inline(always)]
fn previous_token_end(&self) -> usize {
self.input.previous_token_end()
}
#[inline(always)]
fn current_token_start(&self) -> usize {
self.input.current_token_start()
}
}
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
impl<I, E, S> Recover<E> for Stateful<I, S>
where
I: Recover<E>,
I: Stream,
S: Clone + core::fmt::Debug,
{
#[inline(always)]
fn record_err(
&mut self,
_token_start: &Self::Checkpoint,
_err_start: &Self::Checkpoint,
err: E,
) -> Result<(), E> {
Err(err)
}
/// Report whether the [`Stream`] can save off errors for recovery
#[inline(always)]
fn is_recovery_supported() -> bool {
false
}
}
impl<I, S> StreamIsPartial for Stateful<I, S>
where
I: StreamIsPartial,
{
type PartialState = I::PartialState;
#[inline]
fn complete(&mut self) -> Self::PartialState {
self.input.complete()
}
#[inline]
fn restore_partial(&mut self, state: Self::PartialState) {
self.input.restore_partial(state);
}
#[inline(always)]
fn is_partial_supported() -> bool {
I::is_partial_supported()
}
#[inline(always)]
fn is_partial(&self) -> bool {
self.input.is_partial()
}
}
impl<I, S> Offset for Stateful<I, S>
where
I: Stream,
S: Clone + core::fmt::Debug,
{
#[inline(always)]
fn offset_from(&self, start: &Self) -> usize {
self.offset_from(&start.checkpoint())
}
}
impl<I, S> Offset<<Stateful<I, S> as Stream>::Checkpoint> for Stateful<I, S>
where
I: Stream,
S: core::fmt::Debug,
{
#[inline(always)]
fn offset_from(&self, other: &<Stateful<I, S> as Stream>::Checkpoint) -> usize {
self.checkpoint().offset_from(other)
}
}
impl<I, S> AsBytes for Stateful<I, S>
where
I: AsBytes,
{
#[inline(always)]
fn as_bytes(&self) -> &[u8] {
self.input.as_bytes()
}
}
impl<I, S> AsBStr for Stateful<I, S>
where
I: AsBStr,
{
#[inline(always)]
fn as_bstr(&self) -> &[u8] {
self.input.as_bstr()
}
}
impl<I, S, U> Compare<U> for Stateful<I, S>
where
I: Compare<U>,
{
#[inline(always)]
fn compare(&self, other: U) -> CompareResult {
self.input.compare(other)
}
}
impl<I, S, T> FindSlice<T> for Stateful<I, S>
where
I: FindSlice<T>,
{
#[inline(always)]
fn find_slice(&self, substr: T) -> Option<core::ops::Range<usize>> {
self.input.find_slice(substr)
}
}
impl<I, S> UpdateSlice for Stateful<I, S>
where
I: UpdateSlice,
S: Clone + core::fmt::Debug,
{
#[inline(always)]
fn update_slice(mut self, inner: Self::Slice) -> Self {
self.input = I::update_slice(self.input, inner);
self
}
}
+171
View File
@@ -0,0 +1,171 @@
#![cfg(feature = "parser")]
use crate::error::ErrMode;
use crate::error::ErrMode::Backtrack;
use crate::error::InputError;
use crate::token::literal;
use crate::{
combinator::{separated, separated_pair},
ModalResult, Parser,
};
use super::*;
#[cfg(feature = "std")]
#[test]
fn test_fxhashmap_compiles() {
let input = "a=b";
fn pair(i: &mut &str) -> ModalResult<(char, char)> {
let out = separated_pair('a', '=', 'b').parse_next(i)?;
Ok(out)
}
let _: rustc_hash::FxHashMap<char, char> = separated(0.., pair, ',').parse(input).unwrap();
}
#[test]
fn test_offset_u8() {
let s = b"abcd123";
let a = &s[..];
let b = &a[2..];
let c = &a[..4];
let d = &a[3..5];
assert_eq!(b.offset_from(&a), 2);
assert_eq!(c.offset_from(&a), 0);
assert_eq!(d.offset_from(&a), 3);
}
#[test]
fn test_offset_str() {
let a = "abcřèÂßÇd123";
let b = &a[7..];
let c = &a[..5];
let d = &a[5..9];
assert_eq!(b.offset_from(&a), 7);
assert_eq!(c.offset_from(&a), 0);
assert_eq!(d.offset_from(&a), 5);
}
#[test]
fn test_partial_complete() {
let mut i = Partial::new(&b""[..]);
assert!(Partial::<&[u8]>::is_partial_supported());
assert!(i.is_partial(), "incomplete by default");
let incomplete_state = i.complete();
assert!(!i.is_partial(), "the stream should be marked as complete");
i.restore_partial(incomplete_state);
assert!(i.is_partial(), "incomplete stream state should be restored");
}
#[test]
fn test_custom_slice() {
type Token = usize;
type TokenSlice<'i> = &'i [Token];
let mut tokens: TokenSlice<'_> = &[1, 2, 3, 4];
let input = &mut tokens;
let start = input.checkpoint();
let _ = input.next_token();
let _ = input.next_token();
let offset = input.offset_from(&start);
assert_eq!(offset, 2);
}
#[test]
fn test_literal_support_char() {
assert_eq!(
literal::<_, _, ErrMode<InputError<_>>>('π').parse_peek("π"),
Ok(("", "π"))
);
assert_eq!(
literal::<_, _, ErrMode<InputError<_>>>('π').parse_peek("π3.14"),
Ok(("3.14", "π"))
);
assert_eq!(
literal::<_, _, ErrMode<InputError<_>>>("π").parse_peek("π3.14"),
Ok(("3.14", "π"))
);
assert_eq!(
literal::<_, _, ErrMode<InputError<_>>>('-').parse_peek("π"),
Err(Backtrack(InputError::at("π")))
);
assert_eq!(
literal::<_, Partial<&[u8]>, ErrMode<InputError<_>>>('π')
.parse_peek(Partial::new(b"\xCF\x80")),
Ok((Partial::new(Default::default()), "π".as_bytes()))
);
assert_eq!(
literal::<_, &[u8], ErrMode<InputError<_>>>('π').parse_peek(b"\xCF\x80"),
Ok((Default::default(), "π".as_bytes()))
);
assert_eq!(
literal::<_, Partial<&[u8]>, ErrMode<InputError<_>>>('π')
.parse_peek(Partial::new(b"\xCF\x803.14")),
Ok((Partial::new(&b"3.14"[..]), "π".as_bytes()))
);
assert_eq!(
literal::<_, &[u8], ErrMode<InputError<_>>>('π').parse_peek(b"\xCF\x80"),
Ok((Default::default(), "π".as_bytes()))
);
assert_eq!(
literal::<_, &[u8], ErrMode<InputError<_>>>('π').parse_peek(b"\xCF\x803.14"),
Ok((&b"3.14"[..], "π".as_bytes()))
);
assert_eq!(
literal::<_, &[u8], ErrMode<InputError<_>>>('-').parse_peek(b"\xCF\x80"),
Err(Backtrack(InputError::at(&b"\xCF\x80"[..],)))
);
}
#[test]
fn tokenslice_location() {
#[derive(Clone, Debug)]
struct Token {
span: core::ops::Range<usize>,
}
impl Location for Token {
#[inline(always)]
fn previous_token_end(&self) -> usize {
self.span.end
}
#[inline(always)]
fn current_token_start(&self) -> usize {
self.span.start
}
}
let input = [
Token { span: 1..9 },
Token { span: 11..19 },
Token { span: 21..29 },
];
let mut input = TokenSlice::new(&input);
assert_eq!(input.previous_token_end(), 1);
// Parse operation
assert_eq!(input.current_token_start(), 1);
let _ = input.next_token();
assert_eq!(input.previous_token_end(), 9);
// Parse operation
assert_eq!(input.current_token_start(), 11);
let _ = input.next_token();
assert_eq!(input.previous_token_end(), 19);
// Parse operation
assert_eq!(input.current_token_start(), 21);
let _ = input.next_token();
assert_eq!(input.previous_token_end(), 29);
assert_eq!(input.current_token_start(), 29);
}
+308
View File
@@ -0,0 +1,308 @@
use crate::stream::Checkpoint;
use crate::stream::Compare;
use crate::stream::CompareResult;
use crate::stream::Location;
use crate::stream::Needed;
use crate::stream::Offset;
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
use crate::stream::Recover;
use crate::stream::SliceLen;
use crate::stream::Stream;
use crate::stream::StreamIsPartial;
use crate::stream::UpdateSlice;
use core::iter::Enumerate;
use core::slice::Iter;
/// Specialized input for parsing lexed tokens
///
/// Helpful impls
/// - Any `PartialEq` type (e.g. a `TokenKind` or `&str`) can be used with
/// [`literal`][crate::token::literal]
/// - A `PartialEq` for `&str` allows for using `&str` as a parser for tokens
/// - [`ContainsToken`][crate::stream::ContainsToken] for `T` to for parsing with token sets
/// - [`Location`] for `T` to extract spans from tokens
///
/// See also [Lexing and Parsing][crate::_topic::lexing].
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct TokenSlice<'t, T> {
initial: &'t [T],
input: &'t [T],
}
impl<'t, T> TokenSlice<'t, T>
where
T: core::fmt::Debug + Clone,
{
/// Make a stream to parse tokens
#[inline]
pub fn new(input: &'t [T]) -> Self {
Self {
initial: input,
input,
}
}
/// Reset the stream to the start
///
/// This is useful for formats that encode a graph with addresses relative to the start of the
/// input.
#[doc(alias = "fseek")]
#[inline]
pub fn reset_to_start(&mut self) {
let start = self.initial.checkpoint();
self.input.reset(&start);
}
/// Iterate over consumed tokens starting with the last emitted
///
/// This is intended to help build up appropriate context when reporting errors.
#[inline]
pub fn previous_tokens(&self) -> impl Iterator<Item = &'t T> {
let offset = self.input.offset_from(&self.initial);
self.initial[0..offset].iter().rev()
}
}
/// Track locations by implementing [`Location`] on the Token.
impl<T> TokenSlice<'_, T>
where
T: Location,
{
#[inline(always)]
fn previous_token_end(&self) -> Option<usize> {
let index = self.input.offset_from(&self.initial);
index
.checked_sub(1)
.map(|i| self.initial[i].previous_token_end())
}
#[inline(always)]
fn current_token_start(&self) -> Option<usize> {
self.input.first().map(|t| t.current_token_start())
}
}
impl<T> Default for TokenSlice<'_, T>
where
T: core::fmt::Debug + Clone,
{
fn default() -> Self {
Self::new(&[])
}
}
impl<T> core::ops::Deref for TokenSlice<'_, T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.input
}
}
impl<T: core::fmt::Debug> core::fmt::Debug for TokenSlice<'_, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.input.fmt(f)
}
}
impl<T> SliceLen for TokenSlice<'_, T> {
#[inline(always)]
fn slice_len(&self) -> usize {
self.input.slice_len()
}
}
impl<'t, T> Stream for TokenSlice<'t, T>
where
T: core::fmt::Debug + Clone,
{
type Token = &'t T;
type Slice = &'t [T];
type IterOffsets = Enumerate<Iter<'t, T>>;
type Checkpoint = Checkpoint<&'t [T], Self>;
#[inline(always)]
fn iter_offsets(&self) -> Self::IterOffsets {
self.input.iter().enumerate()
}
#[inline(always)]
fn eof_offset(&self) -> usize {
self.input.eof_offset()
}
#[inline(always)]
fn next_token(&mut self) -> Option<Self::Token> {
let (token, next) = self.input.split_first()?;
self.input = next;
Some(token)
}
#[inline(always)]
fn peek_token(&self) -> Option<Self::Token> {
self.input.first()
}
#[inline(always)]
fn offset_for<P>(&self, predicate: P) -> Option<usize>
where
P: Fn(Self::Token) -> bool,
{
self.input.iter().position(predicate)
}
#[inline(always)]
fn offset_at(&self, tokens: usize) -> Result<usize, Needed> {
self.input.offset_at(tokens)
}
#[inline(always)]
fn next_slice(&mut self, offset: usize) -> Self::Slice {
self.input.next_slice(offset)
}
#[inline(always)]
unsafe fn next_slice_unchecked(&mut self, offset: usize) -> Self::Slice {
// SAFETY: Passing up invariants
unsafe { self.input.next_slice_unchecked(offset) }
}
#[inline(always)]
fn peek_slice(&self, offset: usize) -> Self::Slice {
self.input.peek_slice(offset)
}
#[inline(always)]
unsafe fn peek_slice_unchecked(&self, offset: usize) -> Self::Slice {
// SAFETY: Passing up invariants
unsafe { self.input.peek_slice_unchecked(offset) }
}
#[inline(always)]
fn checkpoint(&self) -> Self::Checkpoint {
Checkpoint::<_, Self>::new(self.input)
}
#[inline(always)]
fn reset(&mut self, checkpoint: &Self::Checkpoint) {
self.input = checkpoint.inner;
}
fn trace(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.input.trace(f)
}
}
impl<T> Location for TokenSlice<'_, T>
where
T: Location,
{
#[inline(always)]
fn previous_token_end(&self) -> usize {
self.previous_token_end()
.or_else(|| self.current_token_start())
.unwrap_or(0)
}
#[inline(always)]
fn current_token_start(&self) -> usize {
self.current_token_start()
.or_else(|| self.previous_token_end())
.unwrap_or(0)
}
}
#[cfg(feature = "unstable-recover")]
#[cfg(feature = "std")]
impl<T, E> Recover<E> for TokenSlice<'_, T>
where
T: core::fmt::Debug + Clone,
{
#[inline(always)]
fn record_err(
&mut self,
_token_start: &Self::Checkpoint,
_err_start: &Self::Checkpoint,
err: E,
) -> Result<(), E> {
Err(err)
}
/// Report whether the [`Stream`] can save off errors for recovery
#[inline(always)]
fn is_recovery_supported() -> bool {
false
}
}
impl<'t, T> StreamIsPartial for TokenSlice<'t, T>
where
T: core::fmt::Debug + Clone,
{
type PartialState = <&'t [T] as StreamIsPartial>::PartialState;
#[inline]
fn complete(&mut self) -> Self::PartialState {
#![allow(clippy::semicolon_if_nothing_returned)]
self.input.complete()
}
#[inline]
fn restore_partial(&mut self, state: Self::PartialState) {
self.input.restore_partial(state);
}
#[inline(always)]
fn is_partial_supported() -> bool {
<&[T] as StreamIsPartial>::is_partial_supported()
}
#[inline(always)]
fn is_partial(&self) -> bool {
self.input.is_partial()
}
}
impl<T> Offset for TokenSlice<'_, T>
where
T: core::fmt::Debug + Clone,
{
#[inline(always)]
fn offset_from(&self, other: &Self) -> usize {
self.offset_from(&other.checkpoint())
}
}
impl<T> Offset<<TokenSlice<'_, T> as Stream>::Checkpoint> for TokenSlice<'_, T>
where
T: core::fmt::Debug + Clone,
{
#[inline(always)]
fn offset_from(&self, other: &<TokenSlice<'_, T> as Stream>::Checkpoint) -> usize {
self.checkpoint().offset_from(other)
}
}
impl<T, O> Compare<O> for TokenSlice<'_, T>
where
T: PartialEq<O> + Eq,
{
#[inline]
fn compare(&self, t: O) -> CompareResult {
if let Some(token) = self.first() {
if *token == t {
CompareResult::Ok(1)
} else {
CompareResult::Error
}
} else {
CompareResult::Incomplete
}
}
}
impl<T> UpdateSlice for TokenSlice<'_, T>
where
T: core::fmt::Debug + Clone,
{
#[inline(always)]
fn update_slice(mut self, inner: Self::Slice) -> Self {
self.input = <&[T] as UpdateSlice>::update_slice(self.input, inner);
self
}
}