Макрос для построения перечисления с разными "видами" элементов

Я пытаюсь придумать макрос, который я бы назвал как

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);

детская площадка

Другие вопросы по тегам