使用不同 "kinds" 元素构建枚举的宏

Macro to build enum with different "kinds" of elements

我正在尝试想出一个我会称之为的宏

create_states!(S0, S1, final S2, final S3);

它将创建一个枚举来表示状态机状态,其中一些将是最终(接受)状态 - S2S3。结果枚举及其 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 的标识符。但是编写这样一个宏的正确方法是什么(如果可能而不会过于复杂)?

我对宏调用有充分的灵活性,因为这是我个人的学习练习。主要objective是学习正确的做事方法。如果可能的话,最好让这个宏在任何位置接受 final

这可以通过 TT muncher, push-down accumulation, and handling the trailing separators 来完成。

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 的回答更为笼统,但在您的具体情况下,由于您 "have full flexibility in the macro invocation",您可以将 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);

playground