environment

package
v1.19.0 Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2026 License: Apache-2.0 Imports: 13 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

func AmbientScopes added in v1.19.0

func AmbientScopes() []*syntax.Scope

AmbientScopes returns the ambient scope set: the empty, NON-NIL set that a reference written outside any macro expansion carries.

The read entry points no longer confuse the two: GetBinding and GetLocalIndex take a syntax.ScopeSet, which separates wildcard (ScopeSet.IsAll) from empty structurally, and syntax.ScopesOf(nil) is the empty set rather than a wildcard. (EnvironmentFrame.GetGlobalIndex takes no scope argument at all and is unconditionally a wildcard.) Binding CREATION is the one surviving nil-as-wildcard path (MaybeCreateLocalBinding dedups on `scopes == nil`), so a creation caller that means "ambient" must pass this set rather than nil.

Every reflective read of a bare symbol wants this, not a wildcard: a values.Symbol carries no scope set, so when several hygiene-distinct bindings share a name a wildcard resolves by slot order — an expansion-order artifact, not an answer to the caller's question.

func SameBinding added in v1.19.0

func SameBinding(a, b *Binding) bool

SameBinding reports whether two bindings denote the same variable for the purpose of identifier equality (free-identifier=? and ER-compare): the same binding object, or two bindings that share one provenance root — a library define and its imports, under any rename or re-export. Two distinct defines of one name have different roots and are NOT the same, so this deliberately does not collapse into "same value" the way pointer- or value-equality would. Bindings with no origin (program-top-level defines) match only as the identical object.

A library define carries its self-root from finalization (stampLibraryExport- Origins), so comparing a library-internal binding against an IMPORT of itself matches — which is what lets ER-compare, resolving renames at the definition site, adopt this (option B in plan 2026-07-24-free-identifier-origin-provenance- design).

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 UpdateMeta 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) InlineHOFName added in v1.19.0

func (p *Binding) InlineHOFName() string

InlineHOFName returns the canonical name of the curated HOF this binding is, or "" if it is not a stamped inline HOF. The inline dispatch selects the template by this identity rather than the call-site surface name, so an import-renamed curated HOF inlines its OWN template.

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 UpdateMeta 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-reclaim classifier (validate.classifyCallee). Renamed from the retired IsConstant, which falsely asserted "value known at compile time".

func (*Binding) Meta

func (p *Binding) Meta() *BindingMeta

Meta returns the current BindingMeta snapshot, or nil if no metadata has been attached. For a global binding (cell != nil) the snapshot is immutable — do not write through it; use UpdateMeta. Callers that read metadata fields should nil-check the returned pointer; the convenience getters (Scopes, Source, Doc, IsImported, IsStable) wrap this pattern.

func (*Binding) Origin added in v1.19.0

func (p *Binding) Origin() *OriginRef

Origin returns the import-provenance root of this binding, or nil if it is a plain (non-imported) define, which is its own root.

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) UpdateMeta added in v1.19.0

func (p *Binding) UpdateMeta(fn func(*BindingMeta) bool) bool

UpdateMeta mutates this binding's compile-time metadata and is the only metadata mutator API. fn receives a *BindingMeta to modify and returns whether it changed anything; UpdateMeta returns that same result. Adding a new metadata field thus requires editing only the BindingMeta struct itself; no parallel getter/setter accessor pair is needed. Usage:

b.UpdateMeta(func(m *BindingMeta) bool {
	m.Imported = true
	return true
})

