1. 1. Introduction
  2. 2. 宏,彻底剖析
    1. 2.1. 语法扩展
      1. 2.1.1. 源码解析过程
      2. 2.1.2. AST中的宏
      3. 2.1.3. 展开
    2. 2.2. macro_rules!
    3. 2.3. 细枝末节
      1. 2.3.1. 再探捕获与展开
      2. 2.3.2. 卫生性
      3. 2.3.3. 不是标识符的标识符
      4. 2.3.4. 调试
      5. 2.3.5. 作用域
      6. 2.3.6. 导入/导出
  3. 3. 宏,实践介绍
  4. 4. 常用模式
    1. 4.1. 回调
    2. 4.2. 标记树撕咬机
    3. 4.3. 内用规则
    4. 4.4. 下推累积
    5. 4.5. 重复替代
    6. 4.6. 尾部分隔符
    7. 4.7. 标记树聚束
    8. 4.8. 可见性
    9. 4.9. 临时措施
  5. 5. 轮子
    1. 5.1. AST强转
    2. 5.2. 计数
    3. 5.3. 枚举解析
  6. 6. 实例注解
    1. 6.1. Ook!

标记树聚束

macro_rules! call_a_or_b_on_tail {
    ((a: $a:expr, b: $b:expr), 调a $($tail:tt)*) => {
        $a(stringify!($($tail)*))
    };

    ((a: $a:expr, b: $b:expr), 调b $($tail:tt)*) => {
        $b(stringify!($($tail)*))
    };

    ($ab:tt, $_skip:tt $($tail:tt)*) => {
        call_a_or_b_on_tail!($ab, $($tail)*)
    };
}

fn compute_len(s: &str) -> Option<usize> {
    Some(s.len())
}

fn show_tail(s: &str) -> Option<usize> {
    println!("tail: {:?}", s);
    None
}

fn main() {
    assert_eq!(
        call_a_or_b_on_tail!(
            (a: compute_len, b: show_tail),
            规则的 递归部分  跳过 所有这些 标记
            它们 并不 关心 我们究竟 调b 还是 调a
            只有 终结规则 关心
        ),
        None
    );
    assert_eq!(
        call_a_or_b_on_tail!(
        (a: compute_len, b: show_tail),
        而现在 为了 显式 可能的路径 有两条
        我们也 调a 一哈: 它的 输入 应该
        自我引用 因此 我们给它 一个 72),
        Some(72)
    );
}Run

在十分复杂的递归宏中,可能需要非常多的参数,才足以在每层调用之间传递必要的标识符与表达式。然而,根据实现上的差异,可能存在许多这样的中间层,它们转发了这些参数,但并没有用到。

因此,将所有这些参数聚成一束,通过分组将其放进单独一棵标记树里;可以省事许多。这样一来,那些用不到这些参数的递归层可以直接捕获并替换这棵标记树,而不需要把整组参数完完全全准准确确地捕获替换掉。

上面的例子把表达式$a$b聚束,然后作为一棵tt交由递归规则转发。随后,终结规则将这组标记打开,并访问其中的表达式。