Is there a way to dynamically `alt` an iterator of parsers?

82 Views Asked by At

I'm looking to create a function like alt that instead of consuming parsers from a tuple, it consumes parsers from an iterator.

The trouble I'm having is with the bound FnMut on the parameterized parsers. Parser is implemented for FnMut, but not Fn. FnMut is not cloneable, which means I have to put it in a Box of some sort and then mutably borrow from it.

Here is the signature I would start off with:

use nom::{Parser,error::ParseError};

fn alt<I, O, E, P>(parsers: impl IntoIterator<Item = P>) -> impl Parser<I, O, E>
where
    P: Parser<I, O, E>
{
    move |input| {
        // implementation
    }
}

This implementation works, but only when everything is mutable:

use nom::{error::ParseError, InputIter, InputTake, Parser};

pub fn alt_many<I, O, E, P>(mut parsers: Vec<&mut P>) -> impl Parser<I, O, E> + '_
where
    P: Parser<I, O, E>,
    I: InputIter + InputTake + Copy,
    E: ParseError<I>,
{
    move |mut input| {
        use nom::bytes::complete::take;
        use nom::combinator::fail;

        let mut value: Option<O> = None;

        for parser in &mut parsers {
            if let Ok((input_, o)) = parser.parse(input) {
                input = input_;
                value = Some(o);
                break;
            } else {
                (input, _) = take::<u8, I, E>(1)(input)?;
            };
        }

        value
            .map(|output| Ok((input, output)))
            .unwrap_or_else(|| fail::<I, O, E>(input))
    }
}

Are there any other possible solutions available?

1

There are 1 best solutions below

2
cafce25 On

Assuming you want the actual behavior of alt instead of your different one with the take you can write alt_many like this:

pub fn alt_many<I, O, E, P, Ps>(mut parsers: Ps) -> impl Parser<I, O, E>
where
    P: Parser<I, O, E>,
    I: Clone,
    for<'a> &'a mut Ps: IntoIterator<Item = &'a mut P>,
    E: ParseError<I>,
{
    move |input: I| { 
        for parser in &mut parsers {
            if let r@Ok(_) = parser.parse(input.clone()) {
                return r;
            }
        }
        nom::combinator::fail::<I, O, E>(input)
    }
}