Отлично, теперь переходим к самому вкусному: написанию макроса! Нам нужно разобрать
match
, так что начнём с этого:
macro_rules! ascii_case_insensitive {
(match $value:ident {
$(... ,)*
_ => $catch_all:expr $(,)?
}) => { ... }
}
А теперь на минуту остановимся и подумаем, что из себя представляет паттерн, который мы пытаемся разобрать. В
прошлый раз я совершенно упустил из виду, что обычно мы можем перечислить несколько паттернов, разделив их
|
, равно как и то, что паттерн может также предваряться
|
. Таким образом, корректный кусок макроса для распознавания паттернов должен выглядеть так:
$(|)? $($pattern:literal)|+
$(|)?
отвечает за опциональную черту в начале.
$pattern:literal
говорит, что
$pattern
— это литерал, а
$(...)|+
говорит о том, что то, что внутри скобок, повторяется
один или более раз, и что повторы разделены
|
. Но постойте-ка, есть же ещё и опциональное охранное выражение! С учётом всего этого паттерн для одной ветви принимает такой вид:
$(|)? $($pattern:literal)|+ $(if $condition:expr)? => $arm:expr,
Отлично, с разбором мы справились (правда, всё так же упустив возможность привязать имена к паттернам). Что мы со всем этим делаем? Мы проверяем, что все строки в нижнем регистре:
#[deny(const_err)]
const _ARE_ALL_ASCII_LOWERCASE: [(); 1] = [(); are_all_ascii_lowercase(&[$($($pattern,)+)*]) as _];
И что они все разные:
#[allow(dead_code)]
fn non_repeating(s: &str) {
#[deny(unreachable_patterns)]
match s {
$($(| $pattern)+ => (),)*
_ => (),
}
}
А что нам делать непосредственно самой проверкой? Мы проверяем, что значение равно, за вычетом ASCII-регистра, одному из паттернов... И что охранное выражение также справедливо, если оно есть:
x if ($(x.eq_ignore_ascii_case($pattern))||+) $(&& $condition)? => $arm,
Обратите внимание, здесь мы повторяем (
+
) выражения для паттернов, разделив их
||
.
Что ж, давайте опробуем макрос в действии:
#[derive(Debug)]
enum Example {
Foo,
Bar,
FourtyTwo,
}
impl std::str::FromStr for Example {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(ascii_case_insensitive!(match s {
"foo" => Self::Foo,
"bar" if s.as_bytes()[0].is_ascii_lowercase() => Self::Bar,
"fourtytwo" | "fourty_two" | "42" => Self::FourtyTwo,
_ => return Err(s.into()),
}))
}
}
fn main() {
let inputs = [
"foo",
"Foo",
"FOO",
"bar",
"bAr",
"BAR", // ошибка, первый символ в верхнем регистре
"fourtytwo",
"Fourtytwo",
"FOURTYTWO",
"fourty_two",
"fOuRtY_tWo",
"42",
"bogus",
];
for &input in &inputs[..] {
println!("{:?}", input.parse::<Example>());
}
}
Эта программа выдаёт следующее:
Ok(Foo)
Ok(Foo)
Ok(Foo)
Ok(Bar)
Ok(Bar)
Err("BAR")
Ok(FourtyTwo)
Ok(FourtyTwo)
Ok(FourtyTwo)
Ok(FourtyTwo)
Ok(FourtyTwo)
Ok(FourtyTwo)
Err("bogus")
...как и ожидалось. Что будет, если мы попытаемся сделать два одинаковых паттерна? Скажем, так:
...
"foo" | "foo" => Self::Foo,
...
Компилятор жалуется:
error: unreachable pattern
А если один из паттернов не в нижнем регистре:
...
"Foo" => Self::Foo,
...
то компилятор опять жалуется:
error[E0308]: mismatched types
Всё работает, как и ожидалось! Как всегда, весь код в
гисте.