environment

package
v1.18.0 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

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:

Binding Lookup

EnvironmentFrame.GetBinding performs two-phase lookup:

  1. Local phase: traverse local bindings up the parent chain
  2. 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

View Source
const (
	// BindingTypeUnknown is the zero value used for pre-allocated, not-yet-
	// bound slots in LocalEnvironmentFrame. It must not be observed by
	// callers outside the environment package.
	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
)

Variables

This section is empty.

Functions

This section is empty.

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

func (p *Binding) Copy() *Binding

Copy creates a deep copy of this binding. The meta struct is copied so that mutations through EnsureMeta on the original do not affect the copy. This method is only used during compilation/expansion, never on the runtime hot path.

func (*Binding) Doc

func (p *Binding) Doc() string

Doc returns the documentation string for this binding. Returns empty string for bindings without documentation.

func (*Binding) EnsureMeta

func (p *Binding) EnsureMeta() *BindingMeta

EnsureMeta returns the BindingMeta pointer, lazily allocating an empty BindingMeta on first call. This is the only mutator API for metadata fields: callers assign directly, e.g.

b.EnsureMeta().Imported = true

Adding a new metadata field thus requires editing only the BindingMeta struct itself; no parallel getter/setter accessor pair is needed.

func (*Binding) InlineHOFParam

func (p *Binding) InlineHOFParam() int

InlineHOFParam reports the callback parameter index of a curated inline-HOF binding (callback specialization Strategy A), or -1 when this binding is not a curated inline HOF. The gating BindingMeta.InlineHOF flag makes -1 the correct answer for an unstamped binding and for a binding whose meta exists but carries no inline-HOF stamp (the EnsureMeta zero value). Read by the compiler's inline-HOF dispatch to decide whether to attempt call-site specialization.

func (*Binding) IsCaptureSafe

func (p *Binding) IsCaptureSafe() bool

IsCaptureSafe reports whether this binding's callee cannot invoke a Scheme procedure — a Go primitive stamped from !PrimitiveSpec.InvokesProcedure at registration, or a Scheme procedure proven capture-safe at compile time (ProcedureBodyIsCaptureSafe). The frame-reclaim classifier pairs it with IsStable() — capture-safe AND non-rebindable — to trust a callee. Returns false when no metadata is set: the conservative default, an unstamped binding is never trusted. Unlike IsStable (which ORs in Imported), this reads CaptureSafe alone: Imported does NOT imply capture-safe (see the BindingMeta.CaptureSafe invariant).

func (*Binding) IsImported

func (p *Binding) IsImported() bool

IsImported returns whether this binding was imported from a library.

func (*Binding) IsStable

func (p *Binding) IsStable() bool

IsStable reports the rebind-stability conclusion: the binding will not be rebound. Imported is standing evidence for that conclusion (R7RS forbids set! on imports); Stable carries it when a proof discharges it by other means. This is NOT a set!-permission — that is IsImported alone (R7RS §5.2). Read by the frame optimizer's MayCapture (sibling escape-gated plan). Renamed from the retired IsConstant, which falsely asserted "value known at compile time".

func (*Binding) Meta

func (p *Binding) Meta() *BindingMeta

Meta returns the BindingMeta pointer, or nil if no metadata has been attached. Callers that read metadata fields should nil-check the returned pointer; the convenience getters (Scopes, Source, Doc, IsImported, IsStable) wrap this pattern.

func (*Binding) Scopes

func (p *Binding) Scopes() []*syntax.Scope

Scopes returns the hygiene scopes associated with this binding. Returns nil for bindings without hygiene information.

func (*Binding) SetValue

func (p *Binding) SetValue(value values.Value)

SetValue updates the value stored in this binding. Global bindings publish atomically (paired with the lock-free reader in Value); local bindings write the plain field.

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.

func (*Binding) Value

func (p *Binding) Value() values.Value

Value returns the value stored in this binding. Global bindings (cell != nil) read atomically so a lock-free cachedBindings reader is safe against a concurrent set!; local bindings read the plain field.

type BindingID

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

