Search code examples
rustrust-macros

repeat a group of tokens in a macro


I'm trying to create a macro-based finite state machine, and I'm having issues figuring out how to get my repeating pattern to capture multiple lines of tokens. I've tried moving the repetition block around and changing the delimiters between lines, but that didn't seem to get me closer. I couldn't find examples of using multiple tokens in a single repeat pattern, so I'm guessing I am breaking some sort of rule there which I couldn't find...

macro_rules! build_machine {
    ($($state_a:tt : $activity:tt -> $state_b:tt),* $(,)*) => {
        #[derive(Debug)]
        enum State { $( $state_a, $state_b )* }

        #[derive(Debug)]
        enum Activity { $( $activity )* }
    }
}

build_machine!(
    Locked : TurnKey -> Unlocked,
    //Stopped : Push -> Moving,
);

fn main() {
    println!(
        "{:?} + {:?} -> {:?}",
        State::Locked,
        Activity::TurnKey,
        State::Unlocked,
    );
    // println!(
    //     "{:?} + {:?} -> {:?}",
    //     State::Stopped,
    //     Activity::Push,
    //     State::Moving,
    // );
}

It works as expected like this, but adding in the commented lines gives a weird error complaining about the missing comma at the comma:

error: expected one of `(`, `,`, `=`, `{`, or `}`, found `Stopped`
  --> src/main.rs:13:5
   |
12 |     Locked : TurnKey -> Unlocked,
   |                                 -
   |                                 |
   |                                 expected one of `(`, `,`, `=`, `{`, or `}`
   |                                 help: missing `,`
13 |     Stopped : Push -> Moving,
   |     ^^^^^^^ unexpected token

I'm new to macros, but I think using a macro_rules is a better fit than using a proc_macro. However, I'm fine switching if that's not the case.


Solution

  • You're missing commas in the generated code:

    #[derive(Debug)]
    enum State { $( $state_a, $state_b, )* }
    //                                ^ this
    
    #[derive(Debug)]
    enum Activity { $( $activity, )* }
    //                          ^ and this
    

    Without those your macro is generating invalid code if the outer macro has more than one repetition.

    Playground