[英]Question on invoking another macro_rules in macro_rules definition
I'm implementing writing TLV packet to somewhat impl std::io::Write
.我正在实现将 TLV 数据包写入有点 impl
std::io::Write
。
First I implement WriteBE<T>
trait, whose write_be(&mut self, data: T)
method can write data with type T
to Self
.首先我实现
WriteBE<T>
trait,它的write_be(&mut self, data: T)
方法可以将T
类型的数据写入Self
。 (implementation details omitted) (实现细节省略)
And I'm trying to use macro_rules.我正在尝试使用macro_rules。 to implement calculation of total packet length in compile time (because most packets have fixed length in my case): macros are as follows:
在编译时实现总包长度的计算(因为在我的例子中大多数包的长度是固定的):宏如下:
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) )
)*
};
}
But when I calculating total length like this :但是当我这样计算总长度时:
fn main() {
let y = tlv_len_in_block!({
write_be(self, 0u32,)?;
});
println!("y={}", y);
}
I get a result 0
.我得到一个结果
0
。
If I comment the $other: expr
match arm, I get a compile error:如果我注释
$other: expr
match arm,我得到一个编译错误:
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
What's the problem with my code?我的代码有什么问题? And how can I fix it?
我该如何解决?
Once metavariables inside macro_rules!
一旦宏规则中的
macro_rules!
are captured into some fragment specifier (eg expr
), they cannot be decomposed anymore.被捕获到一些片段说明符(例如
expr
)中,它们不能再被分解。 Quoting 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.
当将匹配的片段转发到另一个宏时,第二个宏中的匹配器将看到片段类型的不透明 AST。 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.
ident、lifetime 和 tt 片段类型是一个例外,可以通过文字标记进行匹配。 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:
下面说明如何在匹配一个 tt 片段后直接匹配令牌:
// compiles OK macro_rules: foo { ($l;tt) => { bar;($l); } } macro_rules! bar { (3) => {} } foo!(3);
Once tlv_len_in_block!()
captured write_be(self, 0u32,)?
一旦
tlv_len_in_block!()
捕获write_be(self, 0u32,)?
inside $e
, it cannot be decomposed into write_be(self, $data:expr $(,)? ) and thus, cannot be matched by the second case of the
len_in_expr,()` macro.在
$e
中,它不能分解为write_be(self, $data:expr $(,)? ) and thus, cannot be matched by the second case of the
。 as it should have been.应该是这样。
There are generally two solutions to this problem:这个问题一般有两种解决方案:
The first is, if possible, decomposing them from the beginning.第一个是,如果可能的话,从一开始就分解它们。 The problem is that this is not always possible.
问题是这并不总是可能的。 In this case, for example, I don't see a way for that to work.
例如,在这种情况下,我看不到它的工作方式。
The second way is much more complicated and it is using the Push-down Accumulation technique together with tt Munching .第二种方法要复杂得多,它使用下推累积技术和tt Munching 。
The idea is as follows: instead of parsing the input as whole, we parse each piece one at a time.想法如下:我们不是将输入整体解析,而是一次解析每个部分。 Then, recursively, we forward the parsed bits and the yet-to-parse bit to ourselves.
然后,递归地,我们将解析的位和尚未解析的位转发给我们自己。 We also should have a stop condition on an empty input.
我们还应该对空输入有一个停止条件。
Here is how it will look like in your example:这是您的示例中的样子:
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)* ]
)
};
}
(Note that this is not exactly the same as your macro - mine requires a trailing semicolon, while in yours it's optional. It's possible to make it optional here, too, but it will be much more complicated. (请注意,这与您的宏并不完全相同 - 我的需要一个尾随分号,而在您的它是可选的。也可以在此处使其成为可选,但它会复杂得多。
Playground . 游乐场。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.