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 ¶
- func BuildValidator(spec PrimitiveSpec) machine.ForeignFunction
- type ApplyOption
- type BindingSpec
- type CloseFunc
- type Closeable
- type Describer
- type DocSearchResult
- type Extension
- type ExtensionFunc
- type ExtensionOption
- type GlobalValue
- type InitFunc
- type LibraryDoc
- type LibraryExportDoc
- type LibraryExportSearcher
- type LibraryNamer
- type LibrarySearcher
- type NamespaceInit
- type PhaseSet
- type PrimitiveRegistration
- type PrimitiveRegistry
- func (p *PrimitiveRegistry) AddBinding(name string)
- func (p *PrimitiveRegistry) AddBindingSpecs(specs []BindingSpec)
- func (p *PrimitiveRegistry) AddBindings(names []string)
- func (p *PrimitiveRegistry) AddCloser(fn CloseFunc)
- func (p *PrimitiveRegistry) AddDocOnlyPrimitive(spec PrimitiveSpec)
- func (p *PrimitiveRegistry) AddDocumentation(name, doc string)
- func (p *PrimitiveRegistry) AddGlobalValue(name string, value values.Value)
- func (p *PrimitiveRegistry) AddInitFunc(f InitFunc)
- func (p *PrimitiveRegistry) AddMacroSource(source string)
- func (p *PrimitiveRegistry) AddNamespaceInit(fn NamespaceInit)
- func (p *PrimitiveRegistry) AddPrimitive(spec PrimitiveSpec, phases PhaseSet)
- func (p *PrimitiveRegistry) AddPrimitives(specs []PrimitiveSpec, phases PhaseSet)
- func (p *PrimitiveRegistry) AddProcedureSource(source string, dependsOn ...string)
- func (p *PrimitiveRegistry) Apply(ctx context.Context, env *environment.EnvironmentFrame, opts ...ApplyOption) error
- func (p *PrimitiveRegistry) ApplyDocs(env *environment.EnvironmentFrame)
- func (p *PrimitiveRegistry) BindingCount() int
- func (p *PrimitiveRegistry) BindingSpecs() []BindingSpec
- func (p *PrimitiveRegistry) Bindings() []string
- func (p *PrimitiveRegistry) Clone() *PrimitiveRegistry
- func (p *PrimitiveRegistry) Closers() []CloseFunc
- func (p *PrimitiveRegistry) DocPrimitives() []PrimitiveSpec
- func (p *PrimitiveRegistry) Docs() []BindingSpec
- func (p *PrimitiveRegistry) FindPrimitive(name string, phase PhaseSet) (PrimitiveRegistration, bool)
- func (p *PrimitiveRegistry) GlobalValues() []GlobalValue
- func (p *PrimitiveRegistry) HasPrimitive(name string, phase PhaseSet) bool
- func (p *PrimitiveRegistry) InitFuncs() []InitFunc
- func (p *PrimitiveRegistry) MacroSources() []string
- func (p *PrimitiveRegistry) PrimitiveByName(name string) (PrimitiveRegistration, bool)
- func (p *PrimitiveRegistry) PrimitiveCount() int
- func (p *PrimitiveRegistry) PrimitiveNames() []string
- func (p *PrimitiveRegistry) Primitives() []PrimitiveRegistration
- func (p *PrimitiveRegistry) PrimitivesByCategory() map[string][]PrimitiveRegistration
- func (p *PrimitiveRegistry) ProcedureSources() []string
- func (p *PrimitiveRegistry) RuntimePrimitiveNamesRange(startIndex, endIndex int) []string
- func (p *PrimitiveRegistry) WithProcedureSources(sources []string) *PrimitiveRegistry
- func (p *PrimitiveRegistry) Without(names ...string) *PrimitiveRegistry
- func (p *PrimitiveRegistry) WithoutBindings(names ...string) *PrimitiveRegistry
- func (p *PrimitiveRegistry) WithoutCategory(categories ...string) *PrimitiveRegistry
- type PrimitiveSpec
- type RegistryBuilder
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 ¶
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 ¶
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 ¶
func SearchDoc(reg *PrimitiveRegistry, env *environment.EnvironmentFrame, libs LibrarySearcher, exports LibraryExportSearcher, pattern string) []DocSearchResult
SearchDoc searches all documentation sources for case-insensitive substring matches on name, doc text, category, or keywords.
Sources searched in order:
- PrimitiveRegistry primitives — real primitives AND doc-only primitives (the latter carry full PrimitiveSpec metadata; both take precedence over non-primitive sources below).
- PrimitiveRegistry binding specs — includes real bindings AND simple DocOnly entries registered via AddDocumentation (post-Phase-1 unification).
- Environment bindings (if env is non-nil)
- Loaded libraries (if libs is non-nil)
- 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.
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 ¶
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:
- environment/phase_registry.go — add a Phase constant; ensure 0 ≤ value < phaseSetBits below if the new phase should be representable in a PhaseSet.
- registry/phase.go (this file) — add PhaseSet<Name> bit constant and extend phaseSetBits if the bitset width must grow.
- registry/apply.go — extend phaseTargets with the new phase if primitives may register at it.
- 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 ¶
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:
- PrimitiveRegistry struct (this file) — add the slice field
- NewRegistry (this file) — initialize the slice with a reasonable capacity
- Registry.deepCopy (this file) — extend the make + copy block to include the new slice
- Add<Category> / Add<Category>s — registration entry points (mutex-locked appender + optional singular forwarder)
- <Category>Count / <Category>s — accessor pair returning a count and a defensive copy
- 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 (*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
func (p *PrimitiveRegistry) Apply(ctx context.Context, env *environment.EnvironmentFrame, opts ...ApplyOption) error
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
func (p *PrimitiveRegistry) Clone() *PrimitiveRegistry
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.
Source Files
¶
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. |