Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New combinator: multi::reduce #1578

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ pub enum ErrorKind {
Float,
Satisfy,
Fail,
Reduce,
}

#[rustfmt::skip]
Expand Down Expand Up @@ -477,6 +478,7 @@ pub fn error_to_u32(e: &ErrorKind) -> u32 {
ErrorKind::Float => 73,
ErrorKind::Satisfy => 74,
ErrorKind::Fail => 75,
ErrorKind::Reduce => 76,
}
}

Expand Down Expand Up @@ -539,6 +541,7 @@ impl ErrorKind {
ErrorKind::Float => "Float",
ErrorKind::Satisfy => "Satisfy",
ErrorKind::Fail => "Fail",
ErrorKind::Reduce => "Reduce",
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ mod tests {
#[cfg(target_pointer_width = "64")]
fn size_test() {
assert_size!(IResult<&[u8], &[u8], (&[u8], u32)>, 40);
assert_size!(IResult<&str, &str, u32>, 40);
assert_size!(IResult<&str, &str, u32>, 32);
assert_size!(Needed, 8);
assert_size!(Err<u32>, 16);
assert_size!(ErrorKind, 1);
Expand Down
145 changes: 93 additions & 52 deletions src/multi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,56 @@ where
}
}

/// Helper function
///
/// Applies the supplied parser `f` repeatedly, combining it with an initial result by merging it
/// using `g`. If anything fails, `error_kind` is returned. This fu
///
/// This function does not take ownership of either the parser or the accumulator function so it can
/// be used from FnMut parser functions without consuming their embedded parsers.
///
/// # Arguments
/// - `input` input to work on
/// - `acc` accumulator value to work on
/// - `f` embedded parser to apply until error
/// - `g` accumulator function to add newly parsed data to the existing data
/// - `error_kind` error kind to return on failure
fn fold_many_helper<I, O, E, F, G, R>(
mut input: I,
mut acc: R,
f: &mut F,
g: &mut G,
error_kind: ErrorKind,
) -> IResult<I, R, E>
where
I: Clone + InputLength,
F: Parser<I, O, E>,
G: FnMut(R, O) -> R,
E: ParseError<I>,
{
loop {
let _input = input.clone();
let len = input.input_len();
match f.parse(_input) {
Err(Err::Error(_)) => {
break;
}
Err(e) => return Err(e),
Ok((i, o)) => {
// infinite loop check: the parser must always consume
if i.input_len() == len {
return Err(Err::Failure(E::from_error_kind(i, error_kind)));
}

acc = g(acc, o);
input = i;
}
}
}

Ok((input, acc))
}

/// Applies a parser until it fails and accumulates
/// the results using a given function and initial value.
/// # Arguments
Expand Down Expand Up @@ -653,32 +703,7 @@ where
H: FnMut() -> R,
E: ParseError<I>,
{
move |i: I| {
let mut res = init();
let mut input = i;

loop {
let i_ = input.clone();
let len = input.input_len();
match f.parse(i_) {
Ok((i, o)) => {
// infinite loop check: the parser must always consume
if i.input_len() == len {
return Err(Err::Error(E::from_error_kind(input, ErrorKind::Many0)));
}

res = g(res, o);
input = i;
}
Err(Err::Error(_)) => {
return Ok((input, res));
}
Err(e) => {
return Err(e);
}
}
}
}
move |i: I| fold_many_helper(i, init(), &mut f, &mut g, ErrorKind::Many0)
}

/// Applies a parser until it fails and accumulates
Expand Down Expand Up @@ -729,32 +754,7 @@ where
match f.parse(_i) {
Err(Err::Error(_)) => Err(Err::Error(E::from_error_kind(i, ErrorKind::Many1))),
Err(e) => Err(e),
Ok((i1, o1)) => {
let mut acc = g(init, o1);
let mut input = i1;

loop {
let _input = input.clone();
let len = input.input_len();
match f.parse(_input) {
Err(Err::Error(_)) => {
break;
}
Err(e) => return Err(e),
Ok((i, o)) => {
// infinite loop check: the parser must always consume
if i.input_len() == len {
return Err(Err::Failure(E::from_error_kind(i, ErrorKind::Many1)));
}

acc = g(acc, o);
input = i;
}
}
}

Ok((input, acc))
}
Ok((i1, o1)) => fold_many_helper(i1, g(init, o1), &mut f, &mut g, ErrorKind::Many1),
}
}
}
Expand Down Expand Up @@ -990,3 +990,44 @@ where
Ok((input, res))
}
}

