2015-08-29 5 views
7

Sto cercando di creare un wrapper per una macro. Il problema è che non voglio ripetere le stesse regole in entrambe le macro. C'è un modo per farlo?Come si scrive un wrapper per una macro senza ripetere le regole?

Ecco cosa ho provato:

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

ma ottengo il seguente errore:

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

Se cambio la macro outer per questo, il codice di compilazione:

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

Cosa sto sbagliando?

risposta

7

macro_rules! è sia intelligente e più stupido quanto si possa realizzare.

Inizialmente, tutto l'input per una macro inizia la vita come zuppa di token indifferenziata. Uno Ident qui, StrLit, ecc. Tuttavia, quando si abbina e si acquisisce un bit dell'input, generalmente l'input verrà analizzato in un nodo Abstract Syntax Tree; questo è il caso di expr.

Il bit "intelligente" è che quando si sostituisce questa cattura (per esempio, $expression), non basta sostituire i gettoni che sono stati originariamente trovati: si sostituisce l'intero nodo AST come un unico token. Quindi ora c'è questo strano non-veramente-un-token nell'output che è un intero elemento di sintassi.

Il bit "stupido" è che questo processo è fondamentalmente irreversibile e per lo più totalmente invisibile. Quindi cerchiamo di prendere il tuo esempio:

outer!(test); 

Noi usiamo questo attraverso un livello di espansione, e diventa questo:

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

Salvo, che è non quello che sembra. Per rendere le cose più chiare, ho intenzione di inventare una sintassi non standard:

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

finta che $(test):expr è un singolo token: è un'espressione che può essere rappresentato dalla sequenza di token test. È non semplicemente la sequenza di token test. Questo è importante, perché quando l'interprete macro va a espandere tale inner! macro, verifica la prima regola:

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

Il problema è che $(test):expr è espressione, non un identificatore. Sì, lo contiene un identificatore, ma l'interprete macro non è così profondo. Vede un'espressione e solo restituisce.

Non riesce a soddisfare la seconda regola per lo stesso motivo.

Quindi cosa fai? ... Beh, questo dipende. Se outer! non fa alcun tipo di lavorazione sul suo ingresso, è possibile utilizzare un matcher tt invece:

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

tt corrisponderà qualsiasi albero di token (vedi la Macros chapter of the Rust Book). $($tts:tt)* corrisponderà a qualsiasi sequenza di token, senza modificarli. Questo è un modo per inoltrare in sicurezza un gruppo di token a un'altra macro.

Se è necessario eseguire l'elaborazione sull'ingresso e inoltrarlo alla macro inner! ... probabilmente si dovrà ripetere le regole.