【问题标题】:How do I write combinators for my own parsers in Rust?如何在 Rust 中为我自己的解析器编写组合器?
【发布时间】:2020-02-11 17:01:52
【问题描述】:

this video 的启发,我认为一个小的解析器组合库将是学习 Rust 中的字符串、借用和键入的好方法——而且到目前为止。

我设法让一个字符解析器和一个数字解析器工作:

pub enum Parsed<'a, T> {
    Some(T, &'a str),
    None(&'a str),
}

impl<T> Parsed<'_, T> {
    // I was neither sure with the third & before the T...
    pub fn unwrap(&self) -> (&T, &str) {
        match self {
            // ... nor with the first one here.
            Parsed::Some(head, tail) => (&head, &tail),
            _ => panic!("Called unwrap on nothing."),
        }
    // But this was the only way that I came up with that compiled.
    }

    pub fn is_none(&self) -> bool {
        match self {
            Parsed::None(_) => true,
            _ => false,
        }
    }
}

pub fn parse<T>(what: fn(&str) -> Parsed<T>, input: &str) -> Parsed<T> {
    what(input)
}

pub fn char(input: &str) -> Parsed<char> {
    match input.chars().next() {
        Some(c) => Parsed::Some(c, &input[1..]),
        None => Parsed::None(input),
    }
}

pub fn digit(input: &str) -> Parsed<u8> {
    match input.chars().next() {
        Some(d @ '0'..='9') => Parsed::Some(d as u8 - ('0' as u8), &input[1..]),
        _ => Parsed::None(input),
    }
}

然后我想求助于组合器,这里是some 以获得给定解析器的任意数量的匹配项。那一个对我打击很大。这是我一开始的版本,能够完成一些单元测试:

pub fn some<T>(input: &str, parser: fn(&str) -> Parsed<T>) -> Parsed<Vec<T>> {
    let mut re = Vec::new();
    let mut pos = input;
    loop {
        match parser(pos) {
            Parsed::Some(head, tail) => {
                re.push(head);
                pos = tail;
            }
            Parsed::None(_) => break,
        }
    }
    Parsed::Some(re, pos)
}

但是为了能够将它与parse::parse 一起使用,它只需要一个解析器函数并返回一个。我尝试了很多变种:

  • fn(&amp;str) -&gt; Parsed&lt;T&gt; 作为返回类型
  • impl Fn(&amp;str) -&gt; Parsed&lt;T&gt; 作为返回类型
  • impl FnOnce(&amp;str) -&gt; Parsed&lt;T&gt; 作为返回类型
  • 编译器吐出的几个for&lt;'r&gt; something 我什至不明白
  • 将代码打包到闭包中并返回,无论有无move

Rust 总是至少有一句话不满意。现在我不知道该尝试什么了。测试代码如下:


#[test]
fn test() {
    assert_eq!(char("foo").unwrap(), (&'f', "oo"));
    assert!(parse(digit, "foo").is_none());
    assert_eq!(parse(digit, "9foo").unwrap(), (&9, "foo"));
    assert_eq!(
        parse(some(digit), "12space").unwrap(),
        (&vec![1, 2], "space")
    );
}

这是playground的链接。

【问题讨论】:

    标签: parsing types rust composition


    【解决方案1】:

    通过返回闭包返回实现Fn* 特征之一的匿名类型:

    fn some<T>(parser: impl Fn(&str) -> Parsed<T>) -> impl FnOnce(&str) -> Parsed<Vec<T>> {
        move |input| {
            let mut re = Vec::new();
            let mut pos = input;
            loop {
                match parser(pos) {
                    Parsed::Some(head, tail) => {
                        re.push(head);
                        pos = tail;
                    }
                    Parsed::None(_) => break,
                }
            }
            Parsed::Some(re, pos)
        }
    }
    

    Playground

    请注意,我已经从函数指针切换到参数的泛型类型:

    fn some<T>(parser: fn(&str) -> Parsed<T>) // before
    fn some<T>(parser: impl Fn(&str) -> Parsed<T>) // after
    

    我提倡为您的所有功能都这样做,以便您拥有一致且可连接的 API。 这是许多解析库采用的模式,包括我自己的peresil

    另见:

    【讨论】:

    • 我很确定我也有这个版本。当我尝试它时,我得到expected fn pointer, found opaque type 部分some(digit)。还有那件事我根本不明白:for&lt;'r&gt; fn(&amp;'r str) -&gt; parse::Parsed&lt;'r, _&gt;
    • @primfaktor 抱歉,但我提供的代码可以与您提供的代码一起使用(当然,忽略您明确决定不提供的代码)。我添加了一个指向操场的链接,显示它正在工作。
    • @primfaktor 看起来像“从函数指针切换到泛型类型”是您需要做的。专门针对 parse 函数(在此示例中它本身似乎并不真正有用):pub fn parse&lt;T&gt;(what: impl FnOnce(&amp;str) -&gt; Parsed&lt;T&gt;, input: &amp;str) -&gt; Parsed&lt;T&gt;
    • Fn 函数中的 parse 是罪魁祸首。谢谢你。你想相应地说出你的答案以便我接受吗?
    猜你喜欢
    • 2014-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多