Documentation
¶
Overview ¶
Package match implements the pattern matching engine for syntax-rules and syntax-case.
The package provides three 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:
- Value 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
- type BindingChecker
- type ByteCodeCaptureCar
- type ByteCodeCaptureCdr
- type ByteCodeCompareCar
- type ByteCodeCompareCdr
- type ByteCodeDone
- type ByteCodeJump
- type ByteCodePopContext
- type ByteCodePushContext
- type ByteCodeRequireCarEmpty
- type ByteCodeRequireCarEmptyVector
- type ByteCodeSkipIfEmpty
- type ByteCodeSkipIfTailCount
- type ByteCodeVisitCar
- type ByteCodeVisitCarAsVector
- type ByteCodeVisitCdr
- type CompilePatternOpts
- type CompiledPattern
- type ExpandOptions
- type FreeIdResolver
- type LiteralMatcher
- type Matcher
- func NewMatcher(variables map[string]struct{}, codes []SyntaxCommand) *Matcher
- func NewMatcherFull(variables map[string]struct{}, codes []SyntaxCommand, ...) *Matcher
- func NewMatcherFullWithDepths(variables map[string]struct{}, codes []SyntaxCommand, ...) *Matcher
- func NewMatcherWithEllipsisVars(variables map[string]struct{}, codes []SyntaxCommand, ...) *Matcher
- type PatternAnalysis
- type SyntaxCommand
- type SyntaxCompiler
- type 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 ¶
This section is empty.
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
}
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 package 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 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 ByteCodeRequireCarEmpty ¶
type ByteCodeRequireCarEmpty struct{}
ByteCodeRequireCarEmpty verifies that the car at the current position is an empty list.
Problem: Pattern () should only match input (). Without this check, VisitCar + Done would match any list, because Done only checks that CDR is empty.
Solution: Generate this instruction instead of VisitCar when the pattern element is (). It verifies the input car is also empty before proceeding.
func (ByteCodeRequireCarEmpty) String ¶
func (p ByteCodeRequireCarEmpty) String() string
type ByteCodeRequireCarEmptyVector ¶ added in v1.5.0
type ByteCodeRequireCarEmptyVector struct{}
ByteCodeRequireCarEmptyVector verifies that the car at the current position is an empty SyntaxVector. Used for empty vector patterns #().
func (ByteCodeRequireCarEmptyVector) String ¶ added in v1.5.0
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 ByteCodeVisitCarAsVector ¶ added in v1.5.0
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 ¶ added in v1.5.0
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 ¶ added in v1.5.0
type CompilePatternOpts struct {
Literals map[string]struct{}
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]map[string]struct{}
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 map[string]struct{}, 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 ¶ added in v1.3.0
type ExpandOptions struct {
// IntroScope is the hygiene scope added to newly created syntax objects (from the
// template), but NOT to syntax objects preserved from pattern variable substitution.
IntroScope *syntax.Scope
// FreeIds maps free identifier names 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.
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).
Per Flatt 2016 "sets of scopes" model: when deciding whether to substitute a template symbol with a captured value, we compare the template symbol's scopes with the pattern variable's scopes. Only substitute if the scopes are compatible, meaning the template symbol and pattern variable have exactly the same set of scopes (pattern scopes ⊆ template scopes and template scopes ⊆ pattern scopes). A template symbol with any additional or missing scopes (e.g., from an outer macro's intro scope) is not substituted.
type FreeIdResolver ¶ added in v1.10.5
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.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 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 map[string]struct{}, codes []SyntaxCommand) *Matcher
NewMatcher creates a new pattern matcher with the default ellipsis ("...").
func NewMatcherFull ¶
func NewMatcherFull(variables map[string]struct{}, codes []SyntaxCommand, ellipsisVars map[int]map[string]struct{}, ellipsisID string) *Matcher
NewMatcherFull creates a matcher with all parameters including custom ellipsis. The ellipsisID parameter specifies the identifier used for ellipsis patterns (default is "..." per R7RS, but can be customized per R7RS §4.3.2).
func NewMatcherFullWithDepths ¶ added in v1.10.7
func NewMatcherFullWithDepths( variables map[string]struct{}, codes []SyntaxCommand, ellipsisVars map[int]map[string]struct{}, ellipsisDepths map[int]int, ellipsisID string, ) *Matcher
NewMatcherFullWithDepths creates a matcher with all parameters including custom ellipsis and compilation-order metadata. The ellipsisDepths parameter maps each ellipsis ID to its compilation order (lower = inner, higher = outer). When nil, order is inferred from the ID values (the compiler assigns IDs sequentially, inner-first).
func NewMatcherWithEllipsisVars ¶
func NewMatcherWithEllipsisVars(variables map[string]struct{}, codes []SyntaxCommand, ellipsisVars map[int]map[string]struct{}) *Matcher
NewMatcherWithEllipsisVars creates a matcher with ellipsis variable mapping. The ellipsisVars parameter maps each ellipsis ID to its captured pattern variables. Uses the default ellipsis identifier ("...").
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 PatternAnalysis ¶
type PatternAnalysis struct {
// contains filtered or unexported fields
}
PatternAnalysis holds analysis results for a pattern
func AnalyzePattern ¶
func AnalyzePattern(pattern *syntax.SyntaxPair, variables map[string]struct{}) *PatternAnalysis
AnalyzePattern analyzes a pattern and returns analysis results
func AnalyzePatternWithLiterals ¶
func AnalyzePatternWithLiterals(pattern *syntax.SyntaxPair, literals map[string]struct{}, isKeyword bool) *PatternAnalysis
AnalyzePatternWithLiterals analyzes a pattern determining variables from literals
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) map[string]struct{}
GetVariables returns the set of variables in a subtree
func (*PatternAnalysis) Merge ¶ added in v1.5.0
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), we also check if the input identifier has a lexical binding. If it does and the pattern literal doesn't, they don't match. This is handled via the bindingChecker field set during Match().
func NewSyntaxMatcher ¶
func NewSyntaxMatcher( variables map[string]struct{}, 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) 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. This is the basic method without binding checking. For full R7RS compliance with auxiliary syntax hygiene, use 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 use scope-based matching only (less strict).
type SyntaxMatcherOpts ¶ added in v1.5.0
type SyntaxMatcherOpts struct {
EllipsisVars map[int]map[string]struct{}
EllipsisDepths map[int]int // ellipsisID -> compilation order (lower = inner)
EllipsisID string
LiteralSyntax map[string]*syntax.SyntaxSymbol
}
SyntaxMatcherOpts holds optional parameters for NewSyntaxMatcher. A nil opts pointer means all defaults (no ellipsis vars, default "...", no literals).