简体   繁体   English

关于在宏规则定义中调用另一个宏规则的问题

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM