Documentation
¶
Overview ¶
Package environment provides variable binding and scoping for the Scheme compiler.
The environment system manages the relationship between variables and values across:
- Lexical scoping via parent chain traversal
- Phase separation (Runtime, Expand, Compile)
- Hygienic macros via Flatt's "sets of scopes" model
- Per-instance syntax interning for consistent syntax identity
- R7RS library import/export mappings
Architecture ¶
Each Wile VM instance owns a Namespace that provides:
- Syntax interning (thread-safe, per-instance)
- Phase registry for accessing phase-specific environments
- The runtime EnvironmentFrame tree
EnvironmentFrame nodes form the lexical scope chain, each containing:
- Local bindings (LocalEnvironmentFrame) for lambda params and let-bound vars
- Global bindings (GlobalEnvironmentFrame) for top-level defines
- Parent pointer for enclosing scopes
Binding Lookup ¶
EnvironmentFrame.GetBinding performs two-phase lookup:
- Local phase: traverse local bindings up the parent chain
- Global phase: search global bindings up the parent chain
When scopes are provided, EnvironmentFrame.GetBinding adds hygiene awareness, using [Scope] sets to match identifiers according to Flatt's algorithm with maximal binding selection.
Index ¶
- Constants
- func ResolveFile(stack *LoadPathStack, path string, fallbackDirs []string) (string, error)
- type Binding
- func (p *Binding) BindingType() BindingType
- func (p *Binding) Copy() values.Value
- func (p *Binding) Doc() string
- func (p *Binding) EqualTo(o values.Value) bool
- func (p *Binding) IsVoid() bool
- func (p *Binding) SchemeString() string
- func (p *Binding) Scopes() []*syntax.Scope
- func (p *Binding) SetBindingType(value BindingType)
- func (p *Binding) SetDoc(doc string)
- func (p *Binding) SetScopes(scopes []*syntax.Scope)
- func (p *Binding) SetSource(source *syntax.SourceContext)
- func (p *Binding) SetValue(value values.Value)
- func (p *Binding) Source() *syntax.SourceContext
- func (p *Binding) Value() values.Value
- type BindingID
- type BindingMeta
- type BindingType
- type EnvironmentFrame
- func (p *EnvironmentFrame) AtPhase(phase int) *EnvironmentFrame
- func (p *EnvironmentFrame) Compile() *EnvironmentFrame
- func (p *EnvironmentFrame) Copy() *EnvironmentFrame
- func (p *EnvironmentFrame) EnsureLocalBinding(key *values.Symbol, bt BindingType) (*LocalIndex, bool)
- func (p *EnvironmentFrame) EqualTo(value values.Value) bool
- func (p *EnvironmentFrame) Expand() *EnvironmentFrame
- func (p *EnvironmentFrame) FileResolver() FileResolver
- func (p *EnvironmentFrame) GetBinding(key *values.Symbol, scopes []*syntax.Scope) *Binding
- func (p *EnvironmentFrame) GetGlobalBinding(key *GlobalIndex) *Binding
- func (p *EnvironmentFrame) GetGlobalIndex(key *values.Symbol) *GlobalIndex
- func (p *EnvironmentFrame) GetGlobalIndexAcrossPhases(key *values.Symbol) *GlobalIndex
- func (p *EnvironmentFrame) GetGlobalIndexFromLibraryScopes(key *values.Symbol, scopes []*syntax.Scope) *GlobalIndex
- func (p *EnvironmentFrame) GetLocalBinding(li *LocalIndex) *Binding
- func (p *EnvironmentFrame) GetLocalBindingByIndex(i int) *Binding
- func (p *EnvironmentFrame) GetLocalBindingBySlotDepth(slot, depth int) *Binding
- func (p *EnvironmentFrame) GetLocalIndex(key *values.Symbol, scopes []*syntax.Scope) *LocalIndex
- func (p *EnvironmentFrame) GlobalEnvironment() *GlobalEnvironmentFrame
- func (p *EnvironmentFrame) HasLocalVariableBinding(sym *values.Symbol, scopes []*syntax.Scope) bool
- func (p *EnvironmentFrame) InitApplyFrame(dst *EnvironmentFrame)
- func (p *EnvironmentFrame) IsTopLevel() bool
- func (p *EnvironmentFrame) IsVoid() bool
- func (p *EnvironmentFrame) LibraryRegistry() LibrarySearcher
- func (p *EnvironmentFrame) LoadPathStack() *LoadPathStack
- func (p *EnvironmentFrame) LocalBindingsSlice() []Binding
- func (p *EnvironmentFrame) LocalEnvironment() *LocalEnvironmentFrame
- func (p *EnvironmentFrame) MaybeCreateLocalBinding(key *values.Symbol, bt BindingType, scopes []*syntax.Scope, ...) (*LocalIndex, bool)
- func (p *EnvironmentFrame) MaybeCreateOwnGlobalBinding(key *values.Symbol, bt BindingType) (*GlobalIndex, bool)
- func (p *EnvironmentFrame) Namespace() *Namespace
- func (p *EnvironmentFrame) NewApplyFrame() *EnvironmentFrame
- func (p *EnvironmentFrame) Parent() *EnvironmentFrame
- func (p *EnvironmentFrame) PhaseLevel() int
- func (p *EnvironmentFrame) PreAllocateBindings(n int)
- func (p *EnvironmentFrame) ResetForPool()
- func (p *EnvironmentFrame) ResolveBindingID(key *values.Symbol, scopes []*syntax.Scope) (BindingID, bool)
- func (p *EnvironmentFrame) Runtime() *EnvironmentFrame
- func (p *EnvironmentFrame) SchemeString() string
- func (p *EnvironmentFrame) SetFileResolver(resolver FileResolver)
- func (p *EnvironmentFrame) SetGlobalBindingByIndex(i int, bd *Binding)
- func (p *EnvironmentFrame) SetLibraryRegistry(registry LibrarySearcher)
- func (p *EnvironmentFrame) SetLocalValue(li *LocalIndex, v values.Value) error
- func (p *EnvironmentFrame) SetLocalValueBySlotDepth(slot, depth int, v values.Value) error
- func (p *EnvironmentFrame) SetOwnGlobalValue(gi *GlobalIndex, v values.Value) error
- func (p *EnvironmentFrame) TopLevel() *EnvironmentFrame
- type FileResolver
- type GlobalEnvironmentFrame
- func (p *GlobalEnvironmentFrame) Bindings() []*Binding
- func (p *GlobalEnvironmentFrame) Copy() *GlobalEnvironmentFrame
- func (p *GlobalEnvironmentFrame) CreateGlobalBinding(key *values.Symbol, bt BindingType) (*GlobalIndex, bool)
- func (p *GlobalEnvironmentFrame) DeleteBinding(sym *values.Symbol) bool
- func (p *GlobalEnvironmentFrame) EqualTo(o values.Value) bool
- func (p *GlobalEnvironmentFrame) GetGlobalIndex(key *values.Symbol) *GlobalIndex
- func (p *GlobalEnvironmentFrame) GetOwnGlobalBinding(gi *GlobalIndex) *Binding
- func (p *GlobalEnvironmentFrame) IsVoid() bool
- func (p *GlobalEnvironmentFrame) Keys() map[values.Symbol]int
- func (p *GlobalEnvironmentFrame) SchemeString() string
- func (p *GlobalEnvironmentFrame) SetBindings(vs []*Binding)
- func (p *GlobalEnvironmentFrame) SetOwnGlobalValue(gi *GlobalIndex, v values.Value) error
- type GlobalIndex
- type LibraryEnvFactory
- type LibrarySearcher
- type LoadPathStack
- type LocalEnvironmentFrame
- func (p *LocalEnvironmentFrame) Bindings() []Binding
- func (p *LocalEnvironmentFrame) Copy() values.Value
- func (p *LocalEnvironmentFrame) CopyForApply() *LocalEnvironmentFrame
- func (p *LocalEnvironmentFrame) EnsureLocalBinding(key *values.Symbol, bt BindingType) (*LocalIndex, bool)
- func (p *LocalEnvironmentFrame) EqualTo(o values.Value) bool
- func (p *LocalEnvironmentFrame) GetLocalBinding(li *LocalIndex) *Binding
- func (p *LocalEnvironmentFrame) GetLocalIndex(key *values.Symbol) *LocalIndex
- func (p *LocalEnvironmentFrame) IsVoid() bool
- func (p *LocalEnvironmentFrame) Keys() map[values.Symbol][]int
- func (p *LocalEnvironmentFrame) MaybeCreateLocalBinding(key *values.Symbol, bt BindingType, scopes []*syntax.Scope, ...) (*LocalIndex, bool)
- func (p *LocalEnvironmentFrame) SchemeString() string
- func (p *LocalEnvironmentFrame) SetBindings(v []Binding)
- func (p *LocalEnvironmentFrame) SetLocalValue(li *LocalIndex, v values.Value) error
- type LocalIndex
- type ModuleInstance
- type Namespace
- func (p *Namespace) AtPhase(phase int) *EnvironmentFrame
- func (p *Namespace) AttachModule(path string, target *Namespace) error
- func (p *Namespace) Authorizer() security.Authorizer
- func (p *Namespace) Compile() *EnvironmentFrame
- func (p *Namespace) Derive() *Namespace
- func (p *Namespace) DeriveWith(opts ...NamespaceDeriveOption) *Namespace
- func (p *Namespace) EqualTo(v values.Value) bool
- func (p *Namespace) Expand() *EnvironmentFrame
- func (p *Namespace) FileResolver() FileResolver
- func (p *Namespace) InternSyntax(k values.Value, v syntax.SyntaxValue) syntax.SyntaxValue
- func (p *Namespace) IsVoid() bool
- func (p *Namespace) LibraryEnvFactory() LibraryEnvFactory
- func (p *Namespace) LibraryRegistry() LibrarySearcher
- func (p *Namespace) LoadPathStack() *LoadPathStack
- func (p *Namespace) LookupLibraryEnv(scope *syntax.Scope) *EnvironmentFrame
- func (p *Namespace) ModuleInstance(path string) (*ModuleInstance, bool)
- func (p *Namespace) NewChildNamespace() *Namespace
- func (p *Namespace) NewChildRuntime() *EnvironmentFrame
- func (p *Namespace) NewSchemeReportNamespace() *Namespace
- func (p *Namespace) Phases() *PhaseRegistry
- func (p *Namespace) RegisterLibraryScope(scope *syntax.Scope, env *EnvironmentFrame)
- func (p *Namespace) Registry() any
- func (p *Namespace) Runtime() *EnvironmentFrame
- func (p *Namespace) SchemeString() string
- func (p *Namespace) SetAuthorizer(auth security.Authorizer)
- func (p *Namespace) SetFileResolver(resolver FileResolver)
- func (p *Namespace) SetLibraryEnvFactory(f LibraryEnvFactory)
- func (p *Namespace) SetLibraryRegistry(registry LibrarySearcher)
- func (p *Namespace) SetModuleInstance(path string, inst *ModuleInstance)
- func (p *Namespace) SetRegistry(reg any)
- func (p *Namespace) SyntaxInternCount() int
- type NamespaceDeriveConfig
- type NamespaceDeriveOption
- type PhaseRegistry
Constants ¶
const ( // BindingTypeUnknown indicates a binding with unknown or uninitialized type. BindingTypeUnknown = BindingType(iota) // BindingTypeVariable indicates a regular variable binding (from define, let, lambda parameters). BindingTypeVariable // BindingTypeSyntax indicates a syntax transformer binding (from define-syntax). // These bindings live in the expand phase environment. BindingTypeSyntax // BindingTypePrimitive indicates a compile-time binding (special forms, auxiliary syntax). BindingTypePrimitive )
const ( PhaseTemplate = -1 // for-template (template instantiation) PhaseRuntime = 0 // Runtime execution (phase 0) PhaseExpand = 1 // Macro expansion (for-syntax, phase 1) PhaseCompile = 2 // Compile-time (for-meta 2, phase 2) )
Phase level constants for standard Scheme phases. These match Racket's phase numbering convention.
Variables ¶
This section is empty.
Functions ¶
func ResolveFile ¶ added in v1.3.0
func ResolveFile(stack *LoadPathStack, path string, fallbackDirs []string) (string, error)
ResolveFile finds a file by trying resolution strategies in order:
- If path is absolute, use as-is
- If stack has a current directory, try relative to it
- Try each fallback directory in order
Returns the absolute path of the first match, or an error listing all searched paths.
All returned paths are guaranteed to be absolute. Symlinks in paths are preserved (not resolved to their target). For example, if /app/lib is a symlink to /usr/local/lib, resolving "foo.scm" from /app/lib/ will return /app/lib/foo.scm, not /usr/local/lib/foo.scm.
Types ¶
type Binding ¶
type Binding struct {
// contains filtered or unexported fields
}
Binding represents a variable binding in the environment. It stores the bound value, the binding type (variable, syntax, or primitive), and an optional pointer to compile-time metadata (scopes, source location).
func NewBinding ¶
func NewBinding(value values.Value, bindingType BindingType) *Binding
NewBinding creates a new binding with the given value and type. The binding has no scopes (for backward compatibility with non-hygienic code).
func NewBindingWithScopes ¶
func NewBindingWithScopes(value values.Value, bindingType BindingType, scopes []*syntax.Scope) *Binding
NewBindingWithScopes creates a binding with associated scopes (for hygiene)
func NewBindingWithSource ¶
func NewBindingWithSource(value values.Value, bindingType BindingType, scopes []*syntax.Scope, source *syntax.SourceContext) *Binding
NewBindingWithSource creates a binding with source location information.
func (*Binding) BindingType ¶
func (p *Binding) BindingType() BindingType
BindingType returns the type of this binding (variable, syntax, or primitive).
func (*Binding) Copy ¶
Copy creates a deep copy of this binding. The meta struct is copied so that SetScopes on the original does not affect the copy. This method is only used during compilation/expansion, never on the runtime hot path.
func (*Binding) Doc ¶ added in v1.10.3
Doc returns the documentation string for this binding. Returns empty string for bindings without documentation.
func (*Binding) EqualTo ¶
EqualTo returns true if this binding is equal to the given value. Two bindings are equal if they have the same value and binding type.
func (*Binding) SchemeString ¶
SchemeString returns a string representation of this binding.
func (*Binding) Scopes ¶
Scopes returns the hygiene scopes associated with this binding. Returns nil for bindings without hygiene information.
func (*Binding) SetBindingType ¶
func (p *Binding) SetBindingType(value BindingType)
SetBindingType updates the type of this binding.
func (*Binding) SetSource ¶
func (p *Binding) SetSource(source *syntax.SourceContext)
SetSource updates the source location for this binding.
func (*Binding) Source ¶
func (p *Binding) Source() *syntax.SourceContext
Source returns the source location where this binding was defined. Returns nil for bindings without source information.
type BindingID ¶ added in v1.9.4
type BindingID struct {
Frame *LocalEnvironmentFrame
Slot int
}
BindingID is a stable identifier for a local binding, safe to use as a map key even when the backing []Binding slice is reallocated by append. The Frame pointer identifies the heap-allocated LocalEnvironmentFrame (stable across slice growth) and Slot is the index within that frame.
This is the local-binding analog of GlobalIndex: GlobalIndex uses (*GlobalEnvironmentFrame, *Symbol), BindingID uses (*LocalEnvironmentFrame, int).
type BindingMeta ¶ added in v1.5.0
type BindingMeta struct {
Scopes []*syntax.Scope
Source *syntax.SourceContext
Doc string
}
BindingMeta holds compile-time metadata (scopes and source location) that is never read during VM execution. Stored behind a pointer so that runtime Binding copies (the hot path) move 32 bytes instead of 56.
type BindingType ¶
type BindingType int
BindingType represents the type of a binding in the environment.
type EnvironmentFrame ¶
type EnvironmentFrame struct {
// contains filtered or unexported fields
}
EnvironmentFrame represents an environment frame in the hierarchy.
Type Relationships ¶
The environment system has four types with distinct responsibilities:
┌─────────────────────────────────────────────────────────────────────────┐
│ Namespace │
│ (Per-VM instance: owns syntax interning, phases, libraries) │
│ │
│ syntaxInterns ──── map[Value]SyntaxValue (thread-safe) │
│ phases ─────────── *PhaseRegistry │
│ libraryRegistry ── LibrarySearcher (*compilation.LibraryRegistry) │
│ runtime ────────── *EnvironmentFrame (phase 0) │
└─────────────────────────────────────────────────────────────────────────┘
│
│ owns
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ EnvironmentFrame │
│ (Lexical scope node: links local/global bindings, parent chain) │
│ │
│ parent ─────────── *EnvironmentFrame (lexical parent, nil at top) │
│ local ──────────── LocalEnvironmentFrame (value; keys==nil → none) │
│ global ─────────── *GlobalEnvironmentFrame (define bindings) │
│ phaseLevel ─────── int (0=runtime, 1=expand, 2=compile) │
│ phases ─────────── *PhaseRegistry (shared reference) │
│ namespace ───────── *Namespace (back-reference) │
└─────────────────────────────────────────────────────────────────────────┘
│ │
│ contains │ contains
▼ ▼
┌───────────────────────────┐ ┌────────────────────────────────────────┐
│ LocalEnvironmentFrame │ │ GlobalEnvironmentFrame │
│ (Single scope bindings) │ │ (Phase-wide global bindings) │
│ │ │ │
│ keys ── map[Symbol][]int │ │ keys ──────── map[Symbol]int │
│ bindings ── []*Binding │ │ bindings ──── []*Binding │
└───────────────────────────┘ │ namespace ──── *Namespace │
└────────────────────────────────────────┘
Ownership and Sharing ¶
- Namespace: Root owner. One per Wile VM instance.
- EnvironmentFrame: Many per VM. Share namespace and phases references.
- GlobalEnvironmentFrame: One per phase. Shares namespace reference.
- LocalEnvironmentFrame: One per lexical scope. No external references.
Lexical Hierarchy (parent chain) ¶
(lambda (x) ; EnvironmentFrame A: local={x}, parent=TopLevel
(let ((y 1)) ; EnvironmentFrame B: local={y}, parent=A
(lambda (z) ; EnvironmentFrame C: local={z}, parent=B
(+ x y z))))
Phase Hierarchy (via PhaseRegistry) ¶
Namespace
└── PhaseRegistry
├── [0] Runtime EnvironmentFrame (normal execution)
├── [1] Expand EnvironmentFrame (macro expansion, for-syntax)
├── [2] Compile EnvironmentFrame (syntax compilers, for-meta 2)
└── [-1] Template EnvironmentFrame (for-template, future)
Each phase has its own GlobalEnvironmentFrame but shares the same Namespace for syntax interning.
Binding Lookup ¶
Two-phase search: first all locals up parent chain, then globals.
func NewEnvironmentFrameWithParent ¶
func NewEnvironmentFrameWithParent(local *LocalEnvironmentFrame, parent *EnvironmentFrame) *EnvironmentFrame
NewEnvironmentFrameWithParent creates a new environment frame with the given local environment frame and parent environment frame. The global environment frame is inherited from the parent. This is used for creating child frames within a phase (e.g., lambda bodies, let-syntax). The phase level, registry, and namespace are inherited from the parent. Panics if parent is nil - use NewNamespaceFrame() instead.
func NewNamespaceFrame
deprecated
added in
v1.7.0
func NewNamespaceFrame() *EnvironmentFrame
NewNamespaceFrame creates a new root environment frame via NewNamespace.
Deprecated: Use NewNamespace().Runtime() instead for per-instance syntax interning. This function now internally uses NewNamespace() to provide proper isolation.
func (*EnvironmentFrame) AtPhase ¶
func (p *EnvironmentFrame) AtPhase(phase int) *EnvironmentFrame
AtPhase returns the environment for the given phase level, creating it if needed. Phase 0 is runtime, phase 1 is expansion (for-syntax), phase 2 is compile-time, etc. Negative phases (e.g., -1 for for-template) are also supported.
This is the primary method for cross-phase access with O(1) lookup time. The environment must have been created via NewNamespace().
func (*EnvironmentFrame) Compile ¶
func (p *EnvironmentFrame) Compile() *EnvironmentFrame
Compile returns the compile phase environment (phase 2), creating it if needed. This is where compile-time procedures (syntax compilers) are stored.
func (*EnvironmentFrame) Copy ¶
func (p *EnvironmentFrame) Copy() *EnvironmentFrame
Copy creates a deep copy of the environment frame. The parent, phase registry, and namespace are shared between the original and the copy.
func (*EnvironmentFrame) EnsureLocalBinding ¶ added in v1.2.0
func (p *EnvironmentFrame) EnsureLocalBinding(key *values.Symbol, bt BindingType) (*LocalIndex, bool)
EnsureLocalBinding returns the local binding for the given key, creating it if it does not already exist. Returns (index, true) if a new binding was created, or (index, false) if the binding already existed. Returns (nil, false) if the receiver is nil or has no local environment.
func (*EnvironmentFrame) EqualTo ¶
func (p *EnvironmentFrame) EqualTo(value values.Value) bool
EqualTo returns true if the environment frame is equal to the given value. Two environment frames are equal if their local and global environments are equal, and their parent environments are either both nil or equal.
func (*EnvironmentFrame) Expand ¶
func (p *EnvironmentFrame) Expand() *EnvironmentFrame
Expand returns the expand phase environment (phase 1), creating it if needed. This is where syntax bindings from define-syntax are stored.
func (*EnvironmentFrame) FileResolver ¶ added in v1.6.0
func (p *EnvironmentFrame) FileResolver() FileResolver
FileResolver returns the file resolver from the Namespace. Returns nil if no resolver has been set or if namespace is nil.
func (*EnvironmentFrame) GetBinding ¶
GetBinding returns the binding for the given symbol that matches the provided scopes. It searches local bindings first (walking up the parent chain), then globals.
Nil scopes means "match any" (no scope filtering). Non-nil scopes enables hygienic resolution per Flatt's model with maximal binding selection (consistent with GetLocalIndex).
func (*EnvironmentFrame) GetGlobalBinding ¶
func (p *EnvironmentFrame) GetGlobalBinding(key *GlobalIndex) *Binding
GetGlobalBinding returns the binding for the given GlobalIndex, searching global bindings in the current and parent environments. It returns nil if the binding does not exist.
func (*EnvironmentFrame) GetGlobalIndex ¶
func (p *EnvironmentFrame) GetGlobalIndex(key *values.Symbol) *GlobalIndex
GetGlobalIndex returns the GlobalIndex of the binding for the given symbol, searching global bindings in the current and parent environments. It returns nil if the binding does not exist.
The returned GlobalIndex records the specific global frame where the binding was found, enabling cross-library macro hygiene (see GlobalIndex.Env).
func (*EnvironmentFrame) GetGlobalIndexAcrossPhases ¶ added in v1.6.0
func (p *EnvironmentFrame) GetGlobalIndexAcrossPhases(key *values.Symbol) *GlobalIndex
GetGlobalIndexAcrossPhases searches for a global binding across phases (runtime → expand → compile) using read-only phase access. Returns the first GlobalIndex found, or nil if not found in any phase.
This is used during macro compilation to resolve free identifiers that may be defined in any phase (e.g., define in runtime, define-syntax in expand).
func (*EnvironmentFrame) GetGlobalIndexFromLibraryScopes ¶ added in v1.6.0
func (p *EnvironmentFrame) GetGlobalIndexFromLibraryScopes(key *values.Symbol, scopes []*syntax.Scope) *GlobalIndex
GetGlobalIndexFromLibraryScopes searches for a binding by checking each scope against the TLE's scope registry. For each scope that maps to a library env, performs a cross-phase lookup in that library's env. Returns the first match, or nil if no library binding is found.
func (*EnvironmentFrame) GetLocalBinding ¶
func (p *EnvironmentFrame) GetLocalBinding(li *LocalIndex) *Binding
GetLocalBinding returns the binding for the given LocalIndex. It returns nil if the binding does not exist.
func (*EnvironmentFrame) GetLocalBindingByIndex ¶
func (p *EnvironmentFrame) GetLocalBindingByIndex(i int) *Binding
GetLocalBindingByIndex returns the local binding at the given index in the current local environment. It does not search parent environments. It returns nil if the binding does not exist.
func (*EnvironmentFrame) GetLocalBindingBySlotDepth ¶ added in v1.4.0
func (p *EnvironmentFrame) GetLocalBindingBySlotDepth(slot, depth int) *Binding
GetLocalBindingBySlotDepth returns the binding at the given slot and depth without requiring a *LocalIndex allocation. This is the hot-path variant used by the VM's OpLoadLocal dispatch.
func (*EnvironmentFrame) GetLocalIndex ¶
func (p *EnvironmentFrame) GetLocalIndex(key *values.Symbol, scopes []*syntax.Scope) *LocalIndex
GetLocalIndex returns the LocalIndex of the binding for the given symbol that matches the given scopes. Nil scopes means "match any".
When scopes are provided, this implements Flatt's "maximal" binding resolution: among all bindings whose scopes are a subset of the reference's scopes, the one with the LARGEST scope set is returned.
Returns nil if no matching local binding exists.
func (*EnvironmentFrame) GlobalEnvironment ¶
func (p *EnvironmentFrame) GlobalEnvironment() *GlobalEnvironmentFrame
GlobalEnvironment returns the global environment frame.
func (*EnvironmentFrame) HasLocalVariableBinding ¶ added in v1.5.0
HasLocalVariableBinding reports whether sym has a local variable binding compatible with the given scopes. This is the shared implementation used by both the macro expander (to decide whether a local variable shadows a macro) and the validator (to decide whether a local variable shadows a special form).
The check implements Flatt's hygiene rule: a binding matches a reference when bindingScopes ⊆ useScopes. Bindings with no scopes (user code) match any use. Only BindingTypeVariable bindings are considered; syntax/primitive bindings do not shadow.
func (*EnvironmentFrame) InitApplyFrame ¶ added in v1.5.0
func (p *EnvironmentFrame) InitApplyFrame(dst *EnvironmentFrame)
InitApplyFrame populates dst from p's closure environment without allocating a new EnvironmentFrame. The caller is responsible for providing dst (e.g. from a pool). This is the pooling-friendly counterpart of NewApplyFrame.
func (*EnvironmentFrame) IsTopLevel ¶
func (p *EnvironmentFrame) IsTopLevel() bool
IsTopLevel returns true if this is the top-level environment frame (no parent).
func (*EnvironmentFrame) IsVoid ¶
func (p *EnvironmentFrame) IsVoid() bool
IsVoid returns true if the environment frame is nil.
func (*EnvironmentFrame) LibraryRegistry ¶
func (p *EnvironmentFrame) LibraryRegistry() LibrarySearcher
LibraryRegistry returns the library registry from the Namespace. Returns nil if no registry has been set or if namespace is nil. Callers needing the full *compilation.LibraryRegistry can type-assert.
func (*EnvironmentFrame) LoadPathStack ¶ added in v1.3.0
func (p *EnvironmentFrame) LoadPathStack() *LoadPathStack
LoadPathStack returns the load path stack for tracking files currently being loaded, or nil if this frame has no Namespace.
func (*EnvironmentFrame) LocalBindingsSlice ¶ added in v1.9.1
func (p *EnvironmentFrame) LocalBindingsSlice() []Binding
LocalBindingsSlice returns the raw local bindings slice, bypassing the nil-keys check in LocalEnvironment(). This exposes the pre-allocated capacity that pooled frames retain across reset cycles.
func (*EnvironmentFrame) LocalEnvironment ¶
func (p *EnvironmentFrame) LocalEnvironment() *LocalEnvironmentFrame
LocalEnvironment returns the local environment frame, or nil if none.
func (*EnvironmentFrame) MaybeCreateLocalBinding ¶
func (p *EnvironmentFrame) MaybeCreateLocalBinding( key *values.Symbol, bt BindingType, scopes []*syntax.Scope, source *syntax.SourceContext, ) (*LocalIndex, bool)
MaybeCreateLocalBinding creates a local binding with scope-aware deduplication. Two bindings with the same key but incompatible scopes get separate slots; compatible scopes reuse the existing slot.
Nil scopes means "match any" during dedup (pre-hygiene callers). Returns (index, true) if created, (index, false) if already existed.
func (*EnvironmentFrame) MaybeCreateOwnGlobalBinding ¶
func (p *EnvironmentFrame) MaybeCreateOwnGlobalBinding(key *values.Symbol, bt BindingType) (*GlobalIndex, bool)
MaybeCreateOwnGlobalBinding creates a new global binding in the current global environment if it does not already exist. The key is interned before use (consistent with GlobalEnvironmentFrame.CreateGlobalBinding). It returns the GlobalIndex of the binding and a boolean indicating whether the binding was created (true) or already existed (false).
func (*EnvironmentFrame) Namespace ¶ added in v1.7.0
func (p *EnvironmentFrame) Namespace() *Namespace
Namespace returns the Namespace for this frame.
func (*EnvironmentFrame) NewApplyFrame ¶ added in v1.4.0
func (p *EnvironmentFrame) NewApplyFrame() *EnvironmentFrame
NewApplyFrame creates a new EnvironmentFrame for a closure application, fusing CopyForApply + NewEnvironmentFrameWithParent into one allocation. The source frame's local bindings are copied into the new frame, and the parent chain is set from the source's parent.
func (*EnvironmentFrame) Parent ¶
func (p *EnvironmentFrame) Parent() *EnvironmentFrame
Parent returns the parent environment frame.
func (*EnvironmentFrame) PhaseLevel ¶
func (p *EnvironmentFrame) PhaseLevel() int
PhaseLevel returns the phase level of this environment frame.
func (*EnvironmentFrame) PreAllocateBindings ¶ added in v1.9.1
func (p *EnvironmentFrame) PreAllocateBindings(n int)
PreAllocateBindings sets the local bindings slice to a zero-length slice with the given capacity. Used by the env frame pool to ensure fresh frames have sufficient capacity for copyForApplyInto to reslice instead of allocate. Must only be called on freshly constructed frames (before any other use). n must be non-negative; negative values are clamped to 0.
func (*EnvironmentFrame) ResetForPool ¶ added in v1.5.0
func (p *EnvironmentFrame) ResetForPool()
ResetForPool clears the EnvironmentFrame for return to the FreeList while preserving the local bindings backing array capacity. This mirrors the Stack pool pattern: clear full capacity (so GC can collect referenced values), zero the struct, then restore the slice header with len=0.
After reset, the frame is a valid zero-value EnvironmentFrame whose local.bindings has cap > 0 but len == 0. The next copyForApplyInto call will reslice instead of allocating when cap >= n.
func (*EnvironmentFrame) ResolveBindingID ¶ added in v1.9.4
func (p *EnvironmentFrame) ResolveBindingID(key *values.Symbol, scopes []*syntax.Scope) (BindingID, bool)
ResolveBindingID looks up a local binding by symbol and scopes and returns a stable BindingID. Returns the zero BindingID and false if the symbol does not resolve to a local binding.
func (*EnvironmentFrame) Runtime ¶
func (p *EnvironmentFrame) Runtime() *EnvironmentFrame
Runtime returns the runtime phase environment (phase 0). This is the root environment where normal bindings live.
func (*EnvironmentFrame) SchemeString ¶
func (p *EnvironmentFrame) SchemeString() string
SchemeString returns a string representation of the environment frame.
func (*EnvironmentFrame) SetFileResolver ¶ added in v1.6.0
func (p *EnvironmentFrame) SetFileResolver(resolver FileResolver)
SetFileResolver sets the file resolver on the Namespace. No-op if namespace is nil.
func (*EnvironmentFrame) SetGlobalBindingByIndex ¶
func (p *EnvironmentFrame) SetGlobalBindingByIndex(i int, bd *Binding)
SetGlobalBindingByIndex sets the global binding at the given index in the current global environment. It does not search parent environments. Thread-safe: uses full Lock for write access.
func (*EnvironmentFrame) SetLibraryRegistry ¶
func (p *EnvironmentFrame) SetLibraryRegistry(registry LibrarySearcher)
SetLibraryRegistry sets the library registry on the Namespace. No-op if namespace is nil.
func (*EnvironmentFrame) SetLocalValue ¶
func (p *EnvironmentFrame) SetLocalValue(li *LocalIndex, v values.Value) error
SetLocalValue sets the value of the binding for the given LocalIndex. It returns an error if the binding does not exist.
func (*EnvironmentFrame) SetLocalValueBySlotDepth ¶ added in v1.4.0
func (p *EnvironmentFrame) SetLocalValueBySlotDepth(slot, depth int, v values.Value) error
SetLocalValueBySlotDepth sets the value of the binding at the given slot and depth without requiring a *LocalIndex allocation. This is the hot-path variant used by the VM's OpStoreLocal dispatch.
func (*EnvironmentFrame) SetOwnGlobalValue ¶
func (p *EnvironmentFrame) SetOwnGlobalValue(gi *GlobalIndex, v values.Value) error
SetOwnGlobalValue sets the value of the binding for the given GlobalIndex. It returns an error if the binding does not exist.
func (*EnvironmentFrame) TopLevel ¶
func (p *EnvironmentFrame) TopLevel() *EnvironmentFrame
TopLevel returns the top-level environment frame in the hierarchy.
type FileResolver ¶ added in v1.10.5
type FileResolver interface {
// ResolveAndOpen finds a file by name and returns an open handle plus
// the resolved path (used for load-path-stack tracking and error messages).
ResolveAndOpen(ctx context.Context, path string) (fs.File, string, error)
}
FileResolver resolves and opens files for include/load operations. Implementations control where files are found: the OS filesystem, an embedded filesystem, or any other fs.FS.
The concrete implementations (OSFileResolver, FSFileResolver, EmbedFileResolver, ChainFileResolver) live in machine/compilation/. This interface is defined here so environment/ can store it without creating a circular import.
type GlobalEnvironmentFrame ¶
type GlobalEnvironmentFrame struct {
// contains filtered or unexported fields
}
GlobalEnvironmentFrame represents global bindings for a single phase.
Design: GlobalEnvironmentFrame has no hierarchy of its own. The environment hierarchy is managed by EnvironmentFrame via its parent field. Each phase (runtime, expand, compile) has its own GlobalEnvironmentFrame.
Note: Symbol and syntax interning are delegated to Namespace, ensuring R7RS symbol identity works correctly across all phases.
Thread safety: All access to keys and bindings is protected by mu. Fixes T2 from architectural review.
func NewGlobalEnvironmentFrame ¶
func NewGlobalEnvironmentFrame() *GlobalEnvironmentFrame
NewGlobalEnvironmentFrame creates a new global environment frame.
func (*GlobalEnvironmentFrame) Bindings ¶
func (p *GlobalEnvironmentFrame) Bindings() []*Binding
Bindings returns a copy of the bindings slice. Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) Copy ¶
func (p *GlobalEnvironmentFrame) Copy() *GlobalEnvironmentFrame
Copy creates a deep copy of the global environment frame. Note that namespace is shared (not copied) between original and copy. Bindings are batch-allocated (contiguous array) for cache locality and reduced GC pressure. Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) CreateGlobalBinding ¶
func (p *GlobalEnvironmentFrame) CreateGlobalBinding(key *values.Symbol, bt BindingType) (*GlobalIndex, bool)
CreateGlobalBinding creates a new global binding with the given key and type. Returns the GlobalIndex and whether a new binding was created (false if the binding already existed). Thread-safe: uses full Lock to prevent TOCTOU races.
func (*GlobalEnvironmentFrame) DeleteBinding ¶ added in v1.7.0
func (p *GlobalEnvironmentFrame) DeleteBinding(sym *values.Symbol) bool
DeleteBinding removes a global binding by symbol key. Returns true if the binding was found and removed, false if not found.
Note: the binding slot in p.bindings is not compacted — index-based references from compiled code would be stale. This is only safe for top-level REPL/eval bindings, not for bindings referenced by compiled bytecode.
Thread-safe: uses full Lock for write access.
func (*GlobalEnvironmentFrame) EqualTo ¶
func (p *GlobalEnvironmentFrame) EqualTo(o values.Value) bool
EqualTo returns true if this global environment equals the given value. Two global environments are equal if they have the same bindings. Thread-safe: uses RLock for read-only access on both frames.
func (*GlobalEnvironmentFrame) GetGlobalIndex ¶
func (p *GlobalEnvironmentFrame) GetGlobalIndex(key *values.Symbol) *GlobalIndex
GetGlobalIndex returns the GlobalIndex for the given symbol. Returns nil if the symbol is not bound in this global environment. Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) GetOwnGlobalBinding ¶
func (p *GlobalEnvironmentFrame) GetOwnGlobalBinding(gi *GlobalIndex) *Binding
GetOwnGlobalBinding returns the binding for the given GlobalIndex from this frame only. Unlike EnvironmentFrame.GetGlobalBinding, this does NOT traverse the parent chain. Returns nil if the binding does not exist in this frame. Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) IsVoid ¶
func (p *GlobalEnvironmentFrame) IsVoid() bool
IsVoid returns true if this global environment frame is nil.
func (*GlobalEnvironmentFrame) Keys ¶
func (p *GlobalEnvironmentFrame) Keys() map[values.Symbol]int
Keys returns a copy of the symbol-to-index mapping. Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) SchemeString ¶
func (p *GlobalEnvironmentFrame) SchemeString() string
SchemeString returns a string representation of this global environment.
func (*GlobalEnvironmentFrame) SetBindings ¶
func (p *GlobalEnvironmentFrame) SetBindings(vs []*Binding)
SetBindings replaces the bindings slice in this global environment. Thread-safe: uses full Lock for write access.
func (*GlobalEnvironmentFrame) SetOwnGlobalValue ¶
func (p *GlobalEnvironmentFrame) SetOwnGlobalValue(gi *GlobalIndex, v values.Value) error
SetOwnGlobalValue sets the value of the binding for the given GlobalIndex. Returns an error if the binding does not exist. Thread-safe: uses full Lock for write access.
type GlobalIndex ¶
type GlobalIndex struct {
Index *values.Symbol
Env *GlobalEnvironmentFrame
}
GlobalIndex identifies a global binding by its symbol key. Unlike LocalIndex which uses numeric indices, GlobalIndex uses the symbol directly since global bindings are accessed by name at runtime.
Env records the definition-site global frame for cross-library macro hygiene. When a macro references a non-exported helper from its defining library, Env ensures the VM resolves the binding in the library's environment rather than the use-site environment. Nil means "use the current environment" (backward compatible default).
func NewGlobalIndex ¶
func NewGlobalIndex(key *values.Symbol) *GlobalIndex
NewGlobalIndex creates a new GlobalIndex for the given symbol.
func (*GlobalIndex) EqualTo ¶
func (p *GlobalIndex) EqualTo(value values.Value) bool
EqualTo returns true if this global index equals the given value.
func (*GlobalIndex) IsVoid ¶
func (p *GlobalIndex) IsVoid() bool
IsVoid returns true if this global index is nil.
func (*GlobalIndex) SchemeString ¶
func (p *GlobalIndex) SchemeString() string
SchemeString returns a string representation of this global index.
type LibraryEnvFactory ¶ added in v1.4.0
type LibraryEnvFactory func(ctx context.Context, callerEnv *EnvironmentFrame, libraryName []string) (*EnvironmentFrame, error)
LibraryEnvFactory creates a fresh environment for an R7RS library. The returned environment must share the caller's Namespace for syntax interning, but have isolated bindings so library definitions don't leak.
The libraryName parameter contains the library name parts (e.g., ["scheme", "base"]) so the factory can implement per-library policies.
type LibrarySearcher ¶ added in v1.10.5
type LibrarySearcher interface {
GetSearchPaths() []string
}
LibrarySearcher is implemented by library registries that support path-based file discovery. It is the minimum interface environment/ needs from a LibraryRegistry: the set of directories to search when resolving include and load paths.
The full *compilation.LibraryRegistry type implements this interface. Callers that need the full registry can type-assert from LibrarySearcher.
type LoadPathStack ¶ added in v1.3.0
type LoadPathStack struct {
// contains filtered or unexported fields
}
LoadPathStack tracks the stack of files currently being loaded. It maintains a LIFO stack of file paths (absolute or relative), enabling path resolution and load provenance tracking.
Threading and Concurrency ¶
The stack is thread-safe for concurrent access (uses sync.RWMutex), but does not guarantee correct LIFO ordering when multiple goroutines push/pop concurrently. This is an acceptable limitation:
- Single-threaded loading (the common case): Fully correct LIFO semantics
- SRFI-18 threads calling (load ...) concurrently: LIFO can corrupt
- Impact: Relative path resolution may use wrong directory
Design Rationale: Per-VM vs Per-Thread ¶
LoadPathStack is stored on Namespace (per-VM, not per-thread). This choice supports library loading across environment boundaries: when a library is loaded, it needs to resolve paths relative to the importing file, even though the library executes in its own isolated environment.
Alternative considered: per-thread stacks (map[threadID]*LoadPathStack). Pros: Correct LIFO even with concurrent loads. Cons: More complex, and Wile's SRFI-18 threading is not yet complete enough to justify the complexity.
Future: If SRFI-18 threading becomes more complete and concurrent file loading becomes common, consider migrating to per-thread stacks.
func NewLoadPathStack ¶ added in v1.3.0
func NewLoadPathStack() *LoadPathStack
NewLoadPathStack creates an empty load path stack.
func (*LoadPathStack) Current ¶ added in v1.3.0
func (p *LoadPathStack) Current() string
Current returns the path at the top of the stack without removing it. Returns empty string if the stack is empty.
func (*LoadPathStack) CurrentDir ¶ added in v1.3.0
func (p *LoadPathStack) CurrentDir() string
CurrentDir returns the directory of the path at the top of the stack. Returns empty string if the stack is empty.
func (*LoadPathStack) Depth ¶ added in v1.3.0
func (p *LoadPathStack) Depth() int
Depth returns the number of paths on the stack.
func (*LoadPathStack) Pop ¶ added in v1.3.0
func (p *LoadPathStack) Pop()
Pop removes the top path from the stack. Does nothing if the stack is empty (no error, no panic). This silent behavior is intentional to support defer patterns where the depth cannot be checked before popping.
func (*LoadPathStack) Push ¶ added in v1.3.0
func (p *LoadPathStack) Push(path string) error
Push adds a path to the top of the stack. Returns a wrapped ErrInvalidLoadPath if the path is empty.
type LocalEnvironmentFrame ¶
type LocalEnvironmentFrame struct {
// contains filtered or unexported fields
}
LocalEnvironmentFrame stores local variable bindings for a single scope. It maps symbols to binding indices for efficient lookup. Local environments are created for lambda parameters and let-bound variables. Note: LocalEnvironmentFrame has no hierarchy of its own; the hierarchy is managed by EnvironmentFrame via its parent field.
func NewLocalEnvironment ¶
func NewLocalEnvironment(pcnt int) *LocalEnvironmentFrame
NewLocalEnvironment creates a new local environment frame with pre-allocated slots for the given parameter count. Each slot is initialized with a void binding of unknown type.
func (*LocalEnvironmentFrame) Bindings ¶
func (p *LocalEnvironmentFrame) Bindings() []Binding
Bindings returns the slice of bindings in this local environment.
func (*LocalEnvironmentFrame) Copy ¶
func (p *LocalEnvironmentFrame) Copy() values.Value
Copy creates a copy of this local environment frame. The keys map is shared by reference (copy-on-write) since it is only mutated during compilation. Copy-on-write (CoW): shares the keys map between original and copy until a mutation forces a clone. Most copies are never mutated, so the clone cost is avoided entirely. See BIBLIOGRAPHY.md "Copy-on-Write". Bindings are allocated as a single contiguous block to reduce GC pressure, and each binding's scopes slice is shared (immutable at runtime).
func (*LocalEnvironmentFrame) CopyForApply ¶ added in v1.4.0
func (p *LocalEnvironmentFrame) CopyForApply() *LocalEnvironmentFrame
CopyForApply creates a lightweight copy optimized for the Apply hot path. The keys map is shared between frames and must be treated as immutable at runtime; callers must not mutate the shared keys map or any map returned by Keys(). Bindings are batch-allocated (contiguous array) for cache locality and reduced GC pressure. Scopes and source are shared (immutable at runtime); only binding values are independent between original and copy.
func (*LocalEnvironmentFrame) EnsureLocalBinding ¶ added in v1.2.0
func (p *LocalEnvironmentFrame) EnsureLocalBinding(key *values.Symbol, bt BindingType) (*LocalIndex, bool)
EnsureLocalBinding returns the local binding for the given key, creating it if it does not already exist. Returns (index, true) if a new binding was created, or (index, false) if the binding already existed.
If the keys map is shared (from Copy), it is cloned before mutation (CoW). In practice, EnsureLocalBinding is only called during compilation, never at runtime, so the CoW path is a safety net rather than a hot path.
Note: With multi-slot keys, this returns slots[0] without scope discrimination. It is only valid for single-slot keys (fresh environments for lambda params, syntax-case pattern variables). Do not use on frames where MaybeCreateLocalBinding has created scope-distinct slots for the same key.
func (*LocalEnvironmentFrame) EqualTo ¶
func (p *LocalEnvironmentFrame) EqualTo(o values.Value) bool
EqualTo returns true if this local environment is equal to the given value. Two local environments are equal if they have the same bindings.
func (*LocalEnvironmentFrame) GetLocalBinding ¶
func (p *LocalEnvironmentFrame) GetLocalBinding(li *LocalIndex) *Binding
GetLocalBinding returns the binding at the given LocalIndex.
func (*LocalEnvironmentFrame) GetLocalIndex ¶
func (p *LocalEnvironmentFrame) GetLocalIndex(key *values.Symbol) *LocalIndex
GetLocalIndex returns the LocalIndex for the given symbol in this local environment. Returns the first slot for the key, or nil if not bound.
func (*LocalEnvironmentFrame) IsVoid ¶
func (p *LocalEnvironmentFrame) IsVoid() bool
IsVoid returns true if this local environment frame is nil.
func (*LocalEnvironmentFrame) Keys ¶
func (p *LocalEnvironmentFrame) Keys() map[values.Symbol][]int
Keys returns a copy of the symbol-to-index mapping for this local environment. Each key maps to a slice of slot indices (common case: one element). Multiple slots per key occur when hygienic expansion creates same-name bindings with different scope sets in the same frame. The returned map is safe to mutate without affecting internal state.
func (*LocalEnvironmentFrame) MaybeCreateLocalBinding ¶ added in v1.10.7
func (p *LocalEnvironmentFrame) MaybeCreateLocalBinding( key *values.Symbol, bt BindingType, scopes []*syntax.Scope, source *syntax.SourceContext, ) (*LocalIndex, bool)
MaybeCreateLocalBinding creates a local binding with scope-aware deduplication. Two bindings with the same key but incompatible scopes get separate slots; compatible scopes reuse the existing slot. Nil scopes means "match any".
If the keys map is shared (from Copy), it is cloned before mutation (CoW). The three-index slice on append prevents mutating a shared backing array.
func (*LocalEnvironmentFrame) SchemeString ¶
func (p *LocalEnvironmentFrame) SchemeString() string
SchemeString returns a string representation of this local environment.
func (*LocalEnvironmentFrame) SetBindings ¶
func (p *LocalEnvironmentFrame) SetBindings(v []Binding)
SetBindings replaces the bindings slice in this local environment.
func (*LocalEnvironmentFrame) SetLocalValue ¶
func (p *LocalEnvironmentFrame) SetLocalValue(li *LocalIndex, v values.Value) error
SetLocalValue sets the value of the binding at the given LocalIndex.
type LocalIndex ¶
type LocalIndex [2]int
LocalIndex represents the location of a local binding as [slot, depth]. The first element (slot/over) is the index within the local environment frame. The second element (depth/up) is how many parent frames to traverse. Example: [2, 1] means "binding at slot 2 in the parent frame".
func NewLocalIndex ¶
func NewLocalIndex(over, up int) *LocalIndex
NewLocalIndex creates a new LocalIndex with the given slot and depth.
func (*LocalIndex) EqualTo ¶
func (p *LocalIndex) EqualTo(i *LocalIndex) bool
EqualTo returns true if this index equals the given index.
func (*LocalIndex) GetBinding ¶
func (p *LocalIndex) GetBinding(env *EnvironmentFrame) *Binding
GetBinding retrieves the binding at this index from the given environment.
func (*LocalIndex) Over ¶
func (p *LocalIndex) Over() int
Over returns the slot index within the local environment frame.
func (*LocalIndex) SchemeString ¶
func (p *LocalIndex) SchemeString() string
SchemeString returns a Scheme-style string representation.
func (*LocalIndex) String ¶
func (p *LocalIndex) String() string
String returns a string representation in "slot:depth" format.
func (*LocalIndex) Up ¶
func (p *LocalIndex) Up() int
Up returns the depth (number of parent frames to traverse).
type ModuleInstance ¶ added in v1.7.0
type ModuleInstance struct {
Env *EnvironmentFrame
Exports map[string]*GlobalIndex
}
ModuleInstance represents a loaded and initialized library.
type Namespace ¶ added in v1.7.0
type Namespace struct {
// Name is an optional descriptive name (e.g., "interaction-environment").
Name string
// contains filtered or unexported fields
}
Namespace represents a complete Wile VM instance. It owns per-instance syntax interning, phase registry, and library registry. This enables multiple independent Wile VMs in a single Go process.
Design: Namespace is the root of the environment hierarchy. Each EnvironmentFrame holds a reference back to its Namespace to access shared resources (syntax interning, phases, libraries).
func NewNamespace ¶ added in v1.7.0
func NewNamespace() *Namespace
NewNamespace creates a new Namespace. This is the primary entry point for creating an isolated Wile VM instance.
func (*Namespace) AtPhase ¶ added in v1.7.0
func (p *Namespace) AtPhase(phase int) *EnvironmentFrame
AtPhase returns the environment for the given phase level, creating it if needed. Phase 0 is runtime, phase 1 is expansion (for-syntax), phase 2 is compile-time, etc. Negative phases (e.g., -1 for for-template) are also supported.
func (*Namespace) AttachModule ¶ added in v1.7.0
AttachModule copies a module instance from this namespace to the target. Returns an error if the module is not loaded in this namespace.
func (*Namespace) Authorizer ¶ added in v1.7.0
func (p *Namespace) Authorizer() security.Authorizer
Authorizer returns the security authorizer for this namespace.
func (*Namespace) Compile ¶ added in v1.7.0
func (p *Namespace) Compile() *EnvironmentFrame
Compile returns the compile phase environment (phase 2), creating it if needed. This is where compile-time procedures (syntax compilers) are stored.
func (*Namespace) Derive ¶ added in v1.7.0
Derive creates a child namespace that shares syntax interning with the parent but has isolated bindings. The parent's registry and authorizer are shared by pointer — safe because registries are immutable after construction and authorizers are stateless interfaces.
func (*Namespace) DeriveWith ¶ added in v1.7.0
func (p *Namespace) DeriveWith(opts ...NamespaceDeriveOption) *Namespace
DeriveWith creates a child namespace with option overrides. Use this when the child needs a restricted registry or different authorizer.
func (*Namespace) EqualTo ¶ added in v1.7.0
EqualTo returns true if the environments are the same object.
func (*Namespace) Expand ¶ added in v1.7.0
func (p *Namespace) Expand() *EnvironmentFrame
Expand returns the expand phase environment (phase 1), creating it if needed. This is where syntax bindings from define-syntax are stored.
func (*Namespace) FileResolver ¶ added in v1.7.0
func (p *Namespace) FileResolver() FileResolver
FileResolver returns the file resolver for include/load operations. Delegates to parent when non-nil, so child environments share the root resolver. Returns nil if no resolver has been set.
func (*Namespace) InternSyntax ¶ added in v1.7.0
func (p *Namespace) InternSyntax(k values.Value, v syntax.SyntaxValue) syntax.SyntaxValue
InternSyntax returns the canonical version of the given syntax value. If an equivalent syntax value has been seen before, it is returned. Otherwise, the value is added to the intern table and returned.
When a parent Namespace exists, interning is delegated to the parent to maintain syntax identity across environments.
This function is thread-safe.
func (*Namespace) LibraryEnvFactory ¶ added in v1.7.0
func (p *Namespace) LibraryEnvFactory() LibraryEnvFactory
LibraryEnvFactory returns the factory for creating library environments. Returns nil if no factory has been set.
func (*Namespace) LibraryRegistry ¶ added in v1.7.0
func (p *Namespace) LibraryRegistry() LibrarySearcher
LibraryRegistry returns the library registry for R7RS library loading. Returns nil if no registry has been set. Callers needing the full *compilation.LibraryRegistry can type-assert.
func (*Namespace) LoadPathStack ¶ added in v1.7.0
func (p *Namespace) LoadPathStack() *LoadPathStack
LoadPathStack returns the load path stack for tracking files currently being loaded. Delegates to parent when non-nil, ensuring child environments share the same stack as the root Namespace.
func (*Namespace) LookupLibraryEnv ¶ added in v1.7.0
func (p *Namespace) LookupLibraryEnv(scope *syntax.Scope) *EnvironmentFrame
LookupLibraryEnv returns the environment associated with the given library scope, or nil if not registered. This function is thread-safe.
func (*Namespace) ModuleInstance ¶ added in v1.7.0
func (p *Namespace) ModuleInstance(path string) (*ModuleInstance, bool)
ModuleInstance returns the cached module instance for the given path, or (nil, false) if not loaded.
func (*Namespace) NewChildNamespace ¶ added in v1.7.0
NewChildNamespace creates a new Namespace whose syntax interning is delegated to the receiver (the parent).
Ownership structure ¶
The child is a fully independent Namespace with its own:
- EnvironmentFrame (runtime, phase 0) — the root lexical scope
- GlobalEnvironmentFrame — isolated global bindings (define, set!, etc.)
- PhaseRegistry — isolated phase hierarchy (expand, compile created on demand)
The child's GlobalEnvironmentFrame.namespace points to the child (not the parent), so new global bindings created in the child are keyed against the child's GlobalEnvironmentFrame. This is what provides binding isolation: definitions in the child do not appear in the parent, and vice versa.
Parent Namespace (root)
+-----------------------------------------------+
| syntaxInterns: map[Value]SyntaxValue ◄────────────── all interning
| syntaxInternsMu (mutex) |
| parent: nil |
| phases: *PhaseRegistry ──► {0: envP} |
| runtime: envP ─────────────────────────────┐ |
| libraryRegistry: LibrarySearcher | |
+--------------------------------------------│--+
│
▼
EnvironmentFrame (envP, phase 0)
+-------------------------------+
| global: *GlobalEnvFrame ───┐ |
| namespace: ──► parent NS | |
+---------------------------│---+
▼
GlobalEnvironmentFrame
+-------------------------+
| keys: {x:0, y:1, ...} |
| bindings: [...] |
| namespace: ──► parent NS|
+-------------------------+
Child Namespace (returned by this method)
+-----------------------------------------------+
| syntaxInterns: nil (never accessed) |
| parent: ──► parent NS (interning delegate) |
| phases: *PhaseRegistry ──► {0: envC} |
| runtime: envC ─────────────────────────────┐ |
| libraryRegistry: ──► same pointer as parent| |
+--------------------------------------------│--+
│
▼
EnvironmentFrame (envC, phase 0)
+-------------------------------+
| global: *GlobalEnvFrame ───┐ |
| namespace: ──► child NS | |
+---------------------------│---+
▼
GlobalEnvironmentFrame
+-------------------------+
| keys: {} (empty) |
| bindings: [] |
| namespace: ──► child NS |
+-------------------------+
Interning delegation ¶
The child stores a parent pointer and has nil interning maps. InternSyntax checks for a non-nil parent and delegates recursively, ultimately reaching the root Namespace where the maps and mutexes live. This avoids sharing map pointers across structs with independent mutexes (which would be a data race).
Inherited state ¶
The child inherits the parent's libraryRegistry (a LibrarySearcher, concretely *compilation.LibraryRegistry) by value copy. This allows the child to load libraries via (import ...) without requiring the caller to set the registry explicitly. The registry itself is a shared pointer; mutations to the registry (e.g., registering a new library) are visible to both parent and child.
Contrast with NewChildRuntime ¶
NewChildRuntime returns an *EnvironmentFrame that shares the parent's Namespace directly (same pointer). It is used for library loading, where the library environment should share the same Namespace for syntax interning. However, because it shares the Namespace, it cannot be returned as a standalone environment value — calling Runtime() on the shared Namespace returns the parent's runtime frame, not the child's.
NewChildRuntime: NewChildNamespace:
Namespace (shared) Parent NS Child NS
+------------------+ +----------+ +----------+
| runtime: envP | | runtime: | | runtime: |
+------------------+ | envP | | envC |
│ +----------+ +----------+
│ │
┌────┴────┐ ▼
▼ ▼ EnvironmentFrame (envC)
envP envC ◄── new child +----------------------+
(parent (has own Global- | namespace: child NS |
frame) EnvFrame, but +----------------------+
namespace points
to shared NS)
envC.Namespace() == parent envC.Namespace() == child
TLE.Runtime() returns envP child.Runtime() returns envC ✓
NewChildNamespace returns a new *Namespace that can be passed as a first-class Scheme value (e.g., returned from the (environment) primitive and accepted by eval). Its Runtime() returns the child's own runtime frame, and its AtPhase/Expand/Compile methods create phase environments scoped to the child.
Usage ¶
Used by PrimEnvironment and PrimNullEnvironment (R7RS §6.12) to create environments that are identity-compatible with the caller's symbol table while providing isolated bindings. TODO: review whether libraryRegistry should be copied here TODO: review for optimization/refactoring opportunities
func (*Namespace) NewChildRuntime ¶ added in v1.7.0
func (p *Namespace) NewChildRuntime() *EnvironmentFrame
NewChildRuntime creates a new runtime environment frame that shares this Namespace for syntax interning, but has its own GlobalEnvironmentFrame and PhaseRegistry for isolated bindings.
This is used for library environments that need to:
- Share syntax interning
- Have isolated bindings (library definitions don't leak)
- Have their own phase hierarchy
func (*Namespace) NewSchemeReportNamespace ¶ added in v1.7.0
NewSchemeReportNamespace creates a new Namespace that is distinct from the receiver (so eq? returns #f) but contains a snapshot of the receiver's current global bindings at the time of the call.
This implements R7RS §6.12 scheme-report-environment semantics: the returned environment is a separate object from interaction-environment and contains the standard bindings. User definitions added after this call are NOT visible in the returned environment.
func (*Namespace) Phases ¶ added in v1.7.0
func (p *Namespace) Phases() *PhaseRegistry
Phases returns the phase registry.
func (*Namespace) RegisterLibraryScope ¶ added in v1.7.0
func (p *Namespace) RegisterLibraryScope(scope *syntax.Scope, env *EnvironmentFrame)
RegisterLibraryScope associates a library scope with its defining environment. This enables cross-library macro hygiene: when a symbol carries a library scope, the compiler can redirect binding lookup to the library's env.
This function is thread-safe.
func (*Namespace) Registry ¶ added in v1.7.0
Registry returns the primitive registry. The caller must type-assert to *registry.Registry.
func (*Namespace) Runtime ¶ added in v1.7.0
func (p *Namespace) Runtime() *EnvironmentFrame
Runtime returns the runtime phase environment (phase 0). This is the main environment where top-level bindings live.
func (*Namespace) SchemeString ¶ added in v1.7.0
SchemeString returns the Scheme representation of the environment.
func (*Namespace) SetAuthorizer ¶ added in v1.7.0
func (p *Namespace) SetAuthorizer(auth security.Authorizer)
SetAuthorizer sets the security authorizer for this namespace.
func (*Namespace) SetFileResolver ¶ added in v1.7.0
func (p *Namespace) SetFileResolver(resolver FileResolver)
SetFileResolver sets the file resolver for include/load operations. Delegates to parent when non-nil, matching the getter's delegation, so the resolver is always stored on the root Namespace.
func (*Namespace) SetLibraryEnvFactory ¶ added in v1.7.0
func (p *Namespace) SetLibraryEnvFactory(f LibraryEnvFactory)
SetLibraryEnvFactory sets the factory for creating library environments.
func (*Namespace) SetLibraryRegistry ¶ added in v1.7.0
func (p *Namespace) SetLibraryRegistry(registry LibrarySearcher)
SetLibraryRegistry sets the library registry for R7RS library loading.
func (*Namespace) SetModuleInstance ¶ added in v1.7.0
func (p *Namespace) SetModuleInstance(path string, inst *ModuleInstance)
SetModuleInstance caches a loaded module instance.
func (*Namespace) SetRegistry ¶ added in v1.7.0
SetRegistry sets the primitive registry.
func (*Namespace) SyntaxInternCount ¶ added in v1.7.0
SyntaxInternCount returns the number of interned syntax objects. This is intended for testing and debugging purposes.
type NamespaceDeriveConfig ¶ added in v1.7.0
type NamespaceDeriveConfig struct {
Registry any // if non-nil, overrides parent's registry
Authorizer security.Authorizer // if non-nil, overrides parent's authorizer
}
NamespaceDeriveConfig holds options for DeriveWith. Zero value means "inherit everything from parent."
type NamespaceDeriveOption ¶ added in v1.7.0
type NamespaceDeriveOption func(*NamespaceDeriveConfig)
NamespaceDeriveOption configures a derived namespace.
type PhaseRegistry ¶
type PhaseRegistry struct {
// contains filtered or unexported fields
}
PhaseRegistry manages phase-indexed environment frames. It provides O(1) access to any phase environment and supports lazy creation of phase environments on first access.
The registry is owned by the Namespace and shared across all child environments via pointer. This enables any environment frame to access any phase directly.
Thread-safe: All operations are protected by a read-write mutex to support concurrent macro expansion.
func (*PhaseRegistry) Get ¶
func (p *PhaseRegistry) Get(phase int) *EnvironmentFrame
Get returns the environment for the given phase, or nil if not yet created.
func (*PhaseRegistry) GetOrCreate ¶
func (p *PhaseRegistry) GetOrCreate(phase int) *EnvironmentFrame
GetOrCreate returns the environment for the given phase, creating it if needed. Phase 0 always returns the runtime environment. Other phases are lazily created with their own GlobalEnvironmentFrame.
func (*PhaseRegistry) Namespace ¶ added in v1.7.0
func (p *PhaseRegistry) Namespace() *Namespace
Namespace returns the owning Namespace.
func (*PhaseRegistry) Phases ¶
func (p *PhaseRegistry) Phases() []int
Phases returns all currently instantiated phase levels. Useful for debugging and introspection.
func (*PhaseRegistry) TopLevelFrame ¶
func (p *PhaseRegistry) TopLevelFrame() *EnvironmentFrame
TopLevelFrame returns the runtime (phase 0) environment frame.