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

Rust 如何在递归宏规则中展开子模式?,rust,rust-macros,Rust,Rust Macros,我正在编写一个宏,以方便地将enum类型化变量中的嵌套结构与编译时模板相匹配。其思想是利用Rust的模式匹配在结构的某些位置强制执行特定值,或者将变量绑定到其他感兴趣的位置。基本思想在我的实现中起作用,但在嵌套模式中失败。我认为问题在于,一旦宏输入的一部分被解析为$:pat,以后就不能被解析为$:tt 为了避免术语模式的使用含糊不清,我将根据Rust文档使用以下符号: 模式是出现在matcharms、if-let语句中的模式,并在宏中由片段说明符$:pat匹配 匹配器是宏中语法规则的左侧 模板

我正在编写一个宏,以方便地将
enum
类型化变量中的嵌套结构与编译时模板相匹配。其思想是利用Rust的模式匹配在结构的某些位置强制执行特定值,或者将变量绑定到其他感兴趣的位置。基本思想在我的实现中起作用,但在嵌套模式中失败。我认为问题在于,一旦宏输入的一部分被解析为
$:pat
,以后就不能被解析为
$:tt

为了避免术语模式的使用含糊不清,我将根据Rust文档使用以下符号:

  • 模式是出现在
    match
    arms、
    if-let
    语句中的模式,并在宏中由片段说明符
    $:pat
    匹配
  • 匹配器是宏中语法规则的左侧
  • 模板是“我的宏”输入的一部分,用于确定宏将如何展开

这是我正在使用的
enum
类型的简化版本:

#[派生(调试、克隆)]
枚举标记值{
Str(&'static Str),
序号(Vec),
}
例如,下面的表达式

使用TaggedValue::*;
让表达式=Seq(vec[
Str(“定义”),
序列号(vec![Str(“mul”)、Str(“x”)、Str(“y”)]),
序列号(vec![Str(“*”),Str(“x”),Str(“y”)],
]);
可以与此宏调用匹配:

match_模板!(
&表达式,//动态输入结构
{println!('fn{}:{:?},name,body)},//成功匹配后要执行的操作
[Str(“define”),[Str(name),,],body]//要匹配的模板
);
这里,在成功匹配时,标识符
名称
主体
被绑定到
表达式
中相应的子元素,并在作为第二个参数传递给宏的块中作为变量提供

这是我编写上述宏的努力:

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")}
    };
}
对于非嵌套模板,它可以按预期工作,对于嵌套模板,我可以手动嵌套宏调用。但是,在单个宏调用中直接指定嵌套模板会失败,并出现编译错误

match_模板!(
&表情,
{
匹配模板(
签名,
{println!(“fn{}:{:?}”,名称,正文)},
[Str(姓名),uu,uu]
)
},
[Str(“定义”)、签名、正文]
);
//印刷品:
//fn mul:Seq([Str(“*”),Str(“x”),Str(“y”)]))
匹配模板!(
&表情,
{println!(“fn{}:{:?}”,名称,正文)},
[Str(“define”),[Str(name),[uu,[uu],body]
);
//错误[E0529]:应为数组或片,找到`TaggedValue`
//-->src/main.rs:66:25
//    |
//66 |[Str(“define”),[Str(name),u,u],body]
//| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^模式无法与输入类型`TaggedValue'匹配`

我怀疑错误是说,
[Str(name),,]
作为单片模式匹配,这被第三个宏规则接受,从而导致类型不匹配。但是,我希望它是一个令牌树,以便第二条规则可以将其分解为一系列模式

我试图将第二条规则更改为
($exp:expr,$action:block,[$first:tt,$($rest:tt)*])=>
,但这只会导致错误发生在外部级别

需要对宏进行哪些修改才能递归地扩展这些模板

(我不认为在这里咀嚼令牌是可行的,因为我明确地希望在模式中绑定标识符。)

这就是我期望宏调用扩展到的内容(为了简洁起见,忽略不匹配的分支。此外,我通过修复
seq
变量来模拟宏卫生):

//宏调用
匹配模板!(
&表情,
{println!(“fn{}:{:?}”,名称,正文)},
[Str(“define”),[Str(name),[uu,[uu],body]
);
//扩张
如果让Seq(Seq_1)=&表达式{
如果让Str(“define”)=&seq_1[0]{
如果让Seq(Seq_1a)=Seq(Seq_1[1..].into()){
如果让Seq(Seq_2)=&Seq_1a[0]{
如果让Str(name)=&seq_2[0]{
如果让Seq(Seq_2a)=Seq(Seq_2[1..].into()){
如果let u=&seq_2a[0]{
如果让Seq(Seq_2b)=Seq(Seq_2a[1..].into()){
如果let u=&seq_2b[0]{
如果让Seq(Seq_1b)=Seq(Seq_1a[1..].into()){
如果let body=&seq_1b[0]{
{println!(“fn{}:{:?}”,名称,正文)}
}
}
}
}
} 
} 
} 
} 
} 
} 
} 
完整的扩展有点冗长,但这个稍微缩短的版本抓住了应该发生的事情的本质:

如果让Seq(Seq)=&表达式{
如果让Str(“define”)=&seq[0]{
如果让Seq(签名)=&Seq[1]{
如果让Str(name)=&签名[0]{
如果让正文=&序号[2]{
println!(“fn{}:{:?}”,名称,正文)
}
}
}
}
}

最后,这里展示了递归扩展的各个步骤。它非常密集。

事实上,问题似乎在于宏与逗号分隔的模式列表相匹配。因此,在输入
[Str(“define”),[Str(name),[uu,[uu],body]
中,宏将内部
[…]
解释为无法匹配类型
TaggedValue
表达式的切片模式

解决方案是将输入扩展为令牌树。然而,这需要一个小技巧,因为单个令牌树不能代表每个模式。特别地
($exp:expr, $action:block, [$single_variant:tt $single_value:tt]) =>
match_template!(&seq[0], $action, $single_variant $single_value)
($exp:expr, $action:block, $atom:pat) =>