registry

package
v1.20.0 Latest Latest
Warning

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

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

Documentation

Overview

Package registry provides a plugin architecture for registering Scheme primitives.

The registry allows extensions to register primitives that are applied to environments at initialization time. Primitives can be registered for different phases: runtime, expand-time, and compile-time.

Key Types

  • PrimitiveRegistry: central store for primitive registrations
  • PrimitiveSpec: defines a single primitive (name, params, implementation)
  • [Phase]: bit flags controlling environment placement (Runtime, Expand, Compile)
  • Extension: interface for modular primitive packages

Registration

reg := registry.NewRegistry()
reg.AddPrimitives([]registry.PrimitiveSpec{
    {Name: "my-func", ParamCount: 1, Impl: myFuncImpl},
}, registry.PhaseSetRuntime|registry.PhaseSetExpand)

Application

After registration, apply the registry to an environment:

err := reg.Apply(ctx, env)

Extensions

Extensions implement the Extension interface and can be composed:

var Extension = registry.NewExtension("myext", AddToRegistry)

The RegistryBuilder type provides a convenient way to compose multiple registration functions into a single extension.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildValidator

func BuildValidator(spec PrimitiveSpec) machine.ForeignFunction

BuildValidator creates a contract validation function from a PrimitiveSpec's ParamTypes. Returns nil if the spec has no type contracts (zero-overhead path for uncontracted primitives).

The returned function is installed on ForeignClosure.validate and runs after argument binding but before the implementation. Each argument is checked against its declared TypeConstraint via TypeConstraint.Check; a mismatch is wrapped with "<primitive>: argument <n>" context so the caller can locate the offending position.

For non-variadic primitives, each position 0..ParamCount-1 is checked against ParamTypes[i].

For variadic primitives, positions 0..ParamCount-2 are fixed args and are checked directly via mc.Arg(i). Position ParamCount-1 holds the rest list (a values.Tuple); each rest element is checked against the last entry in ParamTypes. When len(ParamTypes) is shorter than ParamCount (permitted by PrimitiveSpec.Validate), any fixed position i beyond len(ParamTypes)-1 reuses types[len(ParamTypes)-1] — the last declared constraint acts as the catch-all for unspecified fixed slots and for rest-list elements.

Types

type ApplyOption

type ApplyOption func(*applyConfig)

ApplyOption configures the behavior of PrimitiveRegistry.Apply. Options are applied in order; later options override earlier ones.

func WithContractEnforcement

func WithContractEnforcement() ApplyOption

WithContractEnforcement installs a type-checking validator on each registered primitive whose spec declares ParamTypes. The validator runs after argument binding and before the implementation, rejecting mismatched types with a wrapped error. Disabled by default — validators cost nothing when not installed (ForeignClosure.validate stays nil).

func WithRuntimeTarget

func WithRuntimeTarget(frame *environment.EnvironmentFrame) ApplyOption

WithRuntimeTarget routes PhaseRuntime primitive registration and global values into the given frame instead of env. frame is the sealed-write root view — used by bootstrap to seat primitives in the immutable sealed-write view. This option covers phase 0 only: expand-phase prims go to the phase-1 sealed-write view, which Apply derives from env itself, and compile-time bindings stay in env.Compile(). Defaults to env when unset, but LoadBootstrapCore always sets it, for the engine root and every library env alike — each into its OWN phase-0 sealed-write view (SealedWriteViewAt(PhaseRuntime)); there is no shared "sealed base" and no library env skips the carve.

func WithStableBasePrimitives

func WithStableBasePrimitives() ApplyOption

