- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在编写一个宏来方便地匹配 enum
中的嵌套结构类型变量到编译时模板。这个想法是利用 Rust 的模式匹配在结构的某些位置强制执行特定值,或将变量绑定(bind)到其他有趣的位置。基本思想在我的实现中有效,但它不适用于嵌套模式。我认为问题在于,一旦宏输入的一部分被解析为 $<name>:pat
它以后不能被解析为 $<name>:tt
.
为了避免模棱两可地使用术语模式,我将根据 Rust 文档使用以下符号:
match
中的内容武器,在 if let
语句,并在宏中由片段说明符 $<name>:pat
匹配.这是 enum
的简化版本我正在使用的类型:
#[derive(Debug, Clone)]
enum TaggedValue {
Str(&'static str),
Seq(Vec<TaggedValue>),
}
例如下面的表达式
use TaggedValue::*;
let expression = Seq(vec![
Str("define"),
Seq(vec![Str("mul"), Str("x"), Str("y")]),
Seq(vec![Str("*"), Str("x"), Str("y")]),
]);
可以被这个宏调用匹配:
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
);
此处,成功匹配标识符 name
和 body
绑定(bind)到 expression
中的相应子元素并在作为第二个参数传递给宏的 block 中作为变量提供。
这是我编写上述宏的努力:
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_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`
我怀疑错误是说 [Str(name), _, _]
被匹配为单个切片模式,它被第三个宏规则接受,它导致类型不匹配。但是,我希望它是一个标记树,以便第二条规则可以将它分解为一系列模式。
我尝试将第二条规则更改为 ($exp:expr, $action:block, [$first:tt, $($rest:tt)*]) =>
但这只会导致错误发生在外层。
需要对宏进行哪些修改才能递归扩展此类模板?
(我认为 Recursive macro to parse match arms in Rust 中的 token 咀嚼在这里不起作用,因为我明确想在模式中绑定(bind)标识符。)
这是我希望宏调用扩展到的内容(为简洁起见,忽略不匹配的分支。此外,我通过在 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) }
}
}
}
}
}
}
}
}
}
}
}
完整的扩展有点冗长,但这个略微缩短的版本捕获了应该发生的事情的本质:
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)
}
}
}
}
}
最后,这里是another playground link这显示了递归扩展的各个步骤。它非常密集。
最佳答案
确实,问题似乎出在宏与逗号分隔的模式列表相匹配。因此,在输入 [Str("define"), [Str(name), _, _], body]
宏解释内部 [...]
作为不能匹配 TaggedValue
类型表达式的切片模式。
解决方案是将输入扩展为标记树。然而,这需要一个小技巧,因为单个标记树不能代表所有模式。特别是,Variant(value)
形式的模式由两个标记树组成:Variant
和 (value)
。在调用宏的终端(非递归)规则之前,可以将这两个标记组合回一个模式。
例如,在单元素模板中匹配这种模式的规则是这样开始的:
($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) =>
最终的宏定义包含两个额外的规则来解释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")}
};
}
这是完整示例的链接:playground .
关于rust - 如何在递归 macro_rules 中扩展子模式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56701611/
我正在尝试理解 rust 宏语法。 Here我读到宏通常可以以 3 种方式调用: mymacro!(); mymacro![]; mymacro!{}; ...然后我看到一个示例宏定义也使用宏( ma
我正在尝试制作一个宏,让我遍历类型列表以减少 trait impl 样板。 (我目前正在使用不同的基于宏的解决方案,但如果不添加依赖项可能的话,这似乎更具可读性。) 这是我的目标语法: trait M
我正在创建一个名为 throw_error 的宏。我希望它能编译,但它失败了: // Util structs + types ... // Util macros #[macro_export] m
这个问题在这里已经有了答案: How do I create a Rust macro with optional parameters using repetitions? (1 个回答) Is
我想创建一个对给定类型列表进行操作的宏,但我需要能够存储正在处理的其他类型。 我想做的事情的一个简单例子: struct Foo; struct Bar { foo: Foo, data:
我想使用 macro_rules 来创建一个特征的实现。类型应作为宏参数给出。但是,其中一些类型可能包含生命周期,所以我需要它们。我还有一个来自宏内部的泛型类型。结果应该是这样的 impl Foo f
我正在编写一个宏来方便地匹配 enum 中的嵌套结构类型变量到编译时模板。这个想法是利用 Rust 的模式匹配在结构的某些位置强制执行特定值,或将变量绑定(bind)到其他有趣的位置。基本思想在我的实
如何为这样的宏转义美元符号? macro_rules! test { ($ $name:ident) => { println!(stringify!($name));
我正在尝试在宏中创建生命周期通用的函数。外面没有什么花哨的,只是一个硬编码的局部函数: macro_rules! generate_parse_function { ($rule_name:i
我在理解Rust的tt宏中究竟是什么macro_rules!时遇到了麻烦。 从this answer,我以为 tt will match any single token or any pair of
在 L 系统表示法中,模式看起来像这样: A(a)B(b, c) if a+b+c B(a+b, a+c)A(x+a+b+c) 我正在尝试编写 Rust 宏来扩展它们。所以我有这样的东西: macr
这个问题在这里已经有了答案: What does "expected type `()`" mean on a match expression? (1 个回答) 关闭 3 年前。 我正在浏览 Ru
在 macro_rules 中!您可以在冒号后声明要解析的不同类型的东西(例如标识符的 $x:ident 或类型的 $y:ty ),但是我对如何声明我想捕获一生感到困惑,像 'a 或 '静态的。现在这
考虑以下代码段: macro_rules! quick_hello { ($to_print:expr) => { { let h = "hello";
我正在编写我自己的语言编译器,我想用我的宏将 AST 描述为 S 表达式。 以下是不起作用的最小示例代码。 #[derive(Debug, PartialEq)] pub enum Expressio
我正在尝试定义宏以简化枚举的创建可以转换为 str 或从 str 转换为: macro_rules! define_enum_with_str_values { ($Name:ident {
玩具示例: macro_rules! boo { ($T:ident) => { let x: $T; }; } fn main() { boo!(i32);
有没有办法“重新导出”#[macro_use] extern crate 类似于 pub use 这样使用宏的宏用户就不必手动添加这些依赖的 extern crate 吗? 问题的其余部分是一个例子来
Playground link 我将几个不同的结构归为一个枚举: pub enum Ty { A(AStruct), B(BStruct) } pub struct AStruct {
我是一名优秀的程序员,十分优秀!