Как сопоставить любой из Векторов строк с nom?

Я пытаюсь создать парсер с nom, который будет анализировать некоторый текст, который может быть одним из многих вариантов. Ном имеет alt! когда значения известны во время компиляции, но мои значения не будут.

Это была моя попытка создать свой собственный парсер, который может Vec<String> чтобы противостоять, и я сталкиваюсь с парой вопросов.

#[macro_use]
extern crate nom;

use nom::IResult;

fn alternative_wrapper<'a>(input: &'a [u8], alternatives: Vec<String>) -> IResult<&'a [u8], &'a [u8]> {
    for alternative in alternatives {
        // tag!("alternative");
        println!("{}", alternative);
    }
    return IResult::Done(input, "test".as_bytes());
}

#[test]
fn test_date() {
    let input = "May";
    named!(alternative, call!(alternative_wrapper));
    let months = vec!(
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December"
        ).iter().map(|s| s.to_string()).collect();
    println!("{:?}", alternative("May".as_bytes(), months));
}

Я знаю, что мой alternative_wrapper Функция на самом деле не делает ничего полезного, но это не проблема. Вот на что Руст жалуется на этот фрагмент:

error[E0061]: this function takes 1 parameter but 2 parameters were supplied
  --> src/parser.rs:32:34
   |
17 |     named!(alternative, call!(alternative_wrapper));
   |     ------------------------------------------------ defined here
...
32 |     println!("{:?}", alternative("May".as_bytes(), months));
   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^ expected 1 parameter
   |
   = note: this error originates in a macro outside of the current crate

error[E0061]: this function takes 2 parameters but 1 parameter was supplied
  --> src/parser.rs:17:5
   |
6  | / fn alternative_wrapper<'a>(input: &'a [u8], alternatives: Vec<String>) -> IResult<&'a [u8], &'a
[u8]> {
7  | |     for alternative in alternatives {
8  | |         // tag!("alternative");
9  | |         println!("{}", alternative);
10 | |     }
11 | |     return IResult::Done(input, "test".as_bytes());
12 | | }
   | |_- defined here
...
17 |       named!(alternative, call!(alternative_wrapper));
   |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected 2 parameters
   |
   = note: this error originates in a macro outside of the current crate

Как я могу создать парсер из моей функции? И как я могу использовать существующие парсеры, такие как tag! изнутри alternative_wrapper?

2 ответа

Начиная с ошибок, первая ошибка связана с named! только принимая один аргумент, а именно входную строку. named! объявит функцию для вас, в этом случае с подписью fn(&[u8]) -> IResult<&[u8],&[u8]>, Там нет никакой магии в отношении любых других аргументов, поэтому пытаясь передать ваши months вектор как второй аргумент не будет работать. Там есть вариант named! называется named_args! это может быть использовано для объявления функций с большим количеством аргументов, чем просто вход, который должен разобраться с этим.

Вторая ошибка похожа, но обращена вспять. Вы звоните alternative_wrapper только с входом и без вектора через call!, call! макрос может передавать аргументы, но вы должны делать это явно, т.е. call!(myparser, monts),

После выяснения причин ошибок вы спрашиваете, как создать парсер. А на самом деле, alternative_wrapper уже является синтаксическим анализатором nom по сигнатуре, но поскольку вы не объявили его с помощью макроса nom, никакой магической передачи не происходит, поэтому tag! не работает в теле функции, когда вы пытались.

Чтобы использовать другие комбинаторы в функции, которую вы объявили сами, вы должны передать ввод вручную в самый внешний макрос. В этом случае это только tag!, но если бы вы использовали, скажем, do_parse! а затем несколько макросов в этом, вам нужно только передать ввод do_parse!, Я предоставлю рабочую версию с несколькими дополнительными настройками:

#[macro_use]
extern crate nom;

use std::str;
use nom::IResult;

fn alternative<'a>(input: &'a [u8], alternatives: &Vec<String>) -> IResult<&'a [u8], &'a [u8]> {
    for alternative in alternatives {
        match tag!(input, alternative.as_bytes()) {
            done@IResult::Done(..) => return done,
            _ => () // continue
        }
    }
    IResult::Error(nom::ErrorKind::Tag) // nothing found.
}

fn main() {
    let months: Vec<String> = vec![
        "January", "February", "March", "April", "May", "June", "July",
        "August", "September", "October", "November", "December"
    ].into_iter().map(String::from).collect();

    fn print_res(r: IResult<&[u8],&[u8]>) {
        println!("{:?}", r);
        println!("{:?}\n", str::from_utf8(r.unwrap().1).unwrap());
    }
    print_res(alternative(b"May", &months));
    print_res(alternative(b"August", &months));
    print_res(alternative(b"NoGood", &months));
}

Вы можете проверить это на ржавой детской площадке.

Я не очень знаком с Nom и до сих пор изучаю Rust, но в прошлом я использовал комбинаторы парсеров.

Осторожно, похоже, что named! макрос генерирует функцию, которая принимает только один параметр, строку для анализа.

Чтобы оправдать ожидания Nom, я думаю, что я бы посмотрел на письмо alternative_wrapper как функция, которая возвращает функцию вместо. Тест в конечном итоге будет выглядеть так:

#[test]
fn test_date() {
    let months = vec!(
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December"
        ).iter().map(|s| s.to_string()).collect();
    let parser = generate_alternative_parser(months);
    named!(alternative, call!(parser));
    println!("{:?}", alternative("May".as_bytes()));
}

Похоже, вам нужно построить alt! выражение от tag!с, но из документов не сразу видно, как ты это сделаешь.

Откуда в конечном итоге ваш список вариантов?

В зависимости от того, что именно вы пытаетесь достичь, могут быть и другие способы выполнения того, что вы пытаетесь сделать. Например, вы можете проанализировать любое слово, а затем проверить его по одному из ваших вариантов.

В Nom 4 приведен полностью общий ввод, который работает независимо от того, на чем работает ваш парсер:

/// Dynamic version of `alt` that takes a slice of strings
fn alternative<T>(input: T, alternatives: &[&'static str]) -> IResult<T, T>
where
    T: InputTake,
    T: Compare<&'static str>,
    T: InputLength,
    T: AtEof,
    T: Clone,
{
    let mut last_err = None;
    for alternative in alternatives {
        let inp = input.clone();
        match tag!(inp, &**alternative) {
            done @ Ok(..) => return done,
            err @ Err(..) => last_err = Some(err), // continue
        }
    }
    last_err.unwrap()
}

/// Usage
named!(test<Span, Span>,
    call!(alternative, &["a", "b", "c"])
);
Другие вопросы по тегам