macro_rules Tutorial¶
macro_rules allow us to define macros in terms of pairs of input and output
patterns, referred to as matchers and transcribers. Each pair makes up a rule, and
a group of rules is, well, a macro_rules.
Our First macro_rules¶
Let’s dive right in! Create a new script named first_macro_rules.py and add the
following:
# coding: macro-polo
macro_rules! my_macro:
[]:
'My first macro!'
print(my_macro!())
print('My first macro!')
$ python3 first_macro_rules.py
My first macro!
We defined a macro with a single rule, matching an empty token sequence. What if we try invoking it on a non-empty input?
# coding: macro-polo
macro_rules! my_macro:
[]:
'My first macro!'
print(my_macro!('hello'))
$ python3 first_macro_rules.py
Traceback (most recent call last):
File "/home/docs/checkouts/readthedocs.org/user_builds/macro-polo/envs/latest/lib/python3.12/site-packages/macro_polo/codec.py", line 25, in _decode
result = stringify(macro(tokens) or tokens)
^^^^^^^^^^^^^
File "/home/docs/checkouts/readthedocs.org/user_builds/macro-polo/envs/latest/lib/python3.12/site-packages/macro_polo/macros/super.py", line 26, in __call__
if (new_tokens := macro(tokens)) is not None:
^^^^^^^^^^^^^
File "/home/docs/checkouts/readthedocs.org/user_builds/macro-polo/envs/latest/lib/python3.12/site-packages/macro_polo/macros/super.py", line 51, in __call__
if (new_tokens := macro(tokens)) is not None:
^^^^^^^^^^^^^
File "/home/docs/checkouts/readthedocs.org/user_builds/macro-polo/envs/latest/lib/python3.12/site-packages/macro_polo/macros/super.py", line 102, in __call__
if (transformed_inner := self(inner_tokens)) is not None:
^^^^^^^^^^^^^^^^^^
File "/home/docs/checkouts/readthedocs.org/user_builds/macro-polo/envs/latest/lib/python3.12/site-packages/macro_polo/macros/super.py", line 83, in __call__
new_tokens, match_size = macro(tokens)
^^^^^^^^^^^^^
File "/home/docs/checkouts/readthedocs.org/user_builds/macro-polo/envs/latest/lib/python3.12/site-packages/macro_polo/macros/function.py", line 99, in __call__
result = self._invoke_macro(name, body)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/docs/checkouts/readthedocs.org/user_builds/macro-polo/envs/latest/lib/python3.12/site-packages/macro_polo/macros/function.py", line 85, in _invoke_macro
raise MacroError(
macro_polo.MacroError: invoking function-like macro 'my_macro': body didn't match expected pattern
SyntaxError: encoding problem: macro-polo
We got a macro_polo.MacroError:
macro_polo.MacroError: invoking function-like macro 'my_macro': body didn't match expected pattern
Let’s add a new rule to handle this case:
# coding: macro-polo
macro_rules! my_macro:
[]:
'My first macro!'
['hello']:
'Got "hello"'
print(my_macro!('hello'))
print('Got "hello"')
$ python3 first_macro_rules.py
Got "hello"
Great! But this only handles the specific token 'hello'. How can we handle any
token?
Capture Variables¶
Capture variables allow us to capture tokens of a specific type, binding them to a
name that we can later use in the transcriber. Capture variables have the syntax
$name:type in matchers, and just $name in transcribers.
Let’s modify our rule to accept any string token:
# coding: macro-polo
macro_rules! my_macro:
[]:
'My first macro!'
[$s:string]:
f'Got {$s!r}'
print(my_macro!('howdy'))
print(f'Got {'howdy'!r}')
$ python3 first_macro_rules.py
Got 'howdy'
Nice! But what if we want to accept any number of strings?
Repeaters¶
That brings us to repeaters. Repeaters let us—wait for it—repeat patterns. They come in three flavors, or repition modes:
$(pattern)?matchespattern≤1 times$(pattern)*matchespattern≥0 times$(pattern)+matchespattern≥1 times
Additionally, the latter two accept an optional separator token between the closing parenthesis and mode indicator.
Let’s see an example:
# coding: macro-polo
macro_rules! my_macro:
[]:
'My first macro!'
[$($s:string);+]:
f'Got {[ $(repr($s)),* ]}'
print(my_macro!('hey'; 'hi'; "what's up"))
print(f'Got {[repr('hey'), repr('hi'), repr("what's up")]}')
$ python3 first_macro_rules.py
Got ["'hey'", "'hi'", '"what\'s up"']