type BindingMeta struct {
	Scopes   []*syntax.Scope
	Source   *syntax.SourceContext
	Doc      string
	Imported bool
	// Stable is the conclusion of a rebind-stability proof: the binding will
	// not be rebound. It is set ONLY by a completed proof, never as a synonym
	// for evidence. Imported (above) is *evidence* sufficient for that
	// conclusion — R7RS forbids set! on imports — so IsStable() treats Imported
	// as standing evidence and this flag carries the conclusion when a proof
	// discharges it by other means (defined-once ∧ ¬set! ∧ unit-closed for a
	// top-level define). The WithImmutableTopLevel engine option (the default)
	// discharges it for top-level defines: the compiler sets this from the
	// validator's in-unit evidence (StableInUnit) and the language then forbids
	// the cross-unit set!/redefine that evidence alone could not rule out (set!
	// gate + redefine guard in compile_validated.go), making unit-closure hold
	// by enforcement rather than inference. When the option is off (WithMutableTopLevel),
	// this flag stays false for non-imported bindings — asserting it from
	// partial evidence would be a false conclusion. Read by the frame
	// optimizer's MayCapture. Distinct from set!-permission (Imported alone,
	// unless the option is on) and from the retired "Constant" flag (which
	// conflated provenance, stability, and compile-time-value-known).
	//
	// A second writer also discharges it: under the same WithImmutableTopLevel
	// option, registry.WithStableBasePrimitives stamps the ambient capture-safe
	// core primitives (+, car, <, …) Stable at registration (registry/apply.go),
	// backed by the same set!/redefine enforcement. Both writers mean the same
	// thing — "non-rebindable" — which is why the redefine guard treats a Stable
	// ambient primitive as frozen, stricter than an Imported binding (which a
	// top-level define may still supersede per R7RS §5.3.1).
	Stable bool
	// CaptureSafe marks a binding whose callee cannot invoke a Scheme procedure and
	// therefore cannot transitively capture a continuation. Two writers stamp it:
	// a Go primitive at registration from !PrimitiveSpec.InvokesProcedure
	// (registry/apply.go), and a Scheme procedure proven capture-safe at compile
	// time (compile_define.go via validate.ProcedureBodyIsCaptureSafe — stdlib like
	// zero?/not, or a user helper). It is the classifier-readable form of that
	// capability, because pkg/internal/validate cannot import pkg/registry —
	// mirroring how Stable carries the rebind-stability conclusion across the same
	// boundary. The frame-reclaim classifier trusts a callee only when CaptureSafe
	// AND Stable both hold: CaptureSafe is the "cannot capture" capability, Stable
	// the "cannot be rebound to something that can" guarantee.
	//
	// INVARIANT — do NOT fold any sibling flag into IsCaptureSafe() the way IsStable
	// ORs in Imported: Imported does NOT imply capture-safe (an imported `apply` or
	// `map` is Imported yet invokes a procedure). IsCaptureSafe() reads this field
	// alone, by design. A user redefinition of a primitive name (e.g.
	// (define car <lambda>)) carries this flag ONLY if its own body proves
	// capture-safe — a capturing redefinition is never stamped, which is how the
	// classifier avoids trusting a capturing shadow by name.
	CaptureSafe bool
	// InlineHOF marks a curated higher-order procedure whose single-sequence
	// case-lambda clause may be inlined at a call site that independently proves
	// the callback capture-safe (callback specialization Strategy A). Stamped on
	// the sealed-base tail HOFs post-bootstrap (for-each, vector-map,
	// vector-for-each, string-map, string-for-each) and on import-gated ones from
	// their library (fold, srfi/1); NOT auto-derived. The curated set lives in
	// compilation.inlineHOFSpecs. Consumed by the compiler's inline-HOF dispatch.
	//
	// ORTHOGONAL to CaptureSafe: an inline HOF is itself NOT capture-safe — it
	// applies the callback, which may capture (for-each.IsCaptureSafe() is false,
	// pinned by capture_safety_test.go). This flag says "inlinable WHEN the
	// callback is proven safe," a different question about the same binding.
	//
	// The gating bool is what keeps the capability zero-value-correct: a plain
	// -1-sentinel int would read 0 ("callback param 0") on every binding that ever
	// calls EnsureMeta (which is every primitive — see registry/apply.go), falsely
	// marking them inline HOFs. With the bool, the &BindingMeta{} zero value
	// (InlineHOF=false) correctly means "not an inline HOF," preserving the
	// invariant that adding a metadata field needs no constructor edits.
	InlineHOF bool
	// InlineHOFCallbackParam is the callback's parameter index, read ONLY when
	// InlineHOF is true. Stored as data (not hardcoded 0) so a future HOF whose
	// callback is not the first parameter is handled by the same path.
	InlineHOFCallbackParam int
}

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 BindingRef

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

BindingRef names any binding — a resolved local slot, or a symbolic global that need not exist yet. All fields are comparable, so BindingRef is usable as a Go map key. Exactly one arm is meaningful, selected by kind.

The global arm is deliberately symbolic (a Key string, not a resolved GlobalIndex): a top-level (set! …)/(define …) is named during validation, before the compiler creates the corresponding global binding, so no frame slot exists yet. This is the local-or-global identity used to track "was this binding mutated / is it an inline candidate" within one compilation unit.

BindingRef is NOT a substitute for GlobalIndex. GlobalIndex carries Env *GlobalEnvironmentFrame for cross-library macro hygiene (definition-site resolution); the symbolic global arm here drops that, so two hygiene-distinct top-level bindings of the same name share a ref. That over-match is safe for a conservative mutation/stability set (it can only forfeit an optimization, never wrongly apply one), but it means BindingRef must not be used where cross-library binding identity matters.

func GlobalRef

func GlobalRef(key string) BindingRef

GlobalRef returns a BindingRef naming a global binding symbolically, by the symbol's Key. The named binding need not exist yet.

func LocalRef

func LocalRef(bid BindingID) BindingRef

LocalRef returns a BindingRef naming a resolved local binding.

func (BindingRef) IsGlobal

func (p BindingRef) IsGlobal() bool

IsGlobal reports whether the reference names a global binding.

