This section is for patterns or techniques which are of dubious value, or which might be too niche for inclusion.
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)*)) }; // Check if the final result is zero. (() -> ()) => { true }; (() -> ($($count:tt)+)) => { false }; } fn main() { let equals_zero = abacus!((++-+-+++--++---++----+) -> ()); assert_eq!(equals_zero, true); }Provisional: needs a more compelling example. Although an important part of the
Ook!
macro, matching nested groups that are not denoted by Rust groups is sufficiently unusual that it may not merit inclusion.Note: this section assumes understanding of push-down accumulation and incremental TT munchers.
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)*)) }; // Check if the final result is zero. (() -> ()) => { true }; (() -> ($($count:tt)+)) => { false }; } fn main() { let equals_zero = abacus!((++-+-+++--++---++----+) -> ()); assert_eq!(equals_zero, true); }
This technique can be used in cases where you need to keep track of a varying counter that starts at or near zero, and must support the following operations:
A value of n is represented by n instances of a specific token stored in a group. Modifications are done using recursion and push-down accumulation. Assuming the token used is x
, the operations above are implemented as follows:
($($count:tt)*)
, substitute (x $($count)*)
.(x $($count:tt)*)
, substitute ($($count)*)
.()
.(x)
.(x x)
.In this way, operations on the counter are like flicking tokens back and forth like an abacus.1
In cases where you want to represent negative values, -n can be represented as n instances of a different token. In the example given above, +n is stored as n +
tokens, and -m is stored as m -
tokens.
In this case, the operations become slightly more complicated; increment and decrement effectively reverse their usual meanings when the counter is negative. To whit given +
and -
for the positive and negative tokens respectively, the operations change to:
()
, substitute (+)
.(- $($count:tt)*)
, substitute ($($count)*)
.($($count:tt)+)
, substitute (+ $($count)+)
.()
, substitute (-)
.(+ $($count:tt)*)
, substitute ($($count)*)
.($($count:tt)+)
, substitute (- $($count)+)
.()
.(+)
.(-)
.(++)
.(--)
.Note that the example at the top combines some of the rules together (for example, it combines increment on ()
and ($($count:tt)+)
into an increment on ($($count:tt)*)
).
If you want to extract the actual value of the counter, this can be done using a regular counter macro. For the example above, the terminal rules can be replaced with the following:
fn main() { macro_rules! abacus { // ... // This extracts the counter as an integer expression. (() -> ()) => {0}; (() -> (- $($count:tt)*)) => { {(-1i32) $(- replace_expr!($count 1i32))*} }; (() -> (+ $($count:tt)*)) => { {(1i32) $(+ replace_expr!($count 1i32))*} }; } macro_rules! replace_expr { ($_t:tt $sub:expr) => {$sub}; } }macro_rules! abacus { // ... // This extracts the counter as an integer expression. (() -> ()) => {0}; (() -> (- $($count:tt)*)) => { {(-1i32) $(- replace_expr!($count 1i32))*} }; (() -> (+ $($count:tt)*)) => { {(1i32) $(+ replace_expr!($count 1i32))*} }; } macro_rules! replace_expr { ($_t:tt $sub:expr) => {$sub}; }
JFTE: strictly speaking, the above formulation of
fn main() { macro_rules! abacus { (-) => {-1}; (+) => {1}; ($($moves:tt)*) => { 0 $(+ abacus!($moves))* } } }abacus!
is needlessly complex. It can be implemented much more efficiently using repetition, provided you do not need to match against the counter's value in a macro:macro_rules! abacus { (-) => {-1}; (+) => {1}; ($($moves:tt)*) => { 0 $(+ abacus!($moves))* } }
This desperately thin reasoning conceals the real reason for this name: to avoid having yet another thing with "token" in the name. Talk to your writer about avoiding semantic satiation today! ↩
In fairness, it could also have been called "unary counting".