2016-01-09 8 views
8

Sto cercando di scrivere una macro per la destrutturazione dei dati BSON che assomiglia a questo:fissaggio "nessuna regola prevede che il token" macro errore

let bson: Document = ...; 
let (id, hash, name, path, modification_time, size, metadata, commit_data) = bson_destructure! { 
    get id = from (bson), optional, name ("_id"), as ObjectId; 
    get hash = from (bson), as String, through (|s| ContentHash::from_str(&s)); 
    get name = from (bson), as String; 
    get path = from (bson), as Bson, through (PathBuf::from_bson); 
    get modification_time = from (bson), as UtcDatetime, through (FileTime); 
    get size = from (bson), as I64, through (|n| n as u64); 
    get metadata = from (bson), as Document, through (Metadata::from_bson); 
    get commit_data = from (bson), optional, as Document, through (CommitData::from_bson); 
    ret (id, hash, name, path, modification_time, size, metadata, commit_data) 
}; 

ho scritto la seguente macro (abbastanza grande) per esso :

macro_rules! bson_destructure { 
    // required field 
    (
     @collect req, 
     [$target:ident, $source:expr, $field:expr, Bson, $f:expr], 
     []; 
     $($rest:tt)* 
    ) => {{ 
     let $target = try!(match $source.remove($field) { 
      Some(v) => $f(v), 
      None => Err(BsonDestructureError::MissingField { 
       field_name: $field, 
       expected: "Bson" 
      }), 
     }); 
     bson_destructure!($($rest)*) 
    }}; 
    (
     @collect req, 
     [$target:ident, $source:expr, $field:expr, $variant:ident, $f:expr], 
     []; 
     $($rest:tt)* 
    ) => {{ 
     let $target = try!(match $source.remove($field) { 
      Some(v) => match v { 
       ::ejdb::bson::Bson::$variant(v) => $f(v), 
       v => Err(BsonDestructureError::InvalidType { 
        field_name: $field, 
        expected: stringify!($variant), 
        actual: v 
       }) 
      }, 
      None => Err(BsonDestructureError::MissingField { 
       field_name: $field, 
       expected: stringify!($variant) 
      }), 
     }); 
     bson_destructure!($($rest)*) 
    }}; 

    // optional field 
    (
     @collect opt, 
     [$target:ident, $source:expr, $field:expr, Bson, $f:expr], 
     []; 
     $($rest:tt)* 
    ) => {{ 
     let $target = try!(match $source.remove($field) { 
      Some(v) => $f(v).map(Some), 
      None => Ok(None), 
     }); 
     bson_destructure!($($rest)*) 
    }}; 
    (
     @collect opt, 
     [$target:ident, $source:expr, $field:expr, $variant:ident, $f:expr], 
     []; 
     $($rest:tt)* 
    ) => {{ 
     let $target = try!(match $source.remove($field) { 
      Some(v) => match v { 
       ::ejdb::bson::Bson::$variant(v) => $f(v).map(Some), 
       v => Err(BsonDestructureError::InvalidType { 
        field_name: $field, 
        expected: stringify!($variant), 
        actual: v 
       }) 
      }, 
      None => Ok(None), 
     }); 
     bson_destructure!($($rest)*) 
    }}; 

    // change variant name 
    (
     @collect $k:tt, 
     [$target:ident, $source:expr, $field:expr, $variant:ident, $f:expr], 
     [as $nv:ident, $($word:ident $arg:tt),*]; 
     $($rest:tt)* 
    ) => { 
     bson_destructure!(
      @collect $k, 
      [$target, $source, $field, $nv, $f], 
      [$($word $arg),*]; 
      $($rest)* 
     ) 
    }; 

    // change final mapping function 
    (
     @collect $k:tt, 
     [$target:ident, $source:expr, $field:expr, $variant:ident, $f:expr], 
     [through ($nf:expr), $($word:ident $arg:tt),*]; 
     $($rest:tt)* 
    ) => { 
     bson_destructure!(
      @collect $k, 
      [$target, $source, $field, $variant, $nf], 
      [$($word $arg),*]; 
      $($rest)* 
     ) 
    }; 

    // change field name 
    (
     @collect $k:tt, 
     [$target:ident, $source:expr, $field:expr, $variant:ident, $f:expr], 
     [name ($nn:expr), $($word:ident $arg:tt),*]; 
     $($rest:tt)* 
    ) => { 
     bson_destructure!(
      @collect $k, 
      [$target, $source, $nn, $variant, $f], 
      [$($word $arg),*]; 
      $($rest)* 
     ) 
    }; 

    // main forms 
    (get $target:ident = from ($source:expr), $($word:ident $arg:tt),*; $($rest:tt)*) => { 
     bson_destructure!(
      @collect req, 
      [$target, $source, stringify!($target), Bson, Ok], 
      [$($word $arg),*]; 
      $($rest)* 
     ) 
    }; 
    (get $target:ident = from ($source:expr), optional, $($word:ident $arg:tt),*; $($rest:tt)*) => { 
     bson_destructure!(
      @collect opt, 
      [$target, $source, stringify!($target), Bson, Ok], 
      [$($word $arg),*]; 
      $($rest)* 
     ) 
    }; 

    // final form 
    (ret $e:expr) => { $e } 
} 

Tuttavia, il primo esempio di cui sopra genera il seguente errore di compilazione:

