简体   繁体   English

如何在递归宏规则中扩展子模式?

[英]How to expand subpatterns in recursive macro_rules?

I am writing a macro to conveniently match nested structure in an enum typed variable to a compile-time template.我正在编写一个宏来方便地将enum类型变量中的嵌套结构与编译时模板进行匹配。 The idea is to leverage Rust's pattern matching to enforce specific values in certain locations of the structure, or bind variables to other interesting locations.这个想法是利用 Rust 的模式匹配来在结构的某些位置强制执行特定的值,或者将变量绑定到其他有趣的位置。 The basic idea works in my implementation but it fails for nested patterns.基本思想适用于我的实现,但它不适用于嵌套模式。 I believe the problem is that once a part of the macro input has been parsed as $<name>:pat it cannot later be parsed as a $<name>:tt .我认为问题在于,一旦宏输入的一部分被解析为$<name>:pat它以后就不能被解析为$<name>:tt

To avoid ambiguous use of the term pattern I'll use the following notation in accordance with the Rust documentation:为了避免模棱两可的使用术语模式,我将根据 Rust 文档使用以下符号:

  • A pattern is what appears in match arms, in if let statements, and is matched in macros by the fragment specifier $<name>:pat .模式是出现在match分支、 if let语句中的内容,并在宏中由片段说明符$<name>:pat匹配。
  • A matcher is the left-hand side of a syntax rule in a macro.匹配器是宏中语法规则的左侧。
  • A template is the part of the input to my macro that determines how the macro will expand.模板是我的宏输入的一部分,它决定了宏将如何展开。

Playground MCVE 游乐场 MCVE

This is a simplified version of the enum type I am using:这是我正在使用的enum类型的简化版本:

