nom -- семантический парсер в стиле Lego

задняя часть Rust

Введение

Люди, которые написали синтаксический анализатор, будь то простой пользовательский протокол или сложный протокол, обычно используют метод интерпретации сверху вниз, от первого байта до черного и до последнего байта. встретиться;С суждением, встреча:использовать одинmatchи т.д,switchсоответствующийcaseТак называемое поклонение богам при встрече с богами, убийство призраков при встрече с призраками, но незнание того, что делать при встрече с буддами. Проблема в том, что, плюс обработка ошибок,if elseМожет быть чрезмерно сложным и запутанным, с течением времени и сложным в обслуживании. Слегка навороченный, вы можете написать несколько более сложных регулярных выражений, но, в конце концов, очень вероятно, что вы в конце концов забудете значение регулярного выражения. По оценкам, высококлассные игроки используютlex/yacc flex/bison, он действительно прост в использовании и обслуживании, помимо добавления файла описания, добавления некоторых вещей, которые не имеют ничего общего с языком разработки. Но что толку от бычьего ножа, чтобы зарезать курицу, такой огромный инструмент, надо резать маленькую ЖЖ? !

nomЧтобы дать относительно четкое представление, предполагается, что автор находится под сильным влиянием Lego и создал такое творение. Мы знаем, что игрушки LEGO содержат очень ограниченное количество модулей базовой формы.Комбинируя модули один за другим, можно реализовать и воплотить в жизнь создание различных объектов.nomИдея состоит не в том, чтобы научить вас, как вести себя какlex/yaccСоздавайте файлы шаблонов для определенного синтаксиса и не ждите, что вы будете объяснять сверху вниз, но, как и Lego, предоставьте множество основных базовых форм, таких какtag, take_while1, is_aи т. д., а также обеспечить методы объединения, такие какalt, tupple, precededЖдать. Его можно комбинировать различными способами для формирования самых разных парсеров без потери производительности.

nomДизайн функций очень согласован, и почти все вызовы функций возвращают функции с одной и той же сигнатурой:

// 一般函数返回
impl Fn(Input) -> IResult<Input, Output, Error>
​
// IResult定义
pub type IResult<I, O, E = error::Error<I>> = Result<(I, O), Err<E>>;
​
// ParseError<I> 为 () 实现了,所以一般可以用(),方便使用ParseError
impl<I> ParseError<I> for ()

nomБазовый функциональный строительный блок , разделенный на две версии, одна из которыхcompleteиstreamверсия, самая большая разница между ними интуитивно заключается в способе сообщения об ошибках.streamОшибка версииErr::Incomplete(n),completeпрямой докладErr::Error/Err::Failure. Кроме этого, нет никакой заметной разницы в использовании.

nomЕсть всевозможные парсеры и комбинаторы, а те, что не подходят, можно комбинировать. Кромеdocs.rsАвтор также подробно перечисляет документы (поскольку документы, созданные ржавчиной, неудобно читать вместе), здесь это не громоздко, пожалуйста, обратитесь к введению автора.choosing_a_combinator.

Пример

tokioОфициальный учебникtutorialэто хорошее место, чтобы начать изучать ржавчину, поставить передC/C++Существующие концепции были реализованы в Rust, который является дружественным и знакомым.tokioОфициальный учебникtutorialЭто реализация простой функции Redis, а репозиторий кода находится по адресу:mini-redis.mini-redisПарсинг протокола redis парсится по одному байту, к счастью, он простой, поэтому исходная реализация не грязная. теперь изменено для использованияnomМетод парсинга, репозиторий кода находится по адресу:GitHub.com/Часть 1024/Нет..., файл для разбора протокола:frame.rs

/// A frame in the Redis protocol.
/// 协议格式
#[derive(Clone, Debug)]
pub enum Frame {
    // +xxx\r\n 简单的string
    Simple(String),
    // -xxx\r\n 错误的string
    Error(String),
    // :ddd\r\n u64数值
    Integer(u64),
    // $dd\r\nbbbbb\r\n 有内容的chunk
    Bulk(Bytes),
    // $-1\r\n 空Chunk
    Null,
    // *dd\r\nxxx\r\n array dd 我数量
    Array(Vec<Frame>),
}

mini-redis просто реализует основные протоколы Redis 5. Реализация не сложная, это просто кусок пирога.nomВерсия следующая:

fn parse_simple(src: &str) -> IResult<&str, (Frame, usize)>
    {
        let (input, output) = delimited(tag("+"), take_until1("\r\n"), tag("\r\n"))(src)?;
        Ok((input, (Frame::Simple(String::from(output)), src.len() - input.len())))
    }
​
    fn parse_error(src: &str) -> IResult<&str, (Frame, usize)>
    {
        let (input, output) = delimited(tag("-"), take_until1("\r\n"), tag("\r\n"))(src)?;
        Ok((input, (Frame::Error(String::from(output)), src.len() - input.len())))
    }
