How to write a shell for a macro without repeating the rules?

I am trying to create a wrapper for a macro. The problem is that I do not want to repeat the same rules in both macros. Is there any way to do this?

Here is what I tried:

macro_rules! inner { ($test:ident) => { stringify!($test) }; ($test:ident.run()) => { format!("{}.run()", stringify!($test)) }; } macro_rules! outer { ($expression:expr) => { println!("{}", inner!($expression)); } } fn main() { println!("{}", inner!(test)); println!("{}", inner!(test.run())); outer!(test); outer!(test.run()); } 

but I get the following error:

 src/main.rs:8:31: 8:42 error: expected ident, found test src/main.rs:8 println!("{}", inner!($expression)); ^~~~~~~~~~~ 

If I changed the outer macro for this, compiling the code:

 macro_rules! outer { ($expression:expr) => { println!("{}", stringify!($expression)); } } 

What am I doing wrong?

+7
macros rust
source share
1 answer

macro_rules! smarter and dumber than you might understand.

Initially, all macro input begins life as an undifferentiated soup marker. Here is Ident , StrLit , etc. However, when you map and fix the input bit, usually the input is processed in the abstract node syntax tree; this is the case with expr .

The smart bit is that when you replace this capture (for example, $expression ) you are not just replacing the tokens that were originally mapped: you are replacing the entire AST node as one token. So now this weird non-really-token in the output is that whole syntax element.

The “dumb” bit is that this process is basically irreversible and basically completely invisible. So let's look at your example:

 outer!(test); 

We run this through one level of expansion, and it becomes the following:

 println!("{}", inner!(test)); 

Also, this is not what it looks like. To make things clearer, I'm going to come up with a custom syntax:

 println!("{}", inner!( $(test):expr )); 

Imagine that $(test):expr is the only token: it is an expression that can be represented by the token sequence test . This is not just a sequence of test characters. This is important because when the macro interpreter moves on to expand this inner! macro inner! , it checks the first rule:

  ($test:ident) => { stringify!($test) }; 

The problem is that $(test):expr is an expression, not an identifier. Yes, it does contain an identifier, but the macro interpreter does not look so deep. He sees the expression and just gives up.

He cannot combine the second rule for the same reason.

So what are you doing? ... Well, that depends. If outer! doesn't do any processing on its input, you can use tt instead:

 macro_rules! outer { ($($tts:tt)*) => { println!("{}", inner!($($tts)*)); } } 

tt will match any tree tree (see the macro in the chapter of the book Rust ). $($tts:tt)* will match any sequence of tokens without changing them. This is because of this, in order to safely move a bunch of tokens to another macro.

If you need to do input processing and forward it to the inner! macro inner! ... you may have to repeat the rules.

+7
source share

All Articles