src/db/data.rs:345:22: 345:25 error: no rules expected the token `opt` 
src/db/data.rs:345    @collect opt, 
             ^~~ 

sono un po ' sorpreso che non mostri la posizione dell'errore come al solito (cioè, non ci sono indicazioni su dove si verifica l'espansione), tuttavia, l'errore si annulla quando commento il pezzo di codice che utilizza la macro out.

Non riesco a capire perché dice che nessuna regola prevede questo token perché esiste una regola del genere, ma forse non capisco qualcosa.

Sono abbastanza sicuro che questo è possibile perché è più o meno quello che fa la cassa quick_error, ma sembra che manchi ancora le mie capacità di scrittura macro.

Come devo risolvere la macro in modo che funzioni come previsto?

Per completezza, il seguente è la definizione di BsonDestructureError:

#[derive(Debug, Clone)] 
pub enum BsonDestructureError { 
    InvalidType { 
     field_name: &'static str, 
     expected: &'static str, 
     actual: Bson 
    }, 
    InvalidArrayItemType { 
     index: usize, 
     expected: &'static str, 
     actual: Bson 
    }, 
    MissingField { 
     field_name: &'static str, 
     expected: &'static str 
    } 
} 

Sto anche utilizzando bson cassa riesportati dal ejdb cassa. Here è un esempio minimo, eseguibile con cargo script su Rust stabile.

risposta

12

Entrambi cargo script, un muncher ricorsivo, e la mia sintassi di regola interna preferita; come posso non ?

Innanzitutto, è possibile identificare il problema esatto eseguendo cargo rustc -- -Z trace-macros. Questa uscita volontà ogni regola, come si arriva ampliato, dandoci una "backtrace" che, dopo un po 'riformattazione manuale, esce alla ricerca in questo modo:

bson_destructure! { 
    get id = from (bson) , optional , name ("_id") , as ObjectId ; 
    get hash = from (bson) , as String ; 
    get name = from (bson) , as String ; 
    get path = from (bson) , as Bson ; 
    get modification_time = from (bson) , as UtcDatetime ; 
    get size = from (bson) , as I64 , through (| n | n as u64) ; 
    get metadata = from (bson) , as Document ; 
    get commit_data = from (bson) , optional , as Document ; 
    ret (id , hash , name , path , modification_time , size , metadata , commit_data) 
} 

bson_destructure! { 
    @ collect opt , 
    [ id , bson , stringify ! (id) , Bson , Ok ] , 
    [ name ("_id") , as ObjectId ] ; 

    get hash = from (bson) , as String ; 
    get name = from (bson) , as String ; 
    get path = from (bson) , as Bson ; 
    get modification_time = from (bson) , as UtcDatetime ; 
    get size = from (bson) , as I64 , through (| n | n as u64) ; 
    get metadata = from (bson) , as Document ; 
    get commit_data = from (bson) , optional , as Document ; 
    ret (id , hash , name , path , modification_time , size , metadata , commit_data) 
} 

bson_destructure! { 
    @ collect opt , 
    [ id , bson , "_id" , Bson , Ok ] , [ as ObjectId ] ; 

    get hash = from (bson) , as String ; 
    get name = from (bson) , as String ; 
    get path = from (bson) , as Bson ; 
    get modification_time = from (bson) , as UtcDatetime ; 
    get size = from (bson) , as I64 , through (| n | n as u64) ; 
    get metadata = from (bson) , as Document ; 
    get commit_data = from (bson) , optional , as Document ; 
    ret (id , hash , name , path , modification_time , size , metadata , commit_data) 
} 

Un attento esame delle norme bson_destructure! illustra il problema: non c'è nessuna regola che corrisponde alla terza espansione. macro_rules! è, francamente, spazzatura alla segnalazione di posizioni deboli di errori quando si tratta di regole ricorsive; che sta puntando al token opt è irrilevante. Il problema reale è che non è stato possibile trovare una regola corrispondente.

In particolare, la norma contestata è questa:

// change variant name 
(
    @collect $k:tt, 
    [$target:ident, $source:expr, $field:expr, $variant:ident, $f:expr], 
    [as $nv:ident, $($word:ident $arg:tt),*]; 
    $($rest:tt)* 
) => { 
    ... 
}; 

nota la presenza di una virgola immediatamente dopo$nv:ident. Si noti inoltre che lo non è tale virgola nell'input.Questo può essere risolto spostando la virgola all'interno ripetizione, in questo modo:

// change field name 
(
    @collect $k:tt, 
    [$target:ident, $source:expr, $field:expr, $variant:ident, $f:expr], 
    [name ($nn:expr) $(, $word:ident $arg:tt)*]; 
    $($rest:tt)* 
) => { 
    ... 
}; 

Un'altra alternativa (e quello che ususally andare con), è quello di mutare semplicemente l'ingresso quando viene dapprima verificato per assicurarsi c'è sempre una virgola finale al posto.

Il codice non sarà in realtà compilare sulla mia macchina, a causa di una dipendenza nativa, ma ho verificato che apportare questa modifica (sia qui che alle altre regole con un problema simile) consente di completare la macro espansione. È possibile controllare l'output sembra corretto utilizzando cargo rustc -- -Z unstable-options --pretty=expanded.

+1

Wow, vorrei sapere di '-Z trace-macros' prima. Penso sempre a cose come le virgole, ma ora le ho dimenticate> _