func (BindingRef) IsLocal

func (p BindingRef) IsLocal() bool

IsLocal reports whether the reference names a local binding.

func (BindingRef) IsValid

func (p BindingRef) IsValid() bool

IsValid reports whether the reference names a binding (local or global).

type BindingRefKind

type BindingRefKind uint8

BindingRefKind discriminates the two halves of the binding domain a BindingRef can name. The zero value is intentionally invalid so a zero BindingRef is never == a resolved one.

const (
	// BindingRefInvalid is the zero value: not a reference to any binding.
	BindingRefInvalid BindingRefKind = iota
	// BindingRefLocal names a resolved local binding by frame + slot.
	BindingRefLocal
	// BindingRefGlobal names a global binding symbolically, by symbol Key.
	BindingRefGlobal
)

type BindingType

type BindingType int

BindingType represents the type of a binding in the environment.

Three of the four constants are observable:

  • BindingTypeVariable — regular runtime variables.
  • BindingTypeSyntax — syntax transformers.
  • BindingTypePrimitive — compile-time bindings (special forms, etc).

BindingTypeUnknown (the zero value) is internal scaffolding only. It is the type of pre-allocated slots in NewLocalEnvironment before they are assigned a real binding type, and is never observed by GetBinding or any external consumer. It exists solely to be a meaningful zero value for the BindingType field of a freshly constructed Binding in a pre-allocated frame slot. Removing it would require a sentinel layer (e.g. nil-binding markers in LocalEnvironmentFrame.bindings) and is not justified by the current call-site set.

type EngineServices

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

EngineServices co-locates the engine-lifetime, layering-opaque services that were previously scattered as bare any fields on Namespace. Allocated once by the root Namespace and shared by pointer: every child copies the *EngineServices at construction (exactly as it already copies registry/authorizer), so the whole namespace tree reads and writes one struct — no root() walk, one sync.RWMutex. Adding the next such service is a field here plus its two Namespace accessors.

Concurrency layout invariant: read-mostly handles are grouped first; the exportIndex lazy-cache and its RWMutex are LAST. exportIndexMu's word is written on every RLock (concurrent (apropos)); keeping it off the cache line shared with the read-mostly handles prevents false sharing across SRFI-18 threads. New tenants join the read-mostly group, never after the mutex.

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 ─────── Phase (-1=template, 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              │
└───────────────────────────┘    └────────────────────────────────────────┘

Ownership and Sharing

  • Namespace: Root owner. One per Wile VM instance.
  • EnvironmentFrame: Many per VM. Share namespace and phases references.
  • GlobalEnvironmentFrame: One per phase. Owned by EnvironmentFrame; no direct Namespace back-reference (reach Namespace via the owning frame).
  • 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

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 Phase) *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

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

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 implements values.Value. R7RS §6.12 specifies that environments compare by eq? (pointer identity), not by structural equality of their bindings — the prior structural implementation was a latent correctness trap that no caller actually exercised. Use pointer identity here.

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

func (p *EnvironmentFrame) FileResolver() FileResolver

FileResolver returns the file resolver. Shortcut for p.Namespace().FileResolver(); see the comment block above.

func (*EnvironmentFrame) GetBinding

func (p *EnvironmentFrame) GetBinding(key *values.Symbol, scopes []*syntax.Scope) *Binding

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

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

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

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

func (p *EnvironmentFrame) HasLocalVariableBinding(sym *values.Symbol, scopes []*syntax.Scope) bool

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

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 reports whether this environment frame pointer is nil. Required by values.Value (see SchemeString comment).

func (*EnvironmentFrame) LibraryRegistry

func (p *EnvironmentFrame) LibraryRegistry() LibrarySearcher

LibraryRegistry returns the library registry. Shortcut for p.Namespace().LibraryRegistry(); see the comment block above. Callers needing the full *compilation.LibraryRegistry can type-assert.

func (*EnvironmentFrame) LoadPathStack

func (p *EnvironmentFrame) LoadPathStack() PathTracker

LoadPathStack returns the load path tracker. Shortcut for p.Namespace().LoadPathStack(); see the comment block above.

func (*EnvironmentFrame) LocalBindingsSlice

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) MutableRuntime

func (p *EnvironmentFrame) MutableRuntime() *EnvironmentFrame

MutableRuntime returns the per-Engine MUTABLE runtime global of this frame's namespace — the user top level where user defines land and where eval/load and SRFI-18 threads store top-level state. It is the lexical CHILD of the immutable sealed base; resolution from it reaches sealed primitives via the parent walk.

Use this, NOT TopLevel(), when a primitive needs the frame for user-visible top-level mutations: after the layered-environment carve TopLevel() returns the immutable sealed-base root (home of the optimizer's Stable anchors), so storing a user define or thread state through TopLevel() would target the frozen base. This names the recurring intent that was previously spelled `.Namespace().Runtime()` at every call site. (It resolves the namespace's runtime, which for a flat library frame is the engine's mutable global rather than the library's own transient frame — unlike the receiver-relative Runtime().)

func (*EnvironmentFrame) MutableRuntimeOrNil

