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:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+540
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+562
@@ -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..]);
|
||||
}
|
||||
}
|
||||
Vendored
+320
@@ -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
|
||||
}
|
||||
}
|
||||
Vendored
+2011
File diff suppressed because it is too large.
Load diff
Vendored
+355
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+143
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
Vendored
+308
@@ -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
|
||||
}
|
||||
}
|
||||
Vendored
+171
@@ -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);
|
||||
}
|
||||
Vendored
+308
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user