关于在 macro_rules 定义中调用另一个 macro_rules 的问题
Question on invoking another macro_rules in macro_rules definition
我正在实施编写 TLV 数据包以实现某种程度的实现 std::io::Write
。
首先我实现了WriteBE<T>
trait,其write_be(&mut self, data: T)
方法可以将T
类型的数据写入Self
。 (省略实施细节)
我正在尝试使用 macro_rules!在编译时实现总数据包长度的计算(因为在我的例子中大多数数据包都有固定长度)。宏如下:
macro_rules! len_in_expr {
(
self.write_be( $data: expr $(,)? ) $(?)? $(;)*
) => {
std::mem::size_of_val(&$data)
};
(
write_be(self, $data: expr $(,)? ) $(?)? $(;)*
) => {
std::mem::size_of_val(&$data)
};
(
$other: expr
) => {
0
};
}
/// calculate total write size in block
macro_rules! tlv_len_in_block {
({
$( $e: expr );* $(;)?
}) => {
0 $(
+ ( len_in_expr!($e) )
)*
};
}
但是当我计算总长度时 this:
fn main() {
let y = tlv_len_in_block!({
write_be(self, 0u32,)?;
});
println!("y={}", y);
}
我得到一个结果0
。
如果我评论 $other: expr
匹配臂,我会得到一个编译错误:
6 | macro_rules! len_in_expr {
| ------------------------ when calling this macro
...
30 | + ( len_in_expr!($e) )
| ^^ no rules expected this token in macro call
...
39 | let y = tlv_len_in_block!({
| _____________-
40 | | write_be(self, 0u32,)?;
41 | | });
| |______- in this macro invocation
我的代码有什么问题?我该如何解决?
一旦 macro_rules!
中的元变量被捕获到某个片段说明符(例如 expr
)中,它们就不能再被分解。引用 the reference:
When forwarding a matched fragment to another macro-by-example, matchers in the second macro will see an opaque AST of the fragment type. The second macro can't use literal tokens to match the fragments in the matcher, only a fragment specifier of the same type. The ident, lifetime, and tt fragment types are an exception, and can be matched by literal tokens. The following illustrates this restriction:
macro_rules! foo {
($l:expr) => { bar!($l); }
// ERROR: ^^ no rules expected this token in macro call
}
macro_rules! bar {
(3) => {}
}
foo!(3);
The following illustrates how tokens can be directly matched after matching a tt fragment:
// compiles OK
macro_rules! foo {
($l:tt) => { bar!($l); }
}
macro_rules! bar {
(3) => {}
}
foo!(3);
一旦 tlv_len_in_block!()
在 $e
中捕获了 write_be(self, 0u32,)?
,它就不能分解为 write_be(self, $data:expr $(,)? ) and thus, cannot be matched by the second case of the
len_in_expr!()` 宏,因为它应该是.
这个问题一般有两种解决方法:
首先是,如果可能的话,从头开始分解它们。问题是这并不总是可能的。例如,在这种情况下,我看不到它起作用的方法。
第二种方法要复杂得多,它使用 Push-down Accumulation technique together with tt Munching.
思路如下:我们不是将输入作为一个整体来解析,而是一次一个地解析每一块。然后,递归地,我们将已解析的位和尚未解析的位转发给我们自己。我们还应该有一个空输入的停止条件。
在您的示例中它会是这样的:
macro_rules! tlv_len_in_block_impl {
// Stop condition - no input left to parse.
(
parsed = [ $($parsed:tt)* ]
rest = [ ]
) => {
$($parsed)*
};
(
parsed = [ $($parsed:tt)* ]
rest = [
self.write_be( $data:expr $(,)? ) $(?)? ;
$($rest:tt)*
]
) => {
tlv_len_in_block_impl!(
parsed = [
$($parsed)*
+ std::mem::size_of_val(&$data)
]
rest = [ $($rest)* ]
)
};
(
parsed = [ $($parsed:tt)* ]
rest = [
write_be(self, $data:expr $(,)? ) $(?)? ;
$($rest:tt)*
]
) => {
tlv_len_in_block_impl!(
parsed = [
$($parsed)*
+ std::mem::size_of_val(&$data)
]
rest = [ $($rest)* ]
)
};
}
/// calculate total write size in block
macro_rules! tlv_len_in_block {
({
$($input:tt)*
}) => {
tlv_len_in_block_impl!(
parsed = [ 0 ]
rest = [ $($input)* ]
)
};
}
(请注意,这与您的宏不完全相同 - 我的 需要一个尾随分号 ,而在您的宏中,它是可选的。也可以在此处将其设为可选,但它会更复杂。
我正在实施编写 TLV 数据包以实现某种程度的实现 std::io::Write
。
首先我实现了WriteBE<T>
trait,其write_be(&mut self, data: T)
方法可以将T
类型的数据写入Self
。 (省略实施细节)
我正在尝试使用 macro_rules!在编译时实现总数据包长度的计算(因为在我的例子中大多数数据包都有固定长度)。宏如下:
macro_rules! len_in_expr {
(
self.write_be( $data: expr $(,)? ) $(?)? $(;)*
) => {
std::mem::size_of_val(&$data)
};
(
write_be(self, $data: expr $(,)? ) $(?)? $(;)*
) => {
std::mem::size_of_val(&$data)
};
(
$other: expr
) => {
0
};
}
/// calculate total write size in block
macro_rules! tlv_len_in_block {
({
$( $e: expr );* $(;)?
}) => {
0 $(
+ ( len_in_expr!($e) )
)*
};
}
但是当我计算总长度时 this:
fn main() {
let y = tlv_len_in_block!({
write_be(self, 0u32,)?;
});
println!("y={}", y);
}
我得到一个结果0
。
如果我评论 $other: expr
匹配臂,我会得到一个编译错误:
6 | macro_rules! len_in_expr {
| ------------------------ when calling this macro
...
30 | + ( len_in_expr!($e) )
| ^^ no rules expected this token in macro call
...
39 | let y = tlv_len_in_block!({
| _____________-
40 | | write_be(self, 0u32,)?;
41 | | });
| |______- in this macro invocation
我的代码有什么问题?我该如何解决?
一旦 macro_rules!
中的元变量被捕获到某个片段说明符(例如 expr
)中,它们就不能再被分解。引用 the reference:
When forwarding a matched fragment to another macro-by-example, matchers in the second macro will see an opaque AST of the fragment type. The second macro can't use literal tokens to match the fragments in the matcher, only a fragment specifier of the same type. The ident, lifetime, and tt fragment types are an exception, and can be matched by literal tokens. The following illustrates this restriction:
macro_rules! foo { ($l:expr) => { bar!($l); } // ERROR: ^^ no rules expected this token in macro call } macro_rules! bar { (3) => {} } foo!(3);
The following illustrates how tokens can be directly matched after matching a tt fragment:
// compiles OK macro_rules! foo { ($l:tt) => { bar!($l); } } macro_rules! bar { (3) => {} } foo!(3);
一旦 tlv_len_in_block!()
在 $e
中捕获了 write_be(self, 0u32,)?
,它就不能分解为 write_be(self, $data:expr $(,)? ) and thus, cannot be matched by the second case of the
len_in_expr!()` 宏,因为它应该是.
这个问题一般有两种解决方法:
首先是,如果可能的话,从头开始分解它们。问题是这并不总是可能的。例如,在这种情况下,我看不到它起作用的方法。
第二种方法要复杂得多,它使用 Push-down Accumulation technique together with tt Munching.
思路如下:我们不是将输入作为一个整体来解析,而是一次一个地解析每一块。然后,递归地,我们将已解析的位和尚未解析的位转发给我们自己。我们还应该有一个空输入的停止条件。
在您的示例中它会是这样的:
macro_rules! tlv_len_in_block_impl {
// Stop condition - no input left to parse.
(
parsed = [ $($parsed:tt)* ]
rest = [ ]
) => {
$($parsed)*
};
(
parsed = [ $($parsed:tt)* ]
rest = [
self.write_be( $data:expr $(,)? ) $(?)? ;
$($rest:tt)*
]
) => {
tlv_len_in_block_impl!(
parsed = [
$($parsed)*
+ std::mem::size_of_val(&$data)
]
rest = [ $($rest)* ]
)
};
(
parsed = [ $($parsed:tt)* ]
rest = [
write_be(self, $data:expr $(,)? ) $(?)? ;
$($rest:tt)*
]
) => {
tlv_len_in_block_impl!(
parsed = [
$($parsed)*
+ std::mem::size_of_val(&$data)
]
rest = [ $($rest)* ]
)
};
}
/// calculate total write size in block
macro_rules! tlv_len_in_block {
({
$($input:tt)*
}) => {
tlv_len_in_block_impl!(
parsed = [ 0 ]
rest = [ $($input)* ]
)
};
}
(请注意,这与您的宏不完全相同 - 我的 需要一个尾随分号 ,而在您的宏中,它是可选的。也可以在此处将其设为可选,但它会更复杂。