func (p *EnvironmentFrame) MutableRuntimeOrNil() *EnvironmentFrame

MutableRuntimeOrNil resolves the namespace's mutable runtime by walking the lexical parent chain, returning nil if no frame in the chain carries a namespace (rather than panicking like MutableRuntime). Some transient execution frames — a procedure body frame entered while running a call-with-values producer, say — are detached (nil parent, nil namespace); their owning namespace is only reachable via the MachineContext's parentMC, not the lexical chain. NewSubContext uses this to fall back to the parent context when the local env cannot resolve a namespace.

func (*EnvironmentFrame) Namespace

func (p *EnvironmentFrame) Namespace() *Namespace

Namespace returns the Namespace for this frame.

func (*EnvironmentFrame) NewApplyFrame

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. It is the allocating counterpart of InitApplyFrame (the pooling-friendly form); both share the same parent-copy logic.

func (*EnvironmentFrame) Parent

func (p *EnvironmentFrame) Parent() *EnvironmentFrame

Parent returns the parent environment frame.

func (*EnvironmentFrame) PhaseLevel

func (p *EnvironmentFrame) PhaseLevel() Phase

PhaseLevel returns the phase level of this environment frame.

func (*EnvironmentFrame) PreAllocateBindings

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

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

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) ResolveBindingRef

func (p *EnvironmentFrame) ResolveBindingRef(key *values.Symbol, scopes []*syntax.Scope) BindingRef

ResolveBindingRef names the binding a symbol refers to: a local ref when the symbol resolves to a local binding, otherwise a symbolic global ref. It is total — always returns a valid BindingRef — because the global arm needs no existing binding (a top-level set!/define is named before the compiler creates its global). Unlike ResolveBindingID, callers do not branch on a found/not-found bool; "not local" is itself a nameable (global) outcome.

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 Scheme-level string for this environment frame. EnvironmentFrame reaches the value plumbing because closures capture environments and store them as template literals (see machine.NativeTemplate.MaybeAppendLiteral); this method exists to satisfy values.Value, not because environment frames are ever printed by Scheme programs.

func (*EnvironmentFrame) SealedBaseTarget

func (p *EnvironmentFrame) SealedBaseTarget() *EnvironmentFrame

SealedBaseTarget returns the frame that should receive sealed (immutable) runtime bindings — primitives and bootstrap procedures — when a registry is applied with this frame as its target. For a namespace-owning runtime frame (this frame == its namespace's Runtime()) that is the namespace's sealed base; for a flat library frame (NewChildRuntime, which shares its parent's namespace and has no sealed-base parent to reach) it is the frame itself. This single predicate keeps the carve decision in one place across the engine-root, profile-child, and library-env apply paths.

func (*EnvironmentFrame) SetFileResolver

func (p *EnvironmentFrame) SetFileResolver(resolver FileResolver)

SetFileResolver sets the file resolver. Shortcut for p.Namespace().SetFileResolver(); see the comment block above. Panics if the frame has no namespace (configuration on an un-namespaced frame would be silently dropped — a programmer error).

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. Shortcut for p.Namespace().SetLibraryRegistry(); see the comment block above. Panics if the frame has no namespace (see SetFileResolver).

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

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

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/resolver/, backed by sourceload.Finder for file search. 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 via the owning EnvironmentFrame, ensuring R7RS symbol identity works correctly across all phases. GlobalEnvironmentFrame itself does not hold a back reference to its Namespace; ownership flows through EnvironmentFrame.

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

Copy creates a deep copy of the global environment frame. 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

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) 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) 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) 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 ImmutableLiterals

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

ImmutableLiterals is the engine-scoped set of literal pair and vector objects that R7RS §4.1.2 makes immutable. Membership is determined once, at compile time, when a quoted literal is interned into a template's literal pool, and read on the cold mutation path (set-car!/set-cdr!/list-set!/ vector-set!/vector-fill!).

