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