Введение
Люди, которые написали синтаксический анализатор, будь то простой пользовательский протокол или сложный протокол, обычно используют метод интерпретации сверху вниз, от первого байта до черного и до последнего байта. встретиться;
С суждением, встреча:
использовать один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
Такой-то байт или такой-то регулярный узор, откройте черный...