Nom 5: создание комбинатора с использованием другого парсера несколько раз - PullRequest
0 голосов
/ 02 июля 2019

Предположим, я хочу создать комбинатор, который несколько раз использует другой синтаксический анализатор, например, для анализа строки, разделенной двумя типами кавычек:

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,
    )
}

Этот синтаксический анализатор, как и ожидалось, не скомпилируется сошибка «использования перемещенного значения»:

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

Однако я не могу просто добавить Copy или Clone к F границам: множество парсеров, в частности, возвращаютсяВстроенные функции Nom не реализуют ни Clone, ни Copy.Я также не могу использовать &f в качестве аргумента для tuple, потому что тогда это будет ошибкой проверки заимствования (f - это временное локальное значение, поэтому невозможно вернуть созданный с ним анализатор).

Единственный способ, которым я вижу это, - это на самом деле переопределить логику alt непосредственно в функции, развернув ее в последовательности вложенных операторов match, но это кажется действительно неоптимальным.Или, может быть, я упускаю что-то простое, и на самом деле можно делать то, что я хочу, используя только комбинаторы?

Я почти уверен, что есть лучший способ написать именно комбинатор quoted, как описановыше, и было бы неплохо, если бы кто-то показал это, но мой вопрос более общий: как мне написать комбинаторы, которые повторно используют один и тот же парсер?

1 Ответ

3 голосов
/ 02 июля 2019

Простейшим способом было бы явное возвращение с закрытием:

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)
    }
}

Теперь из-за ключевого слова move значение f перемещено в замыкание. Затем внутри возвращаемого замыкания я вызываю сложный синтаксический анализатор напрямую, и из замыкания ничего не возвращается, кроме output / error, что означает, что я могу свободно использовать ссылки на f.

...