diff --git a/Cargo.toml b/Cargo.toml index 99fa6e9b..ad035e1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,8 +51,9 @@ alloc = [] std = ["alloc", "memchr?/std"] simd = ["dep:memchr"] debug = ["dep:anstream", "dep:anstyle", "dep:is-terminal", "dep:terminal_size"] +unstable-recover = [] -unstable-doc = ["alloc", "std", "simd"] +unstable-doc = ["alloc", "std", "simd", "unstable-recover"] [dependencies] anstream = { version = "0.3.2", optional = true } diff --git a/src/combinator/parser.rs b/src/combinator/parser.rs index 1fb54464..034d56a1 100644 --- a/src/combinator/parser.rs +++ b/src/combinator/parser.rs @@ -1,8 +1,12 @@ use crate::combinator::trace; use crate::combinator::trace_result; +#[cfg(feature = "unstable-recover")] +use crate::error::FromRecoverableError; use crate::error::{AddContext, ErrMode, ErrorKind, FromExternalError, ParserError}; use crate::lib::std::borrow::Borrow; use crate::lib::std::ops::Range; +#[cfg(feature = "unstable-recover")] +use crate::stream::Recover; use crate::stream::StreamIsPartial; use crate::stream::{Location, Stream}; use crate::*; @@ -903,3 +907,203 @@ where .parse_next(i) } } + +/// Implementation of [`Parser::retry_after`] +#[cfg_attr(nightly, warn(rustdoc::missing_doc_code_examples))] +#[cfg(feature = "unstable-recover")] +pub struct RetryAfter +where + P: Parser, + R: Parser, + I: Stream, + I: Recover, + E: FromRecoverableError, + ER: FromRecoverableError, +{ + parser: P, + recover: R, + i: core::marker::PhantomData, + o: core::marker::PhantomData, + e: core::marker::PhantomData, + er: core::marker::PhantomData, +} + +#[cfg(feature = "unstable-recover")] +impl RetryAfter +where + P: Parser, + R: Parser, + I: Stream, + I: Recover, + E: FromRecoverableError, + ER: FromRecoverableError, +{ + #[inline(always)] + pub(crate) fn new(parser: P, recover: R) -> Self { + Self { + parser, + recover, + i: Default::default(), + o: Default::default(), + e: Default::default(), + er: Default::default(), + } + } +} + +#[cfg(feature = "unstable-recover")] +impl Parser for RetryAfter +where + P: Parser, + R: Parser, + I: Stream, + I: Recover, + E: FromRecoverableError, + ER: FromRecoverableError, +{ + #[inline(always)] + fn parse_next(&mut self, i: &mut I) -> PResult { + if I::is_recovery_supported() { + retry_after_inner::(&mut self.parser, &mut self.recover, i) + } else { + self.parser.parse_next(i) + } + } +} + +#[cfg(feature = "unstable-recover")] +fn retry_after_inner(parser: &mut P, recover: &mut R, i: &mut I) -> PResult +where + P: Parser, + R: Parser, + I: Stream, + I: Recover, + E: FromRecoverableError, + ER: FromRecoverableError, +{ + loop { + let token_start = i.checkpoint(); + let mut err = match parser.parse_next(i) { + Ok(o) => { + return Ok(o); + } + Err(ErrMode::Incomplete(e)) => return Err(ErrMode::Incomplete(e)), + Err(err) => err, + }; + let err_start = i.checkpoint(); + let err_start_eof_offset = i.eof_offset(); + if recover.parse_next(i).is_ok() { + let i_eof_offset = i.eof_offset(); + if err_start_eof_offset == i_eof_offset { + // Didn't advance so bubble the error up + } else if let Err(err_) = i.record_err(&token_start, &err_start, err) { + err = err_; + } else { + continue; + } + } + + i.reset(err_start.clone()); + err = err.map(|err| E::from_recoverable_error(&token_start, &err_start, i, err)); + return Err(err); + } +} + +/// Implementation of [`Parser::resume_after`] +#[cfg(feature = "unstable-recover")] +#[cfg_attr(nightly, warn(rustdoc::missing_doc_code_examples))] +pub struct ResumeAfter +where + P: Parser, + R: Parser, + I: Stream, + I: Recover, + E: FromRecoverableError, + ER: FromRecoverableError, +{ + parser: P, + recover: R, + i: core::marker::PhantomData, + o: core::marker::PhantomData, + e: core::marker::PhantomData, + er: core::marker::PhantomData, +} + +#[cfg(feature = "unstable-recover")] +impl ResumeAfter +where + P: Parser, + R: Parser, + I: Stream, + I: Recover, + E: FromRecoverableError, + ER: FromRecoverableError, +{ + #[inline(always)] + pub(crate) fn new(parser: P, recover: R) -> Self { + Self { + parser, + recover, + i: Default::default(), + o: Default::default(), + e: Default::default(), + er: Default::default(), + } + } +} + +#[cfg(feature = "unstable-recover")] +impl Parser, E> for ResumeAfter +where + P: Parser, + R: Parser, + I: Stream, + I: Recover, + E: FromRecoverableError, + ER: FromRecoverableError, +{ + #[inline(always)] + fn parse_next(&mut self, i: &mut I) -> PResult, E> { + if I::is_recovery_supported() { + resume_after_inner::(&mut self.parser, &mut self.recover, i) + } else { + self.parser.parse_next(i).map(Some) + } + } +} + +#[cfg(feature = "unstable-recover")] +fn resume_after_inner( + parser: &mut P, + recover: &mut R, + i: &mut I, +) -> PResult, E> +where + P: Parser, + R: Parser, + I: Stream, + I: Recover, + E: FromRecoverableError, + ER: FromRecoverableError, +{ + let token_start = i.checkpoint(); + let mut err = match parser.parse_next(i) { + Ok(o) => { + return Ok(Some(o)); + } + Err(ErrMode::Incomplete(e)) => return Err(ErrMode::Incomplete(e)), + Err(err) => err, + }; + let err_start = i.checkpoint(); + if recover.parse_next(i).is_ok() { + if let Err(err_) = i.record_err(&token_start, &err_start, err) { + err = err_; + } else { + return Ok(None); + } + } + + i.reset(err_start.clone()); + err = err.map(|err| E::from_recoverable_error(&token_start, &err_start, i, err)); + Err(err) +} diff --git a/src/error.rs b/src/error.rs index fd7d9469..40c8a6b7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -313,6 +313,17 @@ pub trait AddContext: Sized { } } +/// Capture context from when an error was recovered +pub trait FromRecoverableError { + /// Capture context from when an error was recovered + fn from_recoverable_error( + token_start: &::Checkpoint, + err_start: &::Checkpoint, + input: &I, + e: E, + ) -> Self; +} + /// Create a new error with an external error, from [`std::str::FromStr`] /// /// This trait is required by the [`Parser::try_map`] combinator. @@ -387,6 +398,18 @@ impl ParserError for InputError { impl AddContext for InputError {} +impl FromRecoverableError for InputError { + #[inline] + fn from_recoverable_error( + _token_start: &::Checkpoint, + _err_start: &::Checkpoint, + _input: &I, + e: Self, + ) -> Self { + e + } +} + impl FromExternalError for InputError { /// Create a new error from an input position and an external error #[inline] @@ -446,6 +469,18 @@ impl ParserError for () { impl AddContext for () {} +impl FromRecoverableError for () { + #[inline] + fn from_recoverable_error( + _token_start: &::Checkpoint, + _err_start: &::Checkpoint, + _input: &I, + e: Self, + ) -> Self { + e + } +} + impl FromExternalError for () { #[inline] fn from_external_error(_input: &I, _kind: ErrorKind, _e: E) -> Self {} @@ -536,6 +571,18 @@ impl AddContext for ContextError { } } +impl FromRecoverableError for ContextError { + #[inline] + fn from_recoverable_error( + _token_start: &::Checkpoint, + _err_start: &::Checkpoint, + _input: &I, + e: Self, + ) -> Self { + e + } +} + #[cfg(feature = "std")] impl FromExternalError for ContextError @@ -863,6 +910,19 @@ where } } +#[cfg(feature = "std")] +impl FromRecoverableError for TreeError { + #[inline] + fn from_recoverable_error( + _token_start: &::Checkpoint, + _err_start: &::Checkpoint, + _input: &I, + e: Self, + ) -> Self { + e + } +} + #[cfg(feature = "std")] impl FromExternalError for TreeError where diff --git a/src/lib.rs b/src/lib.rs index c2745ea6..7aeef4b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -240,6 +240,8 @@ pub mod prelude { pub use crate::IResult; pub use crate::PResult; pub use crate::Parser; + #[cfg(feature = "unstable-recover")] + pub use crate::RecoverableParser as _; } pub use error::IResult; diff --git a/src/parser.rs b/src/parser.rs index 55d7aba9..0db4b860 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,8 +2,12 @@ use crate::ascii::Caseless as AsciiCaseless; use crate::combinator::*; +#[cfg(feature = "unstable-recover")] +use crate::error::FromRecoverableError; use crate::error::{AddContext, FromExternalError, IResult, PResult, ParseError, ParserError}; use crate::stream::{AsChar, Compare, Location, ParseSlice, Stream, StreamIsPartial}; +#[cfg(feature = "unstable-recover")] +use crate::stream::{Recover, Recoverable}; /// Core trait for parsing /// @@ -662,6 +666,45 @@ pub trait Parser { { ErrInto::new(self) } + + /// Recover from an error by skipping everything `recover` consumes and trying again + /// + /// If `recover` consumes nothing, the error is returned, allowing an alternative recovery + /// method. + /// + /// This commits the parse result, preventing alternative branch paths like with + /// [`winnow::combinator::alt`][crate::combinator::alt]. + #[inline(always)] + #[cfg(feature = "unstable-recover")] + fn retry_after(self, recover: R) -> RetryAfter + where + Self: core::marker::Sized, + R: Parser, + I: Stream, + I: Recover, + E: FromRecoverableError, + ER: FromRecoverableError, + { + RetryAfter::new(self, recover) + } + + /// Recover from an error by skipping this parse and everything `recover` consumes + /// + /// This commits the parse result, preventing alternative branch paths like with + /// [`winnow::combinator::alt`][crate::combinator::alt]. + #[inline(always)] + #[cfg(feature = "unstable-recover")] + fn resume_after(self, recover: R) -> ResumeAfter + where + Self: core::marker::Sized, + R: Parser, + I: Stream, + I: Recover, + E: FromRecoverableError, + ER: FromRecoverableError, + { + ResumeAfter::new(self, recover) + } } impl<'a, I, O, E, F> Parser for F @@ -953,6 +996,71 @@ macro_rules! impl_parser_for_tuples { } } +/// Collect all errors when parsing the input +/// +/// [`Parser`]s will need to use [`Recoverable`] for their input. +#[cfg(feature = "unstable-recover")] +pub trait RecoverableParser { + /// Collect all errors when parsing the input + /// + /// If `self` fails, this acts like [`Parser::resume_after`] and returns `Ok(None)`. + /// Generally, this should be avoided by using + /// [`Parser::retry_after`] and [`Parser::resume_after`] throughout your parser. + /// + /// The empty `input` is returned to allow turning the errors into [`ParserError`]s. + fn recoverable_parse(&mut self, input: I) -> (I, Option, Vec); +} + +#[cfg(feature = "unstable-recover")] +impl RecoverableParser for P +where + P: Parser, O, E>, + I: Stream, + I: StreamIsPartial, + R: FromRecoverableError, E>, + R: crate::lib::std::fmt::Debug, + E: FromRecoverableError, E>, + E: ParserError>, + E: crate::lib::std::fmt::Debug, +{ + #[inline] + fn recoverable_parse(&mut self, input: I) -> (I, Option, Vec) { + debug_assert!( + !I::is_partial_supported(), + "partial streams need to handle `ErrMode::Incomplete`" + ); + + let start = input.checkpoint(); + let mut input = Recoverable::new(input); + let start_token = input.checkpoint(); + let result = ( + self.by_ref(), + crate::combinator::eof.resume_after::<_, R>(rest.void()), + ) + .parse_next(&mut input); + + let (o, err) = match result { + Ok((o, _)) => (Some(o), None), + Err(err) => { + let err = err + .into_inner() + .expect("complete parsers should not report `ErrMode::Incomplete(_)`"); + let err_start = input.checkpoint(); + let err = R::from_recoverable_error(&start_token, &err_start, &input, err); + (None, Some(err)) + } + }; + + let (mut input, mut errs) = input.into_parts(); + input.reset(start); + if let Some(err) = err { + errs.push(err); + } + + (input, o, errs) + } +} + impl_parser_for_tuples!( P1 O1, P2 O2, diff --git a/src/stream/mod.rs b/src/stream/mod.rs index 20caac1b..6873ac3c 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -13,6 +13,8 @@ use core::hash::BuildHasher; use core::num::NonZeroUsize; use crate::ascii::Caseless as AsciiCaseless; +#[cfg(feature = "unstable-recover")] +use crate::error::FromRecoverableError; use crate::error::Needed; use crate::lib::std::iter::{Cloned, Enumerate}; use crate::lib::std::slice::Iter; @@ -21,7 +23,7 @@ use crate::lib::std::str::CharIndices; use crate::lib::std::str::FromStr; #[allow(unused_imports)] -#[cfg(feature = "unstable-doc")] +#[cfg(any(feature = "unstable-doc", feature = "unstable-recover"))] use crate::error::ErrMode; #[cfg(feature = "alloc")] @@ -148,6 +150,84 @@ impl crate::lib::std::fmt::Display for Located } } +/// 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]. +#[cfg(feature = "unstable-recover")] +#[derive(Clone, Debug)] +pub struct Recoverable +where + I: Stream, +{ + input: I, + errors: Vec, + is_recoverable: bool, +} + +#[cfg(feature = "unstable-recover")] +impl Recoverable +where + I: Stream, +{ + /// Track recoverable errors with the stream + pub fn new(input: I) -> Self { + Self { + input, + errors: Default::default(), + is_recoverable: true, + } + } + + /// Act as a normal stream + pub fn unrecoverable(input: I) -> Self { + Self { + input, + errors: Default::default(), + is_recoverable: false, + } + } + + /// Access the current input and errors + pub fn into_parts(self) -> (I, Vec) { + (self.input, self.errors) + } +} + +#[cfg(feature = "unstable-recover")] +impl AsRef for Recoverable +where + I: Stream, +{ + #[inline(always)] + fn as_ref(&self) -> &I { + &self.input + } +} + +#[cfg(feature = "unstable-recover")] +impl crate::lib::std::ops::Deref for Recoverable +where + I: Stream, +{ + type Target = I; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.input + } +} + +#[cfg(feature = "unstable-recover")] +impl crate::lib::std::fmt::Display for Recoverable +where + I: Stream, +{ + fn fmt(&self, f: &mut crate::lib::std::fmt::Formatter<'_>) -> crate::lib::std::fmt::Result { + crate::lib::std::fmt::Display::fmt(&self.input, f) + } +} + /// Thread global state through your parsers /// /// Use cases @@ -415,6 +495,18 @@ where } } +#[cfg(feature = "unstable-recover")] +impl SliceLen for Recoverable +where + I: SliceLen, + I: Stream, +{ + #[inline(always)] + fn slice_len(&self) -> usize { + self.input.slice_len() + } +} + impl SliceLen for Stateful where I: SliceLen, @@ -982,6 +1074,63 @@ impl Stream for Located { } } +#[cfg(feature = "unstable-recover")] +impl Stream for Recoverable +where + I: Stream, +{ + type Token = ::Token; + type Slice = ::Slice; + + type IterOffsets = ::IterOffsets; + + type Checkpoint = Checkpoint; + + #[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.input.next_token() + } + + #[inline(always)] + fn offset_for

