rustrust-decl-macros

How to parse single tokens in rust macros


I'm starting playing with Rust macros and I came to try this little practice example. I want to define a macro that expands into a variable initialization (name doesn't matter) of type i32 (for example, but not really important) and a series of operations to that variable, in this case a var += 1 or a var -= 1 and finally it will call println!("{}", var). The macro will take a series of tokens based on + and - that matches the operations described above.

So for example:

operate_integer![+++---]

would expand to:

let mut var: i32 = 0;
var += 1;
var += 1;
var += 1;
var -= 1;
var -= 1;
var -= 1;
print!("{}", var);

I decided to use 2 macros for this, one for wrapping the initialization and the printing and the other to evaluate the +- tokens:

The base one would be:

macro_rules! operate_integer {
    // $($all_tokens:tt)* should match everything, it will be forward to the helper macro
    ($($all_tokens:tt)*) => {
        let mut var : i32 = 0;
        operate_integer_helper![$($all_tokens:tt)*]
        print!("{}", var);
    }
}

The helper would expand the operations:

macro_rules! operate_integer_helper {
    // the idea is that it matches a `+` followed by more tokens
    (+$($t:tt)*) => {
        val += 1;
        operate_integer_helper![$($t:tt)*] // we recursively handle the remaining tokens
    }

    (-$($t:tt)*) => {
        val -= 1;
        operate_integer_helper![$($t:tt)*]
    }
}

This of course do not work, it fails compilation with the following error (Playground):

error: no rules expected the token `(`
   --> src/lib.rs:102:5
    |
102 |     (+$($t:tt)*) => {
    |     ^ no rules expected this token in macro call

I'm kind of stuck. I know I may be missing many concepts since I just started and I would really appreciate some help understanding how to work with macros. Thank you in advance!


Solution

  • You're actually very close! There are only a couple of minor errors left. (If you want to learn more about macros, only read one bullet point at a time and try to progress on your own from there!)

    And now: it works!

    I would still change one last thing: usually it's not a good idea to have a second helper macro, because that macro might not be in scope where the main macro is called. Instead, one usually uses internal rules. You can read more about those here.

    With this, this is the resulting code.