compilation

package
v1.20.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// TransformerSyntaxRules is the leading symbol for (syntax-rules ...) transformers.
	TransformerSyntaxRules = "syntax-rules"
	// TransformerERMacro is the leading symbol for (er-macro-transformer ...) transformers.
	TransformerERMacro = "er-macro-transformer"
	// FormDefineSyntax is the form name for (define-syntax ...) definitions.
	FormDefineSyntax = "define-syntax"
)
View Source
const DefaultInlineThreshold = 5

DefaultInlineThreshold is the default maximum body length for procedure inlining. A lambda body with more expressions than this is not inlined.

View Source
const DefaultMaxExpandDepth int = 50000

DefaultMaxExpandDepth bounds structural recursion depth during macro expansion. Without a bound, deeply nested syntax — reachable not from text (the parser caps that, see parser.DefaultMaxParseDepth) but from programmatically-constructed syntax such as macro output, datum->syntax, and quasiquote — triggers a fatal, unrecoverable Go stack overflow that kills the host process. 0 means unlimited. Mirrors the VM's DefaultMaxCallDepth and the parser's DefaultMaxParseDepth.

Chosen empirically: the expander overflows the Go stack between ~400k (heavy macro-re-expansion paths) and ~800k (light procedure-call nesting) levels; 50000 leaves an order-of-magnitude margin below the crash while sitting far above any practical program. A flat recursive macro (and/or/cond with N clauses) accumulates expansion depth linearly in N, but such forms are O(N^2) to expand and unusable well before 50000 clauses, so the bound does not regress any program anyone actually runs. Callers with genuinely deeper machine-generated syntax opt out via SetMaxDepth(0) / WithMaxExpandDepth(0).

View Source
const SchemeIncludePathEnv = resolver.SchemeIncludePathEnv

SchemeIncludePathEnv is the environment variable name for the Scheme include path.

Variables

View Source
var DefaultLibraryPaths = []string{
	".",
}

DefaultLibraryPaths are the default directories to search for libraries.

Only the current directory is searched by default. The embedded standard library is the real source of the stdlib (resolved via the FileResolver chain over stdlib.FS); the former "./pkg/stdlib/lib" entry was a development-tree convenience that resolves nothing in a deployed binary.

Functions

func AllFeatures

func AllFeatures() []string

AllFeatures returns all supported feature identifiers.

func BuildInlineHOFTemplates

func BuildInlineHOFTemplates(ctx context.Context, env *environment.EnvironmentFrame) error

BuildInlineHOFTemplates parses, expands, and validates each curated HOF loop template against env (the sealed base) and stores the registry on env's Namespace. Idempotent: a no-op when a store is already installed (the root bootstrap builds it; flat library envs sharing the Namespace skip). A failure aborts engine init — the templates are fixed source, so it is a build bug.

func CompileSyntaxRules

func CompileSyntaxRules(ctx context.Context, env *environment.EnvironmentFrame, syntaxRulesForm syntax.SyntaxValue, libraryScope *syntax.Scope) (*machine.MachineClosure, error)

CompileSyntaxRules compiles a syntax-rules form into a transformer procedure.

R7RS Forms:

(syntax-rules (literal ...) (pattern template) ...)
(syntax-rules <ellipsis> (literal ...) (pattern template) ...)  ; custom ellipsis

The compilation process:

  1. Parse optional custom ellipsis identifier
  2. Parse the literals list - these symbols are matched literally, not as variables
  3. For each clause, identify pattern variables (symbols not in literals list)
  4. Compile each pattern to bytecode (see match/syntax_compiler.go)
  5. Create a machine.MachineClosure that, when invoked: - Tries each pattern in order against the input form - On first match, expands the template with captured bindings - Adds an "intro scope" to the expansion for hygiene

The returned closure is stored in the environment with BindingTypeSyntax, allowing the expander to recognize it as a macro transformer.

func CompileValidatedApply

func CompileValidatedApply(p *CompileTimeContinuation, ctctx CompileTimeCallContext, expr forms.ValidatedExpr) error

CompileValidatedApply compiles a validated (apply proc arg1 ... args) form.

R7RS §6.10: apply calls proc with the arguments arg1 ... concatenated with the elements of args (the final argument, which must be a list).

Bytecode (non-tail):

SaveContinuation →after
<compile proc>          PUSH
<compile arg1>          PUSH
...
<compile argN>          PUSH
<compile finalList>              ; value = finalList
OpUnpackListToStack              ; stack: [proc, arg1, ..., argN, x1, x2, ...]
Pull                             ; value = proc
Apply                            ; calls proc(arg1, ..., argN, x1, x2, ...)
after:

Tail position: same without SaveContinuation/patch.

func CompileValidatedBegin

func CompileValidatedBegin(p *CompileTimeContinuation, ctctx CompileTimeCallContext, expr forms.ValidatedExpr) error

CompileValidatedBegin compiles a validated (begin expr...) form.

begin (R7RS 4.2.3) sequences expressions for side effects. All expressions are evaluated left-to-right; the value of the last expression becomes the value of the entire begin form.

R7RS §5.3.2: Internal definitions use letrec* semantics - all defined names are visible throughout the body, enabling forward references between defines.

Example: (begin (display "hello") (newline) 42) => 42 (after printing)

func CompileValidatedCaseLambda

func CompileValidatedCaseLambda(p *CompileTimeContinuation, ctctx CompileTimeCallContext, expr forms.ValidatedExpr) error

CompileValidatedCaseLambda compiles a validated (case-lambda [clause] ...) form.

case-lambda (R7RS 4.2.9) creates a procedure that dispatches to different implementations based on the number of arguments. For example:

(case-lambda
  ((x) (* x x))           ; 1 arg: square
  ((x y) (* x y))         ; 2 args: multiply
  ((x y . rest) (apply + x y rest)))  ; 2+ args: sum all

At runtime, the VM selects the first clause whose arity matches the call.

func CompileValidatedDefine

func CompileValidatedDefine(p *CompileTimeContinuation, ctctx CompileTimeCallContext, expr forms.ValidatedExpr) error

CompileValidatedDefine compiles a validated define form.

func CompileValidatedDynamicWind

func CompileValidatedDynamicWind(p *CompileTimeContinuation, ctctx CompileTimeCallContext, expr forms.ValidatedExpr) error

CompileValidatedDynamicWind compiles a validated (dynamic-wind before thunk after) form.

R7RS §6.10: dynamic-wind calls thunk without arguments, returning the result(s). Before is called whenever execution enters the dynamic extent of the call to thunk, and after is called whenever it exits.

The key insight is that by compiling to bytecode, the cleanup code (calling after) is in the bytecode stream. When a continuation is captured inside the thunk and later restored, the cleanup code will run on normal completion.

Bytecode structure:

<compile before> PUSH
<compile thunk>  PUSH
<compile after>  PUSH          ; Stack: [before, thunk, after]
PEEK_K 2                       ; value = before
SAVE_CONTINUATION →after_before
APPLY                          ; call before()
after_before:                  ; Stack: [before, thunk, after]
OP_PUSH_WIND                   ; create winding frame
PEEK_K 1                       ; value = thunk
SAVE_CONTINUATION →after_thunk
APPLY                          ; call thunk()
after_thunk:                   ; Stack: [before, thunk, after]
BOX_VALUES                     ; collapse thunk's 0/1/N result values to one carrier
PUSH                           ; save boxed result, Stack: [before, thunk, after, boxed]
OP_POP_WIND                    ; pop winding frame
PEEK_K 1                       ; value = after
SAVE_CONTINUATION →after_after
APPLY                          ; call after()
after_after:                   ; Stack: [before, thunk, after, boxed]
PEEK_K 0                       ; value = boxed result
UNBOX_VALUES                   ; expand carrier back to thunk's 0/1/N values
DROP DROP DROP DROP            ; clean up stack

