用不同的“种类”元素构建枚举的宏

时间:2019-09-24 17:19:24

标签: rust rust-macros rust-decl-macros

我正在尝试提出一个我想调用的宏

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匹配的另一个标识符。但是编写这样的宏的正确方法是什么(如果可能的话,又不会过于复杂)?

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

2 个答案:

答案 0 :(得分:3)

这可以通过TT muncherpush-down accumulation和处理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,
                }
            }
        }
    };
}

另请参阅:

答案 1 :(得分:1)

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

playground