For a global binding (cell != nil) the update is copy-on-write under an atomic CAS, so it is safe against concurrent compiles and lock-free readers, and fn may be re-run on CAS contention — fn MUST therefore depend only on the *BindingMeta it is handed, never on captured cross-call state (return the "did I change it?" answer instead of recording it in a closed-over variable). For a local binding (single-threaded) fn runs exactly once, in place.

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-reclaim
	// classifier (validate.classifyCallee). 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 UpdateMeta (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
	// InlineHOFName is the CANONICAL name of the curated HOF this binding is (the
	// inlineHOFSpecs key: "fold", "map", …), recorded at stamp time and read ONLY
	// when InlineHOF is true. The inline dispatch selects the template by THIS
	// identity, never by the call-site surface name — otherwise a curated HOF
	// imported-and-renamed onto another curated HOF's name (e.g. fold as fold-right)
	// would inline the wrong template. Empty on the zero value / unstamped bindings.
	InlineHOFName string
	// Origin is the provenance root of this binding: the root (define ...) it
	// ultimately came from (see OriginRef); nil for a program-top-level define,
	// which has no library identity. free-identifier=? AND ER-compare read it via
	// SameBinding — a library define and its imports share one root and denote the
	// same variable, while two distinct defines of one name have different roots.
	// Set at the library's finalization for a library define (stampLibraryExport-
	// Origins) or propagated at import (markBindingImported); the nil zero value is
	// meaningful, so like the sibling flags above it needs no constructor edit.
	Origin *OriginRef
}

BindingMeta holds compile-time metadata (scopes and source location) that is never read during VM execution — but IS read and written concurrently across SRFI-18 threads at compile time (two threads compiling a "define" of the same top-level name). For a global binding it is therefore published copy-on-write through the binding's atomicCell (see UpdateMeta); a reader always sees a complete, immutable snapshot. Stored behind a pointer so that runtime Binding copies (the hot path) move a pointer instead of the whole struct.

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.

Phase 1 is RECEIVER-DEPENDENT: for the sealed-base receiver it resolves to the namespace's sealedExpandBase (the sealed sibling that holds bootstrap macros/expanders); for every other receiver, and for every other phase, it resolves through the shared PhaseRegistry. This is what routes a bootstrap-macro define-syntax (compiled with env == sealedBase) into the immutable frame while user code (env == the mutable runtime) lands in the mutable expand child.

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) DefineOwnGlobal added in v1.19.0

func (p *EnvironmentFrame) DefineOwnGlobal(key *values.Symbol, bt BindingType, scopes []*syntax.Scope, v values.Value) error

DefineOwnGlobal creates (or reuses) the binding for key under scopes in this frame's own global environment, then writes v to that binding.

It exists because create and write disagree about what a nil scope set means: creation treats nil as the EXACT empty set (so a macro-introduced binder gets its own slot), while a GlobalIndex built from a bare symbol resolves MATCH ANY (the name's first live slot, whatever its hygiene). Pairing the two by hand therefore creates one binding and writes a different one as soon as any macro has introduced the same name — the host's value lands on the macro's variable and the host's own binding stays void.

Callers that create a global and immediately give it a value should use this instead of pairing MaybeCreateOwnGlobalBinding with SetOwnGlobalValue, so the nil question cannot be asked wrongly at the call site.

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, q syntax.ScopeSet) *Binding

GetBinding returns the binding for the given symbol that matches the provided query. It searches local bindings first (walking up the parent chain), then globals.

A wildcard query (AllScopes) means "match any" (no scope filtering). A specific or empty query enables hygienic resolution per Flatt's model with maximal binding selection (consistent with GetLocalIndex).

Panics with a wrapped werr.ErrAmbiguousBinding when two incomparable scope sets tie for the maximal match (Racket's "ambiguous binding"); the tie is refused, never broken by order.

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. A deferred index (Env == nil) carries the reference's scope set, so this execution-time walk resolves hygienically rather than by bare name.

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

This is the WILDCARD form — see GlobalEnvironmentFrame.GetGlobalIndex. Compiler callers want GetGlobalIndexWithScopes.

func (*EnvironmentFrame) GetGlobalIndexAcrossPhases

func (p *EnvironmentFrame) GetGlobalIndexAcrossPhases(key *values.Symbol, scopes []*syntax.Scope) *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).

scopes is the REFERENCE's scope set and each phase is searched hygienically (maximal subset match), not by bare name. Nil means the empty set, not "any" — the same convention as GetGlobalIndexWithScopes. Bare-name search was correct only while a name owned one slot per frame: once a macro-generating macro is expanded twice, each expansion's phase-0 binder carries its own intro scope, so the name owns two slots and a wildcard walk hands BOTH generated inner macros whichever slot was created first. See the two-expansion jabberwocky case in pkg/wile/toplevel_binder_scope_test.go, which the single-expansion case below cannot detect.