BOX_VALUES/UNBOX_VALUES keep the saved result at exactly one eval-stack slot: OpPush pushes every value in the register (PushAll for multiple, nothing for zero), so without boxing a thunk returning (values …) would mis-align the PeekK/Drop offsets (R7RS §6.10 requires dynamic-wind to return the thunk's values, including zero or several).

func CompileValidatedIf

func CompileValidatedIf(p *CompileTimeContinuation, ctctx CompileTimeCallContext, expr forms.ValidatedExpr) error

CompileValidatedIf compiles a validated (if test conseq [alt]) form. The structure is guaranteed to be valid by the validator.

Constant folding (Aho et al., Compilers §8.5): when the test is a compile-time literal, the entire if-form reduces to one branch. This is the simplest form of constant folding — evaluating known expressions at compile time rather than runtime. See BIBLIOGRAPHY.md "Constant Folding".

func CompileValidatedLambda

func CompileValidatedLambda(p *CompileTimeContinuation, ctctx CompileTimeCallContext, expr forms.ValidatedExpr) error

CompileValidatedLambda compiles a validated (lambda params body...) form.

func CompileValidatedLet

func CompileValidatedLet(p *CompileTimeContinuation, ctctx CompileTimeCallContext, expr forms.ValidatedExpr) error

CompileValidatedLet compiles all binding forms based on Kind.

let: <inits> Push... | OpPushEnv | StoreLocal(reverse) | body | OpPopEnv let*: OpPushEnv | (init Push StoreLocal)... | body | OpPopEnv letrec: OpPushEnv | <inits> Push... | StoreLocal(reverse) | body | OpPopEnv letrec*: OpPushEnv | (init Push StoreLocal)... | body | OpPopEnv

The trailing OpPopEnv is emitted only in non-tail position; a tail let leaves the frame for the enclosing return to unwind.

A MERGED let (canMergeLet, merged_slots.go) emits NEITHER bracket: its slots come out of the enclosing lambda's parameter frame, so the stores and loads above are the whole shape. That is the ordinary case — every let inside a procedure body — and the pushing form survives only where there is no frame to merge into (the top level, a syntax-case clause body).

func CompileValidatedQuasiquote

func CompileValidatedQuasiquote(p *CompileTimeContinuation, ctctx CompileTimeCallContext, expr forms.ValidatedExpr) error

CompileValidatedQuasiquote compiles a validated (quasiquote template) form. Quasiquote has complex runtime semantics, so we delegate to the existing compiler.

func CompileValidatedQuote

func CompileValidatedQuote(p *CompileTimeContinuation, _ CompileTimeCallContext, expr forms.ValidatedExpr) error

CompileValidatedQuote compiles a validated (quote datum) form.

func CompileValidatedSetBang

func CompileValidatedSetBang(p *CompileTimeContinuation, ctctx CompileTimeCallContext, expr forms.ValidatedExpr) error

CompileValidatedSetBang compiles a validated (set! name expr) form.

func CompileValidatedWithContinuationMark

func CompileValidatedWithContinuationMark(p *CompileTimeContinuation, ctctx CompileTimeCallContext, expr forms.ValidatedExpr) error

CompileValidatedWithContinuationMark compiles (with-continuation-mark key val body).

Tail position:

<compile key> PUSH
<compile val>
SetContMark               ; pops key, sets marks[key] = val
<compile body in tail>

Non-tail position:

<compile key> PUSH
<compile val>
SaveContMark              ; pops key, saves (key, old) on stack, sets mark
<compile body in non-tail>
RestoreContMark           ; pops (old, key), restores mark

func CopyLibraryBindingsToEnv

func CopyLibraryBindingsToEnv(lib *CompiledLibrary, bindings map[string]string, targetEnv *environment.EnvironmentFrame) error

CopyLibraryBindingsToEnv copies exported bindings from a library to an environment. bindings is the map from localName -> externalName produced by ApplyToExports. Both runtime and syntax bindings are copied. This is a convenience wrapper that imports to phase 0 (runtime).

func CopyLibraryBindingsToEnvAtPhase

func CopyLibraryBindingsToEnvAtPhase(lib *CompiledLibrary, bindings map[string]string, targetEnv *environment.EnvironmentFrame, targetPhase environment.Phase) error

CopyLibraryBindingsToEnvAtPhase copies exported bindings from a library to a specific phase. bindings is the map from localName -> externalName produced by ApplyToExports.

Phase semantics:

  • targetPhase == 0: Runtime import (default). Runtime bindings go to phase 0. A syntax binding that came from the library's expand phase skips the phase-0 install (skipBase below) and lands only at phase 1, so it cannot shadow the importer's own define-syntax.
  • targetPhase > 0: For-syntax import. Bindings are shifted to the target phase. Runtime bindings become available during macro expansion at targetPhase. Syntax bindings follow the same skipBase rule: targetPhase+1 only.
  • targetPhase < 0: For-template import. Bindings shifted to negative phase (used for generating code that will run at a lower phase).

func ExpandAndCompile

func ExpandAndCompile(ctx context.Context, env *environment.EnvironmentFrame, stx syntax.SyntaxValue, resolver FileResolver, inlineThreshold int, maxExpandDepth int) (*machine.NativeTemplate, error)

ExpandAndCompile runs the expand+compile pipeline on a single syntax value, returning a ready-to-execute template.

A single MacroEvaluator is shared across both phases. If resolver is non-nil, it is set on the compiler for include/load file resolution. inlineThreshold controls procedure inlining (0 disables). maxExpandDepth bounds expander recursion to prevent a fatal Go stack overflow on deeply nested syntax (0 disables the bound; pass DefaultMaxExpandDepth for the standard limit). Errors are wrapped with phase context ("expansion" or "compilation"); callers may add site-specific context on top. Callers may call tpl.Optimize() on the returned template if desired.

func ImportSpecInto

func ImportSpecInto(ctx context.Context, specVal values.Value, callerEnv, targetEnv *environment.EnvironmentFrame, evaluator machine.MacroEvaluator, op string) error

ImportSpecInto parses a single import-spec datum, loads the named library, applies the import set's modifiers (only/except/prefix/rename), and copies the resulting bindings into targetEnv at the spec's phase shift. It is the shared core of the (environment ...), (make-namespace ...), and (namespace-require ...) primitives; op names the calling primitive for error context. callerEnv supplies the library registry for resolution.

func IsFeatureSupported

func IsFeatureSupported(feature string) bool

IsFeatureSupported checks if a feature identifier is supported.

func LoadBootstrapSources

func LoadBootstrapSources(ctx context.Context, env *environment.EnvironmentFrame, sources []string, resolver FileResolver, kind string) error

LoadBootstrapSources runs the canonical bootstrap pipeline — parse → expand → compile → optimize → execute — for each source string against env, using the default inline and expand-depth thresholds. Templates ARE optimized and executed on a pooled top-level context, matching production engine initialization. The kind argument ("macro" / "procedure") names the source category in error context.

This is the single implementation shared by the engine-root (pkg/wile) and internal (internal/bootstrap) bootstrap paths. Each previously kept its own copy of this loop, and the copies had drifted: the internal one ran on a raw, unpooled context and did NOT optimize, so bootstrap procedures loaded via the internal API (test helpers, embedders on the internal API) ran unoptimized while the public NewEngine path optimized them. Consolidating here removes that divergence.

func LookupPhaseBinding

func LookupPhaseBinding[T any](
	phaseEnv *environment.EnvironmentFrame,
	sym *values.Symbol,
	scopes []*syntax.Scope,
) T

LookupPhaseBinding looks up a binding by symbol in the target phase environment. Returns the value cast to type T if found, or the zero value if not found or if the value is not of type T.

This function handles hygiene by using scoped lookup - it will only match bindings whose scopes are a subset of the symbol's scopes.

func NewERCompareClosure

func NewERCompareClosure(useEnv *environment.EnvironmentFrame) *machine.ForeignClosure

NewERCompareClosure creates the `compare` closure for an ER macro invocation. useEnv is the use-site environment for resolving identifiers. The closure accepts two identifier arguments and returns #t if both resolve to the same binding (the same binding object, or two bindings sharing one import-provenance root, see erBindingsEqual) or both are unbound with the same name.

func NewERRenameClosure

func NewERRenameClosure(
	defExpandEnv *environment.EnvironmentFrame,
	introScope *syntax.Scope,
) *machine.ForeignClosure

NewERRenameClosure creates the `rename` closure for an ER macro invocation. defExpandEnv is the definition-site expand environment. introScope is a fresh scope unique to this macro invocation, used to ensure that renamed symbols not found in the definition-site env (e.g., temporary names like 'tmp') get a unique identity that prevents variable capture. The returned closure accepts a single symbol argument and returns a SyntaxSymbol that resolves to the definition-site binding. Results are cached per symbol name so that (eq? (rename 'x) (rename 'x)) is #t.

func RegisterAllPhaseHandlers

func RegisterAllPhaseHandlers(env *environment.EnvironmentFrame) error

RegisterAllPhaseHandlers registers both syntax compilers (compile phase) and primitive expanders (expand phase) in the correct order. Use this instead of calling RegisterSyntaxCompilers and RegisterPrimitiveExpanders separately at engine/bootstrap/test init sites.

func RegisterPhaseBindings

func RegisterPhaseBindings[F any](
	env *environment.EnvironmentFrame,
	phaseEnv func() *environment.EnvironmentFrame,
	entries []PhaseEntry[F],
	wrapper func(name string, fn F) values.Value,
) error

RegisterPhaseBindings binds all entries in the target phase environment. This is a generic helper for registering primitives in expand or compile phases.

Parameters:

  • phaseEnv: Accessor for the target phase (e.g., env.Expand or env.Compile). This alone determines the target; env is not consulted.
  • entries: Slice of (name, function) pairs to register
  • wrapper: Creates the values.Value wrapper from name and function

func RegisterPrimitiveExpanders

func RegisterPrimitiveExpanders(env *environment.EnvironmentFrame) error

RegisterPrimitiveExpanders binds all primitive expanders at (PhaseExpand, handler) — the sealed expand base, or a flat library frame's own expand child. These are looked up by ExpandPrimitiveForm() when the expander encounters a special form.

Each primitive has different expansion behavior:

  • quote, define-syntax, define-library, quasiquote: return unchanged (no expansion)
  • if: expand test, consequent, alternative separately
  • begin: expand all subexpressions
  • set!: expand only the value expression
  • define: expand value if simple define
  • lambda, case-lambda: expand body expressions
  • syntax-case, cond-expand: return unchanged (compile-time forms)

func RegisterSyntaxCompilers

func RegisterSyntaxCompilers(env *environment.EnvironmentFrame) error

RegisterSyntaxCompilers binds all syntax compilers through the level-0 SEALED-WRITE view, which every owner of a sealed axis has: the main namespace's, or a NewChildRuntime library env's own. Unlike the primitive expanders, these WANT the level-0 seal, because that is the one write whose coordinate is the ambient (AnyPhase, sealed) one — every other write, mutable at any level or sealed above 0, lands at an exact level (EnvironmentFrame.writeCoordinates). A binding placed here is therefore reachable from a frame at any level as the ranked probe's T3 tier, instead of being pinned to the PhaseCompile view.

This is a write COORDINATE, not a topology: phase views have no lexical parent, and hermeticity is key disjointness in the one store. Comments here once said "every phase frame parents to that taproot"; createPhaseEnv stopped reparenting when the store was flattened.

These bindings serve two purposes:

  1. Library export/import: findLibraryBinding in library_bindings.go searches the levels the library's own registry has instantiated (PresentPhases) to locate syntax compilers when exporting or importing forms like syntax-case, define-syntax, etc. A NewChildRuntime library env is an island — it owns its own store, so the engine root's ambient tier is not reachable from it at any level — and special forms stay ambient-only, unchanged by this relocation.
  2. Scope-aware lookup via LookupSyntaxCompiler for hygiene resolution.

Compilation dispatch itself goes through the forms registry (register.go), not through these bindings. Both paths are populated from syntaxCompilerEntries to stay in sync.

The syntax compilers are bound with BindingTypePrimitive to distinguish them from syntax transformers (BindingTypeSyntax) and regular variables.

func ResolveAndInstallImportSet

func ResolveAndInstallImportSet(ctx context.Context, datum values.Value, env *environment.EnvironmentFrame, phase environment.Phase, evaluator machine.MacroEvaluator) error

ResolveAndInstallImportSet resolves an import set and installs bindings into env. Used for top-level imports (both expander and compiler). Library-internal imports share the resolution step (resolveImportSet) but use copyLibraryBindingsDirect for installation.

The phase argument is import-observer metadata only; it does NOT select the install phase. That comes from composePhaseShift below, which combines the environment's own phase level with the import set's for-syntax/for-meta shift.

func ResolveLibraryFile

func ResolveLibraryFile(ctx context.Context, res FileResolver, name LibraryName) (fs.File, string, error)

ResolveLibraryFile resolves a library name to an open file handle, trying each extension in libraryExtensions order. Non-file-not-found errors (security denial, I/O) propagate immediately: only the ABSENCE of the .sld licenses trying the .scm, or an unreadable .sld would silently be replaced by a readable file of the same name.

func StampInlineHOFs

func StampInlineHOFs(frame *environment.EnvironmentFrame)

StampInlineHOFs sweeps frame's own bindings, stamping every curated SEALED-BASE tail HOF bound there. Called post-bootstrap on the sealed base. Import-gated entries (fold, fold-right) are skipped here and stamped on their import path instead.

func TemplateLocalEmits added in v1.20.0

func TemplateLocalEmits() uint64

TemplateLocalEmits reports how many syntax-template occurrences have been emitted as local loads in this process. Ratchet on a DIFFERENCE across a known unit of work, never on the absolute value.

func VerifyAllPhaseHandlers

func VerifyAllPhaseHandlers() error

VerifyAllPhaseHandlers cross-checks all three phase registries: form validators (internal/forms), compilers (Tier 1 + Tier 2), and primitive expanders. Returns the first inconsistency found, or nil.

Call from tests only — not on the production init path.

func VerifyCompilers

func VerifyCompilers() error

VerifyCompilers checks that every registered form either carries a valid CompilerFunc on its FormSpec or is classified expand-only in formDispatch. The type-checked assertion catches a mis-typed Compile (any → non-CompilerFunc), which compilerFor would otherwise swallow to a nil "no compiler" diagnostic.

func VerifyExpanders

func VerifyExpanders() error

VerifyExpanders checks that every Tier 2 syntax compiler entry has a corresponding primitive expander entry. A Tier 2 form without an expander is silently treated as a procedure call during expansion — the most dangerous form of registration drift.

Types

type ChainFileResolver

type ChainFileResolver = resolver.ChainFileResolver

func NewChainFileResolver

func NewChainFileResolver(resolvers []environment.FileResolver) *ChainFileResolver

NewChainFileResolver creates a resolver that searches multiple resolvers.

type ClausesWrapper

type ClausesWrapper struct {
	Clauses []*SyntaxRulesClause
}

ClausesWrapper wraps a slice of SyntaxRulesClause as a values.Value for storage in NativeTemplate literals.

func (*ClausesWrapper) EqualTo

func (p *ClausesWrapper) EqualTo(other values.Value) bool

func (*ClausesWrapper) IsVoid

func (p *ClausesWrapper) IsVoid() bool

func (*ClausesWrapper) SchemeString

func (p *ClausesWrapper) SchemeString() string

type CompileTimeCallContext

type CompileTimeCallContext struct {
	// contains filtered or unexported fields
}

CompileTimeCallContext carries contextual information through the compilation process. It tracks whether an expression is in tail position, which controls whether the compiler emits SaveContinuation (non-tail) or reuses the current frame (tail).

This structure is passed by value (not pointer) through the compiler, allowing each compilation step to create modified copies without affecting the caller's context.

Tail Call Optimization

Tail call optimization (Steele 1977, R7RS §3.5): tail calls reuse the caller's continuation frame instead of allocating a new one, making recursive procedures in tail position run in constant stack space. See BIBLIOGRAPHY.md "Tail Call Optimization".

The inTail flag tracks whether an expression is in tail position. An expression is in tail position if its value will be the final result of the enclosing procedure. When inTail is true, the compiler can generate a tail call that reuses the current stack frame instead of creating a new one, preventing stack overflow in recursive procedures.

Per R7RS Section 3.5, these positions are tail positions:

  • The body of a lambda expression
  • The last expression in a begin sequence (if the begin is in tail position)
  • The consequent and alternative of an if expression (if the if is in tail position)
  • The body of a let/let*/letrec (if the let is in tail position)

These are NOT tail positions (use NotInTail()):

  • Function arguments: (f (g x)) - the call to g is not in tail position
  • Condition of if: (if (pred x) ...) - pred is not in tail position
  • Definitions: (define x (expr)) - expr is not in tail position
  • Non-final expressions in begin: (begin (a) (b) (c)) - only c is in tail position

func NewCompileTimeCallContext

func NewCompileTimeCallContext(ctx context.Context, inTail bool) CompileTimeCallContext

NewCompileTimeCallContext creates a new compile-time context. Parameters:

  • inTail: true if compiling an expression in tail position

func (CompileTimeCallContext) Context

Context returns the context associated with this compile-time call context.

func (CompileTimeCallContext) NotInTail

NotInTail returns a copy of the context with inTail set to false. Use this when compiling sub-expressions that are not in tail position:

  • Function arguments
  • Condition expressions in if
  • Initial values in define/let bindings
  • Non-final expressions in begin

Example:

// Compiling (f (g x)) - the call to g is not in tail position
err := p.CompileExpression(ctctx.NotInTail(), argExpr)

func (CompileTimeCallContext) UnderLetFrame added in v1.20.0

func (p CompileTimeCallContext) UnderLetFrame(pushesFrame bool) CompileTimeCallContext

UnderLetFrame returns a copy describing the body of a let. It is the ONLY site that may say so, because it is paired 1:1 with compileValidatedLet's single descent into a body.

pushesFrame says whether that let emitted an OpPushEnv. A merged let does not (compilation/merged_slots.go): its slots live in the enclosing lambda's frame, so the body runs in the SAME runtime frame the head did.

The two dispositions answer differently, and the asymmetry is the phase:

  • SELF-TAIL survives, with letDepth incremented ONLY for a pushing let. OpSelfTailCall pops that many frames before rebinding, so a self call under a let is rewritable after all (frame-reclaim Phase C). Before Phase C this cleared, and the call leaked one parameter frame per iteration. A merged let contributes no frame, so incrementing would unwind the parameter frame's PARENT — the count has to equal the runtime frame count by construction, which is the whole reason it is counted here rather than by a second walk.

  • RELEASE survives a MERGED let and is still cleared by a pushing one. For a pushing let the reason is that OpReleaseEnvFrame releases mc.env, which inside the body is the LET frame — not pool-owned, so the release would be a no-op at best. A merged let removes that reason exactly: mc.env inside its body IS the enclosing pooled parameter frame, and the merged slots the body reads are slots OF that frame, so the enclosing body's releasability proof covers them unchanged.

    Merging on its own recovers only the RETURN-path release (no OpPushEnv means no envPooled clear, so RestoreAndRelease recycles). A frame abandoned at a general TAIL call still needs the explicit OpReleaseEnvFrame, and 92% of let frames are in tail position, so this clause is what reaches them. Measured on the Gabriel corpus at the default immutable top level: nqueens 2,264 -> 868,868 releases, pool hit 78.6% -> 88.8%, wall −3.5%. It does NOT explain schelog zebra's 66.9% — that harness needs a mutable top level, under which no global callee is Stable and frame reclaim arms nowhere at all. See memory/flat-closure-baseline.local.md §6.

    The peephole hazard this used to name is real but belongs to the peephole: a fusion that hoisted a PushLocal callee across the release would resolve it out of the frame just handed to the pool. The interior scans refuse that by callee kind (peephole.go, releaseSafeCallee) rather than by refusing to emit the release.

func (CompileTimeCallContext) WithEnclosingDefines added in v1.19.1

WithEnclosingDefines returns a copy carrying the body sequence whose internal defines form the letrec* group currently being compiled — the group InternalDefineFrameReleasable needs to prove one member releasable, since a define alone cannot name its own siblings.

It must be set wherever internal defines are PREDECLARED (the three predeclareDefineFromValidatedRecursive / predeclareBodyDefines sites), and reset rather than inherited on the way into a nested body: a lambda body starts a fresh context, and a let body overrides with its own. Inheriting a stale body would be a correctness bug, not just imprecision, which is why the predicate independently verifies the define is a member of what it is handed.

func (CompileTimeCallContext) WithFrameReuse

func (p CompileTimeCallContext) WithFrameReuse(fr frameReuse) CompileTimeCallContext

WithFrameReuse returns a copy armed with the given frame-reuse disposition. Preserves inTail and ctx.

type CompileTimeContinuation

type CompileTimeContinuation struct {
	// contains filtered or unexported fields
}

CompileTimeContinuation is a continuation used during the compilation phase

func NewCompileTimeContinuation

func NewCompileTimeContinuation(tpl *machine.NativeTemplate, env *environment.EnvironmentFrame, evaluator machine.MacroEvaluator) *CompileTimeContinuation

NewCompileTimeContinuation creates a new CompileTimeContinuation. The file resolver defaults to the one stored on the Namespace. If none is set, falls back to a fresh OSFileResolver.

func (*CompileTimeContinuation) AppendOperations

func (p *CompileTimeContinuation) AppendOperations(ops ...machine.Operation)

AppendOperations appends operations tagged with the current source from the source stack. Routes through the integer-dispatch code[] path: Wave 1-3 operations become direct instructions, everything else goes via machine.OpComplex to the sideTable.

func (*CompileTimeContinuation) CompileBeginForSyntax

func (p *CompileTimeContinuation) CompileBeginForSyntax(ctctx CompileTimeCallContext, expr syntax.SyntaxValue) error

CompileBeginForSyntax handles (begin-for-syntax expr ...).

Evaluates a sequence of expressions at compile time in the expand phase environment. Used for setting up compile-time state (hash tables, registries) that macros can access. No runtime effect — used for side effects only.

func (*CompileTimeContinuation) CompileCondExpand

func (p *CompileTimeContinuation) CompileCondExpand(ctctx CompileTimeCallContext, expr syntax.SyntaxValue) error

CompileCondExpand compiles a cond-expand expression. cond-expand is evaluated at compile-time and expands to the body of the first clause whose feature requirement is satisfied.

Syntax: (cond-expand <clause> ...) where <clause> is (<feature-requirement> <expression> ...)

Example:

(cond-expand
  (r7rs (display "R7RS"))
  (else (display "other")))

func (*CompileTimeContinuation) CompileDefineForSyntax

func (p *CompileTimeContinuation) CompileDefineForSyntax(ctctx CompileTimeCallContext, expr syntax.SyntaxValue) error

CompileDefineForSyntax handles (define-for-syntax name expr) or (define-for-syntax (name args...) body...).

This form defines a binding in the expand phase environment that is available during macro expansion. The expression is compiled and evaluated at compile time, and the result is stored one phase up from the defining frame (env.NextPhase(); equals env.Expand() at phase 0).

Unlike define-syntax (which stores macro transformers), define-for-syntax stores regular values with BindingTypeVariable.

func (*CompileTimeContinuation) CompileDefineLibrary

func (p *CompileTimeContinuation) CompileDefineLibrary(ctctx CompileTimeCallContext, expr syntax.SyntaxValue) error

CompileDefineLibrary handles (define-library (lib-name) <library-declaration> ...).

R7RS library syntax:

(define-library <library-name>
  <library-declaration> ...)

<library-declaration> =
  | (export <export-spec> ...)
  | (import <import-set> ...)
  | (begin <command-or-definition> ...)
  | (include <filename> ...)
  | (include-ci <filename> ...)
  | (include-library-declarations <filename> ...)
  | (cond-expand <ce-clause> ...)
  | (description <string>)

This creates an isolated environment for the library, processes declarations in order, and hands the finished CompiledLibrary to the library callback (LoadLibrary is what installs it in the registry).

func (*CompileTimeContinuation) CompileDefineSyntax

func (p *CompileTimeContinuation) CompileDefineSyntax(ctctx CompileTimeCallContext, expr syntax.SyntaxValue) error

CompileDefineSyntax handles (define-syntax keyword transformer-expr).

This is the compile-time handler for R7RS define-syntax. Unlike most definitions, define-syntax is processed entirely at compile time:

  1. Parse the form: (define-syntax keyword (syntax-rules ...))
  2. Compile the syntax-rules transformer to a machine.MachineClosure
  3. Store the closure in the environment with BindingTypeSyntax
  4. Emit NO runtime operations (the binding is already established)

The BindingTypeSyntax marker is crucial: when the expander encounters a symbol, it checks if that symbol is bound to a syntax transformer. If so, it invokes the transformer closure to expand the macro.

This is how derived expressions like 'let' work: they're defined as macros using define-syntax, and expand to lambda expressions:

(define-syntax let
  (syntax-rules ()
    ((let ((name val) ...) body)
     ((lambda (name ...) body) val ...))))

Reference: R7RS Section 5.4 (Syntax definitions)

func (*CompileTimeContinuation) CompileEvalWhen

func (p *CompileTimeContinuation) CompileEvalWhen(ctctx CompileTimeCallContext, expr syntax.SyntaxValue) error

CompileEvalWhen handles (eval-when (phase ...) body ...).

This form controls when code is evaluated based on phase specifiers. Phase names follow Chez Scheme's eval-when (Dybvig, TSPL §12.10):

  • expand: evaluate during macro expansion (at compile time)
  • compile: evaluate during compilation (currently same as expand)
  • run: evaluate at runtime (generate code for normal execution)

Multiple phases can be specified. If both expand and run are specified, the body is evaluated at compile time AND code is generated for runtime.

eval-when is not part of R7RS-small; it is a Wile extension.

Examples:

(eval-when (expand)
  (display "at expansion time"))

(eval-when (run)
  (display "at runtime"))

(eval-when (expand run)
  (display "both times"))

func (*CompileTimeContinuation) CompileExport

CompileExport handles top-level (export <export-spec> ...).

This is only valid within a library definition. At top-level, it's an error.

func (*CompileTimeContinuation) CompileExpression

func (p *CompileTimeContinuation) CompileExpression(ctctx CompileTimeCallContext, expr syntax.SyntaxValue) error

CompileExpression compiles a general expression. Pushes the expression's source context onto the source stack so that all operations emitted during compilation (including infrastructure ops like Branch and Push) are tagged with the source location.

func (*CompileTimeContinuation) CompileImport

CompileImport handles top-level (import <import-set> ...).

This is for top-level imports outside of a library definition. It loads the specified libraries and binds their exports in the current environment.

Supports Racket-style phased imports:

  • (import (scheme base)) ; Phase 0 (runtime)
  • (import (for-syntax (scheme base))) ; Phase 1 (expand)
  • (import (for-template (scheme base))) ; Phase -1
  • (import (for-meta 2 (scheme base))) ; Phase 2

func (*CompileTimeContinuation) CompileInclude

CompileInclude compiles an include expression. It reads and compiles all forms from the specified files in order. Each form is expanded and compiled in the current environment.

func (*CompileTimeContinuation) CompileIncludeCi

func (p *CompileTimeContinuation) CompileIncludeCi(ctctx CompileTimeCallContext, expr syntax.SyntaxValue) error

CompileIncludeCi compiles an include-ci expression (R7RS §4.1.7 / §5.6). It is identical to include except that each included file is read with case folding enabled, as if the file began with a #!fold-case directive.

func (*CompileTimeContinuation) CompileMeta

CompileMeta compiles a meta expression.

func (*CompileTimeContinuation) CompileQuasisyntax

func (p *CompileTimeContinuation) CompileQuasisyntax(ctctx CompileTimeCallContext, expr syntax.SyntaxValue) error

CompileQuasisyntax compiles the (quasisyntax template) form.

quasisyntax is like quasiquote but for syntax objects. It supports:

  • (unsyntax expr) - evaluate expr and insert the result at depth 1
  • (unsyntax-splicing expr) - evaluate and splice list at depth 1
  • nested quasisyntax increases depth

Like quasiquote, unsyntax only evaluates when depth reaches 0. The result is a syntax object, not a raw datum.

func (*CompileTimeContinuation) CompileSelfEvaluating

func (p *CompileTimeContinuation) CompileSelfEvaluating(_ CompileTimeCallContext, expr syntax.SyntaxValue) error

CompileSelfEvaluating compiles a self-evaluating expression (literal).

func (*CompileTimeContinuation) CompileSymbol

CompileSymbol compiles a syntax symbol expression.

Resolution order (Flatt 2016 "sets of scopes"): a scope-set local match wins over everything, including a ResolvedBinding pin carried from macro expansion. This lets a binding co-introduced by a template — which carries the same intro scope as the reference — shadow a same-named global. The ResolvedBinding and library-scope fallbacks fire only when no lexical binding shadows. See plans/2026-06-15-macro-hygiene-global-shadow-fix.local.md (Change 2).

func (*CompileTimeContinuation) CompileSyntax

CompileSyntax compiles the (syntax template) form.

Unlike quote which unwraps syntax to raw values, syntax preserves the syntax structure. When used inside syntax-case, pattern variables in the template are substituted with their matched values.

For templates containing ellipsis (...), runtime expansion is used because ellipsis patterns capture variable-length lists that must be expanded dynamically.

(syntax template) -> syntax-object

func (*CompileTimeContinuation) CompileSyntaxCase

func (p *CompileTimeContinuation) CompileSyntaxCase(ctctx CompileTimeCallContext, expr syntax.SyntaxValue) error

CompileSyntaxCase compiles the (syntax-case expr (literal ...) clause ...) form.

R6RS syntax-case is a pattern matching form that provides procedural macro facilities. Unlike syntax-rules which expands to templates, syntax-case evaluates arbitrary Scheme code in the body, with pattern variables bound as local variables.

Syntax:

(syntax-case expr (literal ...)
  (pattern body)
  (pattern fender body)
  ...)

Compilation strategy:

  1. Compile expr to get the input syntax object
  2. For each clause, generate pattern matching and body code
  3. Pattern variables are bound as local variables in the body's scope
  4. If fender exists, it's evaluated as a guard condition

func (*CompileTimeContinuation) CompileUnquote

CompileUnquote errors - unquote outside of quasiquote

func (*CompileTimeContinuation) CompileUnquoteSplicing

func (p *CompileTimeContinuation) CompileUnquoteSplicing(_ CompileTimeCallContext, _ syntax.SyntaxValue) error

CompileUnquoteSplicing errors - unquote-splicing outside of quasiquote

func (*CompileTimeContinuation) CompileUnsyntax

CompileUnsyntax errors - unsyntax outside of quasisyntax

func (*CompileTimeContinuation) CompileUnsyntaxSplicing

func (p *CompileTimeContinuation) CompileUnsyntaxSplicing(_ CompileTimeCallContext, _ syntax.SyntaxValue) error

CompileUnsyntaxSplicing errors - unsyntax-splicing outside of quasisyntax

func (*CompileTimeContinuation) CompileValidatedDefineFn

func (p *CompileTimeContinuation) CompileValidatedDefineFn(ctctx CompileTimeCallContext, v *validate.ValidatedDefine) error

CompileValidatedDefineFn compiles the function shorthand form of define.

Usage: (define (name param ...) body ...)

(define (name param ... . rest) body ...)

This is syntactic sugar equivalent to:

(define name (lambda (param ...) body ...))

Examples:

(define (square x) (* x x))           ; fixed arity
(define (sum . args) (apply + args))  ; variadic (all args)
(define (sum x . rest) (apply + x rest))  ; variadic (1+ args)

The function name is bound before compiling the body to enable self-recursion:

(define (fact n) (if (<= n 1) 1 (* n (fact (- n 1)))))

func (*CompileTimeContinuation) CompileWithSyntax

func (p *CompileTimeContinuation) CompileWithSyntax(ctctx CompileTimeCallContext, expr syntax.SyntaxValue) error

CompileWithSyntax compiles the (with-syntax ((pattern expr) ...) body ...) form.

with-syntax is a convenience form for binding pattern variables from expressions. It's equivalent to:

(syntax-case (list expr ...) ()
  ((pattern ...) (begin body ...)))

For now, this implements a simple transformation approach.

func (*CompileTimeContinuation) SetFileResolver

func (p *CompileTimeContinuation) SetFileResolver(r FileResolver)

SetFileResolver overrides the file resolver used by include/load. Nil resets to the environment's resolver (or OSFileResolver as fallback).

func (*CompileTimeContinuation) SetInlineThreshold

func (p *CompileTimeContinuation) SetInlineThreshold(n int)

SetInlineThreshold sets the maximum body length for procedure inlining. 0 disables inlining entirely.

func (*CompileTimeContinuation) SetLibraryCallback

func (p *CompileTimeContinuation) SetLibraryCallback(cb func(*CompiledLibrary))

SetLibraryCallback sets a callback function that will be called when a library is compiled via CompileDefineLibrary. This is used by LoadLibrary to capture the compiled library.

type CompiledLibrary

type CompiledLibrary struct {
	Name        LibraryName                   // Library name
	Description string                        // from (description ...) clause; "" if absent
	Env         *environment.EnvironmentFrame // Library's private environment
	Exports     map[string]string             // external-name -> internal-name
	SourceFile  string                        // Path to .sld file (for error messages)
	Template    *machine.NativeTemplate       // Compiled bytecode (for execution)

	// Scope is the library's own hygiene scope, the one every binder written in
	// the library body carries. It is the KEY the export lookup resolves under
	// (findLibraryBinding), which is what lets a library define a name it also
	// imports: the define lands at {Scope}, the import at {}, and maximal
	// resolution prefers the define while {} ⊆ {Scope} still reaches a name the
	// library only re-exports. Nil for a library built without a body scope, in
	// which case the export lookup degrades to the empty set.
	Scope *syntax.Scope
}

CompiledLibrary holds a loaded and compiled library.

func LoadLibrary

LoadLibrary loads a library by name, compiling and executing it if not already loaded. Returns the CompiledLibrary which can be used to import bindings.

The function: 1. Checks for circular dependencies 2. Checks if already loaded (returns cached library) 3. Resolves and opens the library file via FileResolver 4. Parses and compiles the define-library form 5. Executes the library to create runtime bindings 6. Registers the library in the registry

func NewCompiledLibrary

func NewCompiledLibrary(name LibraryName, env *environment.EnvironmentFrame) *CompiledLibrary

NewCompiledLibrary creates a new compiled library.

func (*CompiledLibrary) AddExport

func (p *CompiledLibrary) AddExport(externalName, internalName string)

AddExport adds an export to the library. If internalName is empty, it defaults to externalName (no rename).

func (*CompiledLibrary) ExportedBinding added in v1.19.0

func (p *CompiledLibrary) ExportedBinding(internalName string) (*environment.Binding, bool)

ExportedBinding resolves one of the library's exportable bindings by its internal name, using the same hygienic rule the import path uses. Callers outside this package (the doc-registration observer) must go through this rather than reaching into lib.Env with a bare-name lookup, or they will disagree with what an import actually installs.

func (*CompiledLibrary) GetInternalName

func (p *CompiledLibrary) GetInternalName(externalName string) string

GetInternalName returns the internal name for an exported external name. Returns empty string if not exported.

func (*CompiledLibrary) IsExported

func (p *CompiledLibrary) IsExported(externalName string) bool

IsExported returns true if the given external name is exported.

type CompilerFunc

type CompilerFunc func(ctc *CompileTimeContinuation, ctctx CompileTimeCallContext, expr forms.ValidatedExpr) error

CompilerFunc is the uniform codegen signature for both Tier-1 and Tier-2 forms, dispatched by FormName through the per-engine forms registry. Its parameter is the ValidatedExpr interface (not a concrete type) so one type covers every form; each handler asserts its concrete type at its head.

type ERMacroTransformer

type ERMacroTransformer struct {
	// contains filtered or unexported fields
}

ERMacroTransformer wraps a 3-arg machine.MachineClosure to identify it as an explicit-renaming transformer in expandMacroInvocation. The defEnv captures the expand-time environment at the macro definition site, used by the rename closure to resolve definition-site bindings.

func NewERMacroTransformer

func NewERMacroTransformer(closure *machine.MachineClosure, defEnv *environment.EnvironmentFrame) *ERMacroTransformer

func (*ERMacroTransformer) Closure

func (*ERMacroTransformer) DefEnv

func (*ERMacroTransformer) EqualTo

func (p *ERMacroTransformer) EqualTo(o values.Value) bool

func (*ERMacroTransformer) IsVoid

func (p *ERMacroTransformer) IsVoid() bool

func (*ERMacroTransformer) SchemeString

func (p *ERMacroTransformer) SchemeString() string

type EmbedFileResolver

type EmbedFileResolver = resolver.EmbedFileResolver

func NewEmbedFileResolver

func NewEmbedFileResolver(fsys fs.FS) *EmbedFileResolver

NewEmbedFileResolver creates a resolver backed by an embedded filesystem.

type ExpanderContext

type ExpanderContext struct {
	// contains filtered or unexported fields
}

ExpanderContext provides access to the macro expander from within Scheme code during macro expansion. It is set on machine.MachineContext when invoking macro transformers, enabling syntax-local-* primitives.

func NewExpanderContext

func NewExpanderContext(
	env *environment.EnvironmentFrame,
	expander *ExpanderTimeContinuation,
) *ExpanderContext

NewExpanderContext creates a new ExpanderContext.

func (*ExpanderContext) Env

Env returns the environment frame associated with this context.

func (*ExpanderContext) Expand

Expand fully expands a syntax object.

func (*ExpanderContext) ExpandOnce

ExpandOnce performs a single step of macro expansion. Returns (expanded-syntax, did-expand, error). If the input is a macro call, it expands it once and returns (result, true, nil). If the input is not a macro call, it returns (input, false, nil).

func (*ExpanderContext) IntroductionScope

func (p *ExpanderContext) IntroductionScope() *syntax.Scope

IntroductionScope returns the introduction scope for the current macro expansion. This scope is added to identifiers introduced by a macro and can be flipped using syntax-local-introduce.

func (*ExpanderContext) SetIntroductionScope

func (p *ExpanderContext) SetIntroductionScope(scope *syntax.Scope)

SetIntroductionScope sets the introduction scope for the current macro expansion.

func (*ExpanderContext) SetUseSiteScope

func (p *ExpanderContext) SetUseSiteScope(scope *syntax.Scope)

SetUseSiteScope sets the use-site scope for binding forms.

func (*ExpanderContext) UseSiteScope

func (p *ExpanderContext) UseSiteScope() *syntax.Scope

UseSiteScope returns the use-site scope for binding forms. This scope is used by syntax-local-identifier-as-binding to mark identifiers as binding sites.

type ExpanderTimeContinuation

type ExpanderTimeContinuation struct {
	// contains filtered or unexported fields
}

ExpanderTimeContinuation is a continuation used during the expansion phase.

It walks the syntax tree, detecting and expanding macro invocations. The env field provides access to macro definitions (BindingTypeSyntax bindings).

func NewExpanderTimeContinuation

func NewExpanderTimeContinuation(ctx context.Context, env *environment.EnvironmentFrame, evaluator machine.MacroEvaluator) *ExpanderTimeContinuation

NewExpanderTimeContinuation creates a new ExpanderTimeContinuation. The returned expander begins a fresh expansion run with its own depth guard. The bound is read from the env's namespace (WithMaxExpandDepth, forwarded onto the shared EngineServices at engine build) so every expander site — the top-level pass and the ~8 compile-time re-expansion sites reached during library and body compilation — honors it uniformly. A namespace not built by an Engine (e.g., a direct unit-test compiler) reports unset and defaults to DefaultMaxExpandDepth. SetMaxDepth still overrides per-run afterward.

func (*ExpanderTimeContinuation) ExpandBodyWithDefineSyntax

func (p *ExpanderTimeContinuation) ExpandBodyWithDefineSyntax(
	forms []syntax.SyntaxValue,
) ([]syntax.SyntaxValue, error)

ExpandBodyWithDefineSyntax expands a sequence of body forms, compiling define-syntax forms as encountered so subsequent forms can use the macros.

This unifies the expansion pattern used by: - Lambda bodies (internal define-syntax) - Library bodies (top-level define-syntax) - Include files (top-level define-syntax)

R7RS §5.3: Internal define-syntax forms must be processed before expanding subsequent body expressions so that locally-defined macros are visible.

R7RS §5.3.2: Bodies use letrec* semantics where all defined names are visible to all initializers. This enables forward references within macros - a macro can reference a definition that appears later in the same body.

func (*ExpanderTimeContinuation) ExpandExpression

func (p *ExpanderTimeContinuation) ExpandExpression(expr syntax.SyntaxValue) (syntax.SyntaxValue, error)

ExpandExpression expands a syntax expression.

This is the single recursion chokepoint of the expander: every descent into a sub-expression — nested cars, argument lists, primitive-form bodies (via child expanders), and macro re-expansion — funnels through here. The depth guard therefore bounds total Go-stack recursion for the whole expansion run, returning a catchable ErrExpandDepthExceeded instead of letting deeply nested (programmatically-constructed) syntax crash the host with a fatal Go stack overflow. See expandDepthGuard and DefaultMaxExpandDepth.

func (*ExpanderTimeContinuation) ExpandOnce

ExpandOnce performs a single step of macro expansion. Returns (expanded-syntax, did-expand, error). If the input is a macro call, it expands it once and returns (result, true, nil). If the input is not a macro call, it returns (input, false, nil). Unlike ExpandExpression, this does NOT recursively expand the result.

func (*ExpanderTimeContinuation) ExpandPrimitiveForm

func (p *ExpanderTimeContinuation) ExpandPrimitiveForm(primName string, sym *syntax.SyntaxSymbol, expr syntax.SyntaxValue) (syntax.SyntaxValue, error)

ExpandPrimitiveForm handles expansion within primitive forms like if, begin, lambda, define, etc. Some primitives need their subexpressions expanded (like if, begin) while others should be left unchanged (like quote, define-syntax).

This function looks up the primitive expander in the expanded environment registry. If found, it invokes the expander; otherwise returns the form unchanged.

func (*ExpanderTimeContinuation) ExpandSelfEvaluating

func (p *ExpanderTimeContinuation) ExpandSelfEvaluating(expr syntax.SyntaxValue) (syntax.SyntaxValue, error)

ExpandSelfEvaluating handles self-evaluating expressions.

func (*ExpanderTimeContinuation) ExpandSymbol

ExpandSymbol handles a symbol expression.

func (*ExpanderTimeContinuation) ExpandSyntaxArgumentList

func (p *ExpanderTimeContinuation) ExpandSyntaxArgumentList(args syntax.SyntaxValue) (syntax.SyntaxValue, error)

ExpandSyntaxArgumentList expands each argument in the argument list. It returns a new syntax list with the expanded arguments.

func (*ExpanderTimeContinuation) ExpandSyntaxExpression

func (p *ExpanderTimeContinuation) ExpandSyntaxExpression(sym *syntax.SyntaxSymbol, expr syntax.SyntaxValue) (syntax.SyntaxValue, error)

ExpandSyntaxExpression checks if sym is a macro and expands it, or returns the expression as a procedure call if not.

This is where macro invocation happens:

  1. Look up the symbol in the expand environment
  2. If bound with BindingTypeSyntax, it's a macro - invoke the transformer
  3. If it's a primitive (like quote, if, define-syntax), don't expand args
  4. Otherwise, treat as procedure call and expand arguments

The transformer closure (machine.MachineClosure from CompileSyntaxRules) is invoked by creating a machine.MachineContext and running it. The transformer:

  • Receives the full macro invocation form on the eval stack
  • Pattern matches against its clauses (OperationSyntaxRulesTransform)
  • Expands the matching template with captured bindings
  • Adds an intro scope to the expansion for hygiene
  • Returns the expanded syntax in the value register

The expanded result may itself contain macro invocations, so the caller should recursively expand it.

func (*ExpanderTimeContinuation) ExpandSyntaxOrProcedureCall

func (p *ExpanderTimeContinuation) ExpandSyntaxOrProcedureCall(car syntax.SyntaxValue, cdr syntax.SyntaxValue, parentCtx *syntax.SourceContext) (syntax.SyntaxValue, error)

ExpandSyntaxOrProcedureCall handles a list expression. The car may be a symbol (possibly a macro), a nested pair (computed procedure), or a self-evaluating value (like in quoted data or malformed expressions).

func (*ExpanderTimeContinuation) ExpandTopLevelExpression added in v1.19.0

func (p *ExpanderTimeContinuation) ExpandTopLevelExpression(expr syntax.SyntaxValue) (syntax.SyntaxValue, error)

ExpandTopLevelExpression expands a whole TOP-LEVEL form.

Use this at every top-level entry point (the Engine's ExpandAndCompile, the test-harness pipelines); use ExpandExpression for sub-forms.

Hygiene for a macro-introduced top-level binder is a property of the GLOBAL FRAME, not of this expansion step: the binder carries the expansion's intro scope and the frame keys its slots by scope set, so two expansions land in two slots and a bare (empty-scope) reference cannot reach either (R7RS §4.3.2). This used to be restored syntactically here, by renaming every macro-introduced binder and its references to a fresh unique name; see pkg/environment/global_environment_frame.go for the storage that replaced it, and pkg/wile/toplevel_binder_scope_test.go for the pinned behavior.

func (*ExpanderTimeContinuation) SetMaxDepth

func (p *ExpanderTimeContinuation) SetMaxDepth(n int)

SetMaxDepth sets the maximum structural recursion depth allowed during expansion for this run. A value of 0 (or negative, clamped to 0) disables the limit. Mirrors the parser's SetMaxDepth and MachineContext.SetMaxCallDepth.

type FSFileResolver

type FSFileResolver = resolver.FSFileResolver

func NewFSFileResolver

func NewFSFileResolver(fsys fs.FS, env *environment.EnvironmentFrame) *FSFileResolver

NewFSFileResolver creates a resolver backed by a virtual filesystem.

type FeatureRequirement

type FeatureRequirement interface {
	// IsSatisfied returns true if this requirement is satisfied.
	// The registry parameter is used to check if a library is already loaded.
	// The resolver parameter is used to check if a library file exists
	// (via the FileResolver chain, supporting both OS and virtual fs.FS).
	// The load parameter, when non-nil, lets a (library X) requirement attempt
	// a real load (so a .sld that resolves on disk but fails to import counts
	// as unsatisfied); when nil, library availability falls back to a
	// file-existence check via the resolver.
	IsSatisfied(ctx context.Context, registry *LibraryRegistry, resolver FileResolver, load LibraryLoadProbe) bool
}

FeatureRequirement represents a parsed cond-expand feature requirement. Feature requirements can be:

  • A symbol (feature identifier)
  • (library <library-name>) - check if library is available
  • (and <req> ...) - all requirements must be satisfied
  • (or <req> ...) - at least one requirement must be satisfied
  • (not <req>) - requirement must NOT be satisfied

func NewAndRequirement

func NewAndRequirement(reqs ...FeatureRequirement) FeatureRequirement

NewAndRequirement creates an and requirement.

func NewElseRequirement

func NewElseRequirement() FeatureRequirement

NewElseRequirement creates an else requirement (always satisfied).

func NewFeatureIdentifier

func NewFeatureIdentifier(name string) FeatureRequirement

NewFeatureIdentifier creates a feature identifier requirement.

func NewLibraryRequirement

func NewLibraryRequirement(name LibraryName) FeatureRequirement

NewLibraryRequirement creates a library requirement.

func NewNotRequirement

func NewNotRequirement(req FeatureRequirement) FeatureRequirement

NewNotRequirement creates a not requirement.

func NewOrRequirement

func NewOrRequirement(reqs ...FeatureRequirement) FeatureRequirement

NewOrRequirement creates an or requirement.

type FileResolver

type FileResolver = environment.FileResolver

FileResolver resolves and opens files for include/load operations. The interface is defined in the environment package; this alias keeps the name available in compilation without re-declaration.

Concrete resolver types (OSFileResolver, EmbedFileResolver, FSFileResolver, ChainFileResolver) and their constructors live in the resolver sub-package. Backward-compatible aliases are provided in resolver_compat.go.

type FreeIdResolution

type FreeIdResolution struct {
	Global          *environment.GlobalIndex
	LocalScopes     []*syntax.Scope
	HasLocalBinding bool
	LibScope        *syntax.Scope
}

FreeIdResolution records how a free identifier in a syntax-rules template was bound at macro definition time. The match package uses this during template expansion to preserve hygiene.

Implements localScopesProvider, globalBindingProvider, hasLocalBindingProvider, and libraryScopeProvider from the match package.

func (*FreeIdResolution) GetGlobal

func (p *FreeIdResolution) GetGlobal() *environment.GlobalIndex

GetGlobal implements the globalBindingProvider interface.

func (*FreeIdResolution) GetHasLocalBinding

func (p *FreeIdResolution) GetHasLocalBinding() bool

GetHasLocalBinding implements the hasLocalBindingProvider interface.

func (*FreeIdResolution) GetLibraryScope

func (p *FreeIdResolution) GetLibraryScope() *syntax.Scope

GetLibraryScope implements the libraryScopeProvider interface.

func (*FreeIdResolution) GetLocalScopes

func (p *FreeIdResolution) GetLocalScopes() []*syntax.Scope

GetLocalScopes implements the localScopesProvider interface.

type ImportSet

type ImportSet struct {
	LibraryName LibraryName       // Base library to import from
	Modifiers   []importModifier  // only/except/prefix/rename, innermost first
	PhaseShift  environment.Phase // Phase offset: 0=runtime, 1=for-syntax, -1=for-template
}

ImportSet represents a parsed import specification. It can be a simple library reference or include modifiers.

PhaseShift supports Racket-style phased imports:

  • (import (scheme base)) ; Phase 0 (runtime) - default
  • (import (for-syntax (scheme base))) ; Phase +1 (expand)
  • (import (for-template (scheme base))) ; Phase -1
  • (import (for-meta 2 (scheme base))) ; Phase +2
  • (import (for-meta -1 (scheme base))) ; Phase -1 (same as for-template)

Phase shifts compose additively: (for-syntax (for-syntax lib)) = phase +2

Modifiers preserves the written nesting order of only/except/prefix/rename so ApplyToExports can fold them INSIDE-OUT, as R7RS §5.6 requires. The innermost (textually deepest) modifier is Modifiers[0]; each later modifier operates on the output of the one before it. A flat representation (separate Only/Except/Prefix/ Renames fields) cannot express this — it both loses the ordering between different modifier kinds and silently overwrites a repeated kind, so e.g. (prefix (prefix LIB a-) b-) would bind b-car instead of b-a-car.

func NewImportSet

func NewImportSet(name LibraryName) *ImportSet

NewImportSet creates a new import set for a library, with no modifiers.

func ParseImportSetFromDatum

func ParseImportSetFromDatum(ctx context.Context, expr values.Value) (*ImportSet, error)

ParseImportSetFromDatum parses an import set from datum values. Used at both runtime (by the 'environment' procedure) and compile time (via UnwrapAll on syntax objects).

Import sets can be:

  • (<library-name>) : import all exports
  • (only <import-set> <id> ...) : import only specified identifiers
  • (except <import-set> <id> ...): import all except specified
  • (prefix <import-set> <prefix>): add prefix to all imported names
  • (rename <import-set> (<old> <new>) ...): rename specific imports
  • (for-syntax <import-set>) : import at phase +1 (macro expansion)
  • (for-template <import-set>) : import at phase -1
  • (for-meta <n> <import-set>) : import at phase +n

func (*ImportSet) AddExcept

func (p *ImportSet) AddExcept(ids values.StringSet)

AddExcept appends an `except` modifier removing ids from the import. Empty/nil is a no-op.

func (*ImportSet) AddOnly

func (p *ImportSet) AddOnly(ids values.StringSet)

AddOnly appends an `only` modifier restricting the import to ids. An empty/nil ids set installs a modifier that imports NOTHING: R7RS §5.6 grammar is (only <import-set> <identifier> …) with zero-or-more identifiers, so (only LIB) with no identifiers denotes the empty subset. AddOnly is called exactly once per syntactic `only` form, so the empty case is a real "import nothing", not "no filter".

func (*ImportSet) AddPrefix

func (p *ImportSet) AddPrefix(prefix string)

AddPrefix appends a `prefix` modifier prepending prefix to every imported name. An empty prefix is a no-op.

func (*ImportSet) AddRename

func (p *ImportSet) AddRename(renames map[string]string)

AddRename appends a `rename` modifier mapping old names to new names. Empty/nil is a no-op.

func (*ImportSet) ApplyToExports

func (p *ImportSet) ApplyToExports(lib *CompiledLibrary) (map[string]string, error)

ApplyToExports applies the import modifiers inside-out and returns the final bindings as a map of local-name -> external-name (the name in the library).

type LibraryExportIndex

type LibraryExportIndex struct {
	// contains filtered or unexported fields
}

LibraryExportIndex maps library keys to their summaries. Immutable after construction.

func BuildExportIndex

func BuildExportIndex(ctx context.Context, res FileResolver, reg *LibraryRegistry) (*LibraryExportIndex, error)

BuildExportIndex scans all discoverable library files via the resolver's FileEnumerator, parses their exports, and returns a LibraryExportIndex. Libraries already loaded in reg are skipped (their metadata is already available via the registry). If the resolver does not implement FileEnumerator, an empty index is returned (not an error).

Best-effort: per-file name and parse errors are joined and returned alongside a valid (partial) index; only an EnumerateFiles failure or a cancelled context returns a nil index.

func NewLibraryExportIndexFromEntries

func NewLibraryExportIndexFromEntries(entries map[string]*LibrarySummary) *LibraryExportIndex

NewLibraryExportIndexFromEntries creates an index from pre-built entries.

func (*LibraryExportIndex) Entries

func (p *LibraryExportIndex) Entries() []*LibrarySummary

Entries returns all indexed summaries sorted by library key.

func (*LibraryExportIndex) Lookup

Lookup returns the summary for a library, or nil if not indexed.

type LibraryImportEvent

type LibraryImportEvent struct {
	Library    LibraryName       // imported library name, e.g., (scheme base)
	SourceFile string            // path to .sld file (empty for synthetic libraries)
	Exports    []string          // all names exported by the library
	Imported   []string          // names that actually landed in the importer (after only/except/prefix/rename)
	Importer   LibraryName       // importing library name (zero value for top-level import)
	Phase      environment.Phase // pipeline phase: environment.PhaseExpand or environment.PhaseCompile
}

LibraryImportEvent records what happened when a library was imported.

type LibraryImportObserver

type LibraryImportObserver func(LibraryImportEvent)

LibraryImportObserver is called when a library is imported. Observers are read-only — they cannot influence the import.

type LibraryLoadProbe

type LibraryLoadProbe func(name LibraryName) bool

LibraryLoadProbe attempts to actually load a library by name, returning true iff the load (parse + compile + execute + register) succeeds. A successful probe caches the library in the registry, so the subsequent real import is free. cond-expand's (library X) requirement uses this so importability — not mere file presence — decides the clause (R7RS §4.2.1, plan item 5F/P6).

type LibraryName

type LibraryName struct {
	Parts []string // e.g., ["scheme", "base"]
}

LibraryName represents an R7RS library name like (scheme base) or (my lib). Library names are lists of identifiers used to uniquely identify a library.

func DiscoverAvailableLibraries

func DiscoverAvailableLibraries(res FileResolver, reg *LibraryRegistry) ([]LibraryName, error)

DiscoverAvailableLibraries returns all importable library names by combining filesystem discovery (via the resolver's FileEnumerator) with registry-known libraries (synthetic extension libraries). Returns a sorted, deduplicated list.

If the resolver does not implement FileEnumerator, only registry libraries are returned. If reg is nil, only filesystem libraries are returned.

Two error shapes: an EnumerateFiles failure aborts with (nil, err), discarding any partial result; per-path FilePathToLibraryName failures are joined and returned alongside a complete list.

func FilePathToLibraryName

func FilePathToLibraryName(path string) (LibraryName, error)

FilePathToLibraryName converts a forward-slash-separated file path with .sld or .scm extension to a LibraryName. This is the inverse of ToFSPath(). Returns an error if the path has no recognized extension or is empty.

func NewLibraryName

func NewLibraryName(parts ...string) LibraryName

NewLibraryName creates a LibraryName from a list of string parts.

func ParseLibraryNameFromDatum

func ParseLibraryNameFromDatum(ctx context.Context, expr values.Tuple) (LibraryName, error)

ParseLibraryNameFromDatum extracts a LibraryName from a datum list like (scheme base). Used at both runtime (by the library-reflection primitives) and compile time (via ParseLibraryNameFromSyntax).

The parameter is values.Tuple, not values.Value: "a library name is a list" is a precondition, not a parse failure, so it is settled before the call. The two entry points that face untyped input carry that check — ParseLibraryNameFromSyntax for the three compile-time sites, and the primitive itself for user-supplied arguments.

func ParseLibraryNameFromSyntax added in v1.20.0

func ParseLibraryNameFromSyntax(ctx context.Context, expr syntax.SyntaxValue) (LibraryName, error)

ParseLibraryNameFromSyntax unwraps a syntax datum to a library name. It is the compile-time entry point, and the one place the "a library name is a list" precondition is checked for it — the three sites that need a name out of source (define-library's own name, cond-expand's (library ...) feature requirement, and the export-index prescan) all previously repeated UnwrapAll at the call and let the parser sort out non-lists.

A non-list here is a malformed program, not an internal error, so it reports ErrNotAList and leaves the source-context wrapping to the caller, which is the only one that knows which form is at fault.

func (LibraryName) Key

func (p LibraryName) Key() string

Key returns a unique string key for map lookups.

func (LibraryName) SchemeString

func (p LibraryName) SchemeString() string

SchemeString returns the Scheme representation like "(scheme base)".

func (LibraryName) String

func (p LibraryName) String() string

String returns a human-readable representation like "scheme/base".

func (LibraryName) ToFSPath

func (p LibraryName) ToFSPath() string

ToFSPath returns the library name as a forward-slash-separated path with .sld extension, suitable for fs.FS and FileResolver operations.

func (LibraryName) ToSchemeValue

func (p LibraryName) ToSchemeValue() values.Value

ToSchemeValue converts a LibraryName to a Scheme list. Parts that parse as nonnegative integers become exact integers; all others become symbols. Matches R7RS library name syntax.

type LibraryRegistry

type LibraryRegistry struct {
	// contains filtered or unexported fields
}

LibraryRegistry manages loaded libraries and handles library loading.

All fields are guarded by mu so the registry is safe for concurrent use by SRFI-18 threads that load libraries via (environment …) / (eval '(import …)). Read methods take RLock; methods that mutate a map, the search paths, or the observer take Lock. mu is not reentrant — public methods must not call other locking methods while holding the lock (AllNames uses the unlocked all() helper for this reason).

func NewLibraryRegistry

func NewLibraryRegistry() *LibraryRegistry

NewLibraryRegistry creates a new library registry with default search paths.

func (*LibraryRegistry) All

func (p *LibraryRegistry) All() []*CompiledLibrary

All returns all loaded libraries, sorted by name key for determinism.

func (*LibraryRegistry) AllNames

func (p *LibraryRegistry) AllNames() []LibraryName

AllNames returns the names of all registered libraries, sorted by key.

func (*LibraryRegistry) FinishLoading

func (p *LibraryRegistry) FinishLoading(name LibraryName)

FinishLoading marks a library as finished loading and wakes every goroutine waiting on its latch (whether the load succeeded and Registered or failed). Waiters re-consult the registry: a successful load is now cached; a failed one is neither cached nor loading, so a waiter re-claims and retries.

func (*LibraryRegistry) GetSearchPaths

func (p *LibraryRegistry) GetSearchPaths() []string

GetSearchPaths returns a copy of the current library search paths. Returning a copy keeps the registry's internal slice immutable from outside, so a caller cannot mutate the backing array and race a concurrent reader.

func (*LibraryRegistry) ImportObserver

func (p *LibraryRegistry) ImportObserver() LibraryImportObserver

ImportObserver returns the current import observer, or nil.

func (*LibraryRegistry) IsLoading

func (p *LibraryRegistry) IsLoading(name LibraryName) bool

IsLoading reports whether the library is currently being loaded by some goroutine. Diagnostic only: it does not distinguish which goroutine, and it is not part of the claim protocol (that is LookupClaimOrWait → FinishLoading). Cycle detection — re-entry on the caller's own load chain — is handled in LoadLibrary via the ctx-borne load chain, not here.

func (*LibraryRegistry) Lookup

func (p *LibraryRegistry) Lookup(name LibraryName) *CompiledLibrary

Lookup returns a library by name, or nil if not found.

func (*LibraryRegistry) LookupClaimOrWait

func (p *LibraryRegistry) LookupClaimOrWait(name LibraryName) (cached *CompiledLibrary, wait <-chan struct{})

LookupClaimOrWait atomically resolves a library, claims its loading slot, or returns a latch to wait on. It is the single check-and-mark decision point so the lookup → claim sequence cannot interleave across threads. The two return values encode three outcomes; "claimed" is the both-nil case, not a separate flag, so the contradictory states a third flag would admit are unrepresentable:

  • cached != nil ⇒ already loaded; use it.
  • cached == nil, wait != nil ⇒ another goroutine is loading this exact library; block on wait, then re-call to read the now-cached result (or re-claim if that load failed).
  • cached == nil, wait == nil ⇒ the caller now owns the loading slot and MUST load, Register, and FinishLoading (which closes the latch installed here).

Unlike the former LookupOrClaim, a concurrent same-library load is NOT a circular-dependency error: that false positive is what blocked concurrent shared-dependency loads. A genuine import cycle (A→B→A) is re-entry on one goroutine's own synchronous load chain and is caught earlier, in LoadLibrary, via the ctx-borne load chain — before reaching this method — so a goroutine never waits on a latch it installed itself.

func (*LibraryRegistry) PrependSearchPath

func (p *LibraryRegistry) PrependSearchPath(path string)

PrependSearchPath adds a path to the beginning of the search path list.

func (*LibraryRegistry) Register

func (p *LibraryRegistry) Register(lib *CompiledLibrary) error

Register adds a compiled library to the registry.

func (*LibraryRegistry) SetImportObserver

func (p *LibraryRegistry) SetImportObserver(obs LibraryImportObserver)

SetImportObserver sets an optional observer that is called each time a library is imported. The observer is read-only and cannot influence the import. Pass nil to remove the observer.

func (*LibraryRegistry) SetSearchPaths

func (p *LibraryRegistry) SetSearchPaths(paths []string)

SetSearchPaths sets the library search paths. The slice is copied so the registry owns its searchPaths memory: a caller mutating the slice it passed in must not be able to race a concurrent reader of the registry's state.

type LibrarySummary

type LibrarySummary struct {
	Name        LibraryName
	Description string
	Exports     []string
	SourceFile  string
}

LibrarySummary holds the statically-extracted metadata for a single library definition file (.sld). It captures exports and description without compiling or executing any library code.

func ParseLibrarySummary

func ParseLibrarySummary(ctx context.Context, r io.Reader, filePath string, name LibraryName) (*LibrarySummary, error)

ParseLibrarySummary reads a library definition from r and extracts its export list and description without compiling the library. This is a best-effort parser: malformed export specs are silently skipped.

type OSFileResolver

type OSFileResolver = resolver.OSFileResolver

func NewOSFileResolver

func NewOSFileResolver(env *environment.EnvironmentFrame) *OSFileResolver

NewOSFileResolver creates a resolver backed by the OS filesystem.

type OperationBindPatternVars

type OperationBindPatternVars struct {
	machine.OperationBase
	PatternVars []string // Ordered list for consistent indexing
	// MergedSlots is how many anonymous slots a `let` in the clause body took
	// out of this frame instead of pushing one of its own.
	//
	// WRITTEN AFTER THE OPERATION IS EMITTED, by compileSyntaxCaseClause, once
	// the body it brackets has been compiled — the count is not knowable before
	// then, and this operation is reached through the side table by pointer, so
	// the emitted instruction sees the final value. Compare
	// CompileValidatedLet's OpPushEnv operand patch, which solves the same
	// problem for an operand that is packed into the instruction word.
	MergedSlots int
}

OperationBindPatternVars binds pattern variables from the last match into a new local environment frame that is pushed onto the current environment.

This operation creates a new environment frame with local slots for each pattern variable, binds the matched values, and makes this the current environment.

func NewOperationBindPatternVars

func NewOperationBindPatternVars(patternVars values.StringSet) *OperationBindPatternVars

func (*OperationBindPatternVars) Apply

func (*OperationBindPatternVars) EqualTo

func (p *OperationBindPatternVars) EqualTo(other values.Value) bool

EqualTo compares the frame this operation builds: which pattern variables it binds AND how wide the frame ends up. Two clauses over the same pattern whose bodies merge different numbers of `let` slots build different frames, so MergedSlots is part of the identity rather than metadata.

func (*OperationBindPatternVars) OpKind

type OperationBuildSyntaxList

type OperationBuildSyntaxList struct {
	machine.OperationBase
	Count  int
	Dotted bool
}

OperationBuildSyntaxList builds a syntax list from elements on the eval stack. Count elements are popped (in reverse order) and consed into a list.

Dotted makes the LAST of those elements the list's tail rather than its final element, which is what `(a b . c)` needs. Without it every template was built onto SyntaxEmptyList, so an improper template came back proper — (syntax->datum (syntax (a b . c))) answered (a b c) where Chez answers (a b . c). The tail rides on the stack like any other element rather than getting its own operand, so the push protocol is unchanged and Count still says exactly how many values this pops.

func NewOperationBuildDottedSyntaxList added in v1.20.0

func NewOperationBuildDottedSyntaxList(count int) *OperationBuildSyntaxList

NewOperationBuildDottedSyntaxList creates an OperationBuildSyntaxList whose last popped element is the improper tail. count includes that tail, so a template with k elements before the dot has count == k+1.

func NewOperationBuildSyntaxList

func NewOperationBuildSyntaxList(count int) *OperationBuildSyntaxList

NewOperationBuildSyntaxList creates a new OperationBuildSyntaxList.

func (*OperationBuildSyntaxList) Apply

Apply implements the Operation interface.

func (*OperationBuildSyntaxList) EqualTo

func (p *OperationBuildSyntaxList) EqualTo(other values.Value) bool

func (*OperationBuildSyntaxList) OpKind

type OperationClearSyntaxCaseInput

type OperationClearSyntaxCaseInput struct {
	machine.OperationBase
}

OperationClearSyntaxCaseInput drops the PENDING syntax-case state at the end of a syntax-case form, releasing the input syntax object and the matcher.

It is no longer load-bearing for correctness, and that is the point. It used to be the only thing that stopped a later (syntax ...) from expanding against a finished form's matcher, which made it a bug that a clause body ending in a tail CALL never reaches it — the epilogue sits after the body's tail position. Template expansion now resolves through the pattern-variable frame, so skipping this instruction costs a retention, not an answer: the state a tail call leaves installed is read by nothing.

func NewOperationClearSyntaxCaseInput

func NewOperationClearSyntaxCaseInput() *OperationClearSyntaxCaseInput

func (*OperationClearSyntaxCaseInput) Apply

func (*OperationClearSyntaxCaseInput) EqualTo

func (p *OperationClearSyntaxCaseInput) EqualTo(other values.Value) bool

func (*OperationClearSyntaxCaseInput) OpKind

type OperationStoreSyntaxCaseInput

type OperationStoreSyntaxCaseInput struct {
	machine.OperationBase
}

OperationStoreSyntaxCaseInput opens a syntax-case form by installing a FRESH pending syntaxCaseState holding the value register as the input, for OperationSyntaxCaseMatch to match against.

Minting a fresh state rather than reusing whatever is installed is what keeps a nested syntax-case from mutating the enclosing form's snapshot: the enclosing frame holds its own object, not this one.

func NewOperationStoreSyntaxCaseInput

func NewOperationStoreSyntaxCaseInput() *OperationStoreSyntaxCaseInput

func (*OperationStoreSyntaxCaseInput) Apply

func (*OperationStoreSyntaxCaseInput) EqualTo

func (p *OperationStoreSyntaxCaseInput) EqualTo(other values.Value) bool

func (*OperationStoreSyntaxCaseInput) OpKind

type OperationSyntaxCaseMatch

type OperationSyntaxCaseMatch struct {
	machine.OperationBase
}

OperationSyntaxCaseMatch performs pattern matching for syntax-case.

Expects:

  • Value register: *SyntaxCaseClause with compiled pattern
  • Per-context syntaxCaseState.input: input syntax object (set by OperationStoreSyntaxCaseInput)

Results:

  • If match succeeds: value register = #t, pattern bindings stored in context
  • If match fails: value register = #f

func NewOperationSyntaxCaseMatch

func NewOperationSyntaxCaseMatch() *OperationSyntaxCaseMatch

func (*OperationSyntaxCaseMatch) Apply

func (*OperationSyntaxCaseMatch) EqualTo

func (p *OperationSyntaxCaseMatch) EqualTo(other values.Value) bool

func (*OperationSyntaxCaseMatch) OpKind

type OperationSyntaxCaseNoMatch

type OperationSyntaxCaseNoMatch struct {
	machine.OperationBase
}

OperationSyntaxCaseNoMatch is emitted at the end of syntax-case when no clause matches.

func NewOperationSyntaxCaseNoMatch

func NewOperationSyntaxCaseNoMatch() *OperationSyntaxCaseNoMatch

func (*OperationSyntaxCaseNoMatch) Apply

func (*OperationSyntaxCaseNoMatch) EqualTo

func (p *OperationSyntaxCaseNoMatch) EqualTo(other values.Value) bool

func (*OperationSyntaxCaseNoMatch) OpKind

type OperationSyntaxRulesTransform

type OperationSyntaxRulesTransform struct {
	machine.OperationBase
}

OperationSyntaxRulesTransform is a VM operation that performs macro expansion.

Execution context:

  • Value register: contains clausesWrapper with compiled pattern/template pairs
  • Local parameter 0: contains the input form (the macro invocation)

The operation is part of the transformer closure created by CompileSyntaxRules.

func NewOperationSyntaxRulesTransform

func NewOperationSyntaxRulesTransform() *OperationSyntaxRulesTransform

func (*OperationSyntaxRulesTransform) Apply

func (*OperationSyntaxRulesTransform) EqualTo

func (p *OperationSyntaxRulesTransform) EqualTo(other values.Value) bool

func (*OperationSyntaxRulesTransform) OpKind

type OperationSyntaxTemplateExpand

type OperationSyntaxTemplateExpand struct {
	machine.OperationBase
	// FreeIds and PatternVarSyntax carry the enclosing syntax-case clause's
	// hygiene data, computed once at compile time by CompileSyntax. They mirror
	// the SyntaxRulesClause fields the syntax-rules transformer uses, so the
	// ellipsis template-expansion path is hygienic (R7RS §4.3): free template
	// identifiers resolve at the macro definition site and template-introduced
	// binders carry a fresh intro scope rather than capturing use-site identifiers.
	FreeIds          map[string]*FreeIdResolution
	PatternVarSyntax map[string]*syntax.SyntaxSymbol
}

OperationSyntaxTemplateExpand expands a syntax template using the current pattern variable bindings. This is used for templates containing ellipsis, which require runtime expansion rather than compile-time code generation.

The template is stored in the value register (loaded from literals). The result is the expanded syntax object, left in the value register.

func NewOperationSyntaxTemplateExpand

func NewOperationSyntaxTemplateExpand(freeIds map[string]*FreeIdResolution, patternVarSyntax map[string]*syntax.SyntaxSymbol) *OperationSyntaxTemplateExpand

func (*OperationSyntaxTemplateExpand) Apply

func (*OperationSyntaxTemplateExpand) EqualTo

func (p *OperationSyntaxTemplateExpand) EqualTo(other values.Value) bool

func (*OperationSyntaxTemplateExpand) OpKind

type PhaseEntry

type PhaseEntry[F any] struct {
	Name string
	Fn   F
}

PhaseEntry represents a named item to register in a phase environment.

type PrimitiveExpander

type PrimitiveExpander struct {
	// contains filtered or unexported fields
}

PrimitiveExpander wraps a PrimitiveExpanderFunc as a values.Value so it can be stored in the environment.

func LookupPrimitiveExpander

func LookupPrimitiveExpander(env *environment.EnvironmentFrame, sym *values.Symbol, scopes []*syntax.Scope) *PrimitiveExpander

LookupPrimitiveExpander looks up a primitive expander by symbol in the expand environment. Returns the PrimitiveExpander if found, or nil if the symbol does not name a primitive expander.

This function handles hygiene by using scoped lookup - it will only match bindings whose scopes are a subset of the symbol's scopes.

func NewPrimitiveExpander

func NewPrimitiveExpander(name string, fn PrimitiveExpanderFunc) *PrimitiveExpander

NewPrimitiveExpander creates a new primitive expander.

func (*PrimitiveExpander) EqualTo

func (p *PrimitiveExpander) EqualTo(other values.Value) bool

EqualTo implements values.Value interface.

func (*PrimitiveExpander) Expand

Expand invokes the primitive expander function.

func (*PrimitiveExpander) IsVoid

func (p *PrimitiveExpander) IsVoid() bool

IsVoid returns false — named handlers are never void.

func (*PrimitiveExpander) Name

func (p *PrimitiveExpander) Name() string

Name returns the handler's name.

func (*PrimitiveExpander) SchemeString

func (p *PrimitiveExpander) SchemeString() string

SchemeString returns the Scheme representation: #<prefix:name>.

type PrimitiveExpanderFunc

type PrimitiveExpanderFunc func(
	etc *ExpanderTimeContinuation,
	sym *syntax.SyntaxSymbol,
	expr syntax.SyntaxValue,
) (syntax.SyntaxValue, error)

PrimitiveExpanderFunc is the type for expand-time special form handlers. These functions handle macro expansion of primitive forms like `if`, `lambda`, `define`, `quote`, etc.

Parameters:

  • etc: The expander-time continuation (expander state, carries context)
  • sym: The keyword symbol (e.g., 'if', 'lambda')
  • expr: The expression arguments (everything after the keyword)

Returns the expanded syntax value.

type ResolvedImportSet

type ResolvedImportSet struct {
	ImportSet *ImportSet
	Library   *CompiledLibrary
	Bindings  map[string]string // localName -> externalName
}

ResolvedImportSet holds the result of parsing and loading an import set. This is the shared prefix of all import processing: parse the import set datum, load the named library, and apply modifiers (only, except, prefix, rename) to produce the final binding map.

type SourcedError

type SourcedError struct {
	Source *syntax.SourceContext
	Cause  error
}

SourcedError wraps a compilation error with the source location where it occurred. The compiler tracks source context via pushSource/popSource; wrapCompilationError attaches the current source to errors so that callers (especially the public Engine API) can report file:line:col.

Use errors.As to extract the source from an error chain:

var se *compilation.SourcedError
if errors.As(err, &se) && se.Source != nil { ... }

func (*SourcedError) Error

func (p *SourcedError) Error() string

func (*SourcedError) SourceContext added in v1.20.0

func (p *SourcedError) SourceContext() *syntax.SourceContext

SourceContext returns the context this error was stamped with. It exists so that pkg/machine can recognise the type structurally: this package imports pkg/machine, so the concrete type cannot be named from that side of the edge, and an errors.As target there has to be an interface. Source is the same field and stays exported for the callers that already read it.

func (*SourcedError) Unwrap

func (p *SourcedError) Unwrap() error

type SyntaxCaseClause

type SyntaxCaseClause struct {
	Bytecode    []match.SyntaxCommand
	PatternVars values.StringSet
	// LiteralSyntax carries each pattern literal with the scopes it had at the
	// macro definition site, the same data SyntaxRulesClause.LiteralSyntax
	// carries. Without it match.go's hygiene block is gated off on this path
	// (it needs a non-nil literal map AND a non-nil matcher), so a syntax-case
	// pattern literal matched a use-site identifier that shadows it — R7RS
	// §4.3.2 requires the two to share a binding.
	LiteralSyntax map[string]*syntax.SyntaxSymbol
	// LiteralDefs pins each pattern literal to the binding it had in the macro
	// DEFINITION environment. It has to ride on the clause because this path
	// builds its matcher at RUNTIME, in the use site's environment — the one
	// place that cannot recompute a definition-site resolution. (The syntax-rules
	// path needs no such field: its matcher is built at definition time and
	// carries the pins directly.)
	LiteralDefs    map[string]match.LiteralPin
	EllipsisVars   map[int]values.StringSet
	EllipsisDepths map[int]int
}

SyntaxCaseClause wraps compiled pattern info for a syntax-case clause. Created by the compiler, consumed by OperationSyntaxCaseMatch at runtime.

func (*SyntaxCaseClause) EqualTo

func (p *SyntaxCaseClause) EqualTo(other values.Value) bool

func (*SyntaxCaseClause) IsVoid

func (p *SyntaxCaseClause) IsVoid() bool

func (*SyntaxCaseClause) SchemeString

func (p *SyntaxCaseClause) SchemeString() string

type SyntaxCompiler

type SyntaxCompiler struct {
	// contains filtered or unexported fields
}

SyntaxCompiler wraps a SyntaxCompilerFunc as a values.Value so it can be stored in the environment.

func LookupSyntaxCompiler

func LookupSyntaxCompiler(env *environment.EnvironmentFrame, sym *values.Symbol, scopes []*syntax.Scope) *SyntaxCompiler

LookupSyntaxCompiler looks up a syntax compiler by symbol, entering through the PhaseCompile view. Returns the SyntaxCompiler if found, or nil if the symbol does not name a syntax compiler. The compilers live in the ambient tier (see RegisterSyntaxCompilers), which that view's ranked probe reaches as T3, so a PhaseCompile shadow at T1 or T2 still takes precedence.

Naming PhaseCompile by constant is correct here, unlike a climb: it is a fixed registry coordinate rather than a rung of the macro tower, so it does not vary with the level of the frame asking. See environment.Phase.

This function handles hygiene by using scoped lookup - it will only match bindings whose scopes are a subset of the symbol's scopes.

func NewSyntaxCompiler

func NewSyntaxCompiler(name string, fn SyntaxCompilerFunc) *SyntaxCompiler

NewSyntaxCompiler creates a new syntax compiler.

func (*SyntaxCompiler) EqualTo

func (p *SyntaxCompiler) EqualTo(other values.Value) bool

EqualTo implements values.Value interface.

func (*SyntaxCompiler) IsVoid

func (p *SyntaxCompiler) IsVoid() bool

IsVoid returns false — named handlers are never void.

func (*SyntaxCompiler) Name

func (p *SyntaxCompiler) Name() string

Name returns the handler's name.

func (*SyntaxCompiler) SchemeString

func (p *SyntaxCompiler) SchemeString() string

SchemeString returns the Scheme representation: #<prefix:name>.

type SyntaxCompilerFunc

type SyntaxCompilerFunc func(ctc *CompileTimeContinuation, ctctx CompileTimeCallContext, expr syntax.SyntaxValue) error

SyntaxCompilerFunc is the type for compile-time special form handlers. These functions handle syntax-directed compilation of extension forms like `syntax-case`, `import`, `define-syntax`, `include`, etc.

Parameters:

  • ctc: The compile-time continuation (compiler state)
  • ctctx: The compile-time call context (tail position info, etc.)
  • expr: The expression arguments (everything after the keyword)

The function should emit operations via ctc.AppendOperations and return nil on success.

type SyntaxRulesClause

type SyntaxRulesClause struct {
	Template         syntax.SyntaxValue
	Bytecode         []match.SyntaxCommand
	Matcher          *match.SyntaxMatcher
	PatternVars      values.StringSet
	PatternVarSyntax map[string]*syntax.SyntaxSymbol
	EllipsisVars     map[int]values.StringSet
	FreeIds          map[string]*FreeIdResolution
	Ellipsis         string
	LiteralSyntax    map[string]*syntax.SyntaxSymbol
}

SyntaxRulesClause represents a single compiled pattern-template pair in a syntax-rules form. Created by the compiler, consumed by OperationSyntaxRulesTransform at runtime.

func ClausesFromClosure added in v1.19.0

func ClausesFromClosure(closure values.Value) []*SyntaxRulesClause

ClausesFromClosure recovers the syntax-rules clause set from a compiled transformer closure — the *ClausesWrapper stored as the closure's template literal by createTransformerClosure. Returns nil for a non-syntax-rules transformer (an ER-macro or lambda body carries no ClausesWrapper). A syntax-rules closure carries exactly one wrapper, so the first match is the whole set. Shared by pinTemplateSelfReferences and the bootstrap nil-pin census, so both read clauses through one extractor rather than parallel copies.

Source Files

Directories

Path Synopsis
Package resolver provides file resolution infrastructure for the Scheme compiler.
Package resolver provides file resolution infrastructure for the Scheme compiler.
Package sourceload provides file-finding and load-stack tracking for locating source files across virtual filesystems.
Package sourceload provides file-finding and load-stack tracking for locating source files across virtual filesystems.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL