Documentation
¶
Overview ¶
Package match implements the pattern matching engine for syntax-rules and syntax-case.
The package provides four layers:
Pattern Compiler ¶
SyntaxCompiler compiles pattern S-expressions into bytecode at macro definition time. Bytecode instructions handle literal matching, variable capture, and ellipsis repetition.
Matching VM ¶
Matcher executes bytecode against input forms at macro invocation time, capturing pattern variable bindings. The VM uses two stacks:
- Syntax stack: tracks position in the input tree
- Capture stack: tracks captured bindings with nesting for ellipsis
Syntax Adapter ¶
SyntaxMatcher adds hygiene: scope-aware literal matching and template expansion with intro scopes per Flatt's "sets of scopes" model.
Template Expansion (syntax_expand.go) ¶
- SyntaxMatcher.Expand: expand template with captured bindings via ExpandOptions
Reference: R7RS Section 4.3.2 (syntax-rules pattern language).
Index ¶
- Constants
- Variables
- func FreeIdKey(name string, scopes []*syntax.Scope) string
- func FreeIdName(key string) string
- func PatternVarSubstitutions() uint64
- func TemplateDenotesPatternVariable(templateScopes, patternScopes []*syntax.Scope) bool
- type BindingChecker
- type ByteCodeCaptureCar
- type ByteCodeCaptureCdr
- type ByteCodeCompareCar
- type ByteCodeCompareCdr
- type ByteCodeDiscardCar
- type ByteCodeDiscardCdr
- type ByteCodeDone
- type ByteCodeJump
- type ByteCodePopContext
- type ByteCodePushContext
- type ByteCodeRequireCarEmptyVector
- type ByteCodeSkipIfEmpty
- type ByteCodeSkipIfTailCount
- type ByteCodeVisitCar
- type ByteCodeVisitCarAsBox
- type ByteCodeVisitCarAsVector
- type ByteCodeVisitCdr
- type CompilePatternOpts
- type CompiledPattern
- type ExpandOptions
- type FreeIdResolver
- type LiteralMatcher
- type LiteralPin
- type Matcher
- type MatcherOption
- type PatternAnalysis
- type SyntaxCommand
- type SyntaxCompiler
- type SyntaxMatcher
- func (p *SyntaxMatcher) CloneForMatch() *SyntaxMatcher
- func (p *SyntaxMatcher) Expand(template syntax.SyntaxValue, opts ExpandOptions) (syntax.SyntaxValue, error)
- func (p *SyntaxMatcher) GetBindings() map[string]syntax.SyntaxValue
- func (p *SyntaxMatcher) Match(ctx context.Context, input syntax.SyntaxValue) error
- func (p *SyntaxMatcher) MatchWithBindingChecker(ctx context.Context, input syntax.SyntaxValue, checker BindingChecker) error
- type SyntaxMatcherOpts
Constants ¶
const DefaultEllipsis = "..."
DefaultEllipsis is the standard R7RS ellipsis identifier.
Variables ¶
var ( // ErrUnknownOpCode is returned when an unknown bytecode is encountered. ErrUnknownOpCode = werr.ErrUnknownOpCode // ErrNotAMatch is returned when the input does not match the pattern. ErrNotAMatch = werr.ErrNotAMatch )
Functions ¶
func FreeIdKey ¶ added in v1.19.0
FreeIdKey is the map key for a free identifier's pre-resolved binding in ExpandOptions.FreeIds and its compile-time source. It discriminates on the scope set as well as the name, so two same-named template identifiers scoped differently keep separate resolutions. The producer (collectFreeIdentifiers) and this consumer must build the key identically.
The fingerprint contains only [0-9,], so the first '|' unambiguously ends it — the name that follows may itself contain '|' without colliding with another (name, scopes) pair.
func FreeIdName ¶ added in v1.19.0
FreeIdName recovers the bare identifier from a FreeIdKey — the inverse of FreeIdKey. The fingerprint contains only [0-9,], so the first '|' unambiguously ends it (a name may itself contain '|'). Callers that indexed a freeIds map get the composite key, not the bare name; use this to compare against a symbol's own name (e.g. a template's self-reference).
func PatternVarSubstitutions ¶ added in v1.20.0
func PatternVarSubstitutions() uint64
PatternVarSubstitutions reports how many template occurrences have been replaced by a capture in this process. Callers ratchet on a DIFFERENCE across a known unit of work, never on the absolute value, which depends on whatever else ran first in the same binary.
func TemplateDenotesPatternVariable ¶ added in v1.20.0
TemplateDenotesPatternVariable reports whether a template occurrence denotes the pattern variable a capture was recorded under, and may therefore be replaced by it.
**One caller: the runtime expander in this file.** It used to gate both template paths, but the compile-time emit in compileSyntaxTemplateToOps now asks an ordinary scoped GetLocalIndex instead — pattern variables are bound at their pattern identifier's scopes, so resolution answers this question without a separate predicate. This path cannot follow, and the reason is structural rather than pending work: captureContext.bindings is a map[string]SyntaxValue filled by the match VM from pattern-variable NAMES in the compiled pattern bytecode, so there is no environment frame here and no *Binding to compare. Making it resolution-driven means re-keying the capture context and the bytecode by scope set — a redesign of this package's core, not a query change.
The pattern variable is the BINDER and the template occurrence is a REFERENCE to it, so the rule is Flatt's resolution relation and nothing else:
patternScopes ⊆ templateScopes
It is ordinary subset resolution because use-site scopes make it sufficient. Every macro invocation stamps a fresh scope on its input form and never removes it (compilation.newUseSiteScope), so use-site syntax wears a scope macro-introduced syntax does not. An identifier an OUTER macro introduced therefore lacks the use-site scope the inner macro's pattern variable carries, is not a superset of it, and the capture `(outer inner (_ x))` — outer's template saying `(list x)` — is refused here rather than by any extra condition. R7RS §4.3, Chez and Racket agree that `x` denotes outer's definition-site binding.
Before use-site scopes this predicate carried a ceiling as well, naming the scopes binding forms inside the clause body had minted, because bare subset admitted that capture and set equality refused a `(syntax ...)` written under a `let` in a clause body. Both endpoints are now handled by the floor alone: the body binder's scope is on the template and absent from the pattern, which subset permits, while the outer macro's introduction leaves the pattern's use-site scope off the template, which subset refuses. The ceiling, and the binder-scope log that fed it, are gone — see memory/2026-08-20-use-site-scopes-impl.local.md.
Types ¶
type BindingChecker ¶
type BindingChecker interface {
// HasBinding checks if sym with the given scopes has a lexical binding.
// Returns true if the symbol is bound (to a variable, macro, etc.).
HasBinding(sym string, scopes []*syntax.Scope) bool
// GetBinding returns the binding for sym with the given scopes.
// Returns nil if no binding exists. Bindings can be compared for
// pointer equality to check if two identifiers have the same
// binding (per R7RS §4.3.2).
GetBinding(sym string, scopes []*syntax.Scope) *environment.Binding
// GetLiteralBinding resolves the USE-SITE side of the R7RS §4.3.2 comparison:
// the frame's own lexical chain at its own phase, then the special-form
// registry phase, and no other phase. That second probe is what makes
// auxiliary syntax visible — else and => are registered at PhaseCompile,
// ambient to every phase, where GetBinding above (the frame's own phase only)
// reports them unbound. Searching further would let one phase's binding of the
// name decide another phase's literal. ok is false when resolution was
// ambiguous.
GetLiteralBinding(sym string, scopes []*syntax.Scope) (*environment.Binding, bool)
}
BindingChecker is an interface for checking if a symbol has a lexical binding. This is used for R7RS auxiliary syntax hygiene: literals like => and else should not match when the identifier has been locally bound. Implemented by machine/compilation (*envBindingChecker) to avoid circular imports.
type ByteCodeCaptureCar ¶
type ByteCodeCaptureCar struct {
Binding string
}
ByteCodeCaptureCar captures the current car as a pattern variable binding.
func (ByteCodeCaptureCar) String ¶
func (p ByteCodeCaptureCar) String() string
type ByteCodeCaptureCdr ¶
type ByteCodeCaptureCdr struct {
Binding string
}
ByteCodeCaptureCdr captures the current cdr as a pattern variable binding. This is used for improper list patterns like (_ a . rest) where rest should capture the remaining elements of the input list.
R7RS §4.3.2: In a pattern, an identifier followed by . and another identifier matches any input that is a list of one or more elements, binding the first identifier to the first element and the second identifier to the rest of the list.
func (ByteCodeCaptureCdr) String ¶
func (p ByteCodeCaptureCdr) String() string
type ByteCodeCompareCar ¶
type ByteCodeCompareCar struct {
Value syntax.SyntaxValue
}
ByteCodeCompareCar compares the current car position with a literal syntax value.
func (ByteCodeCompareCar) String ¶
func (p ByteCodeCompareCar) String() string
type ByteCodeCompareCdr ¶
type ByteCodeCompareCdr struct {
Value syntax.SyntaxValue
}
ByteCodeCompareCdr compares the cdr of the current pair with a literal syntax value. This is used for improper list patterns where the tail is a literal, e.g., (a . b) where b is a literal symbol to match exactly.
func (ByteCodeCompareCdr) String ¶
func (p ByteCodeCompareCdr) String() string
type ByteCodeDiscardCar ¶ added in v1.20.0
type ByteCodeDiscardCar struct{}
ByteCodeDiscardCar asserts that the current position holds an element and matches it without binding.
R7RS §4.3.2: `_` matches any input and never binds. Emitting NOTHING for it was not equivalent to matching it: the element stays visible to Done, whose one-instruction lookahead then reads the following pattern element's bytecode and cannot tell "the pattern ends here" from "the next pattern element is a wildcard". So `((k (y) _) …)` rejected `(d (1) 9)` and, in the other direction, `((k (y) _ _) …)` accepted a two-element input.
The presence assertion is the whole instruction; advancement is the emitted VisitCdr's job, exactly as for a pattern variable's CaptureCar.
func (ByteCodeDiscardCar) String ¶ added in v1.20.0
func (ByteCodeDiscardCar) String() string
type ByteCodeDiscardCdr ¶ added in v1.19.0
type ByteCodeDiscardCdr struct{}
ByteCodeDiscardCdr consumes the current cdr without binding it.
R7RS §4.3.2: `_` is a wildcard that matches any input but never binds. In a dotted tail (`(_ a . _)`) it must still consume the rest of the input, so the matcher cannot simply emit nothing — that would leave the trailing elements visible to Done, which requires the position to be exhausted.
func (ByteCodeDiscardCdr) String ¶ added in v1.19.0
func (ByteCodeDiscardCdr) String() string
type ByteCodeDone ¶
type ByteCodeDone struct{}
ByteCodeDone signals completion of the current subtree.
func (ByteCodeDone) String ¶
func (ByteCodeDone) String() string
type ByteCodeJump ¶
type ByteCodeJump struct {
Offset int
}
ByteCodeJump performs an unconditional jump by a relative offset. Used for looping back in ellipsis patterns.
func (ByteCodeJump) String ¶
func (p ByteCodeJump) String() string
type ByteCodePopContext ¶
type ByteCodePopContext struct {
EllipsisID int
}
ByteCodePopContext ends the current capture context.
func (ByteCodePopContext) String ¶
func (p ByteCodePopContext) String() string
type ByteCodePushContext ¶
type ByteCodePushContext struct {
EllipsisID int
}
ByteCodePushContext starts a new capture context for an ellipsis iteration. EllipsisID identifies which ellipsis pattern this context belongs to, enabling multiple independent ellipsis patterns in the same clause.
func (ByteCodePushContext) String ¶
func (p ByteCodePushContext) String() string
type ByteCodeRequireCarEmptyVector ¶
type ByteCodeRequireCarEmptyVector struct{}
ByteCodeRequireCarEmptyVector verifies that the car at the current position is an empty SyntaxVector. Used for empty vector patterns #().
func (ByteCodeRequireCarEmptyVector) String ¶
func (ByteCodeRequireCarEmptyVector) String() string
type ByteCodeSkipIfEmpty ¶
type ByteCodeSkipIfEmpty struct {
Offset int
}
ByteCodeSkipIfEmpty implements while-loop semantics for ellipsis patterns.
Problem: Without this, ellipsis patterns use do-while semantics, executing the loop body at least once. This breaks patterns like (foo e1 e2 ...) when matching (foo x) - the e2... part should match zero elements.
Solution: Check if the list is empty BEFORE entering the loop body. If empty, skip forward by Offset instructions to exit the loop.
This is the key fix for zero-iteration ellipsis matching in R7RS.
func (ByteCodeSkipIfEmpty) String ¶
func (p ByteCodeSkipIfEmpty) String() string
type ByteCodeSkipIfTailCount ¶
type ByteCodeSkipIfTailCount struct {
Offset int // Instructions to skip forward when exiting loop
Count int // Number of elements required for trailing pattern
}
ByteCodeSkipIfTailCount implements ellipsis-in-middle pattern matching.
R7RS §4.3.2 allows patterns like (a ... b c) where the ellipsis is followed by additional pattern elements. This instruction enables matching such patterns by checking if exactly Count elements remain in the list.
Behavior:
- If remaining elements == Count: jump forward by Offset (exit loop, match tail)
- If remaining elements > Count: continue (match more ellipsis iterations)
- If remaining elements < Count: return ErrNotAMatch (not enough for tail)
When Count == 0, this behaves identically to ByteCodeSkipIfEmpty.
func (ByteCodeSkipIfTailCount) String ¶
func (p ByteCodeSkipIfTailCount) String() string
type ByteCodeVisitCar ¶
type ByteCodeVisitCar struct{}
ByteCodeVisitCar navigates into the car of the current pair.
func (ByteCodeVisitCar) String ¶
func (ByteCodeVisitCar) String() string
type ByteCodeVisitCarAsBox ¶ added in v1.20.0
type ByteCodeVisitCarAsBox struct{}
ByteCodeVisitCarAsBox checks that the car of the current pair is a SyntaxBox, wraps its content in a one-element SyntaxPair chain, and pushes the chain onto the syntax stack. A box holds exactly one datum, so `#&<pattern>` matches as the one-element sub-list `(<pattern>)` and every pair opcode applies unchanged.
func (ByteCodeVisitCarAsBox) String ¶ added in v1.20.0
func (ByteCodeVisitCarAsBox) String() string
type ByteCodeVisitCarAsVector ¶
type ByteCodeVisitCarAsVector struct{}
ByteCodeVisitCarAsVector checks that the car of the current pair is a SyntaxVector, converts its elements to a SyntaxPair chain, and pushes the chain onto the syntax stack. This enables pair-based matching of vector pattern contents per R7RS §4.3.2.
func (ByteCodeVisitCarAsVector) String ¶
func (ByteCodeVisitCarAsVector) String() string
type ByteCodeVisitCdr ¶
type ByteCodeVisitCdr struct{}
ByteCodeVisitCdr navigates to the cdr of the current pair.
func (ByteCodeVisitCdr) String ¶
func (ByteCodeVisitCdr) String() string
type CompilePatternOpts ¶
type CompilePatternOpts struct {
Literals values.StringSet
EllipsisID string
MatchAllElements bool // false (default): skip first element (R7RS syntax-rules macro keyword); true: match all elements (syntax-case)
}
CompilePatternOpts holds optional parameters for CompileSyntaxPattern. A nil opts pointer means all defaults (no literals, default "...").
type CompiledPattern ¶
type CompiledPattern struct {
Codes []SyntaxCommand
EllipsisVars map[int]values.StringSet
EllipsisDepths map[int]int // ellipsisID -> compilation order (lower = inner)
EllipsisID string // The ellipsis identifier used during compilation
}
CompiledPattern contains the compiled bytecode and ellipsis variable mapping.
func CompileSyntaxPattern ¶
func CompileSyntaxPattern( ctx context.Context, pattern syntax.SyntaxValue, variables values.StringSet, opts *CompilePatternOpts, ) (*CompiledPattern, error)
CompileSyntaxPattern compiles a syntax pattern into bytecode with optional literals and custom ellipsis. Pass nil opts for default behavior.
R7RS §4.3.2: The first subform of each pattern is the keyword of the macro being transformed; it is not matched against the macro use being transformed.
type ExpandOptions ¶
type ExpandOptions struct {
// IntroScope is the hygiene scope added to template-introduced symbols. It is
// not added to newly created pairs or vectors (those carry only a source
// context), nor to symbols preserved from pattern variable substitution, nor
// to symbols resolved to a definition-site local binding.
IntroScope *syntax.Scope
// FreeIds maps free identifiers to their pre-resolved bindings. A non-nil
// value carries resolved binding info from macro definition time (local
// scopes, global index, library scope). A nil value is treated the same as an
// absent key — the identifier receives the intro scope normally.
//
// Keyed by FreeIdKey (name + scope fingerprint), NOT by bare name: one clause
// template can hold two same-named free identifiers under different scope sets
// (a macro-generating macro splices the name in from two scope sources), and
// each resolves to its own binding. A bare-name key let the second collapse
// onto the first. The collector writes each occurrence under its own key; this
// map reads back under the template symbol's own scope set at expansion.
FreeIds map[string]FreeIdResolver
// UseSiteCtx, if provided, is used for the source context of newly created syntax
// objects instead of the template's context. This allows error messages to point to
// where the macro was invoked rather than where it was defined.
UseSiteCtx *syntax.SourceContext
// Origin tracks the macro expansion chain for debugging and error reporting.
// WithOrigin replaces (not appends to) a SourceContext's Origin field, so the
// last expansion pass wins. This is correct because the caller constructs the
// OriginInfo with the full chain: it reads the previous SourceContext.Origin and
// sets it as Parent on the new OriginInfo before calling Expand. The chain lives
// inside OriginInfo.Parent, not across successive SourceContext.Origin values.
//
// The nil guard at each call site skips when no origin is provided — avoiding a
// pointless allocation and preserving any existing origin on the SourceContext
// (e.g., syntax-case expands with ExpandOptions{}, where Origin is nil).
Origin *syntax.OriginInfo
// PatternVarSyntax contains the syntax symbols from the pattern, enabling nested
// macro hygiene via scope comparison. When set, template symbols are only substituted
// if their scopes match the corresponding pattern variable's scopes.
PatternVarSyntax map[string]*syntax.SyntaxSymbol
}
ExpandOptions holds the hygiene and source-tracking parameters for template expansion. All fields are optional; the zero value expands without hygiene (useful for testing).
Whether a template symbol is replaced by a capture is decided by TemplateDenotesPatternVariable over PatternVarSyntax; see that function for the rule and for why plain subset resolution is sufficient.
type FreeIdResolver ¶
type FreeIdResolver interface {
GetLocalScopes() []*syntax.Scope
GetGlobal() *environment.GlobalIndex
GetHasLocalBinding() bool
GetLibraryScope() *syntax.Scope
}
FreeIdResolver provides free identifier resolution information for template expansion hygiene. Implemented by machine/compilation.FreeIdResolution.
In ExpandOptions.FreeIds, a non-nil FreeIdResolver carries binding information from macro definition time. A nil map value is treated the same as an absent key (the identifier receives the intro scope). Methods return zero values when that aspect of resolution is absent.
type LiteralMatcher ¶
type LiteralMatcher func(inputSym *syntax.SyntaxSymbol, patternLiteralKey string) bool
LiteralMatcher is a function that checks if an input symbol matches a pattern literal. Returns true if the input should match, false if it's shadowed and should not match.
type LiteralPin ¶ added in v1.20.0
type LiteralPin struct {
Binding *environment.Binding
Ambiguous bool
}
LiteralPin is the DEFINITION-site resolution of one pattern literal, captured when the syntax-rules or syntax-case form was compiled.
R7RS §4.3.2 compares the literal's binding at the macro DEFINITION site with the input identifier's binding at the USE site. Resolving both sides through the one use-site environment answers neither question for a zero-scoped global: a top-level (define else #f) made after the macro was defined is visible from the definition frame too (both reach one owner store), so the two sides resolve to the identical *Binding and the comparison is vacuous. Only a definition-TIME snapshot discriminates.
The zero value is deliberately NOT a pin. A nil Binding with Ambiguous false means "unbound at definition time", which is R7RS's "the two identifiers are the same and both have no lexical binding" arm, and must leave the use-site-only comparison verbatim — the pin tightens, it never loosens. Ambiguous records an ErrAmbiguousBinding tie at definition time; a scope-aware lookup has three answers, and for this consumer the conservative one is "the literal does not match".
type Matcher ¶
type Matcher struct {
// contains filtered or unexported fields
}
Matcher is the pattern matching VM for syntax-rules.
It executes compiled pattern bytecode against an input form, capturing pattern variable bindings that can be used for template expansion.
func NewMatcher ¶
func NewMatcher(variables values.StringSet, codes []SyntaxCommand, opts ...MatcherOption) *Matcher
NewMatcher creates a pattern matcher. Required: the pattern variables set and the compiled bytecode. Options supply ellipsis-related metadata (see WithEllipsisVars, WithEllipsisDepths, WithEllipsisID).
Defaults: ellipsisID = DefaultEllipsis ("..."); ellipsisVars / ellipsisDepths nil. When ellipsisVars is supplied but ellipsisDepths is not, depths are inferred from the ID values.
func (*Matcher) GetBindings ¶
func (p *Matcher) GetBindings() map[string]syntax.SyntaxValue
GetBindings returns the captured pattern variable bindings from the last match. Bindings are stored as syntax.SyntaxValue to preserve source context. Returns nil if no match has been performed.
func (*Matcher) MatchSyntax ¶
MatchSyntax runs the pattern matcher against the syntax target. This is the syntax-native entry point that operates directly on SyntaxPair. Captured values are stored as syntax.SyntaxValue to preserve source context.
Delegates to MatchSyntaxWithLiterals with nil literal arguments, which skips the literal hygiene check in ByteCodeCompareCar.
func (*Matcher) MatchSyntaxWithLiterals ¶
func (p *Matcher) MatchSyntaxWithLiterals(ctx context.Context, target *syntax.SyntaxPair, literalSyntax map[string]*syntax.SyntaxSymbol, literalMatcher LiteralMatcher) error
MatchSyntaxWithLiterals runs the pattern matcher with literal hygiene checking. The literalSyntax map contains pattern literals that need scope/binding checking. The literalMatcher function is called for each literal comparison to check if the input symbol should match (returns true) or is shadowed (returns false).
type MatcherOption ¶
type MatcherOption func(*Matcher)
MatcherOption configures a Matcher at construction time. Pass options to NewMatcher; later options override earlier ones if they touch the same field.
func WithEllipsisDepths ¶
func WithEllipsisDepths(d map[int]int) MatcherOption
WithEllipsisDepths supplies the ellipsis-id → compilation-order map (lower = inner, higher = outer). When absent and ellipsisVars is non-empty, depth is inferred from the ID values: the compiler assigns IDs sequentially inner-first, so the ID itself is a valid depth proxy.
func WithEllipsisID ¶
func WithEllipsisID(id string) MatcherOption
WithEllipsisID overrides the ellipsis identifier (default "..." per R7RS). R7RS §4.3.2 allows custom ellipsis identifiers; this option supplies that. Empty string is treated as an explicit reset to DefaultEllipsis (rather than a silent no-op), so the option behaves correctly even if a future refactor drops NewMatcher's default-initialization.
func WithEllipsisVars ¶
func WithEllipsisVars(v map[int]values.StringSet) MatcherOption
WithEllipsisVars supplies the ellipsis-id → captured-variables map. When absent, the matcher operates with no ellipsis-bound variables.
type PatternAnalysis ¶
type PatternAnalysis struct {
// contains filtered or unexported fields
}
PatternAnalysis holds analysis results for a pattern
func AnalyzePattern ¶
func AnalyzePattern(pattern *syntax.SyntaxPair, variables values.StringSet) *PatternAnalysis
AnalyzePattern analyzes a pattern and returns analysis results
func NewPatternAnalysis ¶
func NewPatternAnalysis() *PatternAnalysis
NewPatternAnalysis creates a new pattern analysis
func (*PatternAnalysis) ContainsVariables ¶
func (p *PatternAnalysis) ContainsVariables(pair *syntax.SyntaxPair) bool
ContainsVariables returns whether a subtree contains pattern variables
func (*PatternAnalysis) GetVariables ¶
func (p *PatternAnalysis) GetVariables(pair *syntax.SyntaxPair) values.StringSet
GetVariables returns the set of variables in a subtree
func (*PatternAnalysis) Merge ¶
func (p *PatternAnalysis) Merge(other *PatternAnalysis)
Merge incorporates analysis results from another PatternAnalysis. Used when vector patterns are converted to pair chains at compile time, creating fresh SyntaxPair nodes that need analysis entries.
type SyntaxCommand ¶
SyntaxCommand represents a pattern bytecode instruction.
type SyntaxCompiler ¶
type SyntaxCompiler struct {
// contains filtered or unexported fields
}
SyntaxCompiler compiles pattern syntax into bytecode.
func NewSyntaxCompiler ¶
func NewSyntaxCompiler() *SyntaxCompiler
NewSyntaxCompiler creates a new syntax compiler with the default ellipsis ("...").
func NewSyntaxCompilerWithEllipsis ¶
func NewSyntaxCompilerWithEllipsis(ellipsis string) *SyntaxCompiler
NewSyntaxCompilerWithEllipsis creates a new syntax compiler with a custom ellipsis identifier. Per R7RS §4.3.2, syntax-rules can specify an alternative ellipsis identifier.
func (*SyntaxCompiler) Compile ¶
func (p *SyntaxCompiler) Compile(ctx context.Context, pr *syntax.SyntaxPair) error
Compile compiles a pattern pair into bytecode.
func (*SyntaxCompiler) SetSkipMacroKeyword ¶
func (p *SyntaxCompiler) SetSkipMacroKeyword(skip bool)
SetSkipMacroKeyword enables or disables skipping the first pattern element as a macro keyword. R7RS §4.3.2: The first subform of each syntax-rules pattern is the keyword of the macro being transformed; it is not matched against the macro use. Call this with true when compiling syntax-rules patterns.
type SyntaxMatcher ¶
type SyntaxMatcher struct {
// contains filtered or unexported fields
}
SyntaxMatcher adapts the core Matcher to work with syntax objects and hygiene.
It provides:
- Syntax-native pattern matching with source location preservation
- Template expansion with hygiene (intro scope for newly created syntax)
- Literal hygiene checking for R7RS auxiliary syntax
Key features:
Pattern Variable Capture: Pattern variables are captured directly as syntax.SyntaxValue, preserving source context through the entire match. No conversion to raw values is needed - the Matcher's MatchSyntaxWithLiterals operates on SyntaxPair directly.
Literal Hygiene: The literalSyntax map stores pattern literals with their scopes. During matching, if an input symbol has a literal's name but incompatible scopes (e.g., shadowed by let), it won't match the literal. This implements R7RS's requirement that auxiliary syntax like => and else be treated as regular expressions when locally shadowed.
R7RS Binding Check: For full R7RS compliance (§4.3.2), the input identifier's binding is compared with the pattern literal's. WHICH binding stands for the literal depends on literalDefs, the definition-site pins:
- Pinned (the primary path — every syntax-rules/syntax-case form with literals compiled against a non-nil env): the pattern literal is never resolved at match time. pin.Binding, captured when the macro was compiled, is compared against a phase-scoped use-site resolution. An Ambiguous pin refuses the literal outright, before any checker is consulted.
- Unpinned (a literal unbound at definition time, or no env): both sides are resolved through the use-site checker, which is the pre-pin behaviour and is left verbatim so the pin only ever tightens.
The checker can arrive two ways, and both are read-only after construction: on the opts struct, for a matcher built per invocation (syntax-case, whose clause is matched in the environment it was compiled against), or as MatchWithBindingChecker's argument, for a matcher compiled once at macro-definition time and matched against a different use-site environment each expansion (syntax-rules). Match() supplies no per-call checker and falls back to the opts one — that fallback is how syntax-case gets its checker at all. With a checker in neither position the binding comparison is skipped, but an Ambiguous pin still refuses the literal.
func NewSyntaxMatcher ¶
func NewSyntaxMatcher( variables values.StringSet, codes []SyntaxCommand, opts *SyntaxMatcherOpts, ) *SyntaxMatcher
NewSyntaxMatcher creates a syntax-aware matcher that wraps the core Matcher with hygiene support. Pass nil opts for default behavior (default ellipsis "...", no literal syntax).
The literalSyntax in opts enables scope-aware literal matching: if an input symbol has a literal's name but has been shadowed (has additional scopes), it won't match the pattern literal. R7RS §4.3.2 requires this for auxiliary syntax like => and else.
func (*SyntaxMatcher) CloneForMatch ¶
func (p *SyntaxMatcher) CloneForMatch() *SyntaxMatcher
CloneForMatch returns a SyntaxMatcher that shares this matcher's immutable compiled pattern (bytecode, pattern variables, ellipsis metadata, literal syntax, literal pins, binding checker) but has independent per-invocation matching state — the embedded Matcher's capture and syntax stacks.
A SyntaxMatcher built once at macro-definition time is stored on the (shared) macro binding and reused for every expansion of that macro, including concurrent expansions from SRFI-18 threads. The embedded Matcher's stacks are rewritten per call and Expand reads the capture stack the match populated, so concurrent expansions on one shared instance corrupt each other. Each expansion must therefore run on its own clone. Every other field is read-only after construction, so a clone is one small allocation, not a deep copy — and the match stacks are reallocated per match regardless, so this adds no per-call allocation beyond the wrapper itself.
func (*SyntaxMatcher) Expand ¶
func (p *SyntaxMatcher) Expand(template syntax.SyntaxValue, opts ExpandOptions) (syntax.SyntaxValue, error)
Expand performs template expansion with the given hygiene options. Pass a zero-value ExpandOptions{} for expansion without hygiene.
func (*SyntaxMatcher) GetBindings ¶
func (p *SyntaxMatcher) GetBindings() map[string]syntax.SyntaxValue
GetBindings returns the captured pattern variable bindings from the last match. Bindings are now stored as syntax.SyntaxValue directly, preserving source context. This is used by syntax-case to bind pattern variables in the body's environment.
func (*SyntaxMatcher) Match ¶
func (p *SyntaxMatcher) Match(ctx context.Context, input syntax.SyntaxValue) error
Match performs pattern matching on syntax objects, using whatever binding checker was supplied at construction (SyntaxMatcherOpts.BindingChecker) — none, for a matcher built without one. Callers that resolve the checker per invocation, because the matcher outlives one use site, call MatchWithBindingChecker instead.
func (*SyntaxMatcher) MatchWithBindingChecker ¶
func (p *SyntaxMatcher) MatchWithBindingChecker(ctx context.Context, input syntax.SyntaxValue, checker BindingChecker) error
MatchWithBindingChecker performs pattern matching on syntax objects with R7RS-compliant auxiliary syntax hygiene.
The checker parameter enables R7RS §4.3.2 compliant literal matching: literals match only if both identifiers have the same lexical binding, or both have no lexical binding. If the input has a binding (from let, lambda, etc.) but the pattern literal doesn't, they won't match.
Pass nil for checker to fall back to the checker supplied at construction (SyntaxMatcherOpts.BindingChecker), and nil in both places for scope-based matching only (less strict).
The checker is CLOSED OVER rather than stored on the receiver. It used to be assigned to p.bindingChecker and cleared by a defer, which turned a field that reads as configuration into per-call state on a value shared across expansions.
type SyntaxMatcherOpts ¶
type SyntaxMatcherOpts struct {
EllipsisVars map[int]values.StringSet
EllipsisDepths map[int]int // ellipsisID -> compilation order (lower = inner)
EllipsisID string
LiteralSyntax map[string]*syntax.SyntaxSymbol
// LiteralDefs pins each pattern literal to its definition-site binding. It is
// optional and tightening-only: an absent or zero LiteralPin leaves the
// use-site-only comparison in place.
LiteralDefs map[string]LiteralPin
// BindingChecker resolves an identifier to its binding for the R7RS §4.3.2
// literal check. Pair it with LiteralSyntax: the check needs both, and a
// nil in either position turns it off.
BindingChecker BindingChecker
}
SyntaxMatcherOpts holds optional parameters for NewSyntaxMatcher. A nil opts pointer means all defaults (no ellipsis vars, default "...", no literals).