Advanced Techniques

Tips and tricks for writing complex macro_rules.

Counting with null

Let’s write a macro that counts the number of token trees in its input. We’ll do this by replacing each token tree with 1 + and then ending it of with a 0.

We can write a recursive macro to recursively replace the first token tree, one-by-one:

# coding: macro-polo

macro_rules! count_tts_recursive:
    [$t:tt $($rest:tt)*]:
        1 + count_tts_recursive!($($rest)*)

    []: 0


print(count_tts_recursive![a b c d e])
print(1 + 1 + 1 + 1 + 1 + 0)
$ python3 count_tts_recursive.py 
5

Alternatively, we can use the null capture type to “count” the number of tts, and then emit the same number of 1 +s, all in one go:

# coding: macro-polo

macro_rules! count_tts_with_null:
    [$($_:tt $counter:null)*]:
        $($counter 1 +)* 0

print(count_tts_with_null![a b c d e])
print(1 + 1 + 1 + 1 + 1 + 0)
$ python3 count_tts_with_null.py 
5

Matching terminators with negative lookahead

Let’s write a macro that replaces ;s with newlines.

# coding: macro-polo

macro_rules! replace_semicolons_with_newlines_naive:
    [$($($line:tt)*);*]:
        $($($line)*)$^*

replace_semicolons_with_newlines_naive! { if 1: print(1); if 2: print(2) }
if 1 :print (1 );if 2 :print (2 )
$ python3 replace_semicolons_with_newlines_naive.py 
  File "/home/docs/checkouts/readthedocs.org/user_builds/macro-polo/checkouts/stable/docs/intro/macro_rules/_scripts/replace_semicolons_with_newlines_naive.py", line 2
    
    ^
SyntaxError: invalid syntax

When we try to run this, however, we get a SyntaxError.

If we look at the expanded source code, we notice something strange: the input is left completely unchanged!

The reason for this is actually quite simple: the $line:tt capture variable matches the semicolon, so the the entire input is captured in a single repition (of the outer repeater). What we really want is for $line:tt to match anything except ;, which we can do with a negative lookahead:

# coding: macro-polo

macro_rules! replace_semicolons_with_newlines_naive:
    [$($($[!;] $line:tt)*);*]:
        $($($line)*)$^*

replace_semicolons_with_newlines_naive! { if 1: print(1); if 2: print(2) }
if 1:
    print(1)
if 2:
    print(2)
$ python3 replace_semicolons_with_newlines.py 
1
2

Notice the addition of $[!;] before $line:tt. Now when we run this code, we get the output we expected.