如何在Rust中为自己的解析器编写组合器? [英] How do I write combinators for my own parsers in Rust?

查看:81
本文介绍了如何在Rust中为自己的解析器编写组合器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这部影片的启发,我认为一个解析器组合器库将是一个到目前为止,学习字符串,借用和在Rust中打字的好方法。

Inspired by this video, I thought a little parser combinator library would be a good way to learn about strings, borrowing and typing in Rust—and it was so far.

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

I managed to get a char parser and a digit parser to work:

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 可以获取给定解析器的任意数量的匹配项。那个人让我很受打击。这是我刚开始可以完成一些单元测试的版本:

Then I wanted to turn to combinators, here some to get an arbitrary number of matches for a given parser. That one hit me hard. This is the version I had in the beginning that was able to fulfill some unit tests:

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 一起使用,它只需要一个解析器函数并返回一个。我尝试了很多变体:

But to be able to use it with parse::parse it has to take only a parser function and return one. I tried so many variants:


  • fn(& str)->将< T> 解析为返回类型

  • impl Fn(& str)->将< T> 解析为返回类型

  • impl FnOnce(& str)->将< T> 解析为返回类型

  • 多个 c 编译器吐出来的东西,我什至都不明白

  • 将代码打包到一个闭包中,并在有和没有的情况下返回移动

  • fn(&str) -> Parsed<T> as return type
  • impl Fn(&str) -> Parsed<T> as return type
  • impl FnOnce(&str) -> Parsed<T> as return type
  • several for<'r> something which the compiler spat out and I don't even understand
  • packing the code into a closure and returning that, with and without move

Rust总是至少有一行不满意。现在我不知道该怎么办了。测试代码如下:

There was always at least one line that Rust wasn't happy with. Now I don't know what to try anymore. The testing code looks like this:


#[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")
    );
}

这是游乐场

推荐答案

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

Return an anonymous type that implements one of the Fn* traits by returning a closure:

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

游乐场

请注意,我已将函数指针从函数指针切换为通用类型:

Note that I've switched from function pointers to generic types for the arguments:

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

I之后提倡对所有功能执行此操作,以使您拥有一致且可连接的API。
这是很多解析库采用的模式,包括我自己的 peresil

I advocate doing this for all of your functions so that you have a consistent and connectable API. This is the pattern taken by many parsing libraries, including my own peresil.

另请参见:

  • How do you pass a Rust function as a parameter?
  • Why does passing a closure to function which accepts a function pointer not work?

这篇关于如何在Rust中为自己的解析器编写组合器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