Skip to content

Commit

Permalink
Change context to pass output mode information to the child parser. (#…
Browse files Browse the repository at this point in the history
…1743)

Co-authored-by: Geoffroy Couprie <contact@geoffroycouprie.com>
  • Loading branch information
kstrohbeck and Geal authored May 5, 2024
1 parent 1f74da5 commit cea6595
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 17 deletions.
9 changes: 6 additions & 3 deletions examples/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ fn string<'a, E: ParseError<&'a str> + ContextError<&'a str>>(
context(
"string",
preceded(char('\"'), cut(terminated(parse_str, char('\"')))),
)(i)
)
.parse(i)
}

/// some combinators, like `separated_list0` or `many0`, will call a parser repeatedly,
Expand All @@ -116,7 +117,8 @@ fn array<'a, E: ParseError<&'a str> + ContextError<&'a str>>(
preceded(sp, char(']')),
)),
),
)(i)
)
.parse(i)
}

fn key_value<'a, E: ParseError<&'a str> + ContextError<&'a str>>(
Expand Down Expand Up @@ -150,7 +152,8 @@ fn hash<'a, E: ParseError<&'a str> + ContextError<&'a str>>(
preceded(sp, char('}')),
)),
),
)(i)
)
.parse(i)
}

/// here, we apply the space parser before trying to parse a value
Expand Down
9 changes: 6 additions & 3 deletions examples/json_iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ fn string(i: &str) -> IResult<&str, &str> {
context(
"string",
preceded(char('\"'), cut(terminated(parse_str, char('\"')))),
)(i)
)
.parse(i)
}

fn boolean(input: &str) -> IResult<&str, bool> {
Expand All @@ -234,7 +235,8 @@ fn array(i: &str) -> IResult<&str, ()> {
preceded(sp, char(']')),
)),
),
)(i)
)
.parse(i)
}

fn key_value(i: &str) -> IResult<&str, (&str, ())> {
Expand All @@ -251,7 +253,8 @@ fn hash(i: &str) -> IResult<&str, ()> {
preceded(sp, char('}')),
)),
),
)(i)
)
.parse(i)
}

fn value(i: &str) -> IResult<&str, ()> {
Expand Down
97 changes: 86 additions & 11 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Parsers are generic over their error type, requiring that it implements
//! the `error::ParseError<Input>` trait.

use crate::internal::Parser;
use crate::internal::{Mode, OutputMode, PResult, Parser};
use crate::lib::std::fmt;

#[cfg(feature = "alloc")]
Expand Down Expand Up @@ -283,18 +283,37 @@ use crate::internal::{Err, IResult};
/// Create a new error from an input position, a static string and an existing error.
/// This is used mainly in the [context] combinator, to add user friendly information
/// to errors when backtracking through a parse tree
pub fn context<I: Clone, E: ContextError<I>, F, O>(
pub fn context<F>(context: &'static str, parser: F) -> Context<F> {
Context { context, parser }
}

/// Parser implementation for [context]
pub struct Context<F> {
context: &'static str,
mut f: F,
) -> impl FnMut(I) -> IResult<I, O, E>
parser: F,
}

impl<I, F> Parser<I> for Context<F>
where
F: Parser<I, Output = O, Error = E>,
I: Clone,
F: Parser<I>,
<F as Parser<I>>::Error: ContextError<I>,
{
move |i: I| match f.parse(i.clone()) {
Ok(o) => Ok(o),
Err(Err::Incomplete(i)) => Err(Err::Incomplete(i)),
Err(Err::Error(e)) => Err(Err::Error(E::add_context(i, context, e))),
Err(Err::Failure(e)) => Err(Err::Failure(E::add_context(i, context, e))),
type Output = <F as Parser<I>>::Output;
type Error = <F as Parser<I>>::Error;

fn process<OM: OutputMode>(&mut self, input: I) -> PResult<OM, I, Self::Output, Self::Error> {
match self.parser.process::<OM>(input.clone()) {
Err(Err::Error(e)) => Err(Err::Error(OM::Error::map(e, |e| {
<F as Parser<I>>::Error::add_context(input, self.context, e)
}))),
Err(Err::Failure(e)) => Err(Err::Failure(<F as Parser<I>>::Error::add_context(
input,
self.context,
e,
))),
x => x,
}
}
}

Expand Down Expand Up @@ -670,11 +689,67 @@ where
}

#[cfg(test)]
#[cfg(feature = "alloc")]
mod tests {
use super::*;
use crate::character::complete::char;

Check warning on line 694 in src/error.rs

View workflow job for this annotation

GitHub Actions / Test (nightly, --no-default-features)

unused import: `crate::character::complete::char`

Check warning on line 694 in src/error.rs

View workflow job for this annotation

GitHub Actions / Test (stable, --no-default-features)

unused import: `crate::character::complete::char`

#[test]
fn context_test() {
use crate::{character::char, combinator::cut, internal::Needed};

#[derive(Debug, PartialEq)]
struct Error<I> {
input: I,
ctx: Option<&'static str>,
}

impl<I> ParseError<I> for Error<I> {
fn from_error_kind(input: I, _kind: ErrorKind) -> Self {
Self { input, ctx: None }
}

fn append(input: I, _kind: ErrorKind, other: Self) -> Self {
Self {
input,
ctx: other.ctx,
}
}
}

impl<I> ContextError<I> for Error<I> {
fn add_context(input: I, ctx: &'static str, _other: Self) -> Self {
Self {
input,
ctx: Some(ctx),
}
}
}

assert_eq!(
context("ctx", char::<_, Error<_>>('a')).parse("abcd"),
Ok(("bcd", 'a'))
);
assert_eq!(
context("ctx", char::<_, Error<_>>('a')).parse(""),
Err(Err::Incomplete(Needed::new(1)))
);
assert_eq!(
context("ctx", char::<_, Error<_>>('a')).parse_complete(""),
Err(Err::Error(Error {
input: "",
ctx: Some("ctx")
}))
);
assert_eq!(
context("ctx", cut(char::<_, Error<_>>('a'))).parse("bcd"),
Err(Err::Failure(Error {
input: "bcd",
ctx: Some("ctx")
}))
);
}

#[cfg(feature = "alloc")]
#[test]
fn convert_error_panic() {
let input = "";
Expand Down

0 comments on commit cea6595

Please sign in to comment.