@macroforge/mcp-server
Version:
MCP server for Macroforge documentation and code analysis
88 lines (64 loc) • 2.66 kB
Markdown
# Repetitions & Backtracking
## Syntax
A repetition is `$( … )` followed by an optional separator and a required count suffix:
```
$( … )* zero or more, no separator
$( … )+ one or more, no separator
$( … )? zero or one
$( … ),* zero or more, comma-separated
$( … ),+ one or more, comma-separated
$( … );+ one or more, semicolon-separated
```
**Only `,` and `;` are valid separators.** Anything else is a parse error.
The same syntax works on both sides of the arrow. On the pattern side it captures a sequence; on the
body side it replays one:
```typescript
const $vec = macroRules`
($($x:Expr),*) => {
const __v = [];
$( __v.push($x); )*
__v
}
`;
```
## Body-side separators use a lookahead
On the body side, a `,` or `;` immediately after `$( … )` is only treated as a separator when a `*`,
`+`, or `?` follows it. Otherwise it stays literal text. That is what makes this work as written:
```typescript
$($rest),+ // comma is the separator
$( stmt; )+ // semicolon is inside the group, emitted each time
```
## Length inference
The number of iterations is inferred from the sequence-bound metavariables mentioned _inside_ the
group:
- Mention **none** → `UnanchoredRepetition` error. The expander has no way to know how many times to
repeat.
- Mention **two of different lengths** → `InconsistentSequenceLength` error.
```typescript
// Error: nothing inside the repetition is sequence-bound.
($($x:Expr),*) => { $( console.log("hi"); )* }
```
Nested repetitions are supported in bodies, and each level infers its own length from the variables
it mentions.
## Bounded backtracking (value position)
Repetitions are greedy, but the matcher will backtrack to let a trailing metavariable match. This
pattern works:
```typescript
const $last = macroRules`
($($init:Expr),* $last:Expr) => $last
`;
$last(1, 2, 3); // → 3
```
The matcher first lets `$($init),*` consume everything, fails to bind `$last`, then gives back one
element and succeeds. Backtracking is bounded — it gives back one element at a time rather than
exploring every split — so it's predictable and cheap.
## No backtracking in type position
This is the important limitation: **type-position matching is greedy with no retry.** The identical
pattern in a `kind: "type"` macro cannot match, because the repetition consumes every type argument
and never gives one back:
```typescript
// Works in value position, never matches in type position:
($($t:Type),* $last:Type) => …
```
Restructure type-position patterns so the variable-length group is last, or match a fixed arity per
arm.