The phase-0 (runtime) search reaching the mutable runtime frame's OWN defines is DELIBERATE and load-bearing — it is NOT the accidental parent-chain leak the phase-frame reparent (createPhaseEnv) closed, and must NOT be routed through SealedBaseTarget() to "seal" it. A macro-generating-macro introduces a phase-0 define that a generated inner macro references by scope-aware identifier; only searching the runtime frame resolves that intro-scoped binding at compile time. Sealing it breaks R7RS §4.3 referential transparency — concretely, the jabberwocky/march-hare case in integration/testdata/r7rs-tests.scm:

(define-syntax jabberwocky
  (syntax-rules ()
    ((_ hatter)
     (begin (define march-hare 42)
            (define-syntax hatter (syntax-rules () ((_) march-hare)))))))
(jabberwocky mad-hatter) (mad-hatter)  ; => 42; sealing gives "no such binding march-hare"

(Verified 2026-07-10: hermeticizing the phase-0 search passes the compilation/machine/wile suites but fails the integration R7RS conformance suite here. Investigated as a possible "second hermeticity hole"; it is not.)

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 root Namespace'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) GetGlobalIndexWithScopes added in v1.19.0

func (p *EnvironmentFrame) GetGlobalIndexWithScopes(key *values.Symbol, q syntax.ScopeSet) *GlobalIndex

GetGlobalIndexWithScopes is GetGlobalIndex with hygienic resolution: the binding whose scope set maximally matches the query wins. The empty query (EmptyScopes) resolves under the empty scope set, not "any" — pass AllScopes for wildcard resolution.

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 panics if i is out of range for this frame's local bindings; callers must have obtained i from this frame.

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, q syntax.ScopeSet) *LocalIndex

GetLocalIndex returns the LocalIndex of the binding for the given symbol that matches the given query. A wildcard query (AllScopes) means "match any".

For a specific or empty query, 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.

Panics with a wrapped werr.ErrAmbiguousBinding when two incomparable scope sets tie for the maximal match (Racket's "ambiguous binding"); the tie is refused, never broken by order.

func (*EnvironmentFrame) GlobalEnvironment

func (p *EnvironmentFrame) GlobalEnvironment() *GlobalEnvironmentFrame

GlobalEnvironment returns the global environment frame.

func (*EnvironmentFrame) HasLocalVariableBinding

func (p *EnvironmentFrame) HasLocalVariableBinding(sym *values.Symbol, q syntax.ScopeSet) bool

HasLocalVariableBinding reports whether sym has a local variable binding satisfying the scope-set query q. 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. A wildcard query (syntax.AllScopes) matches any binding of the name; pass syntax.ScopesOf(ref.Scopes()) for a hygienic reference-scoped check. 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. A slot is reused only by a binder carrying the SAME scope set; any other scope set, even a compatible one, is a different variable and gets its own slot (see scopeSetsEqual).

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, scopes []*syntax.Scope) (*GlobalIndex, bool)

MaybeCreateOwnGlobalBinding creates a new global binding in the current global environment if it does not already exist. Delegates to GlobalEnvironmentFrame.CreateGlobalBinding. It returns the GlobalIndex of the binding and a boolean indicating whether the binding was created (true) or already existed (false).

scopes become part of the binding's identity in the frame; a nil set is the ordinary user-written top-level define.

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) NextPhase added in v1.19.0

func (p *EnvironmentFrame) NextPhase() *EnvironmentFrame

NextPhase returns the sibling frame one phase up from this frame's own level. Climbing the macro tower: a transformer body compiled against this frame expands as phase (phaseLevel+1) code, so define-syntax storage and macro lookup relative to it climb rather than collapsing into the single expand phase. At phaseLevel 0 this equals Expand(), so top-level behavior is unchanged (level-0 identity). Panics (wrapped) only on the impossible int8 overflow, which NextPhaseChecked rejects.

func (*EnvironmentFrame) NextPhaseChecked added in v1.19.0

