API Stability Notice

Macroforge is under active development. The API is not yet stable and may change between versions. Some documentation sections may be outdated.

Patterns & Fragments

A pattern is a sequence of metavariables ($name:Kind), repetitions, and the literal separators , and ;. Anything else in a pattern is a parse error.

const $pick = macroRules`
  ($obj:Ident, $key:Lit) => $obj[$key]
`;

Fragment specifiers

There are 11 fragment kinds. Each has a PascalCase name and a lowercase alias — note that two of the aliases are not simply the lowercased name:

KindAliasMatches
ExprexprAny expression
StmtstmtAny statement — see below
BlockblockAn arrow function (() => { … })
IdentidentA bare identifier
TypetyA TypeScript type — type position only
PatpatA destructuring pattern — see below
LitliteralA literal value
PathpathAn identifier or dotted member (a.b.c)
ItemitemA top-level declaration — see below
DecoratordecoratorA decorator annotation — see below
TtttStructural fallback

What each kind actually accepts

In value position (a normal $macro(...) call), JavaScript’s grammar only permits expressions as arguments. That constrains things more than the names suggest:

  • Lit accepts string, numeric, boolean, null, BigInt, regex, and template literals.
  • Path accepts an identifier or a static member expression. a.b.c matches; a[b] and a?.b do not.
  • Block accepts only an arrow function expression — not a bare { … }, which isn’t an expression.
  • Tt is a fallback that matches any expression. It is not a universal wildcard: it cannot match statements, patterns, or items.
  • Spread arguments (...xs) never match any fragment kind.

Four kinds parse fine but produce a hard error if you use them in value position, because nothing they describe can appear as a call argument. Each error explains the mismatch and suggests an alternative:

KindWhy it can’t match, and what to do instead
StmtStatements aren’t expressions. Pass () => { …stmts } and capture it with Block.
TypeTypes only appear in type position. Use Expr here, or declare a kind: "type" macro.
PatDestructuring patterns only appear in binding position. Capture the whole object with Expr.
ItemDeclarations can’t be arguments. Capture a function/class expression with Expr.
DecoratorDecorators attach to declarations. Capture the call itself with Expr.

In type position, the rules invert: only Type and Tt are accepted, and any other kind errors. See Type-Position Macros.

Literal separators

, and ; may appear as literal pattern elements, but they are ignored during matching — they’re structural sugar, not something the matcher enforces. A consequence worth knowing: the common $(,)? trailing-comma idiom is a no-op. It parses, and it does no harm, but it isn’t what makes trailing commas work.

Matching order

Arms are tried top to bottom, and the first match wins. Put your most specific arms first:

const $sum = macroRules`
  () => 0
  ($x:Expr) => $x
  ($x:Expr, $($rest:Expr),+) => ($x + $sum($($rest),+))
`;

() matches exactly zero arguments. If no arm matches, you get a NoArmMatched error listing every pattern that was tried — see the diagnostics catalogue.