From 28c65db6049ba04f616fc481a7fa19c0517d39fc Mon Sep 17 00:00:00 2001 From: zjp Date: Mon, 18 Apr 2022 00:05:08 +0800 Subject: [PATCH] update fragment-specifiers.md --- .../minutiae/fragment-specifiers.md | 454 +++++++++++------- 1 file changed, 280 insertions(+), 174 deletions(-) diff --git a/src/decl-macros/minutiae/fragment-specifiers.md b/src/decl-macros/minutiae/fragment-specifiers.md index 54b07b2..4387f6c 100644 --- a/src/decl-macros/minutiae/fragment-specifiers.md +++ b/src/decl-macros/minutiae/fragment-specifiers.md @@ -1,29 +1,95 @@ # 片段分类符 -正如在 [`macro_rules`](../macro_rules.md) 一章看到的, -1.52 版本的 Rust 已有 13 个片段分类符 (Fragment Specifiers,以下简称分类符)[^metavariables] 。 +正如在 [思路](../macros-methodical.md) 一章看到的,截至 1.60 版本, Rust 已有 +14 个片段分类符 (Fragment Specifiers,以下简称分类符)[^metavariables]。 + 这一节会更深入地探讨他们之中的细节,每次都会展示几个匹配的例子。 -> 注意:除了 `ident`、`lifetime` 和 `tt` 分类符之外, -> 其余的分类符在匹配后生成的 AST 是不清楚的 (opaque), -> 这使得在之后的宏调用时不可能检查 (inspect) 捕获的结果。 +> 注意:除了 `ident`、`lifetime` 和 `tt` 分类符之外,其余的分类符在匹配后生成的 +> AST 是不清楚的 (opaque),这使得在之后的宏调用时不可能检查 (inspect) 捕获的结果。 -* [`item`](#item) * [`block`](#block) -* [`stmt`](#stmt) -* [`pat`](#pat) * [`expr`](#expr) -* [`ty`](#ty) * [`ident`](#ident) +* [`item`](#item) +* [`lifetime`](#lifetime) +* [`literal`](#literal) +* [`meta`](#meta) +* [`pat`](#pat) +* [`pat_param`](#pat_param) * [`path`](#path) +* [`stmt`](#stmt) * [`tt`](#tt) -* [`meta`](#meta) -* [`lifetime`](#lifetime) +* [`ty`](#ty) * [`vis`](#vis) -* [`literal`](#literal) [^metavariables]: 最新内容可参考 *Reference* 的 [Metavariables](https://doc.rust-lang.org/nightly/reference/macros-by-example.html#metavariables) 一节。 +## `block` + +`block` 分类符只匹配 +[block 表达式](https://doc.rust-lang.org/reference/expressions/block-expr.html)。 + +块 (block) 由 `{` 开始,接着是一些语句,最后是可选的表达式,然后以 `}` 结束。 +块的类型要么是最后的值表达式类型,要么是 `()` 类型。 + +```rust,editable +macro_rules! blocks { + ($($block:block)*) => (); +} + +blocks! { + {} + { + let zig; + } + { 2 } +} +fn main() {} +``` + +## `expr` + +`expr` 分类符用于匹配任何形式的表达式 +([expression](https://doc.rust-lang.org/reference/expressions.html))。 + +(如果把 Rust 视为面向表达式的语言,那么它有很多种表达式。) + +```rust,editable +macro_rules! expressions { + ($($expr:expr)*) => (); +} + +expressions! { + "literal" + funcall() + future.await + break 'foo bar +} +fn main() {} +``` + +## `ident` + +`ident` 分类符用于匹配任何形式的标识符 +([identifier](https://doc.rust-lang.org/reference/identifiers.html)) 或者关键字。 +。 + +```rust,editable +macro_rules! idents { + ($($ident:ident)*) => (); +} + +idents! { + // _ <- `_` 不是标识符,而是一种模式 + foo + async + O_________O + _____O_____ +} +fn main() {} +``` + ## `item` `item` 分类符只匹配 Rust 的 [item](https://doc.rust-lang.org/reference/items.html) @@ -43,7 +109,7 @@ items! { impl Foo {} /*...*/ } -# fn main() {} +fn main() {} ``` [`item`](https://doc.rust-lang.org/reference/items.html) @@ -63,26 +129,150 @@ items! { * [implementations](https://doc.rust-lang.org/reference/items/implementations.html) * [`extern` blocks](https://doc.rust-lang.org/reference/items/external-blocks.html) -## `block` +## `lifetime` -`block` 分类符只匹配 [block 表达式](https://doc.rust-lang.org/reference/expressions/block-expr.html) 。 +`lifetime` 分类符用于匹配生命周期注解或者标签 +([lifetime or label](https://doc.rust-lang.org/reference/tokens.html#lifetimes-and-loop-labels))。 +它与 [`ident`](#ident) 很像,但是 `lifetime` 会匹配到前缀 `''` 。 -块 (block) 由 `{` 开始,接着是一些语句,最后是可选的表达式,然后以 `}` 结束。 -块的类型要么是最后的值表达式类型,要么是 `()` 类型。 +```rust,editable +macro_rules! lifetimes { + ($($lifetime:lifetime)*) => (); +} + +lifetimes! { + 'static + 'shiv + '_ +} +fn main() {} +``` + +## `literal` + +`literal` 分类符用于匹配字面表达式 +([literal expression](https://doc.rust-lang.org/reference/expressions/literal-expr.html))。 ```rust,editable -macro_rules! blocks { - ($($block:block)*) => (); +macro_rules! literals { + ($($literal:literal)*) => (); } -blocks! { - {} - { - let zig; +literals! { + -1 + "hello world" + 2.3 + b'b' + true +} +fn main() {} +``` + +## `meta` + +`meta` 分类符用于匹配属性 ([attribute](https://doc.rust-lang.org/reference/attributes.html)), +准确地说是属性里面的内容。通常你会在 `#[$meta:meta]` 或 `#![$meta:meta]` 模式匹配中 +看到这个分类符。 + +```rust,editable +macro_rules! metas { + ($($meta:meta)*) => (); +} + +metas! { + ASimplePath + super::man + path = "home" + foo(bar) +} +fn main() {} +``` + +> 针对文档注释简单说一句: +> 文档注释其实是具有 `#[doc="…"]` 形式的属性,`...` 实际上就是注释字符串, +> 这意味着你可以在在宏里面操作文档注释! + +## `pat` + +`pat` 分类符用于匹配任何形式的模式 +([pattern](https://doc.rust-lang.org/reference/patterns.html)),包括 2021 edition +开始的 [or-patterns](https://doc.rust-lang.org/reference/patterns.html#or-patterns)。 + +```rust,editable +macro_rules! patterns { + ($($pat:pat)*) => (); +} + +patterns! { + "literal" + _ + 0..5 + ref mut PatternsAreNice + 0 | 1 | 2 | 3 +} +fn main() {} +``` + +## `pat_param` + +从 2021 edition 起, or-patterns 模式开始应用,这让 `pat` 分类符不再允许跟随 `|`。 + +为了避免这个问题或者说恢复旧的 `pat` 分类符行为,你可以使用 `pat_param` 片段,它允许 +`|` 跟在它后面,因为 `pat_param` 不允许 top level 或 or-patterns。 + +```rust,editable +macro_rules! patterns { + (pat: $pat:pat) => { + println!("pat: {}", stringify!($pat)); + }; + (pat_param: $($pat:pat_param)|+) => { + $( println!("pat_param: {}", stringify!($pat)); )+ + }; +} +fn main() { + patterns! { + pat: 0 | 1 | 2 | 3 } - { 2 } + patterns! { + pat_param: 0 | 1 | 2 | 3 + } +} +``` + +```rust,editable +macro_rules! patterns { + ($( $( $pat:pat_param )|+ )*) => (); } -# fn main() {} + +patterns! { + "literal" + _ + 0..5 + ref mut PatternsAreNice + 0 | 1 | 2 | 3 +} +fn main() {} +``` + +## `path` + +`path` 分类符用于匹配类型中的路径 +([TypePath](https://doc.rust-lang.org/reference/paths.html#paths-in-types))。 + +这包括函数式的 trait 形式。 + +```rust,editable +macro_rules! paths { + ($($path:path)*) => (); +} + +paths! { + ASimplePath + ::A::B::C::D + G::::C + FnMut(u32) -> () +} +fn main() {} ``` ## `stmt` @@ -160,60 +350,32 @@ fn main() { 由此我们知道: -1. 虽然 `stmt` 分类符没有捕获语句末尾的分号,但它依然在所需的时候返回了 (emit) 语句。 -原因很简单,分号本身就是有效的语句。 -所以我们实际输入 11 个语句调用了宏,而不是 8 个! +1. 虽然 `stmt` 分类符没有捕获语句末尾的分号,但它依然在所需的时候返回了 (emit) +语句。原因很简单,分号本身就是有效的语句。所以我们实际输入 10 个语句调用了宏,而不是 8 +个!这在把多个反复捕获放入一个反复展开时很重要,因为此时反复的次数必须相同。 -2. 在这里你应该注意到:`struct Foo;` 被匹配到了。 -否则我们会看到像其他情况一样有一个额外 `;` 语句。 -由前所述,这能想通:item 语句需要分号,所以这个分号能被匹配到。 +2. 在这里你应该注意到:`struct Foo;` 被匹配到了。否则我们会看到像其他情况一样有一个额外 `;` +语句。由前所述,这能想通:item 语句需要分号,所以这个分号能被匹配到。 3. 仅由块表达式或控制流表达式组成的表达式结尾没有分号, 其余的表达式捕获后产生的表达式会尾随一个分号(在这个例子中,正是这里出错)。 这里提到的细节能在 Reference 的 [statement](https://doc.rust-lang.org/reference/statements.html) -一节中找到。 - -[^debugging]: 可阅读 [调试](./debugging.html) 一章 - -## `pat` - -`pat` 分类符用于匹配任何形式的模式 ([pattern](https://doc.rust-lang.org/reference/patterns.html))。 - -```rust,editable -macro_rules! patterns { - ($($pat:pat)*) => (); -} - -patterns! { - "literal" - _ - 0..5 - ref mut PatternsAreNice -} -# fn main() {} -``` +一节中找到。但个细节通常这并不重要,除了要注意反复次数,通常没什么问题。 -## `expr` +[^debugging]: 可阅读 [调试](./debugging.md) 一章 -`expr` 分类符用于匹配任何形式的表达式 -([expression](https://doc.rust-lang.org/reference/expressions.html))。 +## `tt` -(如果把 Rust 视为面向表达式的语言,那么它有很多种表达式。) +`tt` 分类符用于匹配标记树 (TokenTree)。 +如果你是新手,对标记树不了解,那么需要回顾本书 +[标记树](../syntax/source-analysys.html#标记树-token-trees) +一节。`tt` 分类符是最有作用的分类符之一,因为它能匹配几乎所有东西, +而且能够让你在使用宏之后检查 (inspect) 匹配的内容。 -```rust,editable -macro_rules! expressions { - ($($expr:expr)*) => (); -} - -expressions! { - "literal" - funcall() - future.await - break 'foo bar -} -# fn main() {} -``` +这让你可以编写非常强大的宏技巧,比如 +[tt-muncher](../patterns/tt-muncher.md) 和 +[push-down-accumulator](../patterns/push-down-acc.md)。 ## `ty` @@ -230,141 +392,85 @@ types! { foo::bar bool [u8] + impl IntoIterator } -# fn main() {} +fn main() {} ``` -## `ident` +## `vis` -`ident` 分类符用于匹配任何形式的标识符或者关键字。 -([identifier](https://doc.rust-lang.org/reference/identifiers.html))。 +`vis` 分类符会匹配 **可能为空** 的内容。 +([Visibility qualifier](https://doc.rust-lang.org/reference/visibility-and-privacy.html))。 ```rust,editable -macro_rules! idents { - ($($ident:ident)*) => (); +macro_rules! visibilities { + // ∨~~注意这个逗号,`vis` 分类符自身不会匹配到逗号 + ($($vis:vis,)*) => (); } -idents! { - // _ /* `_` 不是标识符,而是一种模式 */ - foo - async - O_________O - _____O_____ +visibilities! { + , // 没有 vis 也行,因为 $vis 隐式包含 `?` 的情况 + pub, + pub(crate), + pub(in super), + pub(in some_path), } -# fn main() {} +fn main() {} ``` -## `path` +`vis` 实际上只支持例子里的几种方式,因为这里的 visibility +指的是可见性,与私有性相对。而涉及这方面的内容只有与 `pub` +的关键字。所以,`vis` 在关心匹配输入的内容是公有还是私有时有用。 -`path` 分类符用于匹配类型中的路径 -([TypePath](https://doc.rust-lang.org/reference/paths.html#paths-in-types)) 。 +此外,如果匹配时,其后没有标记流,整个宏会匹配失败: ```rust,editable -macro_rules! paths { - ($($path:path)*) => (); +macro_rules! non_optional_vis { + ($vis:vis) => (); } - -paths! { - ASimplePath - ::A::B::C::D - G::::C -} -# fn main() {} +non_optional_vis!(); +// ^^^^^^^^^^^^^^^^ error: missing tokens in macro arguments +fn main() {} ``` -## `tt` +重点在于“可能为空”。你可能想到这是隐藏了 `?` +重复操作符的分类符,这样你就不用直接在反复匹配时使用 +`?` —— 其实你不能将它和 `?` 一起在重复模式匹配中使用。 -`tt` 分类符用于匹配标记树 (TokenTree)。 -如果你是新手,对标记树不了解,那么需要回顾本书 -[标记树](../syntax/source-analysys.html#标记树-token-trees) -一节。`tt` 分类符是最有作用的分类符之一,因为它能匹配几乎所有东西, -而且能够让你在使用宏之后检查 (inspect) 匹配的内容。 - -## `meta` - -`meta` 分类符用于匹配属性 ([attribute](https://doc.rust-lang.org/reference/attributes.html)), -准确地说是属性里面的内容。通常你会在 `#[$meta:meta]` 或 `#![$meta:meta]` 模式匹配中 -看到这个分类符。 +可以匹配 `$vis:vis $ident:ident`,但不能匹配 `$(pub)? $ident:ident`,因为 `pub` +表明一个有效的标识符,所以后者是模糊不清的。 ```rust,editable -macro_rules! metas { - ($($meta:meta)*) => (); +macro_rules! vis_ident { + ($vis:vis $ident:ident) => (); } +vis_ident!(pub foo); // this works fine -metas! { - ASimplePath - super::man - path = "home" - foo(bar) +macro_rules! pub_ident { + ($(pub)? $ident:ident) => (); } -# fn main() {} +pub_ident!(pub foo); + // ^^^ error: local ambiguity when calling macro `pub_ident`: multiple parsing options: built-in NTs ident ('ident') or 1 other option. +fn main() {} ``` -> 针对文档注释简单说一句: -> 文档注释其实是具有 `#[doc="…"]` 形式的属性,`...` 实际上就是注释字符串, -> 这意味着你可以在在宏里面操作文档注释! +而且,搭配 `tt` 分类符和递归展开去匹配空标记也会导致有趣而奇怪的事情。 -## `lifetime` +当 `pub` 匹配了空标记,元变量依然算一次被捕获,又因为它不是 `tt`、`ident` 或 +`lifetime`,所以再次展开时是不清楚的。 -`lifetime` 分类符用于匹配生命周期注解或者标签 -([lifetime or label](https://doc.rust-lang.org/reference/tokens.html#lifetimes-and-loop-labels))。 -它与 [`ident`](#ident) 很像,但是 `lifetime` 会匹配到前缀 `''` 。 +这意味着如果这种捕获的结果传递给另一个将它视为 `tt` 的宏调用,你最终得到一棵空的标记树。 ```rust,editable -macro_rules! lifetimes { - ($($lifetime:lifetime)*) => (); +macro_rules! it_is_opaque { + (()) => { "()" }; + (($tt:tt)) => { concat!("$tt is ", stringify!($tt)) }; + ($vis:vis ,) => { it_is_opaque!( ($vis) ); } } - -lifetimes! { - 'static - 'shiv - '_ -} -# fn main() {} -``` - -## `vis` - -`vis` 分类符会匹配 **可能为空** 的内容。 -([Visibility qualifier](https://doc.rust-lang.org/reference/visibility-and-privacy.html))。 -重点在于“可能为空”。你可能想到这是隐藏了 `?` 重复操作符的分类符, -这样你就不用直接在反复匹配时使用 `?` —— 其实你不能将它和 `?` 一起在重复模式匹配中使用。 - -```rust,editable -macro_rules! visibilities { - // 注意这个逗号,`vis` 分类符自身不会匹配到逗号 - ($($vis:vis,)*) => (); -} - -visibilities! { - , - pub, - pub(crate), - pub(in super), - pub(in some_path), +fn main() { + // this prints "$tt is ", as the recursive calls hits the second branch with + // an empty tt, opposed to matching with the first branch! + println!("{}", it_is_opaque!(,)); } -# fn main() {} ``` -`vis` 实际上只支持例子里的几种方式,因为这里的 visibility 指的是可见性,与私有性相对。 -而涉及这方面的内容只有与 `pub` 的关键字。所以,`vis` 在关心匹配输入的内容是公有还是私有时有用。 - -## `literal` - -`literal` 分类符用于匹配字面表达式 -([literal expression](https://doc.rust-lang.org/reference/expressions/literal-expr.html))。 - -```rust,editable -macro_rules! literals { - ($($literal:literal)*) => (); -} - -literals! { - -1 - "hello world" - 2.3 - b'b' - true -} -# fn main() {} -```