规范 5:多次使用另一个解析器创建组合器

Vla*_*eev 0 parsing parser-combinators rust nom

假设我想创建一个多次使用另一个解析器的组合器,例如,解析由两种引号分隔的字符串:

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,
    )
}
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,该解析器无法编译并出现“使用移动值”错误:

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,
    )
}
Run Code Online (Sandbox Code Playgroud)

但是,我不能只将Copyor添加CloneF边界:许多解析器,特别是由 Nom 的内置函数返回的解析器,既不实现也不实现CloneCopy。我也不能用作&f的参数tuple,因为那样的话它将是借用检查错误(f是临时本地值,因此不可能返回用它构造的解析器)。

我认为这样做的唯一方法实际上是alt直接在函数中重新实现逻辑,通过将其展开为一系列嵌套match语句,但这似乎确实不是最理想的。或者也许我错过了一些简单的东西,实际上只使用组合器就可以做我想做的事情?

我很确定有一种更好的方法来专门编写quoted如上所述的组合器,如果有人展示它那就太好了,但我的问题更笼统 - 如何编写重用相同解析器的组合器?

Vla*_*eev 5

最简单的方法是明确返回的闭包:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,由于move关键字的存在,该f值被移至闭包中。然后,在返回的闭包内,我直接调用复杂解析器组合器,并且除了输出/错误之外,闭包不会返回任何内容,这意味着我可以自由地使用对f.