【问题标题】:How to match any of a Vector of strings with nom?如何将任何字符串向量与 nom 匹配?
【发布时间】:2017-11-10 16:08:23
【问题描述】:

我正在尝试使用 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 函数实际上并没有做任何有用的事情,但这不是问题所在。这就是 Rust 对这个 sn-p 的抱怨:

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

如何从我的函数中创建解析器?以及如何在alternative_wrapper 中使用现有的解析器,例如tag!

【问题讨论】:

    标签: parsing rust nom


    【解决方案1】:

    从错误开始,第一个错误是由于named! 只接受一个参数,即输入字符串。 named! 将为您声明一个函数,在这种情况下,签名为 fn(&amp;[u8]) -&gt; IResult&lt;&amp;[u8],&amp;[u8]&gt;。任何其他参数都没有魔法,因此尝试将您的 months 向量作为第二个参数传递是行不通的。 named! 的一个变体称为named_args!,可用于声明具有更多参数的函数,而不仅仅是应该对其进行排序的输入。

    第二个错误类似但相反。您通过call! 调用alternative_wrapper,只有输入,没有向量。 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));
    }
    

    您可以在rust playground 中查看。

    【讨论】:

      【解决方案2】:

      我对 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()));
      }
      

      您似乎需要从 tag!s 构造一个 alt! 表达式,但从文档中我看不出您将如何做到这一点。

      您的选项列表最终来自哪里?

      根据您想要完成的具体任务,可能还有其他一些方法可以完成您想要完成的任务。例如,您可能能够解析任何单词,然后根据您的选项之一对其进行验证。

      【讨论】:

        【解决方案3】:

        对于 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"])
        );
        

        【讨论】:

          猜你喜欢
          • 2020-05-17
          • 1970-01-01
          • 2021-12-09
          • 2014-10-12
          • 1970-01-01
          • 2021-04-28
          • 1970-01-01
          • 1970-01-01
          • 2011-01-16
          相关资源
          最近更新 更多