WithStableBasePrimitives stamps every capture-safe primitive (!spec.InvokesProcedure: +, -, car, cons, assq, vector-ref, sqrt, …) Stable when it is bound ambiently, so the frame-reclaim classifier may trust calls to it as non-rebindable without an explicit (import (scheme base)). Imported primitives are already immutable; this closes the ambient-registration path, where PrimitiveRegistry.Apply binds primitives directly into the base namespace without an Imported flag (Phase 2 finding #1).

Scope is the capture-safe set, which must match CaptureSafe (stamped above from the same !spec.InvokesProcedure): the classifier trusts a primitive callee only when CaptureSafe AND Stable both hold, so a capture-safe primitive left un-stamped here would be CaptureSafe yet never trusted. Procedure-invoking primitives (apply, map, sort, eval, with-exception-handler, …) are InvokesProcedure:true, so they are neither CaptureSafe nor stamped Stable and stay R7RS-mutable even under the flag.

Disabled by default. The engine appends it only under WithImmutableTopLevel(), where the set!/redefine enforcement (compile_validated.go) makes Stable a guarantee the classifier may rest a verdict on. The deviation it introduces — capture-safe primitives become non-rebindable — is exactly the opt-in optimization contract.

type BindingSpec

type BindingSpec struct {
	Name    string
	Doc     string
	DocOnly bool
}

BindingSpec defines a compile-time binding with optional documentation.

DocOnly entries carry doc text but do not install an environment binding — Apply skips them. They are appended via AddDocumentation or AddDocOnlyPrimitive and are the post-Phase-1 unification of what used to be a separate `docs []DocEntry` slice (collapsed per Finding 2 of plans/2026-05-18-registry-structural-reduction.md).

type CloseFunc added in v1.20.0

type CloseFunc func(env *environment.EnvironmentFrame) error

CloseFunc is a per-engine cleanup hook registered from an extension's AddToRegistry — which buildRegistry runs once per engine — and invoked by Engine.Close() with the closing engine's runtime environment frame.

The frame argument is what makes the seam per-engine, and a hook that ignores it is NOT per-engine however it was registered. On a registry shared across engines (WithRegistry) Apply is first-wins, so every engine binds the FIRST engine's registration of a name; a hook closing over state minted in its own AddToRegistry call would therefore reap a tracker that the running primitive never wrote to, while the first engine's hook reaped everyone's resources. A hook that instead reaches its state through env.Namespace().Root() sees exactly the resources created under the engine now closing, whoever's closure recorded them.

Distinct from registry.Closeable/WithClose, which hangs off the process-global Extension value: that one gets no engine handle at all.

type Closeable

type Closeable interface {
	Close() error
}

Closeable is an opt-in interface for extensions that hold resources (goroutines, file handles, connections) and need cleanup when the engine is shut down. Extensions that implement this interface will have Close called by Engine.Close().

type Describer

type Describer interface {
	Description() string
}

Describer is an optional interface that extensions can implement to provide a human-readable library description. The description is shown by ,doc (wile <ext>) and ,libraries in the REPL.

type DocSearchResult

type DocSearchResult struct {
	Name     string
	Doc      string
	Category string
	Keywords []string
}

DocSearchResult holds one search hit from SearchDoc.

func NonPrimitiveDocs

func NonPrimitiveDocs(reg *PrimitiveRegistry) []DocSearchResult

NonPrimitiveDocs returns doc search results from binding specs (including DocOnly entries registered via AddDocumentation). Entries added via AddDocOnlyPrimitive live in docPrimitives and are walked by SearchDoc directly. Each entry's Doc, Category, and Keywords are extracted via docparse.ParseDocstring.

Post-Phase-1: single walk. Pre-Phase-1 this walked BindingSpecs + Docs as two separate sources; they were unified into bindingSpecs (with DocOnly distinguishing them).

func SearchDoc

SearchDoc searches all documentation sources for case-insensitive substring matches on name, doc text, category, or keywords.

Sources searched in order:

  1. PrimitiveRegistry primitives — real primitives AND doc-only primitives (the latter carry full PrimitiveSpec metadata; both take precedence over non-primitive sources below).
  2. PrimitiveRegistry binding specs — includes real bindings AND simple DocOnly entries registered via AddDocumentation (post-Phase-1 unification).
  3. Environment bindings (if env is non-nil)
  4. Loaded libraries (if libs is non-nil)
  5. Unloaded library exports (if exports is non-nil)

Primitives take precedence over non-primitives with the same name. Results are sorted by name. env, libs, and exports may be nil.

type Extension

type Extension interface {
	// Name returns the extension name for logging/debugging.
	Name() string
	// AddToRegistry registers primitives with the registry.
	AddToRegistry(r *PrimitiveRegistry) error
}

Extension represents a loadable extension that adds primitives to a registry.

func NewDescribedExtension

func NewDescribedExtension(name, description string, fn func(*PrimitiveRegistry) error) Extension

NewDescribedExtension creates an Extension with a name, human-readable description, and registration function. It is the canonical constructor for simple extensions and is used by every shipped extension. It is shorthand for NewExtension(name, fn, WithDescription(description)); reach for the option form directly only when composing additional options.

func NewExtension

func NewExtension(name string, fn func(*PrimitiveRegistry) error, opts ...ExtensionOption) Extension

NewExtension creates an Extension from a name, a registration function, and zero or more capability options.

type ExtensionFunc

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

ExtensionFunc adapts a function to the Extension interface and carries the three optional capabilities as slots. The slots are populated via the ExtensionOption functions passed to NewExtension.

*ExtensionFunc unconditionally satisfies LibraryNamer, Describer, and Closeable. Unset slots are reported as the zero value (empty string, nil slice, nil error). Engine code interprets emptiness uniformly as "not set" and falls back to defaults — there is no behavioral distinction between "did not opt in" and "opted in with the zero value."

func (*ExtensionFunc) AddToRegistry

func (p *ExtensionFunc) AddToRegistry(r *PrimitiveRegistry) error

AddToRegistry registers primitives with the registry.

func (*ExtensionFunc) Close

func (p *ExtensionFunc) Close() error

Close runs the configured close hook, or returns nil if no hook was set.

func (*ExtensionFunc) Description

func (p *ExtensionFunc) Description() string

Description returns the extension's description, or "" if unset.

func (*ExtensionFunc) LibraryName

func (p *ExtensionFunc) LibraryName() []string

LibraryName returns the configured R7RS library name parts, or nil if unset. A nil/empty return signals "use the default (wile <name>)" to the engine.

func (*ExtensionFunc) Name

func (p *ExtensionFunc) Name() string

Name returns the extension name.

type ExtensionOption

type ExtensionOption func(*ExtensionFunc)

ExtensionOption configures an ExtensionFunc at construction time.

func WithClose

func WithClose(fn func() error) ExtensionOption

WithClose attaches a cleanup hook called by Engine.Close().

func WithDescription

func WithDescription(s string) ExtensionOption

WithDescription attaches a human-readable description, surfaced by ,doc (wile <ext>) and ,libraries in the REPL.

func WithLibraryName

func WithLibraryName(parts ...string) ExtensionOption

WithLibraryName overrides the default R7RS library name. The default, applied when this option is not used, is (wile <ext.Name()>).

type GlobalValue

type GlobalValue struct {
	Name  string
	Value values.Value
}

GlobalValue pairs a name with a value to be registered as a global binding.

type InitFunc

type InitFunc func() error

InitFunc is called after all primitives and global values are registered.

type LibraryDoc

type LibraryDoc struct {
	Name        string // canonical Scheme form, e.g. "(wile math)"
	Description string
}

LibraryDoc is a read-only view of a loaded library, sufficient for documentation search.

type LibraryExportDoc

type LibraryExportDoc struct {
	Name        string // canonical Scheme form, e.g. "(srfi 1)"
	Description string
	Exports     []string
}

LibraryExportDoc is a read-only view of an indexed (possibly unloaded) library and the names it exports.

type LibraryExportSearcher

type LibraryExportSearcher interface {
	AllLibraryExports() []LibraryExportDoc
}

LibraryExportSearcher enumerates indexed library exports for SearchDoc.

type LibraryNamer

type LibraryNamer interface {
	LibraryName() []string
}

LibraryNamer is an optional interface that extensions can implement to control their R7RS library name. Extensions that don't implement this get the default name (wile <ext.Name()>).

type LibrarySearcher

type LibrarySearcher interface {
	AllLibraries() []LibraryDoc
}

LibrarySearcher enumerates loaded libraries for SearchDoc. Defining the dependency as a narrow interface keeps the registry package free of any import of machine/compilation — the concrete adapter lives in the caller's package (see registry/core). Per the Interface Segregation Principle, SearchDoc depends only on the enumeration it uses, not on the full library-registry surface.

type NamespaceInit

type NamespaceInit func(env *environment.EnvironmentFrame) error

NamespaceInit is a per-engine initializer run by Apply once per engine, with that engine's runtime environment frame. Extensions use it to build per-Namespace state (e.g. I/O port parameters + caches) that must not be shared across engines.

type PhaseSet

type PhaseSet uint8

PhaseSet is a bitset over non-negative environment.Phase values, used to declare which phases a primitive is registered for. The companion type environment.Phase is a single RELATIVE level in one owner's macro tower; PhaseSet is the registration vocabulary that says "this primitive is available at runtime", "at runtime and expand", etc.

Registration only ever names the bottom of a tower, so the relative/absolute distinction does not bite here: Apply installs into the levels of the owner it is handed, and the set is built from compile-time constants naming that owner's own levels 0..2. A PhaseSet is not a way to address a climbed level.

Representable phases are 0 ≤ phase < phaseSetBits. environment.PhaseTemplate (-1) and any phase ≥ phaseSetBits are unrepresentable. With(unrepresentable) panics; Has(unrepresentable) returns false. Both ends of the illegal-value space are checked — silent shift overflow on a uint8 bitset would otherwise hide the upper bound.

PhaseSet is intended as a registration-time API: Add* helpers and the extension authoring path build PhaseSet values from compile-time constants. If a runtime caller appears (constructing a PhaseSet from a Scheme integer, for example), introduce an error-returning sibling such as TryWith rather than relying on the panic semantics here.

ADDING A NEW PHASE requires updates in these locations:

  1. environment/phase_registry.go — add a Phase constant; ensure 0 ≤ value < phaseSetBits below if the new phase should be representable in a PhaseSet.
  2. registry/phase.go (this file) — add PhaseSet<Name> bit constant and extend phaseSetBits if the bitset width must grow.
  3. registry/apply.go — extend phaseTargets with the new phase if primitives may register at it.
  4. wile/options.go — re-export the new environment.Phase constant (PhaseSet bit constants are named through the registry package directly).

The init() assertion below verifies bit positions match Phase indices, catching drift from steps (1)+(2).

const (
	PhaseSetRuntime PhaseSet = 1 << iota // matches environment.PhaseRuntime (=0)
	PhaseSetExpand                       // matches environment.PhaseExpand  (=1)
	PhaseSetCompile                      // matches environment.PhaseCompile (=2)
)

PhaseSet bit constants. Each bit position equals 1 << int(environment.Phase). init() asserts the values stay in sync with environment.Phase.

func (PhaseSet) Has

func (s PhaseSet) Has(p environment.Phase) bool

Has reports whether p is in the set. Returns false for any phase that cannot appear in a PhaseSet — negative phases (e.g., PhaseTemplate) and phases ≥ phaseSetBits.

func (PhaseSet) String

func (s PhaseSet) String() string

String returns a pipe-separated list of phase names in the set, or "none" if the set is empty.

func (PhaseSet) With

func (s PhaseSet) With(p environment.Phase) PhaseSet

With returns a new PhaseSet with p added. Panics if p is unrepresentable (negative, e.g. PhaseTemplate, or ≥ phaseSetBits — would silently shift past the bitset width). PhaseSet is a registration-time API; programmer errors at this layer should fail loudly. If a runtime caller is added, introduce a non-panicking sibling rather than weakening this contract.

type PrimitiveRegistration

type PrimitiveRegistration struct {
	Spec   PrimitiveSpec
	Phases PhaseSet
}

PrimitiveRegistration holds a primitive and its phases.

type PrimitiveRegistry added in v1.20.0

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

PrimitiveRegistry is the central registry for primitives.

ADDING A NEW REGISTRY CATEGORY requires updates in these locations:

  1. PrimitiveRegistry struct (this file) — add the slice field
  2. NewRegistry (this file) — initialize the slice with a reasonable capacity
  3. Registry.deepCopy (this file) — extend the make + copy block to include the new slice
  4. Add<Category> / Add<Category>s — registration entry points (mutex-locked appender + optional singular forwarder)
  5. <Category>Count / <Category>s — accessor pair returning a count and a defensive copy
  6. PrimitiveRegistry.Apply (registry/apply.go) — materialize the new category into the environment at the appropriate lifecycle step

All nine categories today (primitives, bindingSpecs, docPrimitives, initFuncs, macroSources, procedureSources, globalValues, namespaceInits, closeFuncs) follow this pattern, except that closeFuncs has no step 6: a cleanup hook is read by Engine.Close, not bound into an environment. Forgetting any step is a silent aliasing or drift hazard — Clone, Without, WithoutCategory, and WithoutBindings all assume deepCopy covers every field.

Note: the `docs []DocEntry` slice was removed in Phase 1 (plans/2026-05-18-registry-structural-reduction.md, Finding 2): simple AddDocumentation entries now share bindingSpecs (DocOnly=true); rich AddDocOnlyPrimitive entries moved to the dedicated docPrimitives slice (preserves full PrimitiveSpec metadata).

func NewRegistry

func NewRegistry() *PrimitiveRegistry

NewRegistry creates a new empty registry.

func (*PrimitiveRegistry) AddBinding added in v1.20.0

func (p *PrimitiveRegistry) AddBinding(name string)

AddBinding registers a compile-time only binding (no runtime value). Singular forwarder to AddBindings.

func (*PrimitiveRegistry) AddBindingSpecs added in v1.20.0

func (p *PrimitiveRegistry) AddBindingSpecs(specs []BindingSpec)

AddBindingSpecs registers multiple compile-time bindings with optional documentation.

func (*PrimitiveRegistry) AddBindings added in v1.20.0

func (p *PrimitiveRegistry) AddBindings(names []string)

AddBindings registers multiple compile-time only bindings. Forwarder to AddBindingSpecs; the spec-typed form is the single source of truth for mutex lifecycle.

func (*PrimitiveRegistry) AddCloser added in v1.20.0

func (p *PrimitiveRegistry) AddCloser(fn CloseFunc)

AddCloser registers a per-engine cleanup hook, collected by Engine.Close. Extensions holding OS resources (goroutines, child processes) call it from AddToRegistry, closing over the per-engine state that tracks those resources. See CloseFunc for why registry.Closeable cannot serve this.

func (*PrimitiveRegistry) AddDocOnlyPrimitive added in v1.20.0

func (p *PrimitiveRegistry) AddDocOnlyPrimitive(spec PrimitiveSpec)

AddDocOnlyPrimitive registers a documentation-only primitive entry. It does not create a runtime binding — used for Scheme-defined procedures that are already bound in the environment but need registry visibility for apropos/topics. Skips registration if a primitive with the same name already exists (Go primitives take precedence).

Post-Phase-1: the entry lands in the dedicated docPrimitives slice (separate tier from real primitives). The full PrimitiveSpec metadata — Category, ParamNames, ParamTypes, ReturnType, Keywords — is preserved. Surfaced by SearchDoc / ,doc / ,apropos / PrimitivesByCategory; not visible via Primitives() or FindPrimitive (those return real primitives only).

func (*PrimitiveRegistry) AddDocumentation added in v1.20.0

func (p *PrimitiveRegistry) AddDocumentation(name, doc string)

AddDocumentation registers a documentation entry for a named binding. The documentation is applied to existing bindings during ApplyDocs. Forwarder to AddBindingSpecs with DocOnly=true — the doc-only entry lives in the bindingSpecs slice but Apply skips installing a binding for it.

func (*PrimitiveRegistry) AddGlobalValue added in v1.20.0

func (p *PrimitiveRegistry) AddGlobalValue(name string, value values.Value)

AddGlobalValue registers a named value to be bound as a global variable. Unlike AddPrimitive, this takes an arbitrary Value rather than a ForeignFunction.

func (*PrimitiveRegistry) AddInitFunc added in v1.20.0

func (p *PrimitiveRegistry) AddInitFunc(f InitFunc)

AddInitFunc registers an initialization function.

func (*PrimitiveRegistry) AddMacroSource added in v1.20.0

func (p *PrimitiveRegistry) AddMacroSource(source string)

AddMacroSource adds Scheme source code for bootstrap macros.

func (*PrimitiveRegistry) AddNamespaceInit added in v1.20.0

func (p *PrimitiveRegistry) AddNamespaceInit(fn NamespaceInit)

AddNamespaceInit registers a per-engine initializer run by Apply once per engine, with that engine's runtime environment frame. Extensions use it to build per-Namespace state (e.g. I/O port parameters + caches) that must not be shared across engines.

func (*PrimitiveRegistry) AddPrimitive added in v1.20.0

func (p *PrimitiveRegistry) AddPrimitive(spec PrimitiveSpec, phases PhaseSet)

AddPrimitive registers a primitive with the given phases. Singular forwarder to AddPrimitives; the plural form is the single source of truth for validation + mutex lifecycle.

func (*PrimitiveRegistry) AddPrimitives added in v1.20.0

func (p *PrimitiveRegistry) AddPrimitives(specs []PrimitiveSpec, phases PhaseSet)

AddPrimitives registers multiple primitives with the given phases.

Panics on a spec that fails PrimitiveSpec.Validate. Callers building specs from anything other than source literals must Validate first — see that method for the contract.

Registering a name already registered at an overlapping phase is permitted and silent only when both registrations carry the same implementation — the shape produced by applying one extension twice. It is then the first registration that wins: it is what PrimitiveRegistry.FindPrimitive returns and what PrimitiveRegistry.Apply binds. A later same-implementation duplicate is inert — it is retained in the registration list (Primitives reports it) but never becomes the binding.

A duplicate carrying a *different* implementation is a conflict between two extensions rather than an override, and panics. Precedent: runtime.Scheme.AddKnownTypeWithName in k8s.io/apimachinery, where the same name with the same type is idempotent and the same name with a different type is fatal at init. Go func values are not comparable, so implementation identity here is the code pointer, reflect.ValueOf(spec.Impl).Pointer().

Without / WithoutCategory are attenuation only: they narrow the surface and do not license re-adding a same-named replacement. There is no supported way to override or patch an earlier primitive — remove-then-re-add is not one.

Precedence is per phase: the same name registered at runtime by one spec and at expand by another is two distinct bindings, not a duplicate.

func (*PrimitiveRegistry) AddProcedureSource added in v1.20.0

func (p *PrimitiveRegistry) AddProcedureSource(source string, dependsOn ...string)

AddProcedureSource registers a runtime-procedure source (define forms) to be loaded into the sealed-base frame, separate from macro sources (define-syntax forms) which load into the mutable expand frame.

dependsOn names the primitive CATEGORIES this source's body calls. WithoutCategory uses it to drop the source alongside the category, because a bootstrap procedure written against primitives the caller removed cannot compile — it fails inside NewEngine with a "no such binding" naming a name the caller deliberately deleted, which is a confusing way to learn that a documented filter has an undocumented dependency.

Omitting it is the historical behaviour and stays legal: most bootstrap sources depend on categories no one removes (pairs, lists), and declaring those buys nothing. Declare it for a source over a category a sandbox might plausibly strip.

func (*PrimitiveRegistry) Apply added in v1.20.0

Apply materializes registry contents into an environment, in order: compile-time bindings, runtime/expand-time primitives, global values, per-engine namespace initializers, then init functions.

func (*PrimitiveRegistry) ApplyDocs added in v1.20.0

func (p *PrimitiveRegistry) ApplyDocs(env *environment.EnvironmentFrame)

ApplyDocs attaches documentation entries to existing bindings in the environment. It searches all phases for each documented name and sets the doc string on every matching binding. This is necessary because some names (e.g., special forms) have bindings in multiple phases (expand and compile), and the REPL's ,doc command may find any of them.

Post-Phase-1: single walk over bindingSpecs (both real bindings with non-empty Doc and DocOnly entries land here). The earlier two-source merge — `docs` slice + bindingSpecs casted via DocEntry(spec) — collapsed when DocEntry was unified into BindingSpec.

func (*PrimitiveRegistry) BindingCount added in v1.20.0

func (p *PrimitiveRegistry) BindingCount() int

BindingCount returns the number of compile-time bindings.

func (*PrimitiveRegistry) BindingSpecs added in v1.20.0

func (p *PrimitiveRegistry) BindingSpecs() []BindingSpec

BindingSpecs returns a defensive copy of the compile-time binding specs.

func (*PrimitiveRegistry) Bindings added in v1.20.0

func (p *PrimitiveRegistry) Bindings() []string

Bindings returns the names of real compile-time bindings (DocOnly=false). DocOnly entries are excluded — use Docs() for those. BindingSpecs() returns the unfiltered slice if both are needed.

func (*PrimitiveRegistry) Clone added in v1.20.0

Clone creates a copy of the registry.

func (*PrimitiveRegistry) Closers added in v1.20.0

func (p *PrimitiveRegistry) Closers() []CloseFunc

Closers returns a defensive copy of the registered per-engine cleanup hooks.

A registry reused across engines (WithRegistry) accumulates one hook per engine that loaded the extension, and those hooks are duplicates of each other, not one-per-engine handles: an engine takes only the hooks registered by its own extension loop (see buildRegistry's startClosers snapshot) purely so a hook is not run N times. Which engine's resources a hook reaps is decided by the environment frame Engine.Close hands it, not by which loop registered it — see CloseFunc.

func (*PrimitiveRegistry) DocPrimitives added in v1.20.0

func (p *PrimitiveRegistry) DocPrimitives() []PrimitiveSpec

DocPrimitives returns a defensive copy of the doc-only primitives (entries registered via AddDocOnlyPrimitive). These carry the full PrimitiveSpec metadata (Category, ParamNames, ParamTypes, ReturnType, Keywords) but no Impl and no environment binding — used to surface Scheme-defined procedures via ,doc / ,apropos / SearchDoc.

func (*PrimitiveRegistry) Docs added in v1.20.0

func (p *PrimitiveRegistry) Docs() []BindingSpec

Docs returns a defensive copy of the doc-only binding specs (those with DocOnly=true). These are simple Name+Doc entries registered via AddDocumentation — they carry doc text but install no environment binding. Real bindings (DocOnly=false) are returned by Bindings; BindingSpecs returns the unfiltered slice.

Doc-only *primitives* with full metadata (registered via AddDocOnlyPrimitive) live in DocPrimitives, not here.

func (*PrimitiveRegistry) FindPrimitive added in v1.20.0

func (p *PrimitiveRegistry) FindPrimitive(name string, phase PhaseSet) (PrimitiveRegistration, bool)

FindPrimitive returns the first registered primitive with the given name. If phase is the empty PhaseSet (zero), any phase matches; otherwise only primitives whose Phases overlap with phase are considered.

First-match is the registry's duplicate-name precedence, not an accident of iteration order: PrimitiveRegistry.Apply binds the same first registration, so what this reports (and what ,doc renders from it) is what a caller actually invokes. See PrimitiveRegistry.AddPrimitives.

When phase is zero, doc-only primitives (registered via AddDocOnlyPrimitive, stored in docPrimitives with Phases=0) are returned as a fallback after the real-primitives search. When phase is non-zero, doc-only primitives are never returned — they have Phases=0 which cannot overlap.

func (*PrimitiveRegistry) GlobalValues added in v1.20.0

func (p *PrimitiveRegistry) GlobalValues() []GlobalValue

GlobalValues returns a copy of the global value registrations.

func (*PrimitiveRegistry) HasPrimitive added in v1.20.0

func (p *PrimitiveRegistry) HasPrimitive(name string, phase PhaseSet) bool

HasPrimitive reports whether a primitive with the given name is registered. If phase is the empty PhaseSet (zero), any phase matches; otherwise only primitives whose Phases overlap with phase are considered.

func (*PrimitiveRegistry) InitFuncs added in v1.20.0

func (p *PrimitiveRegistry) InitFuncs() []InitFunc

InitFuncs returns a copy of the initialization functions.

func (*PrimitiveRegistry) MacroSources added in v1.20.0

func (p *PrimitiveRegistry) MacroSources() []string

MacroSources returns copies of macro source strings.

func (*PrimitiveRegistry) PrimitiveByName added in v1.20.0

func (p *PrimitiveRegistry) PrimitiveByName(name string) (PrimitiveRegistration, bool)

PrimitiveByName returns the registration for the named primitive, if any. Real primitives take precedence; doc-only primitives (Phases=0) are returned as a fallback when no real primitive with that name exists.

func (*PrimitiveRegistry) PrimitiveCount added in v1.20.0

func (p *PrimitiveRegistry) PrimitiveCount() int

PrimitiveCount returns the number of registered primitives.

func (*PrimitiveRegistry) PrimitiveNames added in v1.20.0

func (p *PrimitiveRegistry) PrimitiveNames() []string

PrimitiveNames returns the names of all registered primitives in registration order.

func (*PrimitiveRegistry) Primitives added in v1.20.0

func (p *PrimitiveRegistry) Primitives() []PrimitiveRegistration

Primitives returns a copy of the primitive registrations.

func (*PrimitiveRegistry) PrimitivesByCategory added in v1.20.0

func (p *PrimitiveRegistry) PrimitivesByCategory() map[string][]PrimitiveRegistration

PrimitivesByCategory returns registered primitives grouped by category. Primitives with no category are grouped under the empty string key. Includes both real primitives and doc-only primitives (the latter surface under their declared Category for ,doc / ,topics presentation). Doc-only entries appear with Phases=0 in the result.

func (*PrimitiveRegistry) ProcedureSources added in v1.20.0

func (p *PrimitiveRegistry) ProcedureSources() []string

ProcedureSources returns copies of procedure source strings.

func (*PrimitiveRegistry) RuntimePrimitiveNamesRange added in v1.20.0

func (p *PrimitiveRegistry) RuntimePrimitiveNamesRange(startIndex, endIndex int) []string

RuntimePrimitiveNamesRange returns the names of runtime primitives registered in the index range [startIndex, endIndex). If endIndex is negative, all primitives from startIndex onward are included. Negative startIndex is treated as 0.

func (*PrimitiveRegistry) WithProcedureSources added in v1.20.0

func (p *PrimitiveRegistry) WithProcedureSources(sources []string) *PrimitiveRegistry

WithProcedureSources returns a new PrimitiveRegistry whose bootstrap procedure sources are replaced by the given slice; all other fields are copied unchanged via deepCopy. Used by a dialect to substitute a bootstrap fragment (e.g. swap the mutating vector-map/string-map for a mutation-free one) without mutating the shared registry. The receiver is never modified.

func (*PrimitiveRegistry) Without added in v1.20.0

func (p *PrimitiveRegistry) Without(names ...string) *PrimitiveRegistry

Without returns a new PrimitiveRegistry with the named primitives removed. Names that don't match any registered primitive are silently ignored. All non-primitive fields are copied unchanged via deepCopy.

func (*PrimitiveRegistry) WithoutBindings added in v1.20.0

func (p *PrimitiveRegistry) WithoutBindings(names ...string) *PrimitiveRegistry

WithoutBindings returns a new PrimitiveRegistry with the named compile-time bindings removed. Use after Without to fully erase a name that exists as both a primitive and a compile-time binding (e.g., set!). All other fields are copied unchanged via deepCopy.

Post-Phase-1: only real bindings (DocOnly=false) are removed. DocOnly entries with the same name are preserved — they carry documentation for names that may live elsewhere (Scheme-defined procedures, library exports). Removing them would silently strip docs that the embedder likely wants kept.

func (*PrimitiveRegistry) WithoutCategory added in v1.20.0

func (p *PrimitiveRegistry) WithoutCategory(categories ...string) *PrimitiveRegistry

WithoutCategory returns a new PrimitiveRegistry with all primitives in the named categories removed. Categories are matched against PrimitiveSpec.Category. All non-primitive fields are copied unchanged via deepCopy.

type PrimitiveSpec

type PrimitiveSpec struct {
	Name       string
	ParamCount int
	IsVariadic bool
	Impl       machine.ForeignFunction
	Doc        string   // optional: brief description
	ParamNames []string // optional: parameter names
	Category   string   // optional: grouping category
	// ParamTypes is an optional type contract per parameter. When IsVariadic
	// is true, the last slot annotates the per-element type of the rest list
	// (the Tuple at mc.Arg(ParamCount-1)), not the type of the rest list
	// itself. So `+` declares ParamCount:1, IsVariadic:true, ParamTypes:[TypeNumber],
	// meaning "every element of the variadic tail must be a Number."
	ParamTypes []values.TypeConstraint
	ReturnType values.TypeConstraint // optional: return type (nil = unspecified)
	Keywords   []string              // optional: searchable tags
	// InvokesProcedure marks a primitive that may call back into a Scheme
	// procedure (apply, map, sort, for-each, call/cc, dynamic-wind, …) and could
	// thereby transitively capture a continuation. Default false = "does not invoke
	// a Scheme procedure" = capture-safe: the frame-reclaim classifier may trust a
	// tail call to it as non-capturing. The flipped default is a SOUNDNESS
	// COMMITMENT — an unannotated procedure-invoking primitive would be wrongly
	// trusted.
	//
	// REQUIRED for any primitive whose Impl reaches ApplyCallable or runs a
	// sub-context (sub.Run()): set InvokesProcedure:true. Three guards in pkg/wile
	// enforce this, and a new primitive must satisfy all three:
	//   - TestInvokesProcedureStaticGuard discovers the requirement statically (AST
	//     of the Impl's call graph) and fails CI when the annotation is missing.
	//   - TestProcedureInvokersMatchesInvokesProcedure derives the pkg/wile name
	//     list from these annotations in both directions, so annotating a primitive
	//     without adding its name to procedureInvokers (capture_safety_test.go)
	//     fails too — as does listing a name nothing annotates.
	//   - TestInvokesProcedureCompleteness pins that list behaviorally
	//     (IsCaptureSafe()==false on the live binding).
	// The set is no longer curated: the second guard is what makes the first two
	// agree, so "add the annotation" and "add the name" are one change.
	//
	// It flows to BindingMeta.CaptureSafe at registration; the classifier reads the
	// binding flag because pkg/internal/validate must not import pkg/registry.
	InvokesProcedure bool
	// Mutates marks a primitive that destructively updates an existing Scheme
	// value — a pair, vector, string, bytevector, hashtable, box, parameter
	// slot, atomic cell or record field that the program is already holding.
	// Default false = "does not destructively update an existing value".
	//
	// REQUIRED for any primitive whose Impl — or a closure that Impl RETURNS,
	// which is how record-modifier escaped for as long as it did — reaches a
	// values-package mutator. TestMutatesMatchesMutationPrimitives pins the
	// annotated set against the NoMutation dialect's removal list, and
	// TestNoMutationRemovesEveryDestructivePrimitive checks the engine
	// actually drops each one.
	//
	// It replaces a "!"-suffix heuristic. The suffix is a naming convention,
	// not a semantic one, and it was wrong in both directions: record-modifier
	// destructively sets a record field and has no bang, so a NoMutation
	// engine handed out a working field setter; and fifteen bang-suffixed
	// primitives (thread-start!, mutex-lock!, set-current-directory!, …) act
	// on the world rather than on a value a program holds, so they must NOT be
	// removed. A property about what a primitive DOES cannot be read off its
	// spelling.
	//
	// Unlike InvokesProcedure this does not flow to BindingMeta: the only
	// consumer is the dialect's removal list, which is consulted once at
	// engine construction.
	//
	// TWO LIMITS, both known and both deliberate.
	//
	// It has no static guard. InvokesProcedure has one — an AST walk of each
	// Impl's call graph — and this does not, so the ratchet pins the
	// annotation against the removal list but derives NEITHER from the code.
	// A new destructive primitive that is neither annotated nor bang-suffixed
	// is invisible to both, which is exactly how record-modifier survived. See
	// the TODO.md row for the SSA pass that would close it.
	//
	// A mutator reachable only by APPLYING a returned object is out of reach
	// by construction. make-parameter is the live case: (p v) on a parameter
	// object destructively sets it, and a NoMutation engine still permits
	// that. Annotating make-parameter would not fix it — it would remove
	// parameterize, which is R7RS 4.2.6 dynamic binding, not mutation, and
	// which works on a NoMutation engine today. That gap belongs to the
	// dialect's stated language-surface boundary, not to this field.
	Mutates bool
	// Identity, when non-nil, is stamped onto every ForeignClosure Apply builds
	// from this spec, so Go code can ask "is this value the registered X?" of a
	// procedure it was handed. Mint it once at package scope with
	// machine.NewPrimitiveIdentity; see that type for why the closure pointer
	// cannot answer the question.
	//
	// Two consumers ask it, for opposite reasons.
	//
	// A primitive that must RECOGNIZE another — make-hashtable deciding whether
	// its (equal-hash, equal?) arguments are the built-in pair.
	//
	// Promoted-opcode dispatch (the promotedOps descriptors in pkg/machine).
	// Both the peephole optimizer and the VM's runtime guard refuse any closure
	// whose identity is not the descriptor's token, so a promoted primitive that
	// loses its Identity is still CORRECT — it just stops being inlined, for
	// good, and no value assertion notices. TestPromotedPrimitivesCarryTheirIdentity
	// is the ratchet against that.
	Identity *machine.PrimitiveIdentity
}

PrimitiveSpec defines a primitive to be registered.

func (PrimitiveSpec) Validate added in v1.19.0

func (p PrimitiveSpec) Validate() error

Validate reports the first internal inconsistency in the spec, or nil.

An empty Name (the primitive's lookup key) or a nil Impl is rejected as an internal inconsistency. A nil Impl passes registration but panics on the first call that dispatches to it, the exact host crash Validate exists to let an embedder pre-empt. A compile-time-only binding with no runtime value belongs on the PrimitiveRegistry.AddDocOnlyPrimitive / [Registry.AddBinding] paths, which do not run this check.

A variadic spec must have ParamCount >= 1: the rest parameter occupies slot ParamCount-1, so ParamCount:0 would make bindArgs index bnds[:-1] and panic on first call (machine/arity.go). For ParamTypes (when non-empty): non-variadic requires len == ParamCount; variadic requires len in [1, ParamCount].

[Registry.AddPrimitive] and PrimitiveRegistry.AddPrimitives panic on a spec that fails this check, on the contract that specs are source literals whose shape is fixed at compile time (the regexp.MustCompile idiom). An embedder that assembles a spec dynamically — from config, a plugin manifest, reflection — is outside that contract and must call Validate first: registering an invalid spec takes down the host process.

type RegistryBuilder

type RegistryBuilder []func(*PrimitiveRegistry) error

RegistryBuilder collects functions that add primitives to a registry.

func NewRegistryBuilder

func NewRegistryBuilder(funcs ...func(*PrimitiveRegistry) error) RegistryBuilder

NewRegistryBuilder creates a builder with the given registration functions.

func (RegistryBuilder) AddToRegistry

func (p RegistryBuilder) AddToRegistry(r *PrimitiveRegistry) error

AddToRegistry applies all registration functions to the registry.

func (RegistryBuilder) Build

func (p RegistryBuilder) Build() (*PrimitiveRegistry, error)

Build creates a new registry and applies all registration functions.

func (*RegistryBuilder) Register

func (p *RegistryBuilder) Register(funcs ...func(*PrimitiveRegistry) error)

Register adds registration functions to the builder.

Directories

Path Synopsis
Package core provides the essential primitives required for Scheme to function.
Package core provides the essential primitives required for Scheme to function.
Package helpers provides shared utility functions for primitive implementations.
Package helpers provides shared utility functions for primitive implementations.
Package testhelpers provides shared test infrastructure for Scheme primitive tests.
Package testhelpers provides shared test infrastructure for Scheme primitive tests.

Jump to

Keyboard shortcuts

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