​
    fn parse_decimal(src: &str) -> IResult<&str, (Frame, usize)>
    {
        let (input, output) = map_res(
            delimited(tag(":"), take_until1("\r\n"), tag("\r\n")),
            |s: &str| u64::from_str_radix(s, 10),
        )(src)?;
        Ok((input, (Frame::Integer(output), src.len() - input.len())))
    }
​
    fn parse_bulk(src: &str) -> IResult<&str, (Frame, usize)>
    {
        let (input, output) = alt((
            map(tag("$-1\r\n"), |_| Frame::Null),
            map(terminated(length_value(
                map_res(
                    delimited(tag("$"), take_until1("\r\n"), tag("\r\n")),
                    |s: &str| u64::from_str_radix(s, 10)),
                rest),
                           tag("\r\n"),
            ), |out| {
                let data = Bytes::copy_from_slice(out.as_bytes());
                Frame::Bulk(data)
            }))
        )(src)?;
        Ok((input, (output, src.len() - input.len())))
    }
​
    fn parse_array(src: &str) -> IResult<&str, (Frame, usize)>
    {
        let (input, output) =
            map(length_count(
                map_res(
                    delimited(tag("*"), take_until1("\r\n"), tag("\r\n")),
                    |s: &str| {
                        println!("{}", s);
                        u64::from_str_radix(s, 10)
                    }),
                Frame::parse,
            ), |out| {
                let data = out.iter().map(|item| item.0.clone()).collect();
                Frame::Array(data)
            },
            )(src)?;
        Ok((input, (output, src.len() - input.len())))
    }
​
    pub fn parse(src: &str) -> IResult<&str, (Frame, usize)>
    {
        alt((Frame::parse_simple, Frame::parse_error, Frame::parse_decimal, Frame::parse_bulk, Frame::parse_array))(src)
    }

можно увидетьnomВерсия имеет очень краткий способ, даже одна строка кода может быть реализована без исходного кода, побайтового суждения, для манипулирования и перемещения ячеек памяти. Так же, как блоки Lego, они сложены один за другим, просто и лаконично.

контрольная работа:

^_^@~/rust-lib/mini-redis]$ RUST_LOG=debug cargo run --bin mini-redis-server
   Compiling mini-redis v0.4.0 (~/rust-lib/mini-redis)
    Finished dev [unoptimized + debuginfo] target(s) in 7.44s
     Running `target/debug/mini-redis-server`
Sep 13 00:10:16.517  INFO mini_redis::server: accepting inbound connections
Sep 13 00:10:40.261 DEBUG mini_redis::server: accept address from 127.0.0.1:55931
5
Sep 13 00:10:40.264 DEBUG run: mini_redis::connection: nom frame: Array([Bulk(b"set"), Bulk(b"hello"), Bulk(b"world"), Bulk(b"px"), Integer(3600)]), n: 50
Sep 13 00:10:40.264 DEBUG run: mini_redis::server: cmd=Set(Set { key: "hello", value: b"world", expire: Some(3.6s) })
Sep 13 00:10:40.264 DEBUG run:apply: mini_redis::cmd::set: response=Simple("OK")
^CSep 13 00:10:52.278 DEBUG my_exit: mini_redis_server: press once more to exit
^CSep 13 00:10:52.934 DEBUG my_exit: mini_redis_server: now exit
Sep 13 00:10:52.934  INFO mini_redis::server: shutting down
​
^_^@~/rust-lib/mini-redis]$ RUST_LOG=debug cargo run --bin mini-redis-cli set hello world 3600 
   Compiling mini-redis v0.4.0 (~/rust-lib/mini-redis)
    Finished dev [unoptimized + debuginfo] target(s) in 1.61s
     Running `target/debug/mini-redis-cli set hello world 3600`
Sep 13 00:10:40.262 DEBUG set_expires{key="hello" value=b"world" expiration=3.6s}: mini_redis::client: request=Array([Bulk(b"set"), Bulk(b"hello"), Bulk(b"world"), Bulk(b"px"), Integer(3600)])
Sep 13 00:10:40.265 DEBUG set_expires{key="hello" value=b"world" expiration=3.6s}: mini_redis::connection: nom frame: Simple("OK"), n: 5
Sep 13 00:10:40.265 DEBUG set_expires{key="hello" value=b"world" expiration=3.6s}: mini_redis::client: response=Some(Simple("OK"))
OK
​

резюме

РжавчинаnomПредоставить способ построения парсера, подобного блокам Lego.Комбинируя различные базовые парсеры, можно построить достаточно сложный парсер без потери производительности из-за комбинации различных парсеров. В то же время, благодаря своему методу именования и унифицированной форме возврата функции, основанной наnomсинтаксические анализаторы, которые семантически меньше, чем те,get_lineд., слишком ясно. Поэтому с точки зрения задач синтаксического анализа, в дополнение к существующим и зрелым библиотекам синтаксического анализа, вполне возможно рассмотреть возможность их принятия вместоget_line,get_u8Такой-то байт или такой-то регулярный узор, откройте черный...