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!

临时措施

本节将留给那些价值有待探讨,以及那些可能有缺陷因而不适合出现在正文中的模式与技巧。

算盘计数

临时信息:需要更合适的例子。虽然它是Ook!的重要组成部分之一,但该用例——匹配Rust分组机制无法表示的嵌套结构——实在是过于特殊,因此不适作为例子使用。

注意:此节预设读者了解下推累积以及标记树撕咬机

macro_rules! abacus {
    ((- $($moves:tt)*) -> (+ $($count:tt)*)) => {
        abacus!(($($moves)*) -> ($($count)*))
    };
    ((- $($moves:tt)*) -> ($($count:tt)*)) => {
        abacus!(($($moves)*) -> (- $($count)*))
    };
    ((+ $($moves:tt)*) -> (- $($count:tt)*)) => {
        abacus!(($($moves)*) -> ($($count)*))
    };
    ((+ $($moves:tt)*) -> ($($count:tt)*)) => {
        abacus!(($($moves)*) -> (+ $($count)*))
    };

    // 检查最终结果是否为零
    (() -> ()) => { true };
    (() -> ($($count:tt)+)) => { false };
}

fn main() {
    let equals_zero = abacus!((++-+-+++--++---++----+) -> ());
    assert_eq!(equals_zero, true);
}Run

当需要记录的计数会发生变化,且初始值为零或在零附近,且必须支持如下操作:

时,可以使用次技巧。

数值n将由一组共n个相同的特定标记来表示。对数值的修改操作将采用下推累积模式由递归调用完成。假设所采用的特定标记是x,则上述操作可实现为:

作用于计数值的操作将所选的标记来回摆动,如同算盘摆动算子。1

在想表示负数的情况下,值-n可被表示成n个相同的其它标记。在上例中,值+n被表示成n+标记,而值-m被表示成m-标记。

有负数的情况下操作起来稍微复杂一些,增减操作在当前数值为负时实际上互换了角色。给定+-分别作为正数与负数标记,相应操作的实现将变成:

注意在顶部的示例中,某些规则被合并到一起了(举例来说,对()($($count:tt)+)的增加操作被合并为对($($count:tt)*)的增加操作)。

如果想要提取出所计数目的实际值,可再使用普通的计数宏。对上例来说,终结规则可换为:

macro_rules! abacus {
    // ...

    // 下列规则将计数替换成实际值的表达式
    (() -> ()) => {0};
    (() -> (- $($count:tt)*)) => {
        {(-1i32) $(- replace_expr!($count 1i32))*}
    };
    (() -> (+ $($count:tt)*)) => {
        {(1i32) $(+ replace_expr!($count 1i32))*}
    };
}

macro_rules! replace_expr {
    ($_t:tt $sub:expr) => {$sub};
}Run

仅限此例:严格来说,想要达到此例的效果,没必要做的这么复杂。如果你不需要在宏中匹配所计的值,可直接采用重复来更加高效地实现:

macro_rules! abacus {
    (-) => {-1};
    (+) => {1};
    ($($moves:tt)*) => {
        0 $(+ abacus!($moves))*
    }
}Run

  1. 在这句极度单薄的辩解下,隐藏着选用此名称的真实理由:避免造出又一个名含“标记”的术语。今天就该跟你认识的作者谈谈规避语义饱和吧!公平来讲,本来也可以称它为“一元计数(unary counting)”。