
Радует, что комбинации байтов несчётны, всегда весело будет.
https://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.html
Size: a a a
macro_rules!
.macro_rules!
можно использовать спецификатор повтора ?
в форме $(some complex pattern)?
. Вопрос: что делать, если опицональный захват нужно развернуть в Some($захват)
, если он есть, и в None
, если его нету? Очень просто: достаточно делегировать эту задачу очень простому макросу make_option!
:macro_rules! make_option {Разумеется, это будет работать без сбоев только в том случае, если захват не может соответствовать пустой последовательности лексем.
() => { ::core::option::Option::None };
($($anything:tt)*) => { ::core::option::Option::Some($($anything)*) };
}
#[derive(Clone)]Да, всё верно, это буквально обёртка над строковым слайсом. Все последующие методы будут принимать мутабельную ссылку на
struct Lexer<'a> {
s: &'a str,
}
impl<'a> Lexer<'a> {
fn of(s: &'a str) -> Self {
Self { s }
}
}
Lexer
и будут возвращать Option<Something>
, где Some
означает, что нечто удалось распарсить, а None
будет обозначать ошибку. При этом все эти методы будут следовать одному правилу: если что-то распарсить не удалось, то Lexer
остаётся ровно в том же состоянии, что был до вызова.fn end(&mut self) -> Option<()> {Следующий метод не очень полезен сам по себе, но он потребуется для других методов. Он просто будет сдвигать позицию, с которой производится парсинг:
if self.s.is_empty() {
Some(())
} else {
None
}
}
fn shift(&mut self, pos: usize) {Что ж, теперь напишем самый простой метод, который действительно что-то парсит, а именно — конкретную переданную строку:
self.s = &self.s[pos..];
}
fn literal(&mut self, literal: &str) -> Option<()> {Не менее полезным будет метод, который возвращает то, что находится до переданной строки:
self.s = self.s.strip_prefix(literal)?;
Some(())
}
fn before_literal(&mut self, literal: &str) -> Option<&'a str> {Ну и первый метод, который делает по настоящему нетривиальный разбор: парсинг беззнакового числа:
let pos = self.s.find(literal)?;
let ret = &self.s[..pos];
self.shift(pos + literal.len());
Some(ret)
}
fn number<Num: std::str::FromStr>(&mut self) -> Option<Num> {Clippy тут, к сожалению, ругается на вызов
let pos = self
.s
.as_bytes()
.iter()
.position(|ch| !ch.is_ascii_digit())
.unwrap_or(self.s.len());
let ret = self.s[..pos].parse().ok()?;
self.shift(pos);
Some(ret)
}
.unwrap_or(self.s.len())
... Точнее, ругался раньше, теперь это исправили. Отлично!#[derive(PartialEq, Debug)]Обратите внимание, благодаря выводу типов нам не пришлось уточнять тип для
struct Ip4Addr([u8; 4]);
fn parse_ip(s: &str) -> Option<Ip4Addr> {
let mut p = Lexer::of(s);
let a = p.number()?;
p.literal(".")?;
let b = p.number()?;
p.literal(".")?;
let c = p.number()?;
p.literal(".")?;
let d = p.number()?;
p.end()?;
Some(Ip4Addr([a, b, c, d]))
}
fn main() {
assert_eq!(parse_ip("12.32.200.21"), Some(Ip4Addr([12, 32, 200, 21])));
}
p.number()
.None
не будет фатальной ошибкой:fn optional<T, F: FnOnce(&mut Self) -> Option<T>>(&mut self, f: F) -> Option<T> {В принципе, теперь мы можем распарсить IP4-адрес с опциональной маской подсети, но я покажу кое-что более интересное: парсинг из задачи Advent of Code 2020.
let backtrack = self.clone();
let ret = f(self);
if ret.is_none() {
*self = backtrack;
}
ret
}
bright white bags contain 1 shiny gold bag.Напишем функцию для разбора одной подобной строки:
vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.
faded blue bags contain no other bags.
fn parse_bags_with_amount(line: &str) -> Option<(&str, Vec<(&str, usize)>)> {Повторяющимся элементом каждой строки является подстрока "bags contain". Извлечём то, что находится до этой строки — это и будет цветом сумки, про содержимое которой идёт речь:
let mut p = Lexer::of(line);
// ...
let outer_color = p.before_literal(" bags contain ")?;А вот теперь возможны варианты. Если далее идёт "no other bags.", то на этом можно разбор заканчивать и возвращать пустой список:
if p.optional(|p| {А вот теперь нужно распарсить список значений, разделённых запятыми. К сожалению, я не придумал ничего лучшего, чем сделать безусловный цикл, который на каждой итерации будет парсить очередную порцию, а в конце итерации пытаться парсить запятую и завершать цикл, если это не удалось:
p.literal("no other bags.")?;
p.end()
})
.is_some()
{
return Some((outer_color, Vec::new()));
}
let mut inner_colors = Vec::new();Тут бы пригодился цикл с постусловием, которого в Rust, увы, нету :/
loop {
// ...
if p.optional(|p| p.literal(", ")).is_none() {
break;
}
}
bag
, учтя при это, что оно может быть во множественном числе:// ...Строка заканчивается точкой:
let amount = p.number()?;
let inner_color = p.before_literal(" bag")?.trim_start_matches(' ');
p.optional(|p| p.literal("s"));
inner_colors.push((inner_color, amount));
// ...
p.literal(".")?;Если выполнение дошло до этой точки, значит, парсинг успешен:
p.end()?;
Some((outer_color, inner_colors))Проверим, как оно работает:
fn main() {Работает! Как всегда, весь код в гисте. Как вы могли заметить, техника не самая изящная, но вместе с тем она достаточно простая, чтобы её при случае, когда требуется быстро написать парсер, его можно было бы написать руками вот прямо сейчас — как я, собственно, тогда и сделал.
let input = "\
bright white bags contain 1 shiny gold bag.
vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.
faded blue bags contain no other bags.";
let parsed = input
.lines()
.map(parse_bags_with_amount)
.map(Option::unwrap)
.collect::<Vec<_>>();
assert_eq!(
parsed,
[
("bright white", vec![("shiny gold", 1)]),
("vibrant plum", vec![("faded blue", 5), ("dotted black", 6)]),
("faded blue", vec![]),
],
);
}