/// Applies a parser until it fails and accumulates the results into a single value. Fails if the
/// embedded parser does not succeed at least once.
///
/// This function is related to [`fold_many1`] in the same way [`Iterator::reduce`] is related to
/// [`Iterator::fold`].
///
/// # Arguments
/// * `f` The parser to apply.
/// * `g` The function that combines a result of `f` with the current accumulator.
/// ```rust
/// # use nom::{Err, error::{Error, ErrorKind}, Needed, IResult};
/// use nom::multi::reduce;
/// use nom::bytes::complete::tag;
/// use nom::sequence::terminated;
///
/// fn parser(s: &[u8]) -> IResult<&[u8], i32> {
/// reduce(
/// terminated(nom::character::complete::i32, nom::character::complete::newline),
/// Ord::max
/// )(s)
/// }
///
/// assert_eq!(parser(b"123\n456\n789\n"), Ok((&b""[..], 789)));
/// assert_eq!(parser(b"123\nfoo\nbar\n"), Ok((&b"foo\nbar\n"[..], 123)));
/// assert_eq!(parser(b"foo"), Err(Err::Error(Error::new(&b"foo"[..], ErrorKind::Reduce))));
/// assert_eq!(parser(b""), Err(Err::Error(Error::new(&b""[..], ErrorKind::Reduce))));
/// ```
pub fn reduce<I, O, E, F, G>(mut f: F, mut g: G) -> impl FnMut(I) -> IResult<I, O, E>
where
I: Clone + InputLength + InputTake,
F: Parser<I, O, E>,
G: FnMut(O, O) -> O,
E: ParseError<I>,
{
move |i: I| match f.parse(i.clone()) {
Err(Err::Error(_)) => Err(Err::Error(E::from_error_kind(i, ErrorKind::Reduce))),
Err(e) => Err(e),
Ok((i1, o1)) => fold_many_helper(i1, o1, &mut f, &mut g, ErrorKind::Reduce),
}
}
25 changes: 22 additions & 3 deletions src/multi/tests.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use super::{length_data, length_value, many0_count, many1_count};
use super::{length_data, length_value, many0_count, many1_count, reduce};
use crate::{
bytes::streaming::tag,
character::streaming::digit1 as digit,
error::{ErrorKind, ParseError},
internal::{Err, IResult, Needed},
lib::std::str::{self, FromStr},
number::streaming::{be_u16, be_u8},
sequence::{pair, tuple},
sequence::{pair, terminated, tuple},
};
#[cfg(feature = "alloc")]
use crate::{
Expand Down Expand Up @@ -429,7 +429,7 @@ fn fold_many0_test() {
assert_eq!(multi(&b""[..]), Err(Err::Incomplete(Needed::new(4))));
assert_eq!(
multi_empty(&b"abcdef"[..]),
Err(Err::Error(error_position!(
Err(Err::Failure(error_position!(
&b"abcdef"[..],
ErrorKind::Many0
)))
Expand Down Expand Up @@ -532,3 +532,22 @@ fn many1_count_test() {
)))
);
}

#[test]
fn reduce_test() {
fn max_num(i: &[u8]) -> IResult<&[u8], i32> {
reduce(
terminated(crate::character::complete::i32, tag(",")),
Ord::max,
)(i)
}

assert_eq!(max_num(&b"3,1,4,1,junk"[..]), Ok((&b"junk"[..], 4)));

assert_eq!(max_num(&b"42,"[..]), Ok((&b""[..], 42)));

assert_eq!(
max_num(&b"junk"[..]),
Err(Err::Error(error_position!(&b"junk"[..], ErrorKind::Reduce)))
)
}