#[derive(Debug, Clone)]
enum TaggedValue {
    Str(&'static str),
    Seq(Vec<TaggedValue>),
}

For example, the following expression例如,下面的表达式

use TaggedValue::*;
let expression = Seq(vec![
    Str("define"),
    Seq(vec![Str("mul"), Str("x"), Str("y")]),
    Seq(vec![Str("*"), Str("x"), Str("y")]),
]);

could be matched by this macro invocation:可以通过这个宏调用匹配:

match_template!(
    &expression,                               // dynamic input structure
    { println!("fn {}: {:?}", name, body) },   // action to take after successful match
    [Str("define"), [Str(name), _, _], body]   // template to match against
);

Here, on a successful match the identifiers name and body are bound to the corresponding subelements in expression and made available as variables in the block passed as second argument to the macro.在这里,在成功匹配时,标识符namebody将绑定到expression的相应子元素,并在作为第二个参数传递给宏的块中作为变量可用。

This is my effort to write said macro:这是我编写上述宏的努力:

macro_rules! match_template {
    // match sequence template with one element
    ($exp:expr, $action:block, [$single:pat]) => {
        if let Seq(seq) = $exp {
            match_template!(&seq[0], $action, $single)
        } else {
            panic!("mismatch")
        }
    };

    // match sequence template with more than one element
    ($exp:expr, $action:block, [$first:pat, $($rest:tt)*]) => {
        if let Seq(seq) = $exp {
            // match first pattern in sequence against first element of $expr
            match_template!(&seq[0], {
                // then match remaining patterns against remaining elements of $expr
                match_template!(Seq(seq[1..].into()), $action, [$($rest)*])
            }, $first)
        } else {
            panic!("mismatch")
        }
    };

    // match a non sequence template and perform $action on success
    ($exp:expr, $action:block, $atom:pat) => {
        if let $atom = $exp $action else {panic!("mismatch")}
    };
}

It works as expected for non-nested templates, and for nested templates I can manually nest macro invocations.对于非嵌套模板,它按预期工作,对于嵌套模板,我可以手动嵌套宏调用。 However, directly specifying a nested template in a single macro invocation fails with a compilation error.但是,在单个宏调用中直接指定嵌套模板会因编译错误而失败。

match_template!(
    &expression,
    {
        match_template!(
            signature,
            { println!("fn {}: {:?}", name, body) },
            [Str(name), _, _]
        )
    },
    [Str("define"), signature, body]
);
// prints:
//   fn mul: Seq([Str("*"), Str("x"), Str("y")])

match_template!(
    &expression,
    { println!("fn {}: {:?}", name, body) },
    [Str("define"), [Str(name), _, _], body]
);
// error[E0529]: expected an array or slice, found `TaggedValue`
//   --> src/main.rs:66:25
//    |
// 66 |         [Str("define"), [Str(name), _, _], body]
//    |                         ^^^^^^^^^^^^^^^^^ pattern cannot match with input type `TaggedValue`

Playground MCVE 游乐场 MCVE

I suspect the error is saying that [Str(name), _, _] is matched as a single slice pattern which is accepted by the third macro rule where it causes the type mismatch.我怀疑错误是说[Str(name), _, _]被匹配为单个切片模式,该模式被第三个宏规则接受,导致类型不匹配。 However, I want it to be a token tree so that the second rule can decompose it into a succession of patterns.但是,我希望它是一个标记树,以便第二条规则可以将其分解为一系列模式。

I tried to change the second rule to ($exp:expr, $action:block, [$first:tt, $($rest:tt)*]) => but this only causes the error to occur at the outer level.我试图将第二条规则更改为($exp:expr, $action:block, [$first:tt, $($rest:tt)*]) =>但这只会导致错误发生在外层。

What modifications to the macro are required so that it can recursively expand such templates?需要对宏进行哪些修改才能递归扩展此类模板?

(I don't think token munching as in Recursive macro to parse match arms in Rust works here because I explicitly want to bind identifiers in patterns.) (我不认为像在递归宏中那样在Rust 中解析匹配臂的令牌咀嚼在这里起作用,因为我明确地想在模式中绑定标识符。)

This is what I expect the macro invocation to expand to (Ignoring the mismatch branches for brevity. Additionally, I simulated macro hygiene by postfixing the seq variable):这就是我希望宏调用扩展到的内容(为了简洁,忽略不匹配分​​支。此外,我通过后缀seq变量来模拟宏卫生):

// macro invocation
match_template!(
    &expression,
    { println!("fn {}: {:?}", name, body) },
    [Str("define"), [Str(name), _, _], body]
);

// expansion
if let Seq(seq_1) = &expression {
    if let Str("define") = &seq_1[0] {
        if let Seq(seq_1a) = Seq(seq_1[1..].into()) {
            if let Seq(seq_2) = &seq_1a[0] {
                if let Str(name) = &seq_2[0] {
                    if let Seq(seq_2a) = Seq(seq_2[1..].into()) {
                        if let _ = &seq_2a[0] {
                            if let Seq(seq_2b) = Seq(seq_2a[1..].into()) {
                                if let _ = &seq_2b[0] {
                                    if let Seq(seq_1b) = Seq(seq_1a[1..].into()) {
                                        if let body = &seq_1b[0] {
                                            { println!("fn {}: {:?}", name, body) }
                                        }
                                    }
                                }
                            }
                        } 
                    } 
                } 
            } 
        } 
    } 
} 

The full expansion is a bit verbose but this slightly shortened version captures the essence of what should happen:完整的扩展有点冗长,但这个稍微缩短的版本抓住了应该发生的事情的本质:

if let Seq(seq) = &expression {
    if let Str("define") = &seq[0] {
        if let Seq(signature) = &seq[1] {
            if let Str(name) = &signature[0] {
                if let body = &seq[2] {
                    println!("fn {}: {:?}", name, body)
                }
            }
        }
    }
}

Finally, here is another playground link that shows the individual steps of recursive expansion.最后,这是另一个游乐场链接,显示了递归扩展的各个步骤。 It's very dense.它非常密集。

Indeed, it seems the problem is that the macro matches a comma separated list of patterns.实际上,问题似乎在于宏匹配逗号分隔的模式列表。 Thus, in the input [Str("define"), [Str(name), _, _], body] the macro interprets the inner [...] as a slice pattern that cannot match an expression of type TaggedValue .因此,在输入[Str("define"), [Str(name), _, _], body] ,宏将内部[...]解释为无法匹配TaggedValue类型表达式的切片模式。

The solution is to expand the input as token trees.解决方案是将输入扩展为令牌树。 However, this requires a small trick because a single token tree cannot represent every pattern.但是,这需要一个小技巧,因为单个令牌树无法表示所有模式。 In particular, a pattern of the form Variant(value) consists of two token trees: Variant and (value) .特别是,形式为Variant(value)由两个标记树组成: Variant(value) These two token can be combined back into a pattern before invoking a terminal (non-recursing) rule of the macro.在调用宏的终端(非递归)规则之前,可以将这两个标记组合回一个模式。

For example, the rule to match such a pattern in a single-element template starts like this:例如,在单元素模板中匹配此类模式的规则如下所示:

($exp:expr, $action:block, [$single_variant:tt $single_value:tt]) =>

These tokens are passed together to another invocation of the macro with这些令牌一起传递给宏的另一个调用

match_template!(&seq[0], $action, $single_variant $single_value)

where they are matched as a single pattern by the terminal rule它们被终端规则匹配为单个模式

($exp:expr, $action:block, $atom:pat) =>

The final macro definition contains two additional rules to account for Variant(value) patterns:最终的宏定义包含两个额外的规则来解释Variant(value)模式:

macro_rules! match_template {
    ($exp:expr, $action:block, [$single:tt]) => {
        if let Seq(seq) = $exp {
            match_template!(&seq[0], $action, $single)
        } else {
            panic!("mismatch")
        }
    };

    ($exp:expr, $action:block, [$single_variant:tt $single_value:tt]) => {
        if let Seq(seq) = $exp {
            match_template!(&seq[0], $action, $single_variant $single_value)
        } else {
            panic!("mismatch")
        }
    };

    ($exp:expr, $action:block, [$first:tt, $($rest:tt)*]) => {
        if let Seq(seq) = $exp {
            match_template!(&seq[0], {
                match_template!(Seq(seq[1..].into()), $action, [$($rest)*])
            }, $first)
        } else {
            panic!("mismatch")
        }
    };

    ($exp:expr, $action:block, [$first_variant:tt $first_value:tt, $($rest:tt)*]) => {
        if let Seq(seq) = $exp {
            match_template!(&seq[0], {
                match_template!(Seq(seq[1..].into()), $action, [$($rest)*])
            }, $first_variant $first_value)
        } else {
            panic!("mismatch")
        }
    };

    ($exp:expr, $action:block, $atom:pat) => {
        if let $atom = $exp $action else {panic!("mismatch")}
    };
}

Here is a link to the complete example: playground .这是完整示例的链接: playground

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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