(&self, predicate: P) -> Option + where + P: Fn(Self::Token) -> bool, + { + self.input.offset_for(predicate) + } + #[inline(always)] + fn offset_at(&self, tokens: usize) -> Result { + self.input.offset_at(tokens) + } + #[inline(always)] + fn next_slice(&mut self, offset: usize) -> Self::Slice { + self.input.next_slice(offset) + } + + #[inline(always)] + fn checkpoint(&self) -> Self::Checkpoint { + Checkpoint(self.input.checkpoint()) + } + #[inline(always)] + fn reset(&mut self, checkpoint: Self::Checkpoint) { + self.input.reset(checkpoint.0); + } + + #[inline(always)] + fn raw(&self) -> &dyn crate::lib::std::fmt::Debug { + &self.input + } +} + impl Stream for Stateful { type Token = ::Token; type Slice = ::Slice; @@ -1106,6 +1255,18 @@ where } } +#[cfg(feature = "unstable-recover")] +impl Location for Recoverable +where + I: Location, + I: Stream, +{ + #[inline(always)] + fn location(&self) -> usize { + self.input.location() + } +} + impl Location for Stateful where I: Location, @@ -1126,6 +1287,232 @@ where } } +/// Capture top-level errors in the middle of parsing so parsing can resume +/// +/// See [`Recoverable`] for adding error recovery tracking to your [`Stream`] +#[cfg(feature = "unstable-recover")] +pub trait Recover: Stream { + /// Capture a top-level error + /// + /// May return `Err(err)` if recovery is not possible (e.g. if [`Recover::is_recovery_supported`] + /// returns `false`). + fn record_err( + &mut self, + token_start: &Self::Checkpoint, + err_start: &Self::Checkpoint, + err: ErrMode, + ) -> Result<(), ErrMode>; + + /// Report whether the [`Stream`] can save off errors for recovery + fn is_recovery_supported() -> bool; +} + +#[cfg(feature = "unstable-recover")] +impl<'a, T, E> Recover for &'a [T] +where + &'a [T]: Stream, +{ + #[inline(always)] + fn record_err( + &mut self, + _token_start: &Self::Checkpoint, + _err_start: &Self::Checkpoint, + err: ErrMode, + ) -> Result<(), ErrMode> { + Err(err) + } + + /// Report whether the [`Stream`] can save off errors for recovery + #[inline(always)] + fn is_recovery_supported() -> bool { + false + } +} + +#[cfg(feature = "unstable-recover")] +impl<'a, E> Recover for &'a str { + #[inline(always)] + fn record_err( + &mut self, + _token_start: &Self::Checkpoint, + _err_start: &Self::Checkpoint, + err: ErrMode, + ) -> Result<(), ErrMode> { + Err(err) + } + + /// Report whether the [`Stream`] can save off errors for recovery + #[inline(always)] + fn is_recovery_supported() -> bool { + false + } +} + +#[cfg(feature = "unstable-recover")] +impl<'a, E> Recover for &'a Bytes { + #[inline(always)] + fn record_err( + &mut self, + _token_start: &Self::Checkpoint, + _err_start: &Self::Checkpoint, + err: ErrMode, + ) -> Result<(), ErrMode> { + Err(err) + } + + /// Report whether the [`Stream`] can save off errors for recovery + #[inline(always)] + fn is_recovery_supported() -> bool { + false + } +} + +#[cfg(feature = "unstable-recover")] +impl<'a, E> Recover for &'a BStr { + #[inline(always)] + fn record_err( + &mut self, + _token_start: &Self::Checkpoint, + _err_start: &Self::Checkpoint, + err: ErrMode, + ) -> Result<(), ErrMode> { + Err(err) + } + + /// Report whether the [`Stream`] can save off errors for recovery + #[inline(always)] + fn is_recovery_supported() -> bool { + false + } +} + +#[cfg(feature = "unstable-recover")] +impl Recover for (I, usize) +where + I: Recover, + I: Stream + Clone, +{ + #[inline(always)] + fn record_err( + &mut self, + _token_start: &Self::Checkpoint, + _err_start: &Self::Checkpoint, + err: ErrMode, + ) -> Result<(), ErrMode> { + Err(err) + } + + /// Report whether the [`Stream`] can save off errors for recovery + #[inline(always)] + fn is_recovery_supported() -> bool { + false + } +} + +#[cfg(feature = "unstable-recover")] +impl Recover for Located +where + I: Recover, + I: Stream, +{ + #[inline(always)] + fn record_err( + &mut self, + _token_start: &Self::Checkpoint, + _err_start: &Self::Checkpoint, + err: ErrMode, + ) -> Result<(), ErrMode> { + Err(err) + } + + /// Report whether the [`Stream`] can save off errors for recovery + #[inline(always)] + fn is_recovery_supported() -> bool { + false + } +} + +#[cfg(feature = "unstable-recover")] +impl Recover for Recoverable +where + I: Stream, + R: FromRecoverableError, + R: crate::lib::std::fmt::Debug, +{ + fn record_err( + &mut self, + token_start: &Self::Checkpoint, + err_start: &Self::Checkpoint, + err: ErrMode, + ) -> Result<(), ErrMode> { + if self.is_recoverable { + match err { + ErrMode::Incomplete(need) => Err(ErrMode::Incomplete(need)), + ErrMode::Backtrack(err) | ErrMode::Cut(err) => { + 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 + } +} + +#[cfg(feature = "unstable-recover")] +impl Recover for Stateful +where + I: Recover, + I: Stream, + S: Clone + crate::lib::std::fmt::Debug, +{ + #[inline(always)] + fn record_err( + &mut self, + _token_start: &Self::Checkpoint, + _err_start: &Self::Checkpoint, + err: ErrMode, + ) -> Result<(), ErrMode> { + Err(err) + } + + /// Report whether the [`Stream`] can save off errors for recovery + #[inline(always)] + fn is_recovery_supported() -> bool { + false + } +} + +#[cfg(feature = "unstable-recover")] +impl Recover for Partial +where + I: Recover, + I: Stream, +{ + #[inline(always)] + fn record_err( + &mut self, + _token_start: &Self::Checkpoint, + _err_start: &Self::Checkpoint, + err: ErrMode, + ) -> Result<(), ErrMode> { + Err(err) + } + + /// Report whether the [`Stream`] can save off errors for recovery + #[inline(always)] + fn is_recovery_supported() -> bool { + false + } +} + /// Marks the input as being the complete buffer or a partial buffer for streaming input /// /// See [`Partial`] for marking a presumed complete buffer type as a streaming buffer. @@ -1258,6 +1645,33 @@ where } } +#[cfg(feature = "unstable-recover")] +impl StreamIsPartial for Recoverable +where + I: StreamIsPartial, + I: Stream, +{ + type PartialState = I::PartialState; + + fn complete(&mut self) -> Self::PartialState { + self.input.complete() + } + + 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 StreamIsPartial for Stateful where I: StreamIsPartial, @@ -1423,6 +1837,30 @@ where } } +#[cfg(feature = "unstable-recover")] +impl Offset for Recoverable +where + I: Stream, + E: crate::lib::std::fmt::Debug, +{ + #[inline(always)] + fn offset_from(&self, other: &Self) -> usize { + self.offset_from(&other.checkpoint()) + } +} + +#[cfg(feature = "unstable-recover")] +impl Offset< as Stream>::Checkpoint> for Recoverable +where + I: Stream, + E: crate::lib::std::fmt::Debug, +{ + #[inline(always)] + fn offset_from(&self, other: & as Stream>::Checkpoint) -> usize { + self.checkpoint().offset_from(other) + } +} + impl Offset for Stateful where I: Stream, @@ -1505,6 +1943,18 @@ where } } +#[cfg(feature = "unstable-recover")] +impl AsBytes for Recoverable +where + I: Stream, + I: AsBytes, +{ + #[inline(always)] + fn as_bytes(&self) -> &[u8] { + self.input.as_bytes() + } +} + impl AsBytes for Stateful where I: AsBytes, @@ -1562,6 +2012,18 @@ where } } +#[cfg(feature = "unstable-recover")] +impl AsBStr for Recoverable +where + I: Stream, + I: AsBStr, +{ + #[inline(always)] + fn as_bstr(&self) -> &[u8] { + self.input.as_bstr() + } +} + impl AsBStr for Stateful where I: AsBStr, @@ -1880,6 +2342,24 @@ where } } +#[cfg(feature = "unstable-recover")] +impl Compare for Recoverable +where + I: Stream, + I: Compare, +{ + #[inline(always)] + fn compare(&self, other: U) -> CompareResult { + self.input.compare(other) + } + + #[inline(always)] + #[allow(deprecated)] + fn compare_no_case(&self, other: U) -> CompareResult { + self.input.compare_no_case(other) + } +} + impl Compare for Stateful where I: Compare, @@ -2141,6 +2621,18 @@ where } } +#[cfg(feature = "unstable-recover")] +impl FindSlice for Recoverable +where + I: Stream, + I: FindSlice, +{ + #[inline(always)] + fn find_slice(&self, substr: T) -> Option { + self.input.find_slice(substr) + } +} + impl FindSlice for Stateful where I: FindSlice, @@ -2232,6 +2724,20 @@ where } } +#[cfg(feature = "unstable-recover")] +impl UpdateSlice for Recoverable +where + I: Stream, + I: UpdateSlice, + E: crate::lib::std::fmt::Debug, +{ + #[inline(always)] + fn update_slice(mut self, inner: Self::Slice) -> Self { + self.input = I::update_slice(self.input, inner); + self + } +} + impl UpdateSlice for Stateful where I: UpdateSlice,