parsing - 如何在 Rust 中为我自己的解析器编写组合器?

标签 parsing types rust composition

灵感来自 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(&str) -> Parsed<T>作为返回类型
  • impl Fn(&str) -> Parsed<T>作为返回类型
  • impl FnOnce(&str) -> Parsed<T>作为返回类型
  • 几个for<'r> 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 的链接.

最佳答案

通过返回一个闭包返回一个实现了 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 .

另见:

关于parsing - 如何在 Rust 中为我自己的解析器编写组合器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60173959/

相关文章:

python - 如何使用 python 使 html 解析更具性能

python - Python如何在同一行输入多个数值类型

c - 以下在编程上下文中是什么意思,特别是 C 编程语言?

import - 如何从同级模块导入?

rust - 将可变特征对象引用移动到框中

c - 我如何实现解析?

java - Java 字节码有通用解析器吗?

解构赋值中的 typescript 松散打字

rust - 为什么我的结构生命周期不够长?

c# 无法解析 xml,收到错误 463