Skip to content

Commit

Permalink
feat: Unstable support for error recovery
Browse files Browse the repository at this point in the history
The main things this leave out include:
- A happy path for error reporting
- Examples

First step towards #96
  • Loading branch information
epage committed Jan 31, 2024
1 parent 1feb518 commit 1f81aa7
Show file tree
Hide file tree
Showing 6 changed files with 883 additions and 2 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
204 changes: 204 additions & 0 deletions src/combinator/parser.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down Expand Up @@ -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<P, R, I, O, E, ER>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: FromRecoverableError<I, E>,
ER: FromRecoverableError<I, E>,
{
parser: P,
recover: R,
i: core::marker::PhantomData<I>,
o: core::marker::PhantomData<O>,
e: core::marker::PhantomData<E>,
er: core::marker::PhantomData<ER>,
}

#[cfg(feature = "unstable-recover")]
impl<P, R, I, O, E, ER> RetryAfter<P, R, I, O, E, ER>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: FromRecoverableError<I, E>,
ER: FromRecoverableError<I, E>,
{
#[inline(always)]
pub(crate) fn new(parser: P, recover: R) -> Self {

Check warning on line 942 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L942

Added line #L942 was not covered by tests
Self {
parser,
recover,
i: Default::default(),
o: Default::default(),
e: Default::default(),
er: Default::default(),

Check warning on line 949 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L946-L949

Added lines #L946 - L949 were not covered by tests
}
}
}

#[cfg(feature = "unstable-recover")]
impl<P, R, I, O, E, ER> Parser<I, O, E> for RetryAfter<P, R, I, O, E, ER>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: FromRecoverableError<I, E>,
ER: FromRecoverableError<I, E>,
{
#[inline(always)]
fn parse_next(&mut self, i: &mut I) -> PResult<O, E> {
if I::is_recovery_supported() {
retry_after_inner::<P, R, I, O, E, ER>(&mut self.parser, &mut self.recover, i)

Check warning on line 967 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L965-L967

Added lines #L965 - L967 were not covered by tests
} else {
self.parser.parse_next(i)

Check warning on line 969 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L969

Added line #L969 was not covered by tests
}
}
}

#[cfg(feature = "unstable-recover")]
fn retry_after_inner<P, R, I, O, E, ER>(parser: &mut P, recover: &mut R, i: &mut I) -> PResult<O, E>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: FromRecoverableError<I, E>,
ER: FromRecoverableError<I, E>,
{
loop {
let token_start = i.checkpoint();
let mut err = match parser.parse_next(i) {
Ok(o) => {
return Ok(o);

Check warning on line 988 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L984-L988

Added lines #L984 - L988 were not covered by tests
}
Err(ErrMode::Incomplete(e)) => return Err(ErrMode::Incomplete(e)),
Err(err) => err,

Check warning on line 991 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L990-L991

Added lines #L990 - L991 were not covered by tests
};
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 {

Check warning on line 997 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L993-L997

Added lines #L993 - L997 were not covered by tests
// Didn't advance so bubble the error up
} else if let Err(err_) = i.record_err(&token_start, &err_start, err) {
err = err_;

Check warning on line 1000 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L999-L1000

Added lines #L999 - L1000 were not covered by tests
} else {
continue;

Check warning on line 1002 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L1002

Added line #L1002 was not covered by tests
}
}

i.reset(err_start.clone());
err = err.map(|err| E::from_recoverable_error(&token_start, &err_start, i, err));
return Err(err);

Check warning on line 1008 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L1006-L1008

Added lines #L1006 - L1008 were not covered by tests
}
}

/// Implementation of [`Parser::resume_after`]
#[cfg(feature = "unstable-recover")]
#[cfg_attr(nightly, warn(rustdoc::missing_doc_code_examples))]
pub struct ResumeAfter<P, R, I, O, E, ER>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: FromRecoverableError<I, E>,
ER: FromRecoverableError<I, E>,
{
parser: P,
recover: R,
i: core::marker::PhantomData<I>,
o: core::marker::PhantomData<O>,
e: core::marker::PhantomData<E>,
er: core::marker::PhantomData<ER>,
}

#[cfg(feature = "unstable-recover")]
impl<P, R, I, O, E, ER> ResumeAfter<P, R, I, O, E, ER>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: FromRecoverableError<I, E>,
ER: FromRecoverableError<I, E>,
{
#[inline(always)]
pub(crate) fn new(parser: P, recover: R) -> Self {

Check warning on line 1043 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L1043

Added line #L1043 was not covered by tests
Self {
parser,
recover,
i: Default::default(),
o: Default::default(),
e: Default::default(),
er: Default::default(),

Check warning on line 1050 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L1047-L1050

Added lines #L1047 - L1050 were not covered by tests
}
}
}

#[cfg(feature = "unstable-recover")]
impl<P, R, I, O, E, ER> Parser<I, Option<O>, E> for ResumeAfter<P, R, I, O, E, ER>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: FromRecoverableError<I, E>,
ER: FromRecoverableError<I, E>,
{
#[inline(always)]
fn parse_next(&mut self, i: &mut I) -> PResult<Option<O>, E> {
if I::is_recovery_supported() {
resume_after_inner::<P, R, I, O, E, ER>(&mut self.parser, &mut self.recover, i)

Check warning on line 1068 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L1066-L1068

Added lines #L1066 - L1068 were not covered by tests
} else {
self.parser.parse_next(i).map(Some)

Check warning on line 1070 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L1070

Added line #L1070 was not covered by tests
}
}
}

