Mae Milano
Mae Milano

Reputation: 754

How do I write macro arguments which capture parenthesis?

I am hoping to write a Rust macro which forwards its entire argument to a second macro — even when that argument contains exciting parenthesization.

Here is what I have tried so far:

macro_rules! parse {
    (done) => (println!("done!"));
    (if ($cond:tt) {$then:tt}) => (println!("if! "); parse!($cond); parse($then));
}

macro_rules! forward {
    ($($e:tt)*) => (parse!($($e)*; done));
}

fn main() {
    forward!(if (done) {done} );
}

This doesn't work, and produces the error:

error: no rules expected the token `if`

What am I doing wrong here?

Edit: Beyond simply forwarding the arguments to forward, I was hoping to "paste" the tokens ; done on to the end of forward's arguments. Is there a way to make this work while preserving that behavior?

Upvotes: 2

Views: 1741

Answers (1)

DK.
DK.

Reputation: 59125

The problem is the ; done in forward. What's going on here is that the macro expansion code matches literal input tokens against arms one at a time. If an arm doesn't match, it gives up and tries the next one. When it runs out of arms to try, it has to fail and explain why.

But which token in the input was the problem? Answering that when there are potentially multiple arms involved is hard, so instead it just picks the first token and says "this was the problem".

Whenever you see a macro expansion complaining about the first token in the input not matching, it's quite possible it's something later in the input that tripped it up.

Fixing that (and fixing the parse invocation that's missing its !) gives:

macro_rules! parse {
    (done) => (println!("done!"));
    (if ($cond:tt) {$then:tt}) => (println!("if! "); parse!($cond); parse!($then));
}

macro_rules! forward {
    ($($e:tt)*) => (parse!($($e)*));
}

fn main() {
    forward!(if (done) {done} );
}

Upvotes: 4

Related Questions