Макрос для построения перечисления с разными "видами" элементов
Я пытаюсь придумать макрос, который я бы назвал как
create_states!(S0, S1, final S2, final S3);
Он создаст перечисление для представления состояний конечного автомата, и некоторые из них будут конечными (принимающими) состояниями - S2
, S3
. Результирующее перечисление и егоimpl
должно выглядеть так:
enum State {
S0,
S1,
S2,
S3,
}
impl State {
fn is_final(&self) -> bool {
match self {
Self::S2 => true,
Self::S3 => true,
_ => false,
}
}
}
Моя наивная попытка:
macro_rules! create_states {
($($r:ident),+, $(final $f:ident),*) => {
#[derive(Copy, Clone)]
enum State {
$($s),*
$($f),*
}
impl State {
fn is_final(&self) -> bool {
match self {
$(Self::$f => true,)*
_ => false,
}
}
}
}
}
приводит к следующей ошибке:
error: local ambiguity: multiple parsing options: built-in NTs ident ('r') or 1 other option.
--> src/lib.rs:20:24
|
20 | create_states!(S0, S1, final S2, final S3);
| ^^^^^
Пробуем убрать запятую между шаблонами во второй строке:
($($r:ident),+ $(final $f:ident),*) => { ...
производит еще один:
error: no rules expected the token `S2`
--> src/lib.rs:20:30
|
1 | macro_rules! create_states {
| -------------------------- when calling this macro
...
20 | create_states!(S0, S1, final S2, final S3);
| ^^ no rules expected this token in macro call
Я думаю, что я понимаю, что причина этих ошибок - он считает, чтоfinal
соответствует ли другой идентификатор r
. Но как правильно написать такой макрос (если это вообще возможно без чрезмерного усложнения)?
У меня есть полная гибкость при вызове макроса, поскольку это мое личное обучающее упражнение. Основная цель - научиться правильно поступать. Было бы неплохо, если бы этот макрос принималfinal
в любом месте, если возможно.
2 ответа
Это может быть выполнено с помощью измельчителя TT, накопления и обработки замыкающих разделителей.
macro_rules! create_states {
// User entry points.
(final $name:ident $($tt:tt)*) => {
create_states!(@ {[] [$name]} $($tt)*);
};
($name:ident $($tt:tt)*) => {
create_states!(@ {[$name] []} $($tt)*);
};
// Internal rules to categorize each value
(@ {[$($n:ident)*] [$($t:ident)*]} $(,)? final $name:ident $($tt:tt)*) => {
create_states!(@ {[$($n)*] [$($t)* $name]} $($tt)*);
};
(@ {[$($n:ident)*] [$($t:ident)*]} $(,)? $name:ident $($tt:tt)*) => {
create_states!(@ {[$($n)* $name] [$($t)*]} $($tt)*);
};
// Final internal rule that generates the enum from the categorized input
(@ {[$($n:ident)*] [$($t:ident)*]} $(,)?) => {
#[derive(Copy, Clone)]
enum State {
$($n,)*
$($t,)*
}
impl State {
fn is_final(&self) -> bool {
match self {
$(Self::$t => true,)*
_ => false,
}
}
}
};
}
Смотрите также:
Ответ Shepmaster является более общим, но в вашем конкретном случае, поскольку у вас "полная гибкость при вызове макроса", вы можете просто заменить final
с @final
и наивная попытка работает, если не считать пары мелких опечаток:
macro_rules! create_states {
($($r:ident),+, $(@final $f:ident),*) => {
#[derive(Copy, Clone)]
enum State {
$($r,)*
$($f),*
}
impl State {
fn is_final(&self) -> bool {
match self {
$(Self::$f => true,)*
_ => false,
}
}
}
}
}
create_states!(S0, S1, @final S2, @final S3);