Vladimir Matveev
Vladimir Matveev

Reputation: 128041

Nom 5: creating a combinator using another parser multiple times

Suppose I want to create a combinator which uses another parser multiple times, for example, to parse a string delimited by two kinds of quotes:

fn quoted<'a, F: 'a, O, E: ParseError<&'a str>>(f: F) -> impl Fn(&'a str) -> IResult<&'a str, O, E>
where
    F: Fn(&'a str) -> IResult<&'a str, O, E>,
{
    map(
        alt((
            tuple((tag("'"), f, tag("'"))),
            tuple((tag("\""), f, tag("\"")))
        )),
        |(_, res, _)| res,
    )
}

This parser, as expected, fails to compile with the "use of moved value" error:

149 |     fn quoted<'a, F: 'a, O, E: ParseError<&'a str>>(f: F) -> impl Fn(&'a str) -> IResult<&'a str, O, E>
    |                   -                                 - move occurs because `f` has type `F`, which does not implement the `Copy` trait
    |                   |
    |                   consider adding a `Copy` constraint to this type argument
...
155 |                 tuple((tag("'"), f, tag("'"))),
    |                                  - value moved here
156 |                 tuple((tag("\""), f, tag("\"")))
    |                                   ^ value used here after move

However, I can't just add Copy or Clone to the F bounds: a lot of parsers, in particular, returned by Nom's builtin functions, do not implement neither Clone nor Copy. I can't also use &f as an argument to tuple, because then it would be a borrow checking error (f is a temporary local value, so it is not possible to return a parser constructed with it).

The only way I see doing this is actually reimplementing the alt logic directly in the function, by unrolling it in a sequence of nested match statements, but this seems really suboptimal. Or maybe I'm missing something simple, and it is actually possible to do what I want using combinators only?

I'm pretty sure that there is a better way of writing specifically the quoted combinator as described above, and it would be nice if someone shows it, but my question is more general - how do I write combinators which reuse the same parser?

Upvotes: 0

Views: 1288

Answers (1)

Vladimir Matveev
Vladimir Matveev

Reputation: 128041

The simplest way would be to be explicit with the returned closure:

fn quoted<'a, F: 'a, O, E: ParseError<&'a str>>(f: F) -> impl Fn(&'a str) -> IResult<&'a str, O, E>
where
    F: Fn(&'a str) -> IResult<&'a str, O, E>,
{
    move |i| {
        map(
            alt((
                tuple((tag("'"), &f, tag("'"))),
                tuple((tag("\""), &f, tag("\"")))
            )),
            |(_, res, _)| res,
        )(i)
    }
}

Now, because of the move keyword, the f value is moved into the closure. Then, inside the returned closure I invoke the complex parser combinator directly, and nothing except output/error is returned from the closure, which means I'm free to use references to f.

Upvotes: 5

Related Questions