rust-bakery / nom

Rust parser combinator framework
MIT License
9.18k stars 792 forks source link

Allow `verify` to take verification function `FnMut`? #1636

Open vwkd opened 1 year ago

vwkd commented 1 year ago

I'm wondering if it's possible for verify to allow a verification function that can mutate its captured variables?

This would allow verify to do stateful parsing since it can use a captured variable to keep track of state between runs.

Currently, we can parse "blindly" and then validate the result afterwards, perhaps in a map_res. However, this loses error information since the error can't be reported when it happens but only after parsing is done.

From local testing it seems that making the G argument mutable (mut second: G, combinator/mod.rs#L424) and accepting a FnMut (G: FnMut(&O2) -> bool combinator/mod.rs#L428) compiles, though I don't know if there are any other implications to consider?

Test case

Here's a parser that counts up consecutive integers separated by space, e.g. 1 2 3 4 5.

It fails if it encounters an integer that's not in the right order, e.g. 1 2 9 4 5.

use nom::{
    character::complete::{char, digit1},
    combinator::map_res,
    error::{ErrorKind, ParseError},
    multi::separated_list1,
    IResult, Parser,
};
use std::borrow::Borrow;

fn validated(input: &str) -> IResult<&str, Vec<u32>> {
    let mut current_index = 1;

    let index = map_res(digit1, |s: &str| s.parse::<u32>());

    let index_verified = verify(index, |digit| {
        if digit == &current_index {
            current_index += 1;
            true
        } else {
            false
        }
    });

    let tmp = separated_list1(char(' '), index_verified)(input);
    tmp
}

fn main() {
    let input = "1 2 3 4 5";
    let o1 = validated(input);
    println!("{:?}", o1);

    let input = "1 2 9 4 5";
    let o2 = validated(input);
    println!("{:?}", o2);
}

/// [`verify`] with proposed updated argument `G`
pub fn verify<I: Clone, O1, O2, E: ParseError<I>, F, G>(
    mut first: F,
    mut second: G,
) -> impl FnMut(I) -> IResult<I, O1, E>
where
    F: Parser<I, O1, E>,
    G: FnMut(&O2) -> bool,
    O1: Borrow<O2>,
    O2: ?Sized,
{
    move |input: I| {
        let i = input.clone();
        let (input, o) = first.parse(input)?;

        if second(o.borrow()) {
            Ok((input, o))
        } else {
            Err(nom::Err::Error(E::from_error_kind(i, ErrorKind::Verify)))
        }
    }
}

Version