Как мне написать комбинаторы для моих собственных парсеров в Rust? - PullRequest
2 голосов
/ 11 февраля 2020

Вдохновленный этим видео , я подумал, что небольшая библиотека комбинатора синтаксического анализатора будет хорошим способом узнать о строках, заимствованиях и вводе в Rust - и это было до сих пор.

I удалось заставить работать синтаксический анализатор и di git:

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
* 1031 и без него всегда хотя бы одну строчку, которой Руст не был доволен. Теперь я не знаю, что попробовать больше. Код тестирования выглядит следующим образом:

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

Вот ссылка на игровую площадку .

1 Ответ

1 голос
/ 11 февраля 2020

Возвращает анонимный тип, который реализует одну из черт 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

Обратите внимание, что я перешел с указатели на функции для типов c для аргументов:

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

Я рекомендую делать это для всех ваших функций, чтобы у вас был согласованный и подключаемый API. Это шаблон, принятый многими библиотеками синтаксического анализа, включая мою собственную peresil .

См. Также:

...