#[cfg(feature = "unstable-recover")]
fn resume_after_inner<P, R, I, O, E, ER>(
parser: &mut P,
recover: &mut R,
i: &mut I,
) -> PResult<Option<O>, E>
where
P: Parser<I, O, E>,
R: Parser<I, (), E>,
I: Stream,
I: Recover<E>,
E: FromRecoverableError<I, E>,
ER: FromRecoverableError<I, E>,
{
let token_start = i.checkpoint();
let mut err = match parser.parse_next(i) {
Ok(o) => {
return Ok(Some(o));

Check warning on line 1092 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L1089-L1092

Added lines #L1089 - L1092 were not covered by tests
}
Err(ErrMode::Incomplete(e)) => return Err(ErrMode::Incomplete(e)),
Err(err) => err,

Check warning on line 1095 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L1094-L1095

Added lines #L1094 - L1095 were not covered by tests
};
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_;

Check warning on line 1100 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L1097-L1100

Added lines #L1097 - L1100 were not covered by tests
} else {
return Ok(None);

Check warning on line 1102 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L1102

Added line #L1102 was not covered by tests
}
}

i.reset(err_start.clone());
err = err.map(|err| E::from_recoverable_error(&token_start, &err_start, i, err));
Err(err)

Check warning on line 1108 in src/combinator/parser.rs

View check run for this annotation

Codecov / codecov/patch

src/combinator/parser.rs#L1106-L1108

Added lines #L1106 - L1108 were not covered by tests
}
60 changes: 60 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,17 @@ pub trait AddContext<I, C = &'static str>: Sized {
}
}

/// Capture context from when an error was recovered
pub trait FromRecoverableError<I: Stream, E> {
/// Capture context from when an error was recovered
fn from_recoverable_error(
token_start: &<I as Stream>::Checkpoint,
err_start: &<I as Stream>::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.
Expand Down Expand Up @@ -387,6 +398,18 @@ impl<I: Clone> ParserError<I> for InputError<I> {

impl<I: Clone, C> AddContext<I, C> for InputError<I> {}

impl<I: Clone + Stream> FromRecoverableError<I, Self> for InputError<I> {
#[inline]
fn from_recoverable_error(
_token_start: &<I as Stream>::Checkpoint,
_err_start: &<I as Stream>::Checkpoint,
_input: &I,
e: Self,
) -> Self {
e

Check warning on line 409 in src/error.rs

View check run for this annotation

Codecov / codecov/patch

src/error.rs#L409

Added line #L409 was not covered by tests
}
}

impl<I: Clone, E> FromExternalError<I, E> for InputError<I> {
/// Create a new error from an input position and an external error
#[inline]
Expand Down Expand Up @@ -446,6 +469,18 @@ impl<I> ParserError<I> for () {

impl<I, C> AddContext<I, C> for () {}

impl<I: Stream> FromRecoverableError<I, Self> for () {
#[inline]
fn from_recoverable_error(
_token_start: &<I as Stream>::Checkpoint,
_err_start: &<I as Stream>::Checkpoint,
_input: &I,
e: Self,
) -> Self {
e

Check failure

Code scanning / clippy

consider adding a ; to the last statement for consistent formatting Error

consider adding a ; to the last statement for consistent formatting

Check warning on line 480 in src/error.rs

View check run for this annotation

Codecov / codecov/patch

src/error.rs#L480

Added line #L480 was not covered by tests
}
}

impl<I, E> FromExternalError<I, E> for () {
#[inline]
fn from_external_error(_input: &I, _kind: ErrorKind, _e: E) -> Self {}
Expand Down Expand Up @@ -536,6 +571,18 @@ impl<C, I> AddContext<I, C> for ContextError<C> {
}
}

impl<I: Stream, C> FromRecoverableError<I, Self> for ContextError<C> {
#[inline]
fn from_recoverable_error(
_token_start: &<I as Stream>::Checkpoint,
_err_start: &<I as Stream>::Checkpoint,
_input: &I,
e: Self,
) -> Self {
e

Check warning on line 582 in src/error.rs

View check run for this annotation

Codecov / codecov/patch

src/error.rs#L582

Added line #L582 was not covered by tests
}
}

#[cfg(feature = "std")]
impl<C, I, E: std::error::Error + Send + Sync + 'static> FromExternalError<I, E>
for ContextError<C>
Expand Down Expand Up @@ -863,6 +910,19 @@ where
}
}

#[cfg(feature = "std")]
impl<I: Clone + Stream, C> FromRecoverableError<I, Self> for TreeError<I, C> {
#[inline]
fn from_recoverable_error(
_token_start: &<I as Stream>::Checkpoint,
_err_start: &<I as Stream>::Checkpoint,
_input: &I,
e: Self,
) -> Self {
e

Check warning on line 922 in src/error.rs

View check run for this annotation

Codecov / codecov/patch

src/error.rs#L922

Added line #L922 was not covered by tests
}
}

#[cfg(feature = "std")]
impl<I, C, E: std::error::Error + Send + Sync + 'static> FromExternalError<I, E> for TreeError<I, C>
where
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 1f81aa7

Please sign in to comment.