2016-01-09 37 views
8

我試圖寫一個宏解構它看起來像這樣BSON數據:修復「沒有規矩預期令牌」宏錯誤

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

我已經寫了下面的宏(相當大的),它:

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 } 
} 

然而,上面下面的編譯錯誤結果的第一個例子:

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

我有點令人驚訝的是它沒有像往常一樣顯示錯誤位置(也就是說,沒有跡象表明擴展發生在哪裏),但是,當我評論使用宏的代碼片段時,錯誤消失。

我不明白爲什麼它說沒有規則預期這個標記,因爲有這樣的規則,但也許我不明白的東西。

我很確定這是可能的,因爲這大概是quick_error箱子的作用,但似乎我的宏寫作技巧仍然不足。

我應該如何修復這個宏,以便它可以像我期望的那樣工作?

爲了完整起見,以下是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 
    } 
} 

我還使用bson箱從箱ejdb再出口。 Here是一個極小的例子,可以在穩定的Rust上運行cargo script

回答

12

cargo script,一個遞歸函數,我最喜歡的內部規則語法;我怎麼能不?

首先,確切的問題可以通過運行cargo rustc -- -Z trace-macros來確定。這將輸出每條規則,因爲它得到擴大,給了我們一個「回溯」的,一些手動重新格式化後,找出來,像這樣:

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

bson_destructure!的規則細讀顯示問題:有沒有符合第三次擴展的規則。 macro_rules!坦率地說,垃圾在報告理智的錯誤地點,當涉及到遞歸規則;它指向opt令牌是無關緊要的。 real問題是它找不到匹配的規則。

特別是違規的規則是這樣的一個:

// 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)* 
) => { 
    ... 
}; 

注意逗號的存在$nv:ident之後。另外請注意,這樣的逗號。這可以通過移動逗號重複,像這樣來解決:

// 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)* 
) => { 
    ... 
}; 

另一種選擇(和一個我後容易一起去),是簡單地變異輸入時,第一次遇到,以確保它有總是一個尾隨逗號。

的代碼實際上不會我的機器上編譯,由於本機的依賴,但我沒有驗證,進行這項調整(都在這裏,並與類似問題的其他規則)允許它來完成宏擴張。您可以使用cargo rustc -- -Z unstable-options --pretty=expanded檢查輸出的外觀是否正確。

+1

哇,我希望以前知道'-Z trace-macros'。我總是考慮逗號等事物,但現在已經忘記了它們_ _ <非常感謝! –

相關問題