mozilla/sweet.js — pull request #244 by natefaubion (2014-02-06 03:39:52+00:00)
Title: invoke/invokeOnce pattern classes, identity rules, throwSyntaxCaseError

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:

```js
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](https://gist.github.com/natefaubion/e022a7defe19a69f468b)). 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.

```js
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:

```js
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.