Skip to content

Instantly share code, notes, and snippets.

@masak
Created August 16, 2011 11:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save masak/1148915 to your computer and use it in GitHub Desktop.
Save masak/1148915 to your computer and use it in GitHub Desktop.
Musings on macros
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. Is there anything that prevents them from existing?
Observation: It seems to me we could at least do a limited type matching
in macros, such that 'macro foo(Int $i) { ... }' could work on at least
literals. Or variables with a declared type. There seems to be a concept
'static type' involved, or something; the type that we can derive with
what the compiler can derive at parse time. If a call to a multi becomes
ambiguous (even though at runtime it wouldn't be), the compiler issues
an error.
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, 40 and 2 get spliced in and
hangs 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 use. But prefix:<`> is free, and it has a Lisp precedent, so I'll
probably define 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 '&macro()' syntax means that macros have to be available
at runtime, to return an AST which gets compiled and run.
Here's a slightly more elaborate, useful example of a macro:
macro metafor(Int $levels, &block) {
return quasi { {{{&block}}}() } if $levels < 1;
return quasi {
for ^10 -> $x {
metafor({{{$levels}}} - 1, &block);
}
}
}
metafor(3, { say "OH HAI" });
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: I'm not sure when recursive macro calls outside of a quasi
are made. If it's right when it's parsed -- that's the immediate guess --
it feels very much like a BEGIN block (execute this at parse time)... but
then not all multi variants of the macro may have been parsed yet. Hm, I
guess that's a problem with BEGIN blocks as well.
Observation: We haven't even begun to think about routines for managing ASTs
so far. There should probably be some.
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), and 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; # $x in quasi scope
say my $x = 2;
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