Как разрешить необязательные конечные запятые в макросах?

Вот синтетический пример того, что я хочу:

macro_rules! define_enum {
    ($Name:ident { $($Variant:ident),* }) => {
        pub enum $Name {
            None,
            $($Variant),*,
        }
    }
}

define_enum!(Foo { A, B });

Этот код компилируется, но если добавить к нему запятую:

define_enum!(Foo { A, B, });
//                     ^

Компиляция не удалась. Я могу это исправить с помощью:

($Name:ident { $($Variant:ident,)* })
//                             ^

но потом define_enum!(Foo { A, B }); выходит из строя,

Как мне написать макрос для обработки обоих случаев:

define_enum!(Foo { A, B });
define_enum!(Foo { A, B, });

3 ответа

Решение

Вы можете обрабатывать оба случая, обрабатывая оба случая:

macro_rules! define_enum {
    ($Name:ident { $($Variant:ident,)* }) => {
        pub enum $Name {
            None,
            $($Variant),*,
        }
    };
    ($Name:ident { $($Variant:ident),* }) => {
        define_enum!($Name { $($Variant,)* });
    };
}

define_enum!(Foo1 { A, B });
define_enum!(Foo2 { A, B, });

fn main() {}

Мы переместили основную реализацию в версию, которая ожидает завершающую запятую. Затем мы добавили второе предложение, которое соответствует регистру с отсутствующей запятой, и переписали его в версию с запятой.


DK. указывает альтернативу, делая конечную запятую необязательной:

($Name:ident { $($Variant:ident),* $(,)* }) => { 

Это позволяет избежать необходимости делегировать от одной реализации к другой.

В ночных версиях Rust вы можете использовать macro_at_most_once_rep особенность, чтобы написать это более очевидным способом и запретить множественные запятые:

($Name:ident { $($Variant:ident),* $(,)? }) => { 
//                                     ^

Изменить линию

($Name:ident { $($Variant:ident),* }) => {

в

($Name:ident { $($Variant:ident),* $(,)? }) => {

добавить необязательную запятую в конце. Это работает в стабильной версии Rust / 2018. Этот синтаксис также работает для других разделителей, таких как точка с запятой.

Другой вариант (если вы используете Incremental TT Muncher) — использовать необязательный захват разделителя + оставшиеся токены. Используя немного другой пример:

      macro_rules! example {

    ($name:ident = $value:expr $(, $($tts:tt)*)?) => {
        println!("{} = {}", stringify!($name), $value);
        example!($($($tts)*)?);
    };

    ($value:expr $(, $($tts:tt)*)?) => {
        println!("{}", $value);
        example!($($($tts)*)?);
    };

    () => {};

}

Затем этот макрос можно вызвать, например, как:

      example! { A = 1, "B", C = 3, "D" }

а замыкающая запятая может быть либо включена, либо опущена.

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