func (p *EnvironmentFrame) NextPhaseChecked(base Phase) (*EnvironmentFrame, error)

NextPhaseChecked returns the sibling frame one phase up from base. The climb is computed in int and rejected if it leaves the int8 phase range, so a runaway self-referential macro hits a wrapped error instead of overflowing int8 (127+1 -> -128). base is explicit (not p.phaseLevel) so the ceiling is testable without constructing a phase-127 frame.

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, q syntax.ScopeSet) (BindingID, bool)

ResolveBindingID looks up a local binding by symbol and scope-set query 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, q syntax.ScopeSet) 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 mutable user top level where normal bindings live; it is the lexical child of the namespace's sealed base.

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) SealedExpandBaseTarget added in v1.19.0

func (p *EnvironmentFrame) SealedExpandBaseTarget() *EnvironmentFrame

SealedExpandBaseTarget returns the frame that should receive sealed (immutable) EXPAND-phase bindings — the special-form primitive expanders — 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 EXPAND base (phase 1); for a flat library frame (NewChildRuntime, which has no sealed expand base) it is the frame's own expand phase, preserving the pre-carve target (env.Expand()). Parallels SealedBaseTarget (phase 0) for the expand phase. It is direct (no AtPhase dependency), so the expander registration and the bootstrap-macro AtPhase redirect are independent mechanisms into the same frame. Mirrors SealedBaseTarget's ns.runtime == p keying (and, like it, trusts the construction invariant: sealedExpandBase is non-nil whenever runtime is, both built together in wireRuntimeFrames — a broken invariant fails loud at the bootstrap install site, it does not silently degrade the namespace-owning path to the mutable frame). See plans/2026-07-22-free-template-id-hygiene-impl.local.md (D1+D3).

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.

After the layered-environment carve this is the namespace's immutable SEALED BASE, not the user global. Use MutableRuntime() for the frame where user defines land.

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: syntax interning is delegated to Namespace via the owning EnvironmentFrame. (Symbols are not interned; eq? on symbols compares the .Key string.) 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) AmbientKeys added in v1.19.0

func (p *GlobalEnvironmentFrame) AmbientKeys() []values.Symbol

AmbientKeys returns the names holding a live binding under the ambient (empty) scope set: the names a reference written outside any macro expansion resolves.

Keys reports every name in the frame, including binders a macro template introduced. Those are different variables that happen to share a name, and no source-written reference in this frame can reach them, so a listing built from Keys disagrees with every scoped read — enumerate-then-dereference fails on exactly those names. Resolution here goes through bestSlotLocked, the same call a single read makes, so the listing cannot drift from what the read finds.

Order is unspecified: the result is built by ranging p.keys. Callers needing determinism must sort. (BoundSymbolNames, the only consumer, documents the same.) Thread-safe: uses RLock for read-only access.

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, scopes []*syntax.Scope) (*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. Reuse requires EXACT scope-set equality — see scopeSetsEqual for why compatibility would be a hygiene hole here.

func (*GlobalEnvironmentFrame) DeleteBinding

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

DeleteBinding removes the global binding for sym that resolves under the given scope set. Returns true if one was found and removed.

Resolution goes through bestSlotLocked with matchAny FALSE — the literal call AmbientKeys and GetGlobalIndexWithScopes make — so delete cannot drift from the read surface. It removes exactly the binding a scoped read would have returned, and deleting a name owned only by a macro-introduced binder is a no-op rather than the destruction of a binding the caller could not read.

A nil scopes argument means NONE — the empty scope set, same as AmbientScopes() — and never MATCH ANY. Nil is indistinguishable from an uninitialized value, so resolving it permissively fails open: a caller that merely forgot to thread its scopes would delete across a hygiene boundary with nothing in the signature to flag it. Delete therefore has no wildcard mode at all; "remove the name and every hygiene-distinct binding under it" is a legitimate but different operation, and nothing asks for it.

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. This is the WILDCARD form: it matches any binding of the name regardless of scopes, which is what introspection and REPL completion mean. Compiler callers must use GetGlobalIndexWithScopes so a bare reference cannot reach a macro-introduced binder.

func (*GlobalEnvironmentFrame) GetGlobalIndexWithScopes added in v1.19.0

func (p *GlobalEnvironmentFrame) GetGlobalIndexWithScopes(key *values.Symbol, q syntax.ScopeSet) *GlobalIndex

GetGlobalIndexWithScopes returns the GlobalIndex for the binding of key whose scope set maximally matches scopes. A nil scopes slice means the EMPTY scope set, not "any scope set" — that distinction is the whole point of the split from GetGlobalIndex, since a reference written outside any macro expansion must not resolve to a binder introduced inside one. 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-slots mapping. A symbol may map to more than one slot: same-named bindings with different scope sets are different variables. Callers that only want names can range over the keys and ignore the slot lists. 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
	Slot  int
	// contains filtered or unexported fields
}

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). Slot addresses the binding within Env.bindings directly. It is meaningful ONLY when Env is non-nil: the two are set together by the frame that resolved the lookup, and a nil Env means no frame has been chosen yet, so the zero Slot is never consulted. This pairing is what lets a resolved global load index the bindings slice instead of re-hashing the symbol at every execution.

query is the hygiene key. For a deferred index (Env == nil) it is the reference's scope-set query, resolved against whatever environment is live when the instruction executes. For a PINNED index it is the query resolution matched on, kept so that re-resolution — which happens whenever the pinned slot no longer holds the binding, e.g. after a delete — stays inside the same hygiene boundary instead of falling back to bare name.

A wildcard query (AllScopes) re-resolves by bare name; a specific or empty query re-resolves under its scope set even when that set is empty, or a stale pinned index would silently cross a hygiene boundary after a delete-then-recreate: DeleteBinding nils the slots and drops the name, so once anything re-creates it a wildcard fallback would land on whatever binding now holds the name — including one whose scope set the reference could never reach. This one ScopeSet subsumes what a nil Scopes slice plus a scopeKeyed bool once encoded: a nil slice could not distinguish "matched the empty set" from "no key at all", and those demand opposite re-resolution.

func NewDeferredGlobalIndex added in v1.19.0

func NewDeferredGlobalIndex(key *values.Symbol, scopes []*syntax.Scope) *GlobalIndex

NewDeferredGlobalIndex creates a deferred GlobalIndex that carries the reference's scope set, so the execution-time parent-chain walk can resolve it hygienically rather than by bare name.

func NewGlobalIndex

func NewGlobalIndex(key *values.Symbol) *GlobalIndex

NewGlobalIndex creates a new deferred GlobalIndex for the given symbol. Env is nil, so Slot is not meaningful; use newResolvedGlobalIndex when the owning frame and slot are known. Its query is the wildcard (AllScopes): a deferred bare-name index re-resolves by name.

func (*GlobalIndex) EqualTo

func (p *GlobalIndex) EqualTo(value values.Value) bool

EqualTo returns true if this global index equals the given value.

Env participates in the comparison, by pointer. It is not provenance metadata: a non-nil Env is the binding store the VM reads and writes directly, with no parent walk (machine_context.go, OpLoadGlobal/OpStoreGlobal via GetOwnGlobalBinding and SetOwnGlobalValue). Two frames are two distinct `bindings` slices, so two GlobalIndex pinned to different frames denote different variables even when their symbol keys agree.

A nil Env is not "some frame we did not record" — it is a deferred lookup, resolved against whatever environment is live when the instruction executes. It is therefore never equal to a pinned index, even one whose frame today's walk would reach: the two are different operations, and a closure with a different env chain resolves them differently.

Slot participates whenever Env does. Once a frame keys its bindings by scope set, one symbol can name several distinct bindings in the same frame, so (Index, Env) no longer identifies a variable — the slot is what separates a macro-introduced binder from a user-written one of the same name.

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. A slot is reused only by a binder carrying the SAME scope set; any other scope set is a different variable and gets its own slot. Nil scopes means "match any".

Creation compares with scopeSetsEqual, not ScopesCompatible, for the reason spelled out at scopeSetsEqual (global_environment_frame.go): compatibility treats an empty binding scope set as matching anything, so a macro-introduced binder (scopes {m}) would reuse a scope-less binding of the same name instead of getting a slot of its own. Compatibility is the right predicate for LOOKUP, where a pre-hygiene binding is legitimately visible to every reference; it is the wrong one for deciding identity. This mirrors the global creation path rather than the local lookup path beside it.

A reused slot backfills Source but never Scopes. Under exact equality the slot already carries the scope set the caller asked for (or the caller passed nil and asked for nothing), so there is nothing to fill in; a Scopes write here could only overwrite an identity, which is the clobber the predicate above exists to prevent. Source is independent metadata and may legitimately be absent on an existing slot.

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, immutableLiterals,
  immutableTopLevel

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, formRegistry, inlineThreshold, maxExpandDepth, exportIndex —
  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)

Per-namespace, owned outright (never inherited; each namespace builds
its own, and a child's is unrelated to its parent's):
  sealedBase, sealedExpandBase, inlineHOFTemplates, effectiveRegistry,
  extensionState

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

AmbientKeys, not Keys: the listing reports only names resolvable under the ambient (empty) scope set, which is what both read families this listing serves look up — environment-ref/environment-bound? and namespace-ref/namespace-bound? all pass AmbientScopes. Listing a macro-introduced binder would break the listing's primary use, enumerate-then-dereference, on exactly the names it added. Moving any of those reads back to a wildcard silently re-opens that gap, since nothing here can detect it.

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) EffectiveRegistry added in v1.19.0

func (p *Namespace) EffectiveRegistry() any

EffectiveRegistry returns the narrowed registry the visible top level was bound from, or nil when nothing narrowed it (in which case Registry is the effective surface). The concrete type is *registry.Registry.

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 mutable user scope, lexical child of the child's own sealed base (wireRuntimeFrames), not a root
  • 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: []            |
                                  +-------------------------+

                         envC.parent ──► the child's OWN sealed base
                         +-------------------------------+
                         | EnvironmentFrame (sealedBaseC)|
                         | parent: nil (structural root) |
                         | global: sealed *GlobalEnvFrame|
                         +-------------------------------+

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
parentNS.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 WithChildRegistry and WithChildAuthorizer.

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) SealedExpandBase added in v1.19.0

func (p *Namespace) SealedExpandBase() *EnvironmentFrame

SealedExpandBase returns this Namespace's immutable sealed EXPAND-phase frame (phase 1): bootstrap macros and special-form primitive expanders, lexical parent of the mutable expand child. PER-NAMESPACE (like SealedBase), reached only via the parent chain, never a PhaseRegistry entry. Enumeration sites (,apropos, REPL completion) must collect it explicitly or bootstrap-macro names/docs vanish from introspection. See plans/2026-07-22-free-template-id-hygiene-impl.local.md (D1).

func (*Namespace) SetAuthorizer

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

SetAuthorizer sets the security authorizer for this namespace.

func (*Namespace) SetEffectiveRegistry added in v1.19.0

func (p *Namespace) SetEffectiveRegistry(reg any)

SetEffectiveRegistry records the narrowed registry the visible top level was bound from. Set once during bootstrap, after strict-namespace reduction and any dialect narrowing.

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.

On a child Namespace this returns 0: children have no map of their own and delegate interning to the parent. Call it on the root.

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 OriginRef added in v1.19.0

type OriginRef struct {
	RootLib  string
	RootName string
}

OriginRef identifies the provenance ROOT of a binding with library identity: the (define ...) that ultimately created it, addressed by the KEY of the library that defines it (RootLib) and the DEFINING name inside that library (RootName, invariant to any export/import renaming). It is value-identity — a library define and every import of it, however renamed or re-exported, carry equal OriginRefs — and is set once (at the library's finalization for a define, or by propagation at import) and never mutated, so it is safe to share across the copy-on-write BindingMeta path. A nil *OriginRef means a binding with NO library identity: a program-top-level (define ...), which is never imported and so is only ever compared as the identical object. Identity assumes RootLib (a LibraryName.Key()) names its library uniquely, the same key assumption ScopeKey/FreeIdKey already rely on.

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