我正在尝试编写一个用于解构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 crate所做的,但似乎我的宏写作技能仍然缺乏.
我应该如何修复宏,以便它可以像我期望的那样工作?
为完整起见,以下是以下定义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
箱子里再出口的箱子.这是一个最小的例子,可以cargo script
在稳定的Rust上运行.
两者cargo script
,一个递归的muncher,以及我最喜欢的内部规则语法; 我怎么能不?
首先,通过运行可以确定确切的问题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
令牌是无关紧要的.在真正的问题是,它无法找到一个匹配的规则.
特别是,违规规则是这样的:
// 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
.