Created
August 16, 2011 11:51
-
-
Save masak/1148915 to your computer and use it in GitHub Desktop.
Musings on macros
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Just grepping for /macro/ in S06, I find this: | |
macro circumfix:«<!-- -->» ($text) is parsed / .*? / { "" } | |
Observation: a circumfix toggles the parser from expect-term mode to | |
expect-op mode. Reasonably, this goes for macros as well. Which means that | |
the parser would croak if the next thing after <!-- --> was a term. That | |
doesn't feel right to me. I call bogus on that example. | |
Observation: no-one has ever mentioned multi macros or proto macros, to | |
my knowledge. Seems there's nothing to prevent them from working, though. | |
Observation: Type matching is certainly possible on a macro, but recall | |
that the parameters coming in are Perl6::AST::Node objects. | |
Observation: A builtin to evaluate a Perl6::AST::Node structure would | |
certainly be useful. Maybe just overload &eval? | |
Observation: 'temp' is called a macro in S06. It's only alluded what it's | |
a macro for. Would really like to see it explicitly spelled out. | |
Ok, here's the simplest macro I can think of. | |
macro four { | |
quasi { 4 } | |
} | |
say four; # 4 | |
It intercepts the macro call 'four' at parse-time and injects a '4' at | |
that point in the AST. | |
Here's another one. | |
macro add($a, $b) { | |
quasi { | |
{{{ $a }}} + {{{ $b }}} | |
} | |
} | |
say add 40, 2; # 42 | |
It intercepts 'add 40, 2', essentially makes a call to that macro, | |
which inserts an infix:<+> in the AST, and the ASTs for 40 and 2 get | |
spliced in and hang off of it. Subsequent optimizer steps might find | |
it and do stuff with the addition of two literals. | |
Observation: I find the {{{...}}} syntax to be heavy-handed considering | |
its fairly frequent use in quasi-quotes. But prefix:<`> is free, and it | |
has a Lisp precedent, so I'll would be fine with defining a language | |
extension which allows that. | |
Observation: $?PARSER is mentioned once in S06 under macros, and once | |
in S29 when talking about &eval. Suspect that it's a pre-braid fossil. | |
Observation: I guess that a macro will have to return either an AST, | |
or something Cool. If it doesn't, it's an error. Maybe it's even an | |
error to return something Cool but non-Str. | |
Observation: The '¯o()' syntax means that macros have to be available | |
at runtime, to return an AST which gets compiled and run. Behind the | |
scenes, this kind of invocation also needs to turn the values of its | |
arguments into AST. | |
Observation: It says in the spec that macros are called at BEGIN time, | |
which means that macro definitions *must* be predefined (as opposed to | |
classes). Therefore, there cannot be mutually recursive macro calls, by | |
definition. (Well, not at parse time, anyway.) | |
Here's a slightly more elaborate, useful example of a macro: | |
macro metafor(Int $levels, &block) { | |
return quasi { {{{&block}}}() } if eval($levels) < 1; | |
return quasi { | |
for ^10 -> $x { | |
metafor({{{$levels}}} - 1, &block); | |
} | |
} | |
} | |
metafor(3, { say "OH HAI" }); | |
(Code assumes the existence of &eval(Perl6::AST::Node).) | |
It intercepts the macro call at the end, recurses three times, creating | |
an AST which corresponds to this code: | |
for ^10 -> $x { | |
for ^10 -> $x { | |
for ^10 -> $x { | |
say "OH HAI"; | |
} | |
} | |
} | |
So it'll print "OH HAI\n" 1000 times. | |
Observation: Some care should be taken to make stack overflow errors due to | |
infinite macro recursions understandable to people. Macro recursion can | |
happen both inside and outside of a quasiquote. | |
Observation: We haven't even begun to think about routines for cutting and | |
pasting ASTs so far. There should probably be some. Unless that's the | |
responsibility of a non-core module. | |
Observation: In the above example I had a desire to access the whole stack of | |
created $x variables, but I see no easy way to do that. Suggestions welcome. | |
Observation: Syntax errors involving macros returning strings (rather than | |
ASTs) would be much improved by giving both the snippet of code as it appears | |
in the original source (which will point to where to fix it), as well as the | |
snippet of code that the macro generated (which will clarify what's wrong). | |
Showing only one will lead to one kind of confusion; showing only the other | |
will lead to another kind. | |
Here's an example of hygiene: | |
macro hygiene($x) { # $x in macro callee scope | |
quasi { | |
my $x = 2; # $x in quasi scope | |
say $x; | |
say {{{$x}}}; | |
} | |
} | |
my $x = 1; # $x in macro caller scope | |
say $x; | |
hygiene(3); | |
It should output "1\n2\n3\n", and not complain about redeclarations of $x ('cus | |
there aren't any). | |
Observation: There are three different scopes involved in a macro: | |
injects into runs at | |
-------------------------------- | |
caller<-------+ runtime | |
callee | compile time ("caller's compile time = macro's runtime") | |
quasi---------+ runtime | |
The quasi scope can, and does, interact with the caller scope in various ways, | |
but there's a default layer of hygiene applied which makes the 'my $x' in the | |
quasi not collide with the 'my $x' in the (mainline) caller code above in the | |
example. | |
If you *want* them to collide, you use $COMPILING::x, as per S06. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment