Created by: natefaubion
This PR contains a few related features:
invoke
and invokeOnce
This is a new pattern class that actually lets you invoke another macro as a pattern class. It sounds a bit weird at first, but it's actually really powerful! It basically inserts the provided macro into the token stream, expands it, and assigns the result to the pattern variable. If the macro fails to match, it fails the invoking rule, just like any other pattern class. Here's an example that matches functions:
macro func {
rule { function ($args (,) ...) { $body ... } } => {
function ($args (,) ...) { $body ... }
}
rule { function $name:ident ($args (,) ...) { $body ... } } => {
function $name ($args (,) ...) { $body ... }
}
}
macro checkFunc {
rule { $f:invoke(func) } => {
$f
}
}
checkFunc function foo() {} // works
checkFunc 12 // fails
It turns out you can encode all sorts of things with this pattern: alternates, optional tokens, a keyword class, a numeric class. You can even write something to circumvent ASI (examples). In fact, you could actually write all the built in classes using this form.
The difference between invoke
and invokeOnce
is that invoke
will keep expanding as long as it keeps returning a macro. This is so you can use (mutually) recursive macros as a pattern class. invokeOnce
only invokes the provided macro the one time and uses that result.
Identity rules
The above example for matching function syntax is a bit verbose because you have to repeat yourself. That's what identity rules are for: they spit out exactly what was matched on. You do this by using ...
instead of a normal body for your rule.
macro func {
rule { function ($args (,) ...) { $body ... } } => ...
rule { function $name:ident ($args (,) ...) { $body ... } } => ...
}
macro checkFunc {
rule { $f:invoke(func) } => {
$f
}
}
checkFunc function() {} // works
checkFunc 12 // fails
This accounts for wildcards, repeaters with separators, pattern groups, etc so nothing should be dropped. Case macros don't have identity rules, because they would just loop forever!
throwSyntaxCaseError
This is a bit like throwSyntaxError
, but should be used specifically when you are using another macro as a pattern class. Internally, sweet.js throws a SyntaxCaseError
when a macro fails to match. This just lets you do it manually. Here's how you might use it in a macro that checks for keyword:
macro keyword {
case { _ $kw } => {
var kw = #{ $kw };
if (kw[0].token.type === parser.Token.Keyword) {
return kw;
}
throwSyntaxCaseError('Not a keyword');
}
}
throwSyntaxError
takes an optional message. The message isn't used anywhere currently, but may be used at some point to provide better errors.