临时措施

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

算盘计数

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

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

  1. macro_rules! abacus {
  2. ((- $($moves:tt)*) -> (+ $($count:tt)*)) => {
  3. abacus!(($($moves)*) -> ($($count)*))
  4. };
  5. ((- $($moves:tt)*) -> ($($count:tt)*)) => {
  6. abacus!(($($moves)*) -> (- $($count)*))
  7. };
  8. ((+ $($moves:tt)*) -> (- $($count:tt)*)) => {
  9. abacus!(($($moves)*) -> ($($count)*))
  10. };
  11. ((+ $($moves:tt)*) -> ($($count:tt)*)) => {
  12. abacus!(($($moves)*) -> (+ $($count)*))
  13. };
  14. // 检查最终结果是否为零
  15. (() -> ()) => { true };
  16. (() -> ($($count:tt)+)) => { false };
  17. }
  18. fn main() {
  19. let equals_zero = abacus!((++-+-+++--++---++----+) -> ());
  20. assert_eq!(equals_zero, true);
  21. }

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

  • 增加一;
  • 减少一;
  • 与0(或任何其它固定有限值)相比较;

时,可以使用次技巧。

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

  • 增加一:匹配($($count:tt)*)并替换为(x $($count)*)
  • 减少一:匹配(x $($count:tt)*)并替换为($($count)*)
  • 与0相比较:匹配()
  • 与1相比较:匹配(x)
  • 与2相比较:匹配(x x)
  • (依此类推…)

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

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

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

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

  • 增加一:
    • 匹配()并替换为(+)
    • 匹配(- $($count:tt)*)并替换为($($count)*)
    • 匹配($($count:tt)+)并替换为(+ $($count)+)
  • 减少一:
    • 匹配()并替换为(-)
    • 匹配(+ $($count:tt)*)并替换为($($count)*)
    • 匹配($($count:tt)+)并替换为(- $($count)+)
  • 与0相比较:匹配 ()
  • 与+1相比较:匹配(+)
  • 与-1相比较:匹配(-)
  • 与+2相比较:匹配(++)
  • 与-2相比较:匹配(--)
  • (依此类推…)

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

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

  1. macro_rules! abacus {
  2. // ...
  3. // 下列规则将计数替换成实际值的表达式
  4. (() -> ()) => {0};
  5. (() -> (- $($count:tt)*)) => {
  6. {(-1i32) $(- replace_expr!($count 1i32))*}
  7. };
  8. (() -> (+ $($count:tt)*)) => {
  9. {(1i32) $(+ replace_expr!($count 1i32))*}
  10. };
  11. }
  12. macro_rules! replace_expr {
  13. ($_t:tt $sub:expr) => {$sub};
  14. }

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

  1. macro_rules! abacus {
  2. (-) => {-1};
  3. (+) => {1};
  4. ($($moves:tt)*) => {
  5. 0 $(+ abacus!($moves))*
  6. }
  7. }