Как анализировать слегка неоднозначные данные с помощью nom?

В RFC1738 BNF для domainlabel имеет следующий вид:

domainlabel = alphadigit | alphadigit * [alphadigit | "-"] альфа-цифра

То есть это либо альфа-цифра, либо строка, в которой первый / последний символы должны быть альфа-цифрой, а промежуточные символы могут быть альфа-цифрой или тире.

Как мне реализовать это с помощью nom? Игнорируя сценарий с одним символом для упрощения случая, моя последняя попытка:

fn domain_label(s: &[u8]) -> IResult<&[u8], (&[u8], &[u8], &[u8])> {
    let left = take_while_m_n(1, 1, is_alphanumeric);
    let middle = take_while(|c| is_alphanumeric(c) || c == b'-');
    let right = take_while_m_n(1, 1, is_alphanumeric);
    let whole = tuple((left, middle, right));
    whole(s)
}

Проблема в том, что middle может использовать последний символ и, следовательно, right терпит неудачу, потому что нет символа для использования.

println!("{:?}", domain_label(b"abcde"));
Err(Error(([], TakeWhileMN)))

Парсеры должны иметь возможность пробовать все возможные пути потребления, но как это сделать с nom?


person Listerone    schedule 13.09.2019    source источник


Ответы (1)


domainlabel = alphadigit | alphadigit * [alphadigit | "-"] альфа-цифра

Это последовательность буквенно-цифровых последовательностей, разделенных любым количеством символов -. Вот один из способов сделать это:

use nom::bytes::complete::{tag, take_while1};
use nom::character::is_alphanumeric;
use nom::combinator::recognize;
use nom::multi::{many1, separated_list};
use nom::IResult;

fn domain_label(input: &[u8]) -> IResult<&[u8], &[u8]> {
    let alphadigits = take_while1(is_alphanumeric);
    let delimiter = many1(tag(b"-"));
    let parser = separated_list(delimiter, alphadigits);

    recognize(parser)(input)
}

fn main() {
    let (_, res) = domain_label(b"abcde").unwrap();
    assert_eq!(res, b"abcde");
    let (_, res) = domain_label(b"abcde-123-xyz-").unwrap();
    assert_eq!(res, b"abcde-123-xyz");
    let (_, res) = domain_label(b"rust-lang--1---37---0.org").unwrap();
    assert_eq!(res, b"rust-lang--1---37---0");
}

Заметьте, вам не нужны отдельные части успешного синтаксического анализа. В результате получается самый длинный ввод, соответствующий метке домена BNF. Вот где пригодится комбинатор recognize.

person edwardw    schedule 13.09.2019
comment
Спасибо. Это решает непосредственную проблему, но позволяет обойти отмеченную мной проблему, заключающуюся в том, что неоднозначный синтаксический анализ может потребовать отслеживания с возвратом. Это делает nom или нужно полагаться на преобразование грамматики в решение синтаксического анализа, не требующее поиска с возвратом? - person Listerone; 13.09.2019
comment
@Listerone, в данном конкретном случае двусмысленности нет. И вы можете заметить мой третий тестовый пример, который не использует все входные данные. Вы можете продолжить синтаксический анализ того, что осталось, любым синтаксическим анализатором, который пожелаете. Это также стратегия работы с ошибками / двусмысленностями; вы пробуете другие варианты с оставшимся вводом. Пока не останется ни одного варианта или весь ввод не будет использован без успеха, это серьезный провал. - person edwardw; 13.09.2019
comment
@Listerone есть также специально разработанный комбинатор peek чтобы не потреблять ввод. - person edwardw; 13.09.2019