The set is grow-only and write-once-per-key. sync.Map is chosen for its lock-free read path (an atomic load of an internal read-only map), which matches the "written once at compile time, read many at run time" profile — reads land on set-car!/set-cdr!, a path the codebase keeps explicitly hot (registry/core/prim_pairs.go). No unsafe: keys are ordinary *Pair/*Vector pointers boxed as values.Value; pointer identity survives interface boxing, and Go's heap GC is non-moving.

The set is NOT a struct field on Pair/Vector by design: type Pair is [2]Value and type Vector is []Value (not structs), and adding a word would grow the 32-byte cons cell ~25% — the dominant heap object, directly opposing the GC-reduction goal. The side-set keeps pairs at 32 bytes; the cost lands only on the cold mutation path.

func (*ImmutableLiterals) Contains

func (p *ImmutableLiterals) Contains(v values.Value) bool

Contains reports whether v was marked immutable. Membership is by pointer identity, not equal? — a distinct but structurally-equal value is not a member unless it too was marked.

func (*ImmutableLiterals) IsImmutable

func (p *ImmutableLiterals) IsImmutable(v values.Value) bool

IsImmutable reports whether in-place mutation of v is forbidden, spanning both immutability mechanisms with a single query:

  • Values that carry an intrinsic flag (values.Immutable, currently *String) answer from that flag. This works even when p is nil, since the flag does not depend on the side-set.
  • Pair and Vector answer from this engine-scoped side-set by pointer identity (the literals marked at compile time per R7RS §4.1.2).
  • Every other value — including non-aggregate scalars and unmarked, runtime-constructed pairs/vectors — is not constrained, so the result is false.

Mutation gate sites that already hold a concrete *Pair/*Vector use this in place of the inline `set != nil && set.Contains(v)` guard. *String primitives keep self-enforcing inside their mutators (SetChar/Fill), which is the correct layer for a value that owns its own bit; this predicate is the canonical answer for any caller that must ask without knowing the type.

func (*ImmutableLiterals) Mark

func (p *ImmutableLiterals) Mark(v values.Value)

Mark records v as immutable. Called at compile time from the quote hook.

type InlineHOFTemplateStore

type InlineHOFTemplateStore interface {
	InlineHOFTemplate(name string) (any, bool)
}

InlineHOFTemplateStore returns a pre-validated inline-HOF loop template by HOF name (callback specialization Strategy A). The returned template is a *validate.ValidatedLambda, exposed here as any because environment/ is below validate/ in the import graph; the compilation consumer type-asserts. Mirrors LibrarySearcher: the minimum environment/ needs to hold a compilation artifact.

type LibraryEnvFactory

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

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 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

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

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

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) 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) 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

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) 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

type ModuleInstance struct {
	Env     *EnvironmentFrame
	Exports map[string]*GlobalIndex
}

ModuleInstance represents a loaded and initialized library.

type Namespace

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).

Field inheritance policy

Child namespaces (NewChildNamespace and NewSchemeReportNamespace) inherit fields from their parent in one of three ways. New fields MUST pick a policy explicitly — the existing per-field decisions are encoded here, not in the constructors.

Per-VM (each namespace has its own; no inheritance):
  Name, parent, phases, runtime, moduleInstances, syntaxInterns
    Note: syntaxInterns is nil in children; InternSyntax delegates
    to parent so symbol identity remains globally consistent.

Captured at construction (the child stores its own copy of the
parent's pointer or map header at fork time; later *reassignments*
on the parent — e.g. parent.SetRegistry(other) — do not flow to
existing children. Mutations *through* the captured pointer — e.g.
parent.Registry().AddPrimitive(...) — ARE visible to children
because the pointer they hold is the same Go object):
  libraryRegistry, libraryEnvFactory, registry, authorizer, envMap
    Rationale: these are capability state. Reassignments on the
    parent must not silently widen capability on existing children
    (envMap is the load-bearing example — see the SetEnvMap doc
    comment in this file for the discussion).

Delegated to root via root() walk (the field lives only on the root
namespace; children reach it through the parent chain in O(depth)):
  fileResolver, loadPathStack, scopeRegistry

Pointer-shared via *EngineServices (allocated once in NewNamespace;
every child receives the same pointer at construction — no root() walk,
one struct, one optional mutex for the lazy-cache block):
  ioState, exportIndex, formRegistry — add new engine-lifetime services here
  (add a field on EngineServices + two Namespace accessors; children inherit
  automatically because they copy the services pointer at construction)

Adding a new field: choose a policy above, document it in this block, then:

  • Captured: copy it from the parent in *both* NewChildNamespace and NewSchemeReportNamespace (these are the two constructors that populate child state from a parent; they share the same captured set).
  • Delegated (root() walk): define accessors through p.root(); do not store it on child namespaces.
  • EngineServices tenant: add a field to EngineServices + two Namespace accessors that read/write p.services.field directly; no constructor change needed (children copy the services pointer already).

Do NOT mix the policies for one field — the asymmetry is the bug source the policy table exists to prevent.

func NewNamespace

func NewNamespace() *Namespace

NewNamespace creates a new Namespace. This is the primary entry point for creating an isolated Wile VM instance. Call SetLoadPathStack before any file loading operations.

func (*Namespace) AtPhase

func (p *Namespace) AtPhase(phase Phase) *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

func (p *Namespace) AttachModule(path string, target *Namespace) error

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

func (p *Namespace) Authorizer() security.Authorizer

Authorizer returns the security authorizer for this namespace.

func (*Namespace) BoundNamesAcrossPhases

func (p *Namespace) BoundNamesAcrossPhases() []string

BoundNamesAcrossPhases returns a sorted, deduplicated list of every binding name visible across all instantiated phases (runtime, expand, compile) plus the sealed base. Unlike BoundSymbolNames — which spans only the runtime global and the sealed base, returning a Scheme list for the bound-names primitives — this also walks the expand and compile phases, so macro and special-form keywords appear. It is the set a REPL wants for tab completion. Iteration order across phases does not change the result set (only names are collected); the output is sorted for determinism.

func (*Namespace) BoundSymbolNames

func (p *Namespace) BoundSymbolNames() values.Value

BoundSymbolNames returns a freshly-consed list of every symbol bound in the namespace's runtime, spanning BOTH the mutable runtime global (user defines) and the sealed base (primitives + sealed stdlib procedures). It is the shared body of the environment-bound-names and namespace-bound-names primitives. Post-carve the sealed base must be included or primitives like `car` vanish from the result (the key map carries no parent walk). Iteration order is unspecified; a name shadowed in both frames appears once (deduped via the seen set).

func (*Namespace) Compile

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) DeleteExtensionState

func (p *Namespace) DeleteExtensionState(key any)

DeleteExtensionState removes any namespace-scoped extension state stored under key. Safe for concurrent use.

func (*Namespace) EnvMap

func (p *Namespace) EnvMap() map[string]string

EnvMap returns the virtual environment variable map for this namespace, or nil if none has been configured. When non-nil, envvars primitives read from this map instead of the process environment, bypassing the authorizer gate.

func (*Namespace) EqualTo

func (p *Namespace) EqualTo(v values.Value) bool

EqualTo returns true if the environments are the same object.

func (*Namespace) Expand

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) ExportIndex

func (p *Namespace) ExportIndex() (any, bool)

ExportIndex returns the cached library export index and whether a build has been attempted. Returns (nil, false) if no build has run. Reads the shared EngineServices (one per engine tree). The concrete type is *compilation.LibraryExportIndex.

func (*Namespace) ExtensionState

func (p *Namespace) ExtensionState(key any) (any, bool)

ExtensionState returns the namespace-scoped state stored under key, and a boolean reporting whether any value was present. Extensions use this for per-Namespace storage whose lifetime is tied to the Namespace. The key is extension-chosen; an unexported sentinel type avoids cross-extension collisions. Safe for concurrent use.

func (*Namespace) FileResolver

func (p *Namespace) FileResolver() FileResolver

FileResolver returns the file resolver for include/load operations. Returns nil if no resolver has been set. Delegated to root.

func (*Namespace) FormRegistry

func (p *Namespace) FormRegistry() any

FormRegistry returns the per-engine forms registry (an *forms.FormRegistry), or nil if unset. Opaque here because environment/ sits below internal/forms in the layering. Reads the shared EngineServices (one per engine tree).

func (*Namespace) IOState

func (p *Namespace) IOState() any

IOState returns the per-engine I/O extension state, or nil if unset. Reads the shared EngineServices (one per engine tree).

func (*Namespace) ImmutableLiterals

func (p *Namespace) ImmutableLiterals() *ImmutableLiterals

ImmutableLiterals returns the engine-scoped set of immutable literal pair/vector objects (R7RS §4.1.2). Defined once on the root Namespace; children delegate through root(), so every mutator sees the same set.

func (*Namespace) ImmutableTopLevel

func (p *Namespace) ImmutableTopLevel() bool

ImmutableTopLevel reports whether top-level-define immutability is enforced for THIS namespace. It is a property of the engine's PRIMARY (root) namespace only — the home of compiled-program top-level defines, which are the frame-reclaim optimizer's Stable anchors. CHILD namespaces (parent != nil) are mutable interaction/eval scratch spaces — (environment ...), scheme-report-environment, profile children — modeled on Chez's mutable interaction-environment (see plans/2026-06-13-immutable-toplevel-by-default-scoping.local.md:357-370): a define there shadows/redefines freely and its bindings are never stamped Stable. This is the "compilation units only" scope: immutability is for the compiled program, not for interactive eval. Reclaim soundness is preserved because set! of a Stable anchor copied into a child is still rejected by the set!-gate, which keys on IsStable() directly (compile_validated.go) rather than on this flag.

func (*Namespace) InlineHOFTemplates

func (p *Namespace) InlineHOFTemplates() InlineHOFTemplateStore

InlineHOFTemplates returns the per-Namespace inline-HOF template store, or nil if templates have not been built for this Namespace (in which case the compiler performs no inline-HOF specialization).

func (*Namespace) InlineThreshold

func (p *Namespace) InlineThreshold() (int, bool)

InlineThreshold returns the engine's configured inlining threshold and whether it was set. Reads the shared EngineServices (one per engine tree). A false bool means the namespace was not built by an Engine (e.g. a direct LoadLibrary in a unit test); the caller should fall back to its own default.

func (*Namespace) InternSyntax

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) IsVoid

func (p *Namespace) IsVoid() bool

IsVoid returns true if the environment is nil.

func (*Namespace) LibraryEnvFactory

func (p *Namespace) LibraryEnvFactory() LibraryEnvFactory

LibraryEnvFactory returns the factory for creating library environments. Returns nil if no factory has been set.

func (*Namespace) LibraryRegistry

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

func (p *Namespace) LoadPathStack() PathTracker

LoadPathStack returns the load path tracker for tracking files currently being loaded. Delegated to root.

func (*Namespace) LookupLibraryEnv

func (p *Namespace) LookupLibraryEnv(scope *syntax.Scope) *EnvironmentFrame

LookupLibraryEnv returns the environment associated with the given library scope, or nil if not registered. Delegated to root. This function is thread-safe.

func (*Namespace) MaxExpandDepth

func (p *Namespace) MaxExpandDepth() (int, bool)

MaxExpandDepth returns the engine's configured expansion-depth bound and whether it was set. Reads the shared EngineServices (one per engine tree). A false bool means the namespace was not built by an Engine (e.g. a direct LoadLibrary in a unit test); the caller should fall back to its own default.

func (*Namespace) ModuleInstance

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

func (p *Namespace) NewChildNamespace(opts ...NamespaceOption) *Namespace

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 runtime EnvironmentFrame.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: [...]         |
                                  +-------------------------+

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: []            |
                                  +-------------------------+

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.

The child also inherits the parent's envMap (virtual environment variable map) by reference. envMap is capability state — it constrains what the envvars primitives can read — so derived namespaces must not silently widen capability by acquiring a nil map that falls through to os.Getenv. The reference is safe to share because SetEnvMap always reassigns the field rather than mutating the existing map.

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; reaches            +----------------------+
              shared NS via the
              owning EnvFrame)

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. Optional NamespaceOption arguments override fields that would otherwise be inherited from the parent (currently registry and authorizer); see WithRegistry and WithAuthorizer.

All captured fields (libraryRegistry, libraryEnvFactory, registry, authorizer, envMap) are copied from the parent in one place — adding a new captured field requires a single edit here, not a sweep of multiple constructors.

func (*Namespace) NewChildRuntime

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

func (p *Namespace) NewSchemeReportNamespace() *Namespace

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

func (p *Namespace) Phases() *PhaseRegistry

Phases returns the phase registry.

func (*Namespace) RegisterLibraryScope

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. Delegated to root: the registry always lives on the root Namespace.

This function is thread-safe.

func (*Namespace) Registry

func (p *Namespace) Registry() any

Registry returns the primitive registry. The caller must type-assert to *registry.Registry.

func (*Namespace) Runtime

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

func (p *Namespace) SchemeString() string

SchemeString returns the Scheme representation of the environment.

func (*Namespace) SealedBase

func (p *Namespace) SealedBase() *EnvironmentFrame

SealedBase returns this Namespace's immutable sealed-base runtime frame (phase 0), the lexical parent of the mutable runtime global. PER-NAMESPACE (NOT root-delegated, unlike immutableLiterals): each Namespace OWNS its sealed base so a profile child's curated apply does not write into the engine root's base. Report namespaces copy the parent's sealed base into their own (see NewSchemeReportNamespace).

func (*Namespace) SetAuthorizer

func (p *Namespace) SetAuthorizer(auth security.Authorizer)

SetAuthorizer sets the security authorizer for this namespace.

func (*Namespace) SetEnvMap

func (p *Namespace) SetEnvMap(m map[string]string)

SetEnvMap sets the virtual environment variable map. When set, envvars primitives read from this map instead of os.Getenv.

The provided map is defensively copied so that subsequent mutation by the caller does not leak into the VM's sandbox state. A nil argument clears the virtual map (falls back to os.Getenv, gated by the authorizer).

Note: EnvMap() still returns the internal map by reference for zero-cost primitive access. Callers who reach for EnvMap() must treat the result as read-only; mutating it bypasses the defensive copy applied here.

func (*Namespace) SetExportIndex

func (p *Namespace) SetExportIndex(idx any)

SetExportIndex stores the library export index and marks it as built, preventing subsequent build attempts. Writes the shared EngineServices.

func (*Namespace) SetExtensionState

func (p *Namespace) SetExtensionState(key, value any)

SetExtensionState stores namespace-scoped extension state under key. Safe for concurrent use.

func (*Namespace) SetFileResolver

func (p *Namespace) SetFileResolver(resolver FileResolver)

SetFileResolver sets the file resolver for include/load operations. Delegated to root: the resolver always lives on the root Namespace.

func (*Namespace) SetFormRegistry

func (p *Namespace) SetFormRegistry(v any)

SetFormRegistry stores the per-engine forms registry. The value is opaque here (an *forms.FormRegistry owned by internal/forms).

func (*Namespace) SetIOState

func (p *Namespace) SetIOState(v any)

SetIOState stores the per-engine I/O extension state on the shared EngineServices. The value is opaque here (an *io.State owned by extensions/io); this package sits below extensions/io in the layering and never inspects it.

func (*Namespace) SetImmutableTopLevel

func (p *Namespace) SetImmutableTopLevel(on bool)

SetImmutableTopLevel enables or disables top-level-define immutability for the engine. Set once at engine construction (WithImmutableTopLevel / WithMutableTopLevel). Stored on the root; child namespaces ignore it and are always mutable (see ImmutableTopLevel).

func (*Namespace) SetInlineHOFTemplates

func (p *Namespace) SetInlineHOFTemplates(store InlineHOFTemplateStore)

SetInlineHOFTemplates installs the inline-HOF template store. Called once per Namespace at bootstrap, after the sealed base is loaded.

func (*Namespace) SetInlineThreshold

func (p *Namespace) SetInlineThreshold(n int)

SetInlineThreshold stores the engine's configured procedure-inlining threshold (WithInlineThreshold) on the shared EngineServices. Set once at engine build so runtime-triggered library compilation can honor it. An explicit 0 (inlining disabled) is retained and distinguished from "never set" via the bool returned by InlineThreshold.

func (*Namespace) SetLibraryEnvFactory

func (p *Namespace) SetLibraryEnvFactory(f LibraryEnvFactory)

SetLibraryEnvFactory sets the factory for creating library environments.

func (*Namespace) SetLibraryRegistry

func (p *Namespace) SetLibraryRegistry(registry LibrarySearcher)

SetLibraryRegistry sets the library registry for R7RS library loading.

func (*Namespace) SetLoadPathStack

func (p *Namespace) SetLoadPathStack(s PathTracker)

SetLoadPathStack sets the load path tracker for this namespace. Delegated to root: the tracker always lives on the root Namespace. Must be called before any file loading operations.

func (*Namespace) SetMaxExpandDepth

func (p *Namespace) SetMaxExpandDepth(n int)

SetMaxExpandDepth stores the engine's configured macro-expansion recursion bound (WithMaxExpandDepth) on the shared EngineServices. Set once at engine build so runtime-triggered library compilation can honor it. An explicit 0 (bound disabled / unlimited) is retained and distinguished from "never set" via the bool returned by MaxExpandDepth.

func (*Namespace) SetModuleInstance

func (p *Namespace) SetModuleInstance(path string, inst *ModuleInstance)

SetModuleInstance caches a loaded module instance.

func (*Namespace) SetRegistry

func (p *Namespace) SetRegistry(reg any)

SetRegistry sets the primitive registry.

func (*Namespace) SyntaxInternCount

func (p *Namespace) SyntaxInternCount() int

SyntaxInternCount returns the number of interned syntax objects. This is intended for testing and debugging purposes.

type NamespaceOption

type NamespaceOption func(*namespaceConfig)

NamespaceOption configures a derived namespace at construction time. Use it with NewChildNamespace to override fields that would otherwise be inherited from the parent (e.g. a restricted registry or a different authorizer).

The "*Set" booleans on namespaceConfig let WithChildRegistry(nil) and WithChildAuthorizer(nil) mean "explicitly set the field to nil" — distinct from "no override supplied" which means "inherit from parent."

func WithChildAuthorizer

func WithChildAuthorizer(a security.Authorizer) NamespaceOption

WithChildAuthorizer overrides the parent's security authorizer on a derived namespace. Use this to give a child a stricter (or different) capability profile.

Distinct from wile.WithAuthorizer, which configures an EngineOption at the top level.

func WithChildRegistry

func WithChildRegistry(r any) NamespaceOption

WithChildRegistry overrides the parent's primitive registry on a derived namespace. Use this to install a restricted or alternative registry in a child without mutating the parent.

Distinct from wile.WithRegistry, which configures an EngineOption at the top level — the wile.* and environment.* option families operate on different config types and have different semantics.

type PathTracker

type PathTracker interface {
	Push(path string)
	Pop()
	Current() string
	CurrentDir() string
	Depth() int
}

PathTracker tracks the stack of files currently being loaded. Implementations provide relative path resolution for include/load and load provenance introspection.

The concrete implementation is sourceload.LoadStack. This interface is defined here so environment/ can store it without importing machine/compilation/sourceload/.

type Phase

type Phase int8

Phase identifies a stage of compilation/evaluation. Values match Racket's phase numbering convention.

Phase indexes PhaseRegistry.envs and serves as the typed value for EnvironmentFrame.phaseLevel. The companion type registry.PhaseSet is a bitset over non-negative Phase values used for primitive registration.

ADDING A NEW PHASE requires updates in these locations:

  1. environment/phase_registry.go (this file) — add a Phase constant and a String() case.
  2. registry/phase.go — add the matching PhaseSet<Name> bit constant if the new phase is representable in a PhaseSet (i.e. phase ≥ 0 and phase < phaseSetBits). The init() assertion verifies the bit position matches the Phase index.
  3. registry/apply.go — extend phaseTargets if primitives may register at the new phase.
  4. wile/options.go — re-export so embedders can name the constant.
const (
	PhaseTemplate Phase = -1 // for-template (template instantiation)
	PhaseRuntime  Phase = 0  // Runtime execution (phase 0)
	PhaseExpand   Phase = 1  // Macro expansion (for-syntax, phase 1)
	PhaseCompile  Phase = 2  // Compile-time (for-meta 2, phase 2)
)

func (Phase) Compare

func (p Phase) Compare(other Phase) int

Compare orders two phases numerically (Template < Runtime < Expand < Compile). Suitable for slices.SortFunc.

func (Phase) String

func (p Phase) String() string

String returns a human-readable name for the phase.

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 Phase) *EnvironmentFrame

Get returns the environment for the given phase, or nil if not yet created.

func (*PhaseRegistry) GetOrCreate

func (p *PhaseRegistry) GetOrCreate(phase Phase) *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

func (p *PhaseRegistry) Namespace() *Namespace

Namespace returns the owning Namespace.

func (*PhaseRegistry) Phases

func (p *PhaseRegistry) Phases() []Phase

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.

Jump to

Keyboard shortcuts

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