Documentation
¶
Overview ¶
Package lisp implements ELPS values, environments, evaluation, and reusable templates for constructing independent VMs.
Loading and evaluating ¶
An LEnv is an executable environment. Configure its Runtime, initialize the required packages, and load the program before executing requests. A VM must not be evaluated concurrently by multiple goroutines.
Reusable templates ¶
The supported lifecycle is initial load, NewTemplate, then many calls to Template.NewVM. A Template is an immutable construction plan, not an executable environment. Publication validates and indexes the source graph once; NewVM allocates private mutable objects and reconnects their indexed references without rediscovering the graph. The Template example demonstrates minimal instance isolation.
The source must be quiescent throughout NewTemplate: no evaluation is in progress, there are no active call frames or condition handlers, and no goroutine or host callback is changing its reachable state. Quiescence is a caller-held exclusion requirement, not a lock acquired by NewTemplate. The implementation rejects recorded active evaluation state, but that check cannot synchronize with a concurrent writer. After publication, the source may resume evaluation independently of the plan.
For replayable initialization and equivalent request context and external state, separately cold-loaded VMs and template-constructed VMs must produce the same values, errors, and effects. Mutation in one VM must not affect the source, another VM, or a later instance. Aliases and slice views within a VM retain their meaning. Load-time clock, randomness, ledger reads, and effects must run per instance when a cold load would observe them per instance; NewTemplate cannot infer that requirement or replay those effects.
A saved error with a diagnostic call-stack payload makes publication fail, even if the stack is empty; embedders can then cold-load the program instead. Ordinary errors can carry these stacks without a debugger attached. The boundary concerns diagnostics retained in the state being published, not errors generated during later requests: those remain supported and private to the instance. A stack-free error value is not rejected on this basis. Publication also requires the live Runtime.Stack.GoStack to be nil and rejects byte values retaining that field's slice header, even while nil. Otherwise a later byte mutation could change how Lisp classifies and catches errors. Ordinary unretained request errors do not prevent later publication.
Ownership and host code ¶
Mutable values, scopes, maps, byte/cell storage, and explicit builtin captures are private to each VM. Transitively sealed code and read-only function formals/body vectors may be shared. Embedders must not write their exported fields or backing storage. Use the supported value operations and copy mutable data before changing it.
TemplateWithBuiltinPolicy and TemplateWithNativePolicy are explicit host contracts, not automatic proofs about arbitrary Go closures or native graphs. Approve only audited callbacks and transitively immutable native values. Request-bound services belong in the new VM's context via VMWithContext; callbacks obtain them from the environment passed to each invocation. Readers, source libraries, load caches and diagnostic writers are shared services and must satisfy their read-only/concurrency contracts. VMWithStderr can install a per-instance writer.
Shared Go pointers and slices keep their entire enclosing allocations alive, not just the fields or elements accessible through them. Host-provided code backing or frozen metadata can therefore indirectly retain a source VM even when the reconstructed mutable graph contains no source references. Isolation does not imply a garbage-collection guarantee for shared Go allocations.
Verification and limits ¶
ELPS CI runs elpsvet ownership, freshness/alias and location-escape checks, normal and checked-build tests, race detection, and differential fuzzing of cold-load parity and isolation. Negative controls deliberately corrupt sharing and observations to test the oracles themselves. These checks complement one another: elpsvet is not a complete alias analysis, checked builds cannot intercept arbitrary Go field stores, and bounded fuzzing is not a proof over every program. Consumers must also run the analyzers over their own Go code.
The full design argument, admission rules, sharing contracts, migration guide, and executable coverage matrix are in the repository's template design.
Index ¶
- Constants
- Variables
- func CapturedNames(fn *LVal) ([]string, bool)
- func ChargeRecord(env *LEnv, n int) error
- func ChargeStartedKiB(env *LEnv, n int) error
- func ConditionIsA(c, ancestor string) bool
- func ConditionOf(err error) string
- func Decode[T any](v *LVal) (T, error)
- func DecodeInto(v *LVal, dst any) error
- func DeprecationNotice(doc string) (string, bool)
- func GoError(v *LVal) error
- func GoFloat64(v *LVal) (float64, bool)
- func GoInt(v *LVal) (int, bool)
- func GoMap(v *LVal) (map[any]any, bool)
- func GoMapOf[K comparable, V any](v *LVal) (map[K]V, bool)
- func GoMapOfWithRuntime[K comparable, V any](rt *Runtime, v *LVal) (map[K]V, bool)
- func GoMapWithRuntime(rt *Runtime, v *LVal) (map[any]any, bool)
- func GoSlice(v *LVal) ([]any, bool)
- func GoSliceOf[T any](v *LVal) ([]T, bool)
- func GoSliceOfWithRuntime[T any](rt *Runtime, v *LVal) ([]T, bool)
- func GoSliceWithRuntime(rt *Runtime, v *LVal) ([]any, bool)
- func GoString(v *LVal) (string, bool)
- func GoValue(v *LVal) any
- func GoValueWithRuntime(rt *Runtime, v *LVal) any
- func GrowCap(c, n int) int
- func IsInternalPanic(v *LVal) bool
- func JoinDocStrings(parts []string) string
- func Lookup[T any, K Key](m MapView, key K) (T, bool)
- func NativeValue[T any](v *LVal) (T, bool)
- func Not(v *LVal) bool
- func RegisterDefaultBuiltin(name string, formals *LVal, fn LBuiltin)
- func RegisterDefaultMacro(name string, formals *LVal, fn LBuiltin)
- func RegisterDefaultSpecialOp(name string, formals *LVal, fn LBuiltin)
- func RequireNative[T any](v *LVal) (T, error)
- func ResetOwnershipTable()
- func ResultAs[T any](v *LVal, err error) (T, error)
- func SealedASTFingerprint(roots []*LVal) uint64
- func SeqOf[T any](v *LVal) ([]T, bool)
- func SetMatchTypes(rt *Runtime, is MatchTypeFunc, field MatchFieldFunc) error
- func SymbolName(v *LVal) (string, bool)
- func True(v *LVal) bool
- func VerifySealedASTs() error
- type ArgDecoder
- func CustomArg[T any](f func(a *ArgReader, i int) T) ArgDecoder[T]
- func OptArg() ArgDecoder[*LVal]
- func OptIntArg(what string, def int) ArgDecoder[int]
- func OptStringArg(what string, def string) ArgDecoder[string]
- func StringArg(what string) ArgDecoder[string]
- func TypedArg(t LType, format string) ArgDecoder[*LVal]
- func ValueArg() ArgDecoder[*LVal]
- type ArgReader
- func (a *ArgReader) Bytes(i int, what string) []byte
- func (a *ArgReader) Check(ok bool, format string, args ...any) bool
- func (a *ArgReader) Err() *LVal
- func (a *ArgReader) Fun(i int, what string) *LVal
- func (a *ArgReader) Int(i int, what string) int
- func (a *ArgReader) Map(i int, what string) *LVal
- func (a *ArgReader) Opt(i int) *LVal
- func (a *ArgReader) OptInt(i int, what string, def int) int
- func (a *ArgReader) OptString(i int, what string, def string) string
- func (a *ArgReader) Seq(i int, what string) []*LVal
- func (a *ArgReader) String(i int, what string) string
- func (a *ArgReader) Typed(i int, t LType, format string) *LVal
- func (a *ArgReader) Value(i int) *LVal
- type BindOpts
- type Binding
- type BuiltinRef
- type CachedSource
- type CallFrame
- type CallStack
- func (s *CallStack) CheckHeight() error
- func (s *CallStack) CheckTailCall() error
- func (s *CallStack) CheckTailIterations() error
- func (s *CallStack) Copy() *CallStack
- func (s *CallStack) DebugPrint(w io.Writer) (int, error)
- func (s *CallStack) Pop() CallFrame
- func (s *CallStack) PushFID(src *token.Location, fid string, pkg string, name string) error
- func (s *CallStack) SourceSite() string
- func (s *CallStack) TerminalFID(fid string) int
- func (s *CallStack) Top() *CallFrame
- type CapturedFun
- type CellReader
- func (r *CellReader) Bytes() []byte
- func (r *CellReader) Err() *LVal
- func (r *CellReader) Float() float64
- func (r *CellReader) Fun() *LVal
- func (r *CellReader) Int() int
- func (r *CellReader) Map() MapView
- func (r *CellReader) Name() string
- func (r *CellReader) OptFloat(def float64) float64
- func (r *CellReader) OptInt(def int) int
- func (r *CellReader) OptName(def string) string
- func (r *CellReader) OptStr(def string) string
- func (r *CellReader) OptValue() *LVal
- func (r *CellReader) Rest() Cells
- func (r *CellReader) Seq() Cells
- func (r *CellReader) Str() string
- func (r *CellReader) Text() []byte
- func (r *CellReader) Value() *LVal
- type Cells
- func (c Cells) Append(xs ...*LVal) Cells
- func (c Cells) Clone() Cells
- func (c Cells) List() *LVal
- func (c Cells) Map(f func(*LVal) *LVal) Cells
- func (c Cells) MapIfChanged(f func(*LVal) *LVal) (Cells, bool)
- func (c Cells) Read(env *LEnv) CellReader
- func (c Cells) SExpr() *LVal
- func (c Cells) Strings() ([]string, *LVal)
- func (c Cells) Vector() *LVal
- type ClosureIssue
- type ClosureIssueKind
- type CodeVisitor
- type CodeWalker
- type CodecError
- type Config
- func WithContext(ctx context.Context) Config
- func WithDebugger(d Debugger) Config
- func WithLegacyKeywordFormals(on bool) Config
- func WithLibrary(l SourceLibrary) Config
- func WithLoader(fn Loader) Config
- func WithMaxAlloc(n int) Config
- func WithMaxEvalNesting(n int) Config
- func WithMaxMacroExpansionDepth(n int) Config
- func WithMaxSleep(d time.Duration) Config
- func WithMaxSteps(n int64) Config
- func WithMaxTailIterations(n int) Config
- func WithMaxValueDepth(n int) Config
- func WithMaximumLogicalStackHeight(n int) Config
- func WithMaximumPhysicalStackHeight(n int) Config
- func WithReader(r Reader) Config
- func WithStderr(w io.Writer) Config
- type DebugAction
- type Debugger
- type DiagnosticRenderer
- type ErrorVal
- func (e *ErrorVal) Condition() string
- func (e *ErrorVal) Error() string
- func (e *ErrorVal) ErrorContext() []string
- func (e *ErrorVal) ErrorMessage() string
- func (e *ErrorVal) ErrorMessageContext(ctx context.Context) string
- func (e *ErrorVal) FunName() string
- func (e *ErrorVal) Source() (token.Location, bool)
- func (e *ErrorVal) Unwrap() error
- func (e *ErrorVal) WriteDiagnosticContext(ctx context.Context, w io.Writer, renderer *diagnostic.Renderer, ...) (int, error)
- func (e *ErrorVal) WriteTrace(w io.Writer) (int, error)
- func (e *ErrorVal) WriteTraceContext(ctx context.Context, w io.Writer) (int, error)
- type FSLibrary
- type FunctionInfo
- type GenSyms
- type GoHelper
- type Key
- type LBuiltin
- func Func1[A any](da ArgDecoder[A], f func(env *LEnv, a A) *LVal) LBuiltin
- func Func1E[A any, R funcResult](f func(env *LEnv, a A) (R, error)) LBuiltin
- func Func2[A, B any](da ArgDecoder[A], db ArgDecoder[B], f func(env *LEnv, a A, b B) *LVal) LBuiltin
- func Func2E[A, B any, R funcResult](f func(env *LEnv, a A, b B) (R, error)) LBuiltin
- func Func3[A, B, C any](da ArgDecoder[A], db ArgDecoder[B], dc ArgDecoder[C], ...) LBuiltin
- func Func3E[A, B, C any, R funcResult](f func(env *LEnv, a A, b B, c C) (R, error)) LBuiltin
- func FuncE(f func(env *LEnv, args *LVal) (*LVal, error)) LBuiltin
- type LBuiltinDef
- type LEnv
- func (env *LEnv) Add(xs ...*LVal) (*LVal, error)
- func (env *LEnv) AddBuiltins(external bool, funs ...LBuiltinDef)
- func (env *LEnv) AddMacros(external bool, macs ...LBuiltinDef)
- func (env *LEnv) AddSpecialOps(external bool, ops ...LBuiltinDef)
- func (env *LEnv) Append(kind SeqKind, seq *LVal, vals ...*LVal) (*LVal, error)
- func (env *LEnv) AppendInPlace(seq *LVal, vals ...*LVal) (*LVal, error)
- func (env *LEnv) ApplyFunction(fn, args *LVal) (*LVal, error)
- func (env *LEnv) BindBuiltins(opts BindOpts, defs ...LBuiltinDef) *LVal
- func (env *LEnv) Bindings() iter.Seq2[string, *LVal]
- func (env *LEnv) CallBuiltin(b BuiltinRef, args ...*LVal) (*LVal, error)
- func (env *LEnv) CallFunction(fn *LVal, args ...*LVal) (*LVal, error)
- func (env *LEnv) CallGlobal(sym string, args ...*LVal) (*LVal, error)
- func (env *LEnv) CallValue(fn *LVal, args ...*LVal) (*LVal, error)
- func (env *LEnv) Car(lis *LVal) (*LVal, error)
- func (env *LEnv) Cdr(lis *LVal) (*LVal, error)
- func (env *LEnv) ChargeSteps(n int64) error
- func (env *LEnv) CheckAlloc(n int) error
- func (env *LEnv) CheckAllocs(sizes ...int) error
- func (env *LEnv) CheckCallable(fn *LVal) error
- func (env *LEnv) CheckClosure(fn *LVal) ([]ClosureIssue, error)
- func (env *LEnv) CheckContext() error
- func (env *LEnv) Concat(kind SeqKind, seqs ...*LVal) (*LVal, error)
- func (env *LEnv) Context() context.Context
- func (env *LEnv) DebugStack() (*LVal, error)
- func (env *LEnv) DeepCopy(v *LVal) (*LVal, error)
- func (env *LEnv) DefinePackage(name *LVal) *LVal
- func (env *LEnv) Error(msg ...any) *LVal
- func (env *LEnv) ErrorAssociate(lerr *LVal) *LVal
- func (env *LEnv) ErrorCondition(condition string, v ...any) *LVal
- func (env *LEnv) ErrorConditionf(condition string, format string, v ...any) *LVal
- func (env *LEnv) Errorf(format string, v ...any) *LVal
- func (env *LEnv) ErrorfAt(form *LVal, format string, v ...any) *LVal
- func (env *LEnv) Eval(v *LVal) *LValdeprecated
- func (env *LEnv) EvalContext(ctx context.Context, v *LVal) *LVal
- func (env *LEnv) EvalSExpr(s *LVal) *LVal
- func (env *LEnv) First(seq *LVal) (*LVal, error)
- func (env *LEnv) FormatString(f string, fvals ...*LVal) (string, error)
- func (env *LEnv) FunCall(fun, args *LVal) *LValdeprecated
- func (env *LEnv) FunCallContext(ctx context.Context, fun, args *LVal) *LVal
- func (env *LEnv) GenSym() *LVal
- func (env *LEnv) Get(k *LVal) *LVal
- func (env *LEnv) GetFun(fun *LVal) *LVal
- func (env *LEnv) GetFunGlobal(fun *LVal) *LVal
- func (env *LEnv) GetFunName(f *LVal) string
- func (env *LEnv) GetGlobal(k *LVal) *LVal
- func (env *LEnv) InPackage(name *LVal) *LVal
- func (env *LEnv) IsEmpty(seq *LVal) (bool, error)
- func (env *LEnv) Lambda(formals *LVal, body []*LVal) *LVal
- func (env *LEnv) Length(seq *LVal) (int, error)
- func (env *LEnv) Load(name string, r io.Reader) *LValdeprecated
- func (env *LEnv) LoadContext(ctx context.Context, name string, r io.Reader) *LVal
- func (env *LEnv) LoadFile(loc string) *LValdeprecated
- func (env *LEnv) LoadFileContext(ctx context.Context, loc string) *LVal
- func (env *LEnv) LoadLocation(name string, loc string, r io.Reader) *LValdeprecated
- func (env *LEnv) LoadLocationContext(ctx context.Context, name, loc string, r io.Reader) *LVal
- func (env *LEnv) LoadProgram(p Program) *LValdeprecated
- func (env *LEnv) LoadProgramContext(ctx context.Context, p Program) *LVal
- func (env *LEnv) LoadString(name, exprs string) *LValdeprecated
- func (env *LEnv) LoadStringContext(ctx context.Context, name, exprs string) *LVal
- func (env *LEnv) Locals() []Binding
- func (env *LEnv) MacroCall(fun, args *LVal) *LVal
- func (env *LEnv) MacroExpandAll(form *LVal) *LVal
- func (env *LEnv) MakeSequence(start, stop *LVal, step ...*LVal) (*LVal, error)
- func (env *LEnv) MapAssoc(m, k, v *LVal) (*LVal, error)
- func (env *LEnv) MapDelete(m, k *LVal) (*LVal, error)
- func (env *LEnv) MapDissoc(m, k *LVal) (*LVal, error)
- func (env *LEnv) MapHasKey(m, k *LVal) (bool, error)
- func (env *LEnv) MapKeys(m *LVal) (*LVal, error)
- func (env *LEnv) MapLookup(m, k *LVal) (*LVal, error)
- func (env *LEnv) MapOf(kv ...any) (*LVal, error)
- func (env *LEnv) MapPut(m, k, v *LVal) (*LVal, error)
- func (env *LEnv) MapRange(m *LVal, fn func(key MapKey, val *LVal) bool) error
- func (env *LEnv) MapSeq(kind SeqKind, fn, seq *LVal) (*LVal, error)
- func (env *LEnv) MapSeqCells(seq *LVal) ([]*LVal, error)
- func (env *LEnv) MapSeqFunc(kind SeqKind, seq *LVal, f func(*LVal) (*LVal, error)) (*LVal, error)
- func (env *LEnv) Mod(a, b *LVal) (*LVal, error)
- func (env *LEnv) Mul(xs ...*LVal) (*LVal, error)
- func (env *LEnv) New(typ *LVal, args *LVal) *LVal
- func (env *LEnv) NewLambdaCode(formals *LVal, body []*LVal) (*LambdaCode, error)
- func (env *LEnv) NewRenderer(ctx context.Context) *DiagnosticRenderer
- func (env *LEnv) NewRendererWithLimit(ctx context.Context, limit int) *DiagnosticRenderer
- func (env *LEnv) Nth(seq, n *LVal) (*LVal, error)
- func (env *LEnv) NumBindings() int
- func (env *LEnv) NumEqual(a, b *LVal) (bool, error)
- func (env *LEnv) NumGreater(a, b *LVal) (bool, error)
- func (env *LEnv) NumGreaterEqual(a, b *LVal) (bool, error)
- func (env *LEnv) NumLess(a, b *LVal) (bool, error)
- func (env *LEnv) NumLessEqual(a, b *LVal) (bool, error)
- func (env *LEnv) PanicError(p any) *LVal
- func (env *LEnv) Parent() *LEnv
- func (env *LEnv) ParseProgram(name, loc string, r io.Reader) (Program, error)
- func (env *LEnv) Put(k, v *LVal) *LVal
- func (env *LEnv) PutGlobal(k, v *LVal) *LVal
- func (env *LEnv) PutGlobalFromLisp(k, v *LVal) *LVal
- func (env *LEnv) QuasiquoteWith(form *LVal, resolve func(expr *LVal) *LVal) *LVal
- func (env *LEnv) RegisterBuiltins(funs ...LBuiltinDef) []*LVal
- func (env *LEnv) Render(v *LVal) string
- func (env *LEnv) RenderContext(ctx context.Context, v *LVal) string
- func (env *LEnv) Rest(seq *LVal) (*LVal, error)
- func (env *LEnv) RestoreLambda(pkg string, code *LambdaCode) *LVal
- func (env *LEnv) Second(seq *LVal) (*LVal, error)
- func (env *LEnv) SetPackageDoc(doc string)
- func (env *LEnv) SetSymbolDoc(name, doc string)
- func (env *LEnv) Slice(kind SeqKind, seq, start, end *LVal) (*LVal, error)
- func (env *LEnv) SortedMapOf(kv ...*LVal) (*LVal, error)
- func (env *LEnv) Source() *token.Location
- func (env *LEnv) SpecialOpCall(fun, args *LVal) *LVal
- func (env *LEnv) Step() error
- func (env *LEnv) StepEach(n int64) error
- func (env *LEnv) StringEqual(a, b *LVal) (bool, error)
- func (env *LEnv) StringGreater(a, b *LVal) (bool, error)
- func (env *LEnv) StringLess(a, b *LVal) (bool, error)
- func (env *LEnv) Sub(xs ...*LVal) (*LVal, error)
- func (env *LEnv) TaggedValue(typ *LVal, val *LVal) *LVal
- func (env *LEnv) Terminal(expr *LVal) *LVal
- func (env *LEnv) ToBytes(val *LVal) ([]byte, error)
- func (env *LEnv) ToInt(val *LVal) (int, error)
- func (env *LEnv) ToString(val *LVal) (string, error)
- func (env *LEnv) Update(k, v *LVal) *LVal
- func (env *LEnv) UpdateFromLisp(k, v *LVal) *LVal
- func (env *LEnv) UsePackage(name *LVal) *LVal
- func (env *LEnv) Zip(kind SeqKind, list *LVal, lists ...*LVal) (*LVal, error)
- type LFunType
- type LType
- type LVal
- func Array(dims *LVal, cells []*LVal) *LVal
- func Bool(b bool) *LVal
- func Bytes(b []byte) *LVal
- func Encode(x any) (*LVal, error)
- func Error(err error) *LVal
- func ErrorCondition(condition string, err error) *LVal
- func ErrorConditionf(condition string, format string, v ...any) *LVal
- func Errorf(format string, v ...any) *LVal
- func Float(x float64) *LVal
- func Formals(argSymbols ...string) *LVal
- func Fun(fid string, formals *LVal, fn LBuiltin) *LValdeprecated
- func FunInPackage(pkg, fid string, formals *LVal, fn LBuiltin) *LVal
- func FunInPackageDoc(pkg, fid string, formals *LVal, fn LBuiltin, doc string) *LVal
- func FunRef(symbol, fun *LVal) *LVal
- func GetType(v *LVal) *LVal
- func InitializeTypedef(env *LEnv) *LVal
- func InitializeUserEnv(env *LEnv, config ...Config) *LVal
- func Int(x int) *LVal
- func Macro(fid string, formals *LVal, fn LBuiltin) *LValdeprecated
- func MacroInPackage(pkg, fid string, formals *LVal, fn LBuiltin) *LVal
- func MakeVector(n int) *LVal
- func MapOf(kv ...any) *LVal
- func MatchPatternNames(p *LVal) ([]*LVal, error)
- func Native(v any) *LVal
- func NativeOf[T any](x T) *LVal
- func NewCapturedFun(spec CapturedFun) *LVal
- func Nil() *LVal
- func QExpr(cells []*LVal) *LVal
- func Quote(v *LVal) *LVal
- func Result(v *LVal) (*LVal, error)
- func SExpr(cells []*LVal) *LVal
- func SortedMap() *LVal
- func SortedMapFromData(data *MapData) *LVal
- func SortedMapSized(n int) *LVal
- func SpecialOp(fid string, formals *LVal, fn LBuiltin) *LValdeprecated
- func SpecialOpInPackage(pkg, fid string, formals *LVal, fn LBuiltin) *LVal
- func Splice(v *LVal) *LVal
- func SplitSymbol(sym *LVal) *LVal
- func String(str string) *LVal
- func StringList(ss []string) *LVal
- func Symbol(s string) *LVal
- func Value(v any) *LVal
- func ValueOrError(v *LVal, err error) *LVal
- func Vector(cells []*LVal) *LVal
- func WrapError(lerr *LVal, format string, v ...any) *LVal
- func (v *LVal) All() iter.Seq2[MapKey, *LVal]
- func (v *LVal) AppendMapKeyPairs(dst []MapKeyPair) ([]MapKeyPair, bool)
- func (v *LVal) AppendSortedPairs(dst []MapPair) ([]MapPair, bool)
- func (v *LVal) ArrayDims() *LVal
- func (v *LVal) ArrayIndex(index ...*LVal) *LVal
- func (v *LVal) ArrayParts() (*LVal, *LVal)
- func (v *LVal) Builtin() LBuiltin
- func (v *LVal) Bytes() []byte
- func (v *LVal) CallStack() *CallStack
- func (v *LVal) Copy() *LVal
- func (v *LVal) Docstring() string
- func (v *LVal) Equal(other *LVal) *LVal
- func (v *LVal) EqualNum(other *LVal) *LVal
- func (v *LVal) EqualWithEnv(other *LVal, env *LEnv) *LVal
- func (v *LVal) EqualWithRuntime(other *LVal, rt *Runtime) *LVal
- func (v *LVal) FID() string
- func (v *LVal) InheritSeal(src *LVal)
- func (v *LVal) IsError() bool
- func (v *LVal) IsMacro() bool
- func (v *LVal) IsNil() bool
- func (v *LVal) IsNumeric() bool
- func (v *LVal) IsQuoted() bool
- func (v *LVal) IsSealed() bool
- func (v *LVal) IsSpecialFun() bool
- func (v *LVal) IsSpecialOp() bool
- func (v *LVal) IsSpliced() bool
- func (v *LVal) IsSymbol(name string) bool
- func (v *LVal) IsVector() bool
- func (v *LVal) KeyArg(i int) *LVal
- func (v *LVal) Keys() iter.Seq[MapKey]
- func (v *LVal) Len() int
- func (v *LVal) MacroExpansion() (MacroExpansionMeta, bool)
- func (v *LVal) Map() *MapData
- func (v *LVal) MapEntries() *LVal
- func (v *LVal) MapKeys() *LVal
- func (v *LVal) MapSet(k any, val *LVal) *LValdeprecated
- func (v *LVal) MapSetLVal(k *LVal, val *LVal) *LVal
- func (v *LVal) MapSetString(k string, val *LVal) *LVal
- func (v *LVal) Package() string
- func (v *LVal) ReqArg(env *LEnv, i int) *LVal
- func (v *LVal) SealAST()
- func (v *LVal) SeqCells() ([]*LVal, bool)
- func (v *LVal) SetArrayCells(cells []*LVal, dims ...int)
- func (v *LVal) SetArrayData(data *LVal, dims ...int)
- func (v *LVal) SetCallStack(stack *CallStack)
- func (v *LVal) SetSource(loc *token.Location)
- func (v *LVal) Source() (token.Location, bool)
- func (v *LVal) String() string
- func (v *LVal) UserData() *LVal
- type LambdaCode
- type LoadCache
- type Loader
- type LocationReader
- type LogicalStackOverflowError
- type MacroExpansionMeta
- type Map
- type MapData
- type MapKey
- type MapKeyPair
- type MapLoadCache
- type MapPair
- type MapView
- type MatchFieldFunc
- type MatchTypeFunc
- type Name
- type NativeCloner
- type Package
- func (pkg *Package) Export(names ...string)
- func (pkg *Package) Exports(sym ...string)
- func (pkg *Package) Externals() []string
- func (pkg *Package) FirstNameOf(fn *LVal) (string, bool, int)
- func (pkg *Package) Frozen() bool
- func (pkg *Package) FunNameByFID(fid string) (string, bool)
- func (pkg *Package) FunNamesByFID() (map[string]string, int)
- func (pkg *Package) Get(k *LVal) *LVal
- func (pkg *Package) GetFunName(fid string) string
- func (pkg *Package) NumBindings() int
- func (pkg *Package) NumExternals() int
- func (pkg *Package) Put(k, v *LVal) *LVal
- func (pkg *Package) Seal(policy SealPolicy) error
- func (pkg *Package) Sealed() bool
- func (pkg *Package) Symbol(name string) (*LVal, bool)
- func (pkg *Package) SymbolDoc(name string) string
- func (pkg *Package) SymbolNames() []string
- func (pkg *Package) Update(k, v *LVal) *LVal
- type PackageRegistry
- func (r *PackageRegistry) AddPackage(p *Package) bool
- func (r *PackageRegistry) DefinePackage(name string) *Package
- func (r *PackageRegistry) Package(name string) *Package
- func (r *PackageRegistry) PackageNames() []string
- func (r *PackageRegistry) RegisteredBuiltin(pkg, name string) *LVal
- func (r *PackageRegistry) RegisteredBuiltinName(fn *LVal) (string, string, bool)
- type ParamInfo
- type ParamKind
- type PhysicalStackOverflowError
- type Profiler
- type Program
- type Reader
- type ReaderIdentity
- type RelativeFileSystemLibrary
- type Runtime
- func (r *Runtime) BeginLoad() func()
- func (r *Runtime) CheckAlloc(n int) string
- func (rt *Runtime) ConditionIsA(c, ancestor string) bool
- func (rt *Runtime) ConditionParent(c string) string
- func (r *Runtime) CurrentCondition() *LVal
- func (rt *Runtime) DefineCondition(child, parent string) error
- func (rt *Runtime) DeleteSettingValue(name string)
- func (rt *Runtime) DeleteVMValue(name string)
- func (r *Runtime) EvalNesting() int
- func (r *Runtime) GenEnvID() uint
- func (r *Runtime) GenSym() string
- func (r *Runtime) Loading() bool
- func (r *Runtime) MaxAllocBytes() int
- func (r *Runtime) MaxEvalNestingDepth() int
- func (r *Runtime) MaxMacroExpansions() int
- func (r *Runtime) MaxSleepCeiling() time.Duration
- func (r *Runtime) PopCondition() *LVal
- func (r *Runtime) PushCondition(err *LVal)
- func (r *Runtime) ResetStepBudget()
- func (r *Runtime) ResetSteps()
- func (rt *Runtime) SetSetting(name string, value bool)
- func (rt *Runtime) SetSettingValue(name string, v any) error
- func (r *Runtime) SetStepBudget(n int64)
- func (rt *Runtime) SetVMValue(name string, v *LVal)
- func (rt *Runtime) Setting(name string) (bool, bool)
- func (rt *Runtime) SettingValue(name string) (any, bool)
- func (r *Runtime) StepBudget() (int64, int64)
- func (r *Runtime) Steps() int64
- func (r *Runtime) TotalSteps() int64
- func (rt *Runtime) VMValue(name string) (*LVal, bool)
- func (r *Runtime) ValueDepthLimit() int
- type RuntimeBound
- type SealPolicy
- type SealViolation
- type SealWrite
- type SeqKind
- type Shape
- type SingletonSnapshot
- type SourceContext
- type SourceLibrary
- type StringKeyRanger
- type TailIterationLimitError
- type Template
- type TemplateOption
- func TemplateWithBuiltinPolicy(approve func(*LVal) bool) TemplateOption
- func TemplateWithEagerInstantiation() TemplateOption
- func TemplateWithFrozenPackages(names ...string) TemplateOption
- func TemplateWithNativePolicy(approve func(any) bool) TemplateOption
- func TemplateWithThawHook(fn func(pkg string)) TemplateOption
- type Text
- type VMOption
- type ValueDepthError
- type WalkEvent
- type WalkNode
Examples ¶
Constants ¶
const ( CondParseError = "parse-error" CondScanError = "scan-error" CondUnmatchedSyntax = "unmatched-syntax" CondMismatchedSyntax = "mismatched-syntax" CondInvalidSymbol = "invalid-symbol" CondInvalidOctalLiteral = "invalid-octal-literal" CondInvalidHexLiteral = "invalid-hex-literal" CondInvalidFloat = "invalid-float" CondInvalidString = "invalid-string" CondOverflow = "integer-overflow-error" )
Parse error condition names. These are stable API for programmatic error classification in LSP and tooling integrations.
const ( CondContextCancelled = "context-cancelled" CondStepLimitExceeded = "step-limit-exceeded" // CondStepBudgetExceeded reports that the shared step budget installed // by Runtime.SetStepBudget (or VMWithStepBudget), which spans top-level // evaluations, is exhausted. It is distinct from step-limit-exceeded, // the per-evaluation WithMaxSteps limit. CondStepBudgetExceeded = "step-budget-exceeded" // CondEvalNestingExceeded reports that the evaluator recursed into // itself more deeply than Runtime.MaxEvalNesting allows. It is the // recoverable substitute for a Go stack overflow, which is a // runtime.throw that neither recover() nor handler-bind can intercept. CondEvalNestingExceeded = "eval-nesting-exceeded" // CondSleepLimitExceeded reports that a requested sleep was longer than // the caller is allowed to sleep for, and was refused WITHOUT sleeping. // It is distinct from context-cancelled: nothing was cancelled and no // time passed, the request was rejected on entry. See // DefaultMaxSleep and Runtime.MaxSleep. CondSleepLimitExceeded = "sleep-limit-exceeded" )
Evaluation limit condition names.
const ( DefaultMaxLogicalStackHeight = 0 DefaultMaxPhysicalStackHeight = 25000 DefaultMaxTailIterations = 1000000 DefaultMaxEvalNesting = 100000 )
Default evaluation depth limits.
DefaultMaxPhysicalStackHeight — the memory guard, keep it on
This bounds the number of frames physically present on the call stack. It protects against unbounded *non-tail* recursion exhausting the Go goroutine stack, which aborts the whole process with an unrecoverable "stack overflow" that no handler-bind can catch. Measured on linux/amd64 with the limit disabled, ELPS non-tail recursion survives 200,000 levels (~678MB of Go stack) and crashes the runtime at 400,000. 25000 sits 8-16x below that threshold, and the margin is load-bearing: the crash point moves with Go's stack settings, per-frame size, and architecture. Do not raise it casually and do not default it off.
Frames elided by tail-call optimization do not count toward it, so a tail-recursive loop runs at constant physical height no matter how many iterations it performs.
It bounds FRAMES, not evaluation depth. A frame is pushed when a function is invoked, and LEnv.evalSExprCells deliberately evaluates a call's arguments BEFORE pushing anything (pushing there would corrupt the error messages and stack dumps produced while an argument is being evaluated). Nested arguments therefore recurse through the Go evaluator at physical height zero, which is exactly the shape this limit exists to stop and exactly the shape it cannot see. DefaultMaxEvalNesting covers that path; see issue #316.
DefaultMaxEvalNesting — the same memory guard, on the evaluator's own recursion
This bounds how deeply LEnv.eval may recurse into itself, which is the true measure of Go stack consumed by the evaluator. Every nested evaluation passes through eval — a call's arguments, a special operator's subforms, a builtin re-entering via env.Eval — so a single counter incremented on entry and decremented on exit bounds all of them, and unlike a stack frame it costs no allocation and does not appear in a stack trace.
Measured on linux/amd64 against the 1GB default goroutine stack: argument-nesting depth 700,000 completes, 800,000 aborts the process with "fatal error: stack overflow" (~1.4KB of Go stack per level). 100,000 sits 7-8x below that, matching the margin DefaultMaxPhysicalStackHeight keeps.
THAT MARGIN IS POINTER-SIZE DEPENDENT. Go caps a goroutine stack at 1GB on 64-bit but 250MB on 32-bit (runtime/proc.go: maxstacksize). At ~1.4KB per level the default costs ~140MB, so the same 100,000 is a 7-8x margin on amd64 and roughly 1.8x on a 32-bit build -- and an embedder whose own call stack is already deep when it enters Eval eats into what is left. Nothing here is exercised on 32-bit: CI builds ubuntu-latest and windows-latest, both amd64. A 32-bit embedder should lower this with WithMaxEvalNesting rather than trust the default.
It also sits comfortably above ordinary recursion. Measured with (defun sum (n) (if (<= n 0) 0 (+ n (sum (- n 1))))), the physical limit binds first for any MaxEvalNesting of 38,000 or more: that recursion costs ~1.5 eval levels per physical frame, so it reaches physical height 25,001 at nesting ~38,000. Only code whose body nests expressions more than ~4 deep per recursion level can reach 100,000 nesting before 25,000 frames -- such code costs 4 eval levels per frame, ~2.6x what ordinary recursion costs, so it really is consuming that much more Go stack per frame.
The two limits bound different quantities and neither implies the other. Deeply nested arguments raise nesting at constant height; a long chain of tail-position calls raises neither.
Before this limit existed the only thing bounding evaluator recursion was rdparser.DefaultMaxParseDepth (10,000) — incidentally, because nesting had to be parsed before it could be evaluated. That bound was never sound: a recursive macro generates nesting at expansion time from an integer argument, so ~120 bytes of source reached the fatal overflow with every documented limit at its default. Parse depth is no longer load-bearing for stack safety.
DefaultMaxTailIterations — the runaway-loop backstop
This bounds turns per contiguous tail-call sequence at a single stack frame. The counter resets when that call returns; another call starts a fresh sequence. Nested calls have their own counters. Repeated calls that each return below the limit can therefore do unbounded aggregate work. WithMaxSteps is the total-work bound across those calls within a top-level evaluation; it does not reset when a nested call returns.
It is a backstop against a loop that never terminates, NOT a time bound. One million turns of a trivial O(1) body costs ~4s of interpreter overhead, but turns say nothing about the work done per turn: a body that conses a list, concatenates a string, or calls any O(n) builtin can run for minutes — or effectively forever — inside the same turn budget, and a step limit does not help either because an O(n) builtin is one step (unless the builtin charges its work with LEnv.ChargeSteps). To bound wall-clock time, pass a context with a deadline (WithContext, or the *Context methods on LEnv). That is the only limit here that measures time.
DefaultMaxLogicalStackHeight — off by default
Logical height accumulates every frame elided by tail-call optimization, so it is a running total of *elided frames*, not a nesting depth and not an iteration count. One turn of a tail loop adds the length of the elided terminal chain — 2 for a trivial body, more when the body nests terminal forms more deeply — so the same numeric limit permits a different number of iterations depending on the shape of the loop. That makes it unusable as a default runaway-loop bound: it fails data-dependently with a message that reads like runaway recursion when the program is a correct constant- space loop. MaxTailIterations does that job in honest units instead.
Logical height remains valuable as a stack-trace diagnostic, and callers who specifically want to bound it can opt in via WithMaximumLogicalStackHeight.
Bounding total work
None of these limits catch a non-recursive infinite loop (a host-provided `while`, for example) because such a loop neither grows the stack nor performs tail calls. WithMaxSteps bounds the number of evaluation steps, which does catch that shape.
None of them — including WithMaxSteps — bound elapsed time, because a single step may perform an arbitrary amount of work inside a builtin. A context deadline is the only real time bound; see WithContext and the *Context methods on LEnv.
Bounding total memory
Runtime.MaxAlloc is PER-OPERATION, not cumulative: each builtin checks its own output size against it, so a program that allocates a thousand buffers of MaxAlloc-1 bytes passes every check. Nothing here tracks total heap. The incidental bound is whatever limit stops the loop doing the allocating — MaxTailIterations for a tail loop, MaxSteps for a stepped one — multiplied by MaxAlloc, which is not a memory budget in any useful sense. A host that must bound total memory has to do it outside the interpreter (a cgroup, a container limit, or Go's GOMEMLIMIT plus a watchdog); see Runtime.MaxAlloc and CheckAlloc.
const AnonArgSymbolPrefix = "%"
AnonArgSymbolPrefix is used to indicate unnamed arguments in the anonymous function shorthand “(expr ...)”.
const CondArgumentError = "argument-error"
CondArgumentError reports that a builtin was called with an argument of the wrong type, or with the wrong number of arguments, as found by elps's argument helpers: ArgReader (its typed reads, Typed and Check), the Cells reader (Cells.Read), and the Func1E, Func2E and Func3E argument decoding and arity check. A builtin that checks its arguments by hand with LEnv.Errorf raises condition error instead.
argument-error is a child of error (see ConditionIsA): a handler-bind on error, or on the catch-all condition, catches it, as does ignore-errors. A handler that reads the condition symbol sees 'argument-error. An error whose condition is a child of error renders as error does, with the failing function's name and no condition name.
const CondCatchAll = "condition"
CondCatchAll is the type specifier handler-bind and condition-is? treat as the root of every condition. It is not a condition elps raises, and it cannot take part in a definition.
const CondInternalPanic = "internal-panic"
const CondMatchError = "match-error"
CondMatchError reports that the value of a let or let* binding does not match the binding's pattern (luthersystems/elps#910). Its message names the pattern. match-error is a child of error, as argument-error is, so a handler-bind on error catches it and it renders as error does.
const CondMissingArgument = "missing-argument"
CondInternalPanic is the condition type of an error produced by recovering a Go panic that escaped a builtin, special operator, or any other host code called during evaluation.
It is deliberately NOT an ordinary error condition. A panic means host Go code hit a bug — a nil dereference, an out-of-range index, a failed invariant — and left its data structures in an unknown state. Treating that as a routine, catchable lisp error lets `ignore-errors` and a catch-all `handler-bind` swallow it silently, so a genuine host defect looks exactly like `(error 'my-condition "...")` and the program keeps running on top of it.
Accordingly, `ignore-errors` and the catch-all `condition` handler specifier do not intercept this condition; it propagates to the caller. For the same reason an error raised by an `with-cleanup` cleanup form does not mask this condition, though it does replace an ordinary one. A handler that genuinely wants to intercept host panics must name the condition explicitly:
(handler-bind ((internal-panic (lambda (c &rest args) ...)))
(risky))
CondMissingArgument reports that a builtin was invoked with fewer argument cells than it reads. The evaluator supplies one cell per declared formal, so this cannot arise from lisp; it means an embedder bound the builtin to formals declaring fewer arguments than the Go function requires. Raised as an ordinary condition so a caller can handler-bind it, rather than panicking in the embedder's process.
const CondModifyLiteral = "modify-literal-error"
CondModifyLiteral reports an attempt to modify a sealed program literal in place. Quoted data, macro arguments and defun bodies are part of the program text, shared by every environment evaluating the same parse (lisp/seal.go), so the in-place mutators that could write them — stable-sort on a sealed list, and the (slice 'vector ...) / (append 'vector ...) forms that would wrap or write a sealed list's backing array — raise this condition instead. It is an ordinary catchable condition: handler-bind can name it and ignore-errors swallows it. The remedy the message names is `(copy x)`, which returns a fresh, fully mutable deep copy.
The empty list is the deliberate carve-out: builtins such as cdr, rest and keys return the shared (sealed) empty-list value, so erroring on it would make `(stable-sort < (rest xs))` fail only when xs happens to have fewer than two elements — a data-dependent error on correct runtime code. An empty list has no storage to write or alias, so the guarded sites accept it and return fresh storage.
const CondRecordError = "record-error"
CondRecordError reports that a program broke a rule of a libschema record type that the runtime enforces (luthersystems/elps#908): an s:set-field! while no method of the object's type runs on the object, an s:set-field! while the object's :check runs, or an s:defmethod after the load ends. Its message names the type and the rule. record-error is a child of error, as argument-error is, so a handler-bind on error catches it and it renders as error does.
const DefaultLangPackage = "lisp"
DefaultLangPackage is the name of default language package
const DefaultMaxAlloc = 10 * 1024 * 1024 // 10 million (bytes or elements)
DefaultMaxAlloc is the per-operation allocation size cap (in bytes for strings, in elements for sequences) enforced by builtins like concat, append, map, zip, reverse, make-sequence, and JSON load. Each operation checks its own output size independently — this is not a cumulative memory budget. It prevents a single malicious or accidental call from exhausting memory. Applications can override this via Runtime.MaxAlloc.
const DefaultMaxMacroExpansionDepth = 1000
DefaultMaxMacroExpansionDepth is the maximum number of successive macro expansions allowed before Eval returns an error. This prevents infinite macro expansion from exhausting memory or running forever.
const DefaultMaxSleep = time.Hour
DefaultMaxSleep bounds a single (time:sleep d) that does not pass an explicit :max (time:sleep meaning a host-registered libtime.BuiltinSleep; the standard library no longer registers one, #757), and is the last of the execution limits because it bounds WALL CLOCK rather than work.
Every other limit here counts something the interpreter does — steps, frames, iterations, bytes, nesting. A sleeping goroutine does none of them, which is why time:sleep was unbounded by all of them at once and "9223372036854775807ns" blocked for ~292 years (issue #314). Bounding it needs a wall-clock number, and there is no work-based limit that implies one.
One hour is chosen to be far above any plausible legitimate sleep in an embedded interpreter — a backoff, a poll interval, a test delay are all orders of magnitude below it — while still refusing the 292-year shape immediately rather than after 292 years. A caller who genuinely wants longer says so with :max, which is the point: the length becomes explicit at the call site instead of being an accident of arithmetic.
:max raises the per-call cap but CANNOT exceed Runtime.MaxSleep when the embedder has set one. That split is deliberate. Program source and the embedder are different trust domains: downstream (luthersystems/substrate) the program is customer-supplied phylum running as chaincode, so a cap that program source could raise would be decorative. DefaultMaxSleep is a guard against accidents, which the program may relax; Runtime.MaxSleep is a containment bound, which only the host may relax.
const DefaultUserPackage = "user"
DefaultUserPackage is the name of the entry point package for interpreting user code.
const ElpsVersion = "1.7"
const FalseSymbol = "false"
FalseSymbol is the language's defacto false boolean value, though nil is also considered false by functions and operators expecting a boolean.
const KeyArgSymbol = "&key"
KeyArgSymbol is the symbol used to indicate keyword arguments to a function. Keyword arguments are declared following optional arguments. Keyword arguments may be supplied in any order and must specify the symbol they wish should be bound to by preceding the argument value with a keyward symbol.
const MaxConditionDepth = 64
MaxConditionDepth is the most parents a condition can have above it, counting its parent, its parent's parent and so on (luthersystems/elps#831). DefineCondition refuses a definition that makes a chain longer. handler-bind walks the chain for each clause without charging a step, so the bound keeps that walk small.
const MaxExprFormals = 1024
MaxExprFormals bounds the positional-placeholder index accepted by the `expr` special operator (the #^ reader macro).
The index is read out of the source text and used directly as the length of the generated formals slice, so `#^(%N)` asks opExpr to construct N fresh symbols -- each an *LVal plus a fmt.Sprintf'd name -- from an input whose size is log10(N). Measured on linux/amd64 that is ~136 bytes per formal, so the unbounded form reached `fatal error: out of memory` (a runtime.throw, which recover() cannot intercept and no handler-bind can contain) from 14 bytes of source.
This is deliberately a dedicated bound and NOT Runtime.MaxAlloc. MaxAlloc's unit at every other call site is bytes of a buffer or elements of a slice the caller already holds; here one unit is a whole constructed value, so the two are ~130x apart in cost and no single number serves both. The mismatch is visible in both directions: MaxAlloc's 10,000,000 default still permits ~1.3GB here, while a MaxAlloc of 8 -- reasonable for a byte buffer -- would reject `#^(list %9)`, an ordinary nine-argument lambda.
1024 is chosen against real programs rather than against memory: C's standard guarantees only 127 parameters (C99 5.2.4.1) and the JVM caps a method at 255, so a bound 4-8x above the most permissive mainstream limit cannot plausibly reject a program a human wrote, while capping this site at ~136KB. It is a constant rather than a Runtime knob because it bounds a syntactic construct -- how many parameters a lambda literal may declare -- which is not something an embedder's workload varies.
const MaxValueDepth = 1_000_000
MaxValueDepth is the default limit for iterative walks over program-built values, independently of evaluator nesting, cycle detection and allocation budgets. Runtime walkers accept WithMaxValueDepth overrides of at least 1024, including values above this default: their stacks live on the heap. Rendered text has a separate fixed maxRenderDepth of 1024.
Walker inventory: walker | file:line | guard | regression. Paths are relative to the repository root. Input and path-composition limits remain separate from value depth. rendered text | lisp/render_bounded.go:17 | separate 1024 counter / marker | TestValueWalkDepth/100000/format-string format value conversion | lisp/builtins.go builtinFormatString | rendered-text counter only (no value-depth verdict since the post-render check was dropped) | TestValueWalkDepth/{100000,3000000}/format-string copy / detach | lisp/detach.go:146 | explicit jobs / runtime counter | TestValueWalkDepth/{100000,3000000}/{copy,detach} LVal.Copy | lisp/copier.go:307 | explicit jobs / runtime counter | TestValueWalkDepth/{100000,3000000}/Copy; TestValueDepthRaisedLimit GoValue / GoSlice / GoMap | lisp/embed.go:50 | explicit stack / runtime counter via the WithRuntime forms, default counter otherwise | TestValueWalkDepth/{100000,3000000}/GoValue; TestGoConversionRootDepth; TestGoValueHonoursRuntimeDepthLimit JSON dump | lisp/lisplib/libjson/encode.go:313 | explicit stack / counter in both passes | TestJSONValueDepth; TestJSONValueDepthRaisedLimit JSON Go conversion | lisp/lisplib/libjson/json.go:717 | explicit stack / default counter | TestJSONValueDepth template admission (values / sealed / envs) | lisp/template.go:234 | explicit jobs / runtime counter | TestValueWalkDepth/{100000,3000000}/{template,template-sealed} template compilation / instantiation | lisp/template_plan.go:113 | indexed loops | TestTemplatePlanGraph; TestValueWalkDepth equality | lisp/lisp.go:1493 | explicit stack / runtime counter / pair memo | TestValueWalkDepth/{100000,3000000}/equal; TestAllowedValuesDepthError macro stamping | lisp/macro.go:595 | explicit stack / runtime counter | TestValueWalkDepth/{100000,3000000}/stamp quasiquote | lisp/macro.go:723 | explicit stack / runtime counter | TestValueWalkDepth/{100000,3000000}/quasiquote sealing | lisp/seal.go:145 | explicit stack / sealed memo (void API completes) | TestValueWalkDepth/{100000,3000000}/seal macro source locations | lisp/macro.go:471 | explicit stack / identity memo (void API completes) | TestValueWalkDepth/{100000,3000000}/locate package classification | lisp/package_admit.go:206 | explicit stack / runtime counter (registry's runtime) | TestValueWalkDepth/{100000,3000000}/classify; TestPackageAdmissionHonoursRuntimeDepthLimit elpspath copy | lisp/lisplib/libelpspath/path.go:71 | explicit stack / runtime counter (carried down the walk by setPath/deletePath/nilPath) | TestPathValueDepth; TestCopyHonoursRuntimeDepthLimit elpspath copying chains | lisp/lisplib/libelpspath/path.go:933 | separate maxPathSteps (1024) | TestPathTraversalDepth elpspath nested iterators | lisp/lisplib/libelpspath/query.go:125 | separate maxPathSteps (1024) | TestPathTraversalDepth struct codec (Decode / DecodeInto / Encode) | lisp/structcodec.go:205 | separate fixed maxCodecDepth (10,000), Go recursion, memo per container and type | TestCodecDepthLimit reader admission | lisp/loader.go:673 | existing loaderWalkMaxDepth | TestLoadCacheDepthCapSurvivesMemoHit program seal classification | lisp/program.go:333 | existing reader admission | TestLoadCacheDepthCapSurvivesMemoHit seal fingerprint | lisp/sealfp.go:166 | existing depth / work limit | TestSealedASTFingerprintCycleTerminates JSON load conversion | lisp/lisplib/libjson/json.go:368 | existing encoding/json input limit | TestLoadNestingLimitIsWhereWeThinkItIs
const MetaArgPrefix = "&"
MetadaArgPrefix is a disallowed prefix for formal argument symbols. Any symbol beginning with MetaArgPrefix in a formal argument list will be treated with special meaning an unrecognized symbols will cause a runtime error to occur.
const OptArgSymbol = "&optional"
OptArgSymbol is the symbol used to indicate optional arguments to a function. Optional arguments are bound to given arguments if there are arguments left over following the binding of required arguments, otherwise they are bound to nil.
const TrueSymbol = "true"
TrueSymbol is the language's defacto true boolean value, though anything other than nil and 'false are considered true by functions and operators expecting a boolean.
const VarArgSymbol = "&rest"
VarArgSymbol is the symbol that indicates a variadic function argument in a function's list of formal arguments. Functions may have at most one variadic argument. Variadic arguments must be defined following optional arguments and are bound after all optional arguments are bound.
Currently, it is an error to declare a function which has both variadic and keyword arguments. While this may change it will always be discouraged due to the difficulty handling such mixtures of argument types. It would be better to define separate functions, one with keyward args and the other with variadic args.
Variables ¶
var ErrPackageSealed = errors.New("package is already sealed")
ErrPackageSealed is returned by Package.Seal for a package that is already sealed. A seal cannot be changed: a policy that a later call could swap would let any Go code that reaches the package unseal it, and the lisp package's seal must never be relaxed.
Functions ¶
func CapturedNames ¶ added in v1.75.0
CapturedNames returns the sorted, deduplicated names bound in the lexical environment that the lambda fn captured: its innermost frame and each frame above it, the root environment's own scope included. A host can bind lexical names in the root with Put, and evaluation finds them before package globals, so they count as captured. Package globals are not included; they live in package tables, not in any environment's scope. A name bound in more than one frame appears once.
The second result is false, and the names nil, when fn is not a lambda: a non-function value, a builtin, a macro, a special operator, or a function value carrying no function data. A lambda that captured no local bindings, such as one made by defun at top level, returns an empty, non-nil slice and true.
CapturedNames is a Go-side read of function metadata. It evaluates nothing, charges no steps, and returns a fresh slice: the captured environment itself stays unexported (issue #382), so the caller cannot rebind or reach the captured values through the result.
func ChargeRecord ¶ added in v1.73.0
ChargeRecord charges env for one record of n value bytes: max(1, ceil(n/1024)). A record of up to 1 KiB, empty included, costs exactly one step; a larger one costs what ChargeStartedKiB(n) does.
func ChargeStartedKiB ¶ added in v1.73.0
ChargeStartedKiB charges env one step per started KiB of n bytes of native work: ceil(n/1024). 0 bytes cost nothing, 1..1024 cost one step, 1025 cost two.
func ConditionIsA ¶ added in v1.78.0
ConditionIsA reports whether condition c is ancestor or a descendant of it in elps's condition hierarchy. handler-bind uses it to match a handler's type specifier: a handler for error catches argument-error. The catch-all specifier condition is not part of the hierarchy; it is handled by handler-bind itself, and ConditionIsA(c, "condition") is false unless c is "condition".
Code that must react to an error compares its condition with ConditionIsA, never its message text: the text may change in any release.
func ConditionOf ¶ added in v1.77.0
ConditionOf returns the condition Lisp code sees when err is raised as an error, as FuncE and LEnv.Error raise it. It returns:
- "" when err is nil;
- the condition of err when err is a *ErrorVal itself;
- the condition of a wrapped internal panic, which keeps its identity through a wrap (see ErrorCondition);
- "error" for every other error. A *ErrorVal wrapped with fmt.Errorf and %w becomes a new error with condition "error", so handler-bind no longer sees the inner condition. ConditionOf agrees with that.
ConditionOf does not look through wraps the way errors.Is does, because handler-bind does not.
func Decode ¶ added in v1.88.0
Decode converts v to the Go type T. It is DecodeInto on a new T: see DecodeInto for the conversions. When v is an LError it returns v as the error.
type openArgs struct {
Prefix string `elps:"prefix"`
HDK int `elps:"hdk"`
Autoflush bool `elps:"autoflush"`
}
o, err := lisp.Decode[openArgs](m)
func DecodeInto ¶ added in v1.88.0
DecodeInto stores v in the Go value dst points to. A struct field or pointer target that v does not name keeps its value, so dst can hold defaults. The conversions, by the type of the target:
*LVal any value, as is
int types an int, or a float with no fraction, in range
uint types the same, not negative
float types any number
string a string, or a symbol other than true and false (a
keyword without its colon)
bool the symbol true or false, or () for false
[]byte bytes (copied), or a list or vector of ints
slice a list or a vector; () is an empty slice
array a list or a vector of the array's length
map a sorted map; its keys convert to the key type, and two
keys that convert to one Go key are an error
struct a sorted map, whose keys name fields by the rules of
package elpstag; an unnamed field keeps its value
pointer () is a nil pointer; anything else fills the pointee,
allocated when nil
interface () is nil; a value with methods takes only a native
any a natural Go value: () is nil, an int is int64, a float
float64, a string string, true and false bool, bytes
[]byte, a list or vector []any, a map with string or
symbol keys map[string]any (a keyword key without its
colon), a native its payload, and any other value the
*LVal
A native value converts to a target its payload is assignable to, before the rules above. A map that holds a native, function or other value with no Go equivalent decodes into an *LVal or any field.
A container v reaches along several paths decodes once per target type, and the result is shared at each place: two slices, maps or new pointers of the result can be the same. So a value with shared structure decodes in time linear in its distinct containers. Treat a result as read-only, or copy it before writing to it.
The error names the path of the value that failed, such as "hdk" or "items[2].name", and the types; it never renders a value. DecodeInto makes no check and charges no step. It panics when dst is not a non-nil pointer.
func DeprecationNotice ¶ added in v1.58.0
DeprecationNotice reports whether a docstring marks its symbol as deprecated and returns the notice text.
The convention mirrors Go's: a docstring paragraph beginning with "Deprecated:" (or "DEPRECATED:") marks the symbol deprecated, and the rest of that paragraph tells callers what to use instead. Paragraphs are separated by blank lines. Leading and trailing whitespace on each line is ignored, so tab-indented builtin docstrings written as Go raw string literals work unchanged.
The returned notice is the marker paragraph's text with the marker removed and its lines joined by single spaces. A marker paragraph with no text after the marker returns ("", true).
func GoFloat64 ¶
GoFloat64 converts the numeric value that v represents to a float64 and returns it with the value true. If v does not represent a number GoFloat64 returns a false second argument
func GoInt ¶
GoInt converts the numeric value that v represents to and int and returns it with the value true. If v does not represent a number GoInt returns a false second argument
func GoMap ¶
GoMap converts an LSortMap to its Go equivalent and returns it with a true second argument. If v does not represent a map GoMap returns a false second argument. Application's using custom Map implementations which allow arbitrary keys may not be able to construct a native Go map, in which case GoMap returns (nil, true). Cycles and excessive nesting return (nil, false). Like GoValue it has no runtime and so cannot read a WithMaxValueDepth override; GoMapWithRuntime does.
func GoMapOf ¶ added in v1.66.0
func GoMapOf[K comparable, V any](v *LVal) (map[K]V, bool)
GoMapOf is GoMap with every key asserted to K and every value to V (elps#690). Keys and values are matched against what GoValue produces, as for GoSliceOf; a sorted-map's keys are strings. It returns (nil, false) wherever GoMap does and on the first key or value of the wrong type, and (nil, true) where GoMap does, for a custom map with no Go equivalent.
Like GoMap it has no runtime and walks to MaxValueDepth; GoMapOfWithRuntime reads a WithMaxValueDepth override.
func GoMapOfWithRuntime ¶ added in v1.66.0
func GoMapOfWithRuntime[K comparable, V any](rt *Runtime, v *LVal) (map[K]V, bool)
GoMapOfWithRuntime is GoMapOf bounded by rt's configured value-depth limit.
func GoMapWithRuntime ¶ added in v1.62.0
GoMapWithRuntime is GoMap bounded by rt's configured value-depth limit.
func GoSlice ¶
GoSlice converts a list to a Go slice. Non-lists, cycles and walks exceeding MaxValueDepth return (nil, false). Like GoValue it has no runtime and so cannot read a WithMaxValueDepth override; GoSliceWithRuntime does.
func GoSliceOf ¶ added in v1.66.0
GoSliceOf is GoSlice with every element asserted to T (elps#690). It returns (nil, false) wherever GoSlice does, and also when any element's GoValue conversion is not a T; there is no partial result.
T is matched against what GoValue produces for each element, not against the lisp type: a string or symbol is a string, an int an int, a float a float64, a nested list a []interface{}, a sorted-map a map[interface{}]interface{}, a native value its own payload, and nil (the empty list) the untyped nil, which only an interface T accepts. A value GoValue returns as is -- a function, for example -- is a *LVal, so GoSliceOf[*LVal] collects those and nothing else; it is not a way to get the list's cells back unconverted.
Like GoSlice it has no runtime and walks to MaxValueDepth; GoSliceOfWithRuntime reads a WithMaxValueDepth override.
func GoSliceOfWithRuntime ¶ added in v1.66.0
GoSliceOfWithRuntime is GoSliceOf bounded by rt's configured value-depth limit.
func GoSliceWithRuntime ¶ added in v1.62.0
GoSliceWithRuntime is GoSlice bounded by rt's configured value-depth limit.
func GoString ¶
GoString returns the string that v represents and the value true. If v does not represent a string GoString returns a false second argument
func GoValue ¶
GoValue converts v to its natural representation in Go. Quotes are ignored and all lists are turned into slices. Symbols are converted to strings. The value Nil() is converted to nil. Functions are returned as is.
A bytes value is returned as a []byte that COPIES the lisp value's storage, so writing to it cannot be observed through the original (issue #548). The cost is proportional to the length; see the LBytes arm of conversionLeaf for why the copy is not optional. A native value is the opposite case and is returned BY REFERENCE: the payload is the embedder's own, so GoValue hands back what the caller already owns.
SHARING. The result MAY share containers where v shared them: a list or map reached along several paths of v can come back as one []any or map[any]any appearing at each of those places, rather than one copy per path. That is how a value with nested sharing -- (set! x (list x x)) repeated D times, 2^D paths -- converts in time and memory linear in its distinct containers (see lisp/sharing.go); whether a given shared container is shared in the result depends on how much of the value was converted before it. A value without sharing converts to distinct Go containers throughout. Treat a result as read-only, or deep-copy it before writing to it. GoValueWithRuntime, GoSlice, GoMap and their Of forms follow the same rule.
Excessive nesting returns an ordinary *ErrorVal implementing error, using MaxValueDepth. No partial converted container is returned. Cycles discovered within the cap retain the historical return of the original *LVal.
GoValue takes no Runtime, so it cannot read a WithMaxValueDepth override and always walks to MaxValueDepth. A caller holding the runtime whose values these are uses GoValueWithRuntime instead.
NOTE: These semantics may change. It's unclear what the exact need is in corner cases.
func GoValueWithRuntime ¶ added in v1.62.0
GoValueWithRuntime is GoValue bounded by rt's configured value-depth limit (Runtime.ValueDepthLimit). A nil runtime is MaxValueDepth, which is what GoValue passes.
The runtime is a parameter rather than a field read off the value because a *LVal carries no runtime: the same value is reachable from every environment that was handed it, and only the caller knows which runtime's budget applies.
func GrowCap ¶ added in v1.75.0
GrowCap is the capacity append! gives a vector's data when n cells do not fit in its capacity c: max(2c, n, 4). append! lowers it to the runtime's allocation cap, never below n. It is part of the language, not of the Go runtime, so a vector's capacity, and with it which later append! calls write in place, is the same on every platform and Go version. A saved document records capacity (durable JSON views), so it must not depend on where it was computed.
func IsInternalPanic ¶ added in v1.49.0
IsInternalPanic reports whether v is an error produced by recovering a Go panic that escaped host code during evaluation.
This is the check `ignore-errors`, `handler-bind` and `with-cleanup` use -- the last to refuse to let an error raised by a cleanup form mask an in-flight panic, rather than to catch one -- and embedders should use it too, rather than comparing the condition name against CondInternalPanic. The condition name alone is forgeable: lisp code can write (error 'internal-panic "...") and, if the name were the only test, would produce an error that no catch-all handler could contain.
The marker is the Go stack snapshot the recover handler attaches to the error's CallStack copy. Nothing reachable from lisp can populate it — the live Runtime stack's GoStack is always nil, so an error raised by the `error` builtin always copies a nil GoStack. A forged 'internal-panic is therefore treated as an ordinary condition and stays containable.
func JoinDocStrings ¶ added in v1.16.12
JoinDocStrings joins multiple doc string parts into a single string. Non-empty strings are joined with spaces. Empty strings produce blank lines, acting as paragraph separators.
func Lookup ¶ added in v1.77.0
Lookup reads the value of key in m as a T, with the conversions of ResultAs: string, int, float64, bool, []byte, []*LVal, Cells, Text, Name, *LVal, MapView, or the payload of a native value. It returns the value and true, or T's zero value and false when m has no such key, when the value is an error, or when the value does not convert to T.
status, ok := lisp.Lookup[string](m, "status") n, _ := lisp.Lookup[int](m, 3) inner, ok := lisp.Lookup[lisp.MapView](m, "response") raw, ok := lisp.Lookup[*lisp.LVal](m, k)
Lookup makes no check and charges no step. A string or int key allocates nothing on the interpreter's own maps.
func NativeValue ¶ added in v1.57.0
NativeValue reads the Go payload of an LNative value as a T, reporting whether the value actually was one. It replaces the hand-rolled pair every embedder writes at each boundary where lisp hands Go data back —
if v.Type != lisp.LNative {
return fmt.Errorf("not a native value: %v", v.Type)
}
h, ok := v.Native.(*Handle)
— which is unremarkable once and a liability eleven times (issue #546): the two halves are separable, and the half that gets dropped when a caller is in a hurry is the type check, because the assertion alone *looks* like it is doing the work.
The three gates run in this order, and any failure returns the zero T and false. NativeValue never panics:
- v is non-nil;
- v.Type == LNative;
- v.Native asserts to T.
Gate 2 preceding gate 3 is the point of the function, not a stylistic preference, because LVal.Native is NOT reserved for embedder payloads. It is also the interpreter's own backing storage for three lisp-reachable types: an LBytes value keeps its *[]byte there, an LSortMap its *MapData, and an LError its *CallStack. A bare `v.Native.(*[]byte)` therefore succeeds on any ordinary lisp bytes value and hands the caller the pointer the interpreter is itself holding — a write through it mutates a live value behind the kernel's back, past the ownership and seal invariants that make sharing safe (docs/internals/sealed-ast.md, lisp/seal.go) and past `copy`'s promise that a copy shares no storage with its original (issue #378). The same shape reaches a sorted map's whole backing implementation, and an error's captured call stack, whose Go-stack snapshot is the non-forgeable marker IsInternalPanic keys off. Gate 2 makes all three unreachable through this accessor.
The gate is on the LVAL's type, never on the payload's, and the difference is deliberate: an embedder who stores a *[]byte of their own with Native(&b) gets it back from NativeValue[*[]byte], because that value is an LNative and the slice is theirs. What is refused is the interpreter's storage, not a Go type.
T may be an interface: a payload stored as a concrete type is retrieved through any interface it implements, since gate 3 is an ordinary type assertion. A T of `any` succeeds for every non-nil payload of an LNative and is the way to ask "is there anything here" without naming the type.
A caller whose false branch only builds an error message wants RequireNative, which runs these same three gates and renders the failure as an LError.
func RegisterDefaultBuiltin ¶
RegisterDefaultBuiltin adds the given function to the list returned by DefaultBuiltins.
func RegisterDefaultMacro ¶
RegisterDefaultMacro adds the given function to the list returned by DefaultMacros.
func RegisterDefaultSpecialOp ¶
RegisterDefaultSpecialOp adds the given function to the list returned by DefaultSpecialOps.
func RequireNative ¶ added in v1.57.0
RequireNative reads the Go payload of an LNative value as a T, rendering the failure as an LError rather than as a bool. On success it returns the payload and a nil second value; the second value is nil ON SUCCESS ONLY, so a caller's whole failure branch is
h, err := lisp.RequireNative[*Handle](v)
if err != nil {
return lisp.Error(err)
}
which is the message the accessor pair was introduced to supply once instead of at each boundary (issue #546). NativeValue's `false` says only that something did not match, so every caller of it that reports a failure writes its own wording, and the substrate accumulated eleven of them — differing in which of the three gates they name, and none naming the type that was expected, which is the one fact the caller has and the value does not.
The gate sequence is NativeValue's, because this is a call to it: nil v, then v.Type == LNative, then the assertion, with the LVal type checked before the payload for the reasons documented there. RequireNative only re-examines v afterwards to say which gate refused it, so there is no second copy of the check to drift.
The three messages name the expected type and what was found:
expected native *pkg.Handle value, got nil expected native *pkg.Handle value, got bytes expected native *pkg.Handle value, got native *pkg.Other
The expected type comes from reflect.TypeFor[T] rather than a %T verb because T is a type parameter, not a value: %T of the zero T renders an interface T as <nil>, which loses precisely the type the message exists to report. The reflection is confined to the failure path.
The error carries no call stack of its own: with no *LEnv parameter there is no environment to capture one from, so it is built with the package-level Errorf (the env.Errorf methods attach a stack eagerly; this accessor cannot). Nothing is lost — an error returned up through eval acquires the current stack from LEnv.ErrorAssociate, which fills one in for any error still lacking it.
func ResetOwnershipTable ¶ added in v1.95.0
func ResetOwnershipTable()
ResetOwnershipTable drops the elpscheck build's ownership records. It is a no-op in production builds, which keep no records. See the elpscheck version for when a host calls it.
func ResultAs ¶ added in v1.77.0
ResultAs converts the result of a Go form to the Go type T. It takes the form's two results, so it wraps the call:
keys, err := lisp.ResultAs[lisp.Cells](env.MapKeys(m)) s, err := lisp.ResultAs[string](env.Concat(lisp.SeqString, a, b))
When err is not nil it returns err. When v is an LError it returns v as the error. When v converts to T it returns the Go value and a nil error. Otherwise it returns an error with condition "error", such as "value is not a string: int". For a value that is not a form's result, pass a nil err: lisp.ResultAs[string](v, nil).
T selects the conversion:
string a string only (use SymbolName for a symbol) int an integer only; a float is not truncated float64 any number; an integer is converted bool any value, by truthiness (see True); it never fails []byte bytes []*LVal the cells of a list Cells the cells of a list Text a string (copied) or bytes Name a string or a symbol, its text *LVal any value, as is MapView a sorted-map any other T the payload of a native value, through NativeValue[T]
A named type such as `type Status string` is "any other T": it reads a native, not a string. A []byte, []*LVal or Cells result shares the value's storage, so treat it as read-only.
Use ResultAs only where the value always has type T, such as the list that keys returns or the string that format-string returns. A mismatch is then a bug in the Go code. ResultAs uses no reflection and allocates nothing unless the conversion fails.
func SealedASTFingerprint ¶ added in v1.52.0
SealedASTFingerprint computes a structural digest of the sealed nodes reachable from roots: types, scalar payloads, cell structure, and source-location contents, in traversal order. Any in-place change to a sealed node changes the digest; see the file comment for the exact coverage (and non-coverage) contract.
The digest is content-based — no pointer identity — so it is deterministic across processes: fingerprint a tree at parse time, store the value, and compare at any later point to prove the sealed bytes never changed. Fingerprinting never mutates the tree and allocates nothing.
func SeqOf ¶ added in v1.77.0
SeqOf converts a list or a one-dimensional vector to a Go slice of T, with the conversions of ResultAs for each element. It returns ok=false when v is not such a sequence and when any element does not convert to T; there is no partial result.
names, ok := lisp.SeqOf[string](methodArgs)
SeqOf allocates the slice it returns and nothing else. It makes no check and charges no step. It reads one level only: GoSliceOf converts nested values and reads lists only.
func SetMatchTypes ¶ added in v1.93.0
func SetMatchTypes(rt *Runtime, is MatchTypeFunc, field MatchFieldFunc) error
SetMatchTypes registers the functions match uses for type and record patterns in rt. A template publishes them to each VM it makes. Both functions must keep no state of their own: every VM shares them.
func SymbolName ¶
SymbolName returns the name of the symbol that v represents and the value true. If v does not represent a symbol SymbolName returns a false second argument
func True ¶
True interprets v as a boolean and returns the result.
NOTE: I don't like this name, really. But I can't think of a better one.
func VerifySealedASTs ¶ added in v1.52.0
func VerifySealedASTs() error
VerifySealedASTs re-verifies every sealed parse recorded in this process against its parse-time fingerprint. Production builds (no elpscheck tag) record nothing, so this always returns nil at zero cost; under `go test -tags elpscheck` it performs the real check. See lisp/seal_check_elpscheck.go.
Types ¶
type ArgDecoder ¶ added in v1.73.0
type ArgDecoder[T any] struct { // contains filtered or unexported fields }
ArgDecoder decodes argument i of a builtin's argument list as a T, recording a failure in the ArgReader. Construct decoders once, at package initialization, with the functions below; they hold only their message text and default. For a decoder of your own, pass a function to CustomArg and use ArgReader.Check to record its failure.
func CustomArg ¶ added in v1.74.0
func CustomArg[T any](f func(a *ArgReader, i int) T) ArgDecoder[T]
CustomArg returns a decoder that calls f. The call is indirect, so a builtin with a custom decoder puts its ArgReader on the heap: one allocation per call, shared by all of the call's decoders. Prefer the constructors above when one of them fits.
func OptArg ¶ added in v1.74.0
func OptArg() ArgDecoder[*LVal]
OptArg decodes an &optional or &key argument unchecked, nil when it was not supplied, as ArgReader.Opt.
func OptIntArg ¶ added in v1.74.0
func OptIntArg(what string, def int) ArgDecoder[int]
OptIntArg decodes an &optional or &key integer, def when it was not supplied: "<what> is not an integer: <type>", as ArgReader.OptInt.
func OptStringArg ¶ added in v1.74.0
func OptStringArg(what string, def string) ArgDecoder[string]
OptStringArg decodes an &optional or &key string, def when it was not supplied: "<what> is not a string: <type>", as ArgReader.OptString.
func StringArg ¶ added in v1.73.0
func StringArg(what string) ArgDecoder[string]
StringArg decodes a required string: "<what> is not a string: <type>".
func TypedArg ¶ added in v1.73.0
func TypedArg(t LType, format string) ArgDecoder[*LVal]
TypedArg decodes a required argument of type t; otherwise the failure is env.Errorf(format, actualType), as ArgReader.Typed.
func ValueArg ¶ added in v1.73.0
func ValueArg() ArgDecoder[*LVal]
ValueArg decodes a required argument unchecked.
type ArgReader ¶ added in v1.73.0
type ArgReader struct {
// contains filtered or unexported fields
}
ArgReader decodes a Go builtin's argument list into Go values, raising the same "<what> is not a <type>: <actual type>" messages builtins write by hand (luthersystems/elps#745), under condition argument-error (CondArgumentError), a child of error. Error text is program output -- a phylum can catch and return it, and every endorsing peer must produce the same bytes -- so an ArgReader read never picks a subject: the caller passes it ("first argument", "name") exactly as its hand-written Errorf spelled it, or a whole format to Typed. Func1E, Func2E and Func3E pass the standard subject for the argument's position ("argument", "first argument", ...), for ports that keep only the error condition; elps's own builtins do not use them.
The first failure is recorded and every later read returns a zero value without checking, so a builtin reads all its arguments and then checks Err once. Err follows the helpers' convention: Nil() when every read succeeded, so `lerr.Type == lisp.LError` is the check. That is equivalent to returning at the first failed check, provided the reads come in the order the checks did: decoding has no side effects and charges no steps.
a := lisp.ReadArgs(env, args)
source := a.String(0, "first argument")
name := a.OptString(1, "name", "")
if lerr := a.Err(); lerr.Type == lisp.LError {
return lerr
}
An ArgReader is a small value meant to live in the builtin's frame; it does not allocate unless a check fails.
func (*ArgReader) Bytes ¶ added in v1.77.0
Bytes returns required argument i, which must be bytes; otherwise it records "<what> is not bytes: <type>". The result shares the argument's storage, so treat it as read-only.
func (*ArgReader) Check ¶ added in v1.73.0
Check records env.ErrorConditionf(CondArgumentError, format, args...) when ok is false and no earlier read failed. It reports whether the reader is still free of failures, so a custom decoder can stop at the first problem:
v := a.Value(i)
if !a.Check(v.Type == lisp.LString, "argument is not a date: %v", v.Type) {
return date{}
}
func (*ArgReader) Err ¶ added in v1.73.0
Err returns the first failure, or Nil() when there was none.
func (*ArgReader) Fun ¶ added in v1.77.0
Fun returns required argument i, which must be a function; otherwise it records "<what> is not a function: <type>".
func (*ArgReader) Int ¶ added in v1.77.0
Int returns required argument i, which must be an integer; otherwise it records "<what> is not an integer: <type>".
func (*ArgReader) Map ¶ added in v1.77.0
Map returns required argument i, which must be a sorted-map; otherwise it records "<what> is not a map: <type>".
func (*ArgReader) Opt ¶ added in v1.73.0
Opt returns &optional or &key argument i, or nil when it was not supplied (LVal.KeyArg).
func (*ArgReader) OptInt ¶ added in v1.73.0
OptInt returns &optional or &key argument i as an integer, def when it is nil, and otherwise records "<what> is not an integer: <type>".
func (*ArgReader) OptString ¶ added in v1.73.0
OptString returns &optional or &key argument i as a string, def when it is nil, and otherwise records "<what> is not a string: <type>".
func (*ArgReader) Seq ¶ added in v1.77.0
Seq returns the cells of required argument i, which must be a list or a one-dimensional vector; otherwise it records "<what> is not a proper sequence: <type>". The cells share the argument's storage, so treat them as read-only.
func (*ArgReader) String ¶ added in v1.73.0
String returns required argument i, which must be a string; otherwise it records "<what> is not a string: <type>".
type BindOpts ¶ added in v1.73.0
type BindOpts struct {
// Export exports every bound name from the package.
Export bool
// Shadow binds a name the package already has -- typically one it
// imports, such as get from the language package -- the way a Lisp
// defun of that name would, replacing the binding in this package only.
// Without Shadow such a name is an error, as it is for AddBuiltins.
Shadow bool
}
BindOpts controls LEnv.BindBuiltins.
type Binding ¶ added in v1.73.0
Binding is one local variable reported by LEnv.Locals: the symbol's name and the value it is bound to.
type BuiltinRef ¶ added in v1.73.0
type BuiltinRef struct {
// contains filtered or unexported fields
}
BuiltinRef is a resolved handle on one of the builtins DefaultBuiltins returns (the language package's builtins plus any RegisterDefaultBuiltin registration made before it was resolved). Resolve one with BuiltinFunc and call it with LEnv.CallBuiltin.
It exists for Go builtins that reuse a language builtin for its exact error text and guards (sealed maps, MaxAlloc, typed keys) -- a native that replaces a Lisp definition calling get, + or assoc! must raise exactly what the Lisp raised (luthersystems/elps#745). The zero BuiltinRef is invalid.
A BuiltinRef is only a position in the builtin tables, which are append-only, so it keeps no *LVal reachable: a package-level BuiltinRef shares nothing between runtimes and passes elpsvet's elpsownership rule.
func BuiltinFunc ¶ added in v1.73.0
func BuiltinFunc(name string) BuiltinRef
BuiltinFunc resolves the default builtin named name. It panics when no such builtin exists, so resolve handles at package initialization, where a misspelled name fails the program at start-up rather than a call:
var builtinGet = lisp.BuiltinFunc("get")
Resolution is a linear scan, done once per handle; the handle itself is what makes each call cheap.
type CachedSource ¶ added in v1.54.0
type CachedSource struct {
// contains filtered or unexported fields
}
CachedSource is an opaque, immutable, sealed parse result. Only elps mints one — an embedder holds and returns handles but cannot construct one, open one, or reach the AST nodes inside it.
A *CachedSource is safe to share: its expressions are sealed throughout (see newCachedSource), so it may be held by a process-wide cache and served to any number of Runtimes, concurrently. Its own fields are written once at construction and never again.
The zero value is not usable; obtain one only from a LoadCache's Load.
func (*CachedSource) Fingerprint ¶ added in v1.54.0
func (s *CachedSource) Fingerprint() uint64
Fingerprint returns the canonical structural digest of the sealed parse (lisp/sealfp.go), taken at admission. It is a value, not a reference: exposing it lets a host log or deduplicate entries without any way to reach the nodes it summarises.
func (*CachedSource) Key ¶ added in v1.54.0
func (s *CachedSource) Key() string
Key returns the cache key elps derived for this entry. It is the key the entry was Stored under, and a cache that hands back an entry under a different key is treated as a miss (see (*LEnv).readCached).
func (*CachedSource) Len ¶ added in v1.54.0
func (s *CachedSource) Len() int
Len returns the number of top-level expressions in the cached parse.
func (*CachedSource) Location ¶ added in v1.54.0
func (s *CachedSource) Location() string
Location returns the physical location the source was parsed under, or the empty string when the parse had no location (the Reader, rather than LocationReader, path).
func (*CachedSource) Name ¶ added in v1.54.0
func (s *CachedSource) Name() string
Name returns the stream name the source was parsed under.
func (*CachedSource) String ¶ added in v1.54.0
func (s *CachedSource) String() string
String returns a short debugging description. Like Program.String it deliberately does not render the cached expressions.
type CallFrame ¶
type CallFrame struct {
Source *token.Location
FID string
Package string
Name string
// HeightLogical is the frame's virtual depth: its physical depth plus
// every frame elided beneath it by tail-call optimization. It is a
// diagnostic quantity — a tail loop runs in constant physical height
// but its HeightLogical grows without bound.
HeightLogical int
Terminal bool
TROBlock bool // Stop tail-recursion optimization from collapsing this frame
// TailIterations counts tail-call iterations performed at this frame.
// Each turn of a tail loop increments it by exactly one, regardless of
// how many frames that turn elided.
//
// DELIBERATELY int32, AND DELIBERATELY PLACED AFTER THE BOOLS. A
// CallFrame is allocated per call, so its size is on the hot path: as
// an `int` declared above Terminal/TROBlock this field grew the struct
// from 72 to 80 bytes (+11.1%), which the benchmark gate caught as
// +8.44% B/op on EnvFunCallRecursion with allocs/op unchanged — same
// allocation count, larger allocations. Two bools leave 6 bytes of
// tail padding that an 8-byte int cannot use but a 4-byte int32 can, so
// this placement is free: the struct stays 72 bytes.
//
// int32 caps at ~2.1e9 iterations against a default budget of 1e6. If
// MaxTailIterations is ever raised near that ceiling, widen this field
// and accept the 8 bytes rather than letting the counter wrap.
TailIterations int32
}
CallFrame is one frame in the CallStack
func (*CallFrame) QualifiedFunName ¶
QualifiedFunName returns the qualified name for the function on the top of the stack. If ignore is non-empty QualifiedFunName returns unqualified names for functions in the given packages.
type CallStack ¶
type CallStack struct {
Frames []CallFrame
GoStack []byte
// MaxHeightLogical bounds CallFrame.HeightLogical, which accumulates
// every frame elided by tail-call optimization. Its unit is *elided
// frames*, not loop turns: one iteration of a tail loop adds the length
// of the elided terminal chain (2 for a trivial (defun f (n) (if ...
// (f ...))), more for a body with deeper terminal nesting). Because
// the ratio depends on the shape of the loop body, this is a poor knob
// for bounding iteration count — use MaxTailIterations for that. Zero
// disables the check (the default).
MaxHeightLogical int
// MaxHeightPhysical bounds the number of frames physically on the
// stack. This is the memory guard: exceeding the Go goroutine stack
// aborts the process with an unrecoverable "stack overflow", so this
// limit must stay comfortably below that threshold. Frames elided by
// tail-call optimization do not count toward it. Zero disables the
// check.
MaxHeightPhysical int
// MaxTailIterations bounds CallFrame.TailIterations — the number of
// tail-call iterations performed at a single stack frame. Unlike
// MaxHeightLogical its unit is loop turns, independent of the shape of
// the loop body, so an operator can reason about it directly. It is a
// runaway-loop backstop, not a business limit. Zero disables the
// check.
MaxTailIterations int
// contains filtered or unexported fields
}
CallStack is a function call stack.
For errors produced by recovery of a Go panic, GoStack carries the runtime.Stack output captured at the panic site so callers (via ErrorVal.WriteTrace or direct access) can render the Go-level origin alongside the ELPS frames. It is nil for non-panic errors.
Keep slice headers before scalars.
func (*CallStack) CheckHeight ¶
func (*CallStack) CheckTailCall ¶ added in v1.49.0
CheckTailCall runs the limit checks that apply after a tail-call optimization elides frames: the (opt-in) logical height bound and the tail-iteration bound. Physical height is unchanged by a tail call, so it is deliberately not rechecked here.
func (*CallStack) CheckTailIterations ¶ added in v1.49.0
CheckTailIterations returns an error if the top frame has performed more tail-call iterations than MaxTailIterations allows.
func (*CallStack) Copy ¶
Copy creates a copy of the current stack so that it can be attach to a runtime error.
func (*CallStack) DebugPrint ¶
DebugPrint prints s under its captured output limit, or the default output limit for a live stack, with no cancellation. Exhaustion emits #<truncated>. A caller with a request context in hand renders the stack through the error's WriteTraceContext instead.
func (*CallStack) Pop ¶
Pop removes the top CallFrame from the stack and returns it. If the stack is empty Pop returns nil.
func (*CallStack) SourceSite ¶ added in v1.85.0
SourceSite names where the innermost call with a source position runs and the top-level form that reached it. It returns "" when no frame has a source position, a nil stack included. Otherwise it returns
inner <- reach
where inner is the position of the innermost frame with a source position. reach is the position of the outermost frame with a source position inside the innermost source stream that encloses inner. A source stream starts after a load-file, load-string or load-bytes frame below inner; with no such frame, the stream is the whole stack. So reach is the top-level form, in the file being loaded, whose evaluation reached inner. When inner and reach are the same position, SourceSite returns inner alone.
A position is the frame's token.Location.String: "file:line:col", "file:line" when Col is 0, or "file[pos]" when Line is 0. A frame has a source position when its Source is not nil, its Pos is not negative and its File is neither "" nor token.NativeFile. A builtin that Go calls has token.NativeLocation, which has no source position.
Limits:
- Only the load-file, load-string and load-bytes builtins start a source stream. A load that Go starts (LEnv.LoadFile, LoadString, LoadProgramContext, an elpsutil.FileLoader) pushes no frame, so for code it loads, reach is a form of the enclosing stream.
- A load frame counts only when its package is DefaultLangPackage ("lisp"), the language package InitializeUserEnv sets. A CallStack does not know its runtime's Registry.Lang, so an embedder that names another language package gets no stream boundaries.
- A position names the file as the reader named it, so two files with the same name in different directories give the same position.
SourceSite only reads the frames. It takes no lock, so call it on the goroutine that runs the evaluation, for example from a builtin, or when no evaluation runs.
func (*CallStack) TerminalFID ¶
TerminalFID determines if a chain of terminal stack frames that ends with fid (i.e. fid is a candidate for tail-recursion optimization) and returns the number of frames in the shortest such chain. If no such chain of terminal frames can be found then 0 is returned.
If a stack frame with TROBlock is found then the search for a terminal chain is prematurely terminated as a failure.
NOTE: If tail-recursion optimization is working then the chain of calls found by TerminalFID is unique.
type CapturedFun ¶ added in v1.93.0
type CapturedFun struct {
// Formals is the formal argument list, as for FunInPackage.
Formals *LVal
// Captures is the value's VM state.
Captures *LVal
// Eval runs a call. It must not be nil.
Eval func(env *LEnv, args, captures *LVal) *LVal
// Package and FID identify the function, as for FunInPackage. All the
// values that one constructor makes share them.
Package string
FID string
// Name is the display name: the value prints as #<Name>. An empty
// Name prints #<builtin>, as any Go function does. A Name must be
// valid UTF-8 with no space, control character or '>'.
Name string
}
CapturedFun describes a Go function value whose VM state is explicit. An embedder uses it to give Lisp code an object that dispatches in Go, where it would otherwise return a Lisp closure over the object's state.
Eval receives the call's arguments and the capture graph of the VM that calls it. Template.NewVM remaps the graph with the rest of the VM, so a value that a template holds gets a private copy of its state in each fork, as a Lisp closure does. Eval must keep every mutable value it uses in Captures; it may close over immutable, concurrency-safe Go data only. Captures is held by reference, so aliases to other values in the same VM stay intact. A nil Captures declares no VM state.
Lisp code cannot read Captures: the state is as private as a closure's environment. A durable dump saves the value only through a codec registered for its Package and FID (libjson.DurableFunCodec).
type CellReader ¶ added in v1.77.0
type CellReader struct {
// contains filtered or unexported fields
}
CellReader reads a builtin's arguments in order, with no index and no subject string (luthersystems/elps#829). Get one from Cells.Read:
r := lisp.Cells(args.Cells).Read(env)
collection, key := r.Name(), r.Name()
limit := r.OptInt(100)
if lerr := r.Err(); lerr.IsError() {
return lerr
}
Each read takes the next cell. The subject of a failure comes from its position, as Func1E, Func2E and Func3E name it: "argument" when there is one cell, otherwise "first argument", "second argument" and so on. A failure raises condition argument-error (CondArgumentError), a child of error. The first failure sticks: every later read returns a zero value without checking, and Err returns that failure. A read past the last cell fails with "invalid number of arguments: <n>".
A CellReader is a small value meant to live in the builtin's frame. It allocates nothing and charges no step unless a read fails. Values it returns that are slices ([]byte, Cells) share the argument's storage, so treat them as read-only, except Text of a string, which is a copy.
Use Func1E, Func2E or Func3E for one to three required typed arguments, CellReader for optional or rest arguments, a raw *LVal or a receiver method, and ArgReader for a custom message per argument.
func (*CellReader) Bytes ¶ added in v1.77.0
func (r *CellReader) Bytes() []byte
Bytes returns the next argument, which must be bytes.
func (*CellReader) Err ¶ added in v1.77.0
func (r *CellReader) Err() *LVal
Err returns the first failure, or Nil() when every read succeeded.
func (*CellReader) Float ¶ added in v1.77.0
func (r *CellReader) Float() float64
Float returns the next argument, which must be a number; an integer is converted.
func (*CellReader) Fun ¶ added in v1.77.0
func (r *CellReader) Fun() *LVal
Fun returns the next argument, which must be a function.
func (*CellReader) Int ¶ added in v1.77.0
func (r *CellReader) Int() int
Int returns the next argument, which must be an integer.
func (*CellReader) Map ¶ added in v1.77.0
func (r *CellReader) Map() MapView
Map returns the next argument, which must be a sorted-map, as a MapView. After a failure it returns the zero MapView.
func (*CellReader) Name ¶ added in v1.77.0
func (r *CellReader) Name() string
Name returns the text of the next argument, which must be a string or a symbol.
func (*CellReader) OptFloat ¶ added in v1.77.0
func (r *CellReader) OptFloat(def float64) float64
OptFloat returns the next argument, which must be a number, or def when it is absent.
func (*CellReader) OptInt ¶ added in v1.77.0
func (r *CellReader) OptInt(def int) int
OptInt returns the next argument, which must be an integer, or def when it is absent.
func (*CellReader) OptName ¶ added in v1.77.0
func (r *CellReader) OptName(def string) string
OptName returns the text of the next argument, which must be a string or a symbol, or def when it is absent.
func (*CellReader) OptStr ¶ added in v1.77.0
func (r *CellReader) OptStr(def string) string
OptStr returns the next argument, which must be a string, or def when it is absent.
func (*CellReader) OptValue ¶ added in v1.77.0
func (r *CellReader) OptValue() *LVal
OptValue returns the next argument unchecked, or nil when it is absent.
func (*CellReader) Rest ¶ added in v1.77.0
func (r *CellReader) Rest() Cells
Rest returns the cells not read yet, which share the argument list's storage. After a failure it returns nil.
func (*CellReader) Seq ¶ added in v1.77.0
func (r *CellReader) Seq() Cells
Seq returns the cells of the next argument, which must be a list or a one-dimensional vector.
func (*CellReader) Str ¶ added in v1.77.0
func (r *CellReader) Str() string
Str returns the next argument, which must be a string. It is not named String, so a CellReader is not a fmt.Stringer: printing a reader must not consume an argument.
func (*CellReader) Text ¶ added in v1.77.0
func (r *CellReader) Text() []byte
Text returns the bytes of the next argument, which must be a string (the bytes are a copy) or bytes (the argument's own storage).
func (*CellReader) Value ¶ added in v1.77.0
func (r *CellReader) Value() *LVal
Value returns the next argument unchecked.
type Cells ¶ added in v1.77.0
type Cells []*LVal
Cells is a slice of values. Go assigns it to and from []*LVal with no conversion, and it costs nothing at run time. It lets Go code build a list or a vector as one expression:
lisp.Cells{lisp.String("info"), merged, lisp.String("generating exception")}.List()
List, SExpr and Vector use the receiver as the new value's storage, as QExpr, SExpr and Vector do: they do not copy it. So lisp.Cells(v.Cells).List() shares v's cells. Build the slice fresh, or copy it first, when the result must not alias another value. Map, Clone and Append always return fresh storage, and MapIfChanged does when it reports a change, so their results chain into List, SExpr and Vector:
lisp.Cells(args).Append(bookmark).List()
func (Cells) Append ¶ added in v1.77.0
Append returns a fresh slice holding the cells of c followed by xs, of length and capacity len(c)+len(xs). Unlike the append builtin it never writes c's backing array, even when c has spare capacity, so c and any value that shares it are unchanged. It is one allocation, where append(slices.Clone(c), xs...) is two. A nil c with no xs gives an empty non-nil slice.
func (Cells) Clone ¶ added in v1.77.0
Clone returns a fresh copy of c whose length and capacity are both len(c). A nil c gives nil, and an empty non-nil c gives an empty non-nil slice. It copies the cell pointers, not the values they point to.
slices.Clone and append([]*LVal(nil), c...) may round the capacity up, so a later append to their result can write past len(c) without a copy; Clone's exact capacity makes any append copy.
func (Cells) List ¶ added in v1.77.0
List returns a list (a quoted s-expression) whose cells are c. It is QExpr(c).
func (Cells) Map ¶ added in v1.77.0
Map returns a fresh slice holding f(x) for each cell x of c, in order. Its length and capacity are len(c). A nil c gives nil, and an empty non-nil c gives an empty non-nil slice. Map never aliases c, so the result may become a new value's storage:
lisp.Cells(form.Cells).Map(expand).SExpr()
Map makes no allocation check and charges no step; f does what it does.
func (Cells) MapIfChanged ¶ added in v1.77.0
MapIfChanged is Map that copies only when it must. When f returns each cell itself (pointer-equal), MapIfChanged returns c, the original slice, and false, having allocated nothing. Otherwise it returns a fresh slice of length and capacity len(c), with the unchanged prefix copied from c, and true. The input is never written. This is the copy-on-change loop a code walker writes by hand:
out, changed := lisp.Cells(form.Cells).MapIfChanged(rewrite)
if !changed {
return form
}
return out.SExpr()
When it reports false the result is c itself, so do not use it as the storage of a new value then.
func (Cells) Read ¶ added in v1.77.0
func (c Cells) Read(env *LEnv) CellReader
Read returns a CellReader over the cells c, which are a builtin's arguments.
func (Cells) SExpr ¶ added in v1.77.0
SExpr returns an unquoted s-expression whose cells are c. It is SExpr(c).
func (Cells) Strings ¶ added in v1.77.0
Strings returns the text of every cell of c, which must all be strings, in a fresh slice of exact length and capacity. When a cell is not a string it returns nil and that cell, so the caller writes its own message:
parts, bad := lisp.Cells(args.Cells[1:]).Strings()
if bad != nil {
return env.Errorf("docstring argument is not a string: %v", bad.Type)
}
A symbol is not a string here. An empty c gives nil and nil. It makes no check beyond the types and charges no step.
type ClosureIssue ¶ added in v1.79.0
type ClosureIssue struct {
// Name is the symbol as the code spells it.
Name string
// Message says what is wrong, for example "calls f with 3
// argument(s); f takes 1 to 2".
Message string
// Args is the number of arguments the call passes, for ClosureArity
// and ClosureNotFunction, else 0.
Args int
// Kind is the kind of finding.
Kind ClosureIssueKind
}
ClosureIssue is one finding of LEnv.CheckClosure.
type ClosureIssueKind ¶ added in v1.79.0
type ClosureIssueKind uint8
ClosureIssueKind classifies a ClosureIssue.
const ( // ClosureUnbound is a global the code reads, calls, names with // function or assigns with set!, and that no binding defines. ClosureUnbound ClosureIssueKind = iota + 1 // ClosureNotFunction is a call of a global bound to a value that is // not a function. ClosureNotFunction // ClosureArity is a call of a global function or macro with a number // of arguments its formals refuse. ClosureArity )
The kinds of ClosureIssue.
type CodeVisitor ¶ added in v1.73.0
CodeVisitor receives the events of a code walk. For WalkForm, returning false skips the form's arguments; the return value is otherwise ignored. The *WalkNode is reused for the next event: copy it to keep it.
type CodeWalker ¶ added in v1.73.0
type CodeWalker struct {
// SpecialOp classifies a head symbol that is not lexically bound. It
// returns the builtin form name the symbol denotes, or false for a
// function or macro. Nil means defaultSpecialOpName.
SpecialOp func(head *LVal) (string, bool)
// Expand1 expands form once when its head names a macro. ok is false
// when it is not a macro call. An expansion that fails returns an
// LError, which stops the walk. Nil disables expansion: Walk then only
// visits.
Expand1 func(form *LVal) (expansion *LVal, ok bool)
// Visit receives the walk's events. It may be nil.
Visit CodeVisitor
// MaxDepth bounds form nesting (default DefaultMaxEvalNesting) and
// MaxExpansions the macro expansions of one form's head (default
// DefaultMaxMacroExpansionDepth).
MaxDepth int
MaxExpansions int
// KeepGoing makes failures local: a macro call whose expansion fails
// or does not terminate is walked unexpanded as a function call, and
// a form nested past MaxDepth is reported as opaque, instead of
// stopping the walk. Tools that must see all of a file (lint) set
// it; macroexpand-all does not.
KeepGoing bool
// contains filtered or unexported fields
}
CodeWalker walks ELPS code, expanding macros and reporting what each part of every form is. The walk understands every builtin special form's binding shape, tracks lexical bindings (so a local function or variable that shadows a macro name is not expanded), and never descends into quoted data.
The walk never writes to its input. Walk returns a new tree when any macro was expanded: every list on the path to an expansion is a fresh header over a fresh cells array carrying the original's source location and quoting, and every subtree the walk did not change is returned as the same node. So a sealed program literal can be walked, and the result shares no mutable storage with it.
func (*CodeWalker) Walk ¶ added in v1.73.0
func (w *CodeWalker) Walk(form *LVal) *LVal
Walk walks form as code and returns it with every macro call expanded. A failed expansion returns the LError.
type CodecError ¶ added in v1.88.0
type CodecError struct {
// Path is the place of the value, such as "items[2].name"; "" is the
// value itself.
Path string
// Msg says what failed.
Msg string
}
CodecError is the error of Decode, DecodeInto and Encode for a value they cannot convert.
func (*CodecError) Error ¶ added in v1.88.0
func (e *CodecError) Error() string
type Config ¶
Config is a function that configures a root environment or its runtime.
func WithContext ¶ added in v1.25.0
WithContext returns a Config that sets the initial context.Context for the root environment. The context is checked at each evaluation step; if it is cancelled or its deadline expires, evaluation returns a CondContextCancelled error. For per-call context control, use the *Context methods on LEnv instead.
func WithDebugger ¶ added in v1.21.0
WithDebugger returns a Config that attaches a debugger to the runtime. When a debugger is attached, tail recursion optimization is disabled to provide predictable stepping behavior and stack traces.
func WithLegacyKeywordFormals ¶ added in v1.65.0
WithLegacyKeywordFormals returns a Config that restores the v1.61.0 treatment of a keyword used as a parameter or lexical binding name (issue #686). It is OFF by default, and off is the intended end state: such a name is a bug, and a definition declaring one is refused with an error that says to use &key.
When on, `(defun f (:a a :b b) ...)` is accepted and declares four POSITIONAL parameters, exactly as v1.61.0 did: `(f :a 1 :b 2)` binds a to 1 and b to 2 because the caller passes the keywords in the keyword slots. The keyword itself is bound in the lexical scope, but reading it still yields the keyword (a keyword evaluates to itself); set! on it succeeds. let, let*, dotimes, flet and labels accept keyword names the same way. Package (global) bindings of keywords stay refused, as they always were, and so do keyword formals in host-registered functions.
The setting belongs to the runtime: it does not affect other environments, and a Template published from an environment carries it into every VM. It is meant as a migration window; `elps lint` reports every affected definition.
func WithLibrary ¶ added in v1.16.3
func WithLibrary(l SourceLibrary) Config
WithLibrary returns a Config that makes environments use l as a source library.
func WithLoader ¶
WithLoader returns a Config that executes fn and ensures that the environment's working package is reset following execution of fn. Despite fn having the same signature as a Config WithLoader allows a Loader to function more like the LEnv methods LoadFile, LoadString, etc.
func WithMaxAlloc ¶ added in v1.20.0
WithMaxAlloc returns a Config that sets the per-operation allocation size cap (in bytes for strings and byte buffers, in elements for sequences). It bounds the logical sizes of newly built data containers, not existing values returned by reference, implementation temporaries or cumulative memory usage. See docs/lang.md#allocation-limits.
func WithMaxEvalNesting ¶ added in v1.49.0
WithMaxEvalNesting returns a Config that bounds how deeply the evaluator may recurse into itself while evaluating a single expression.
This is a distinct quantity from stack height and is not implied by it. A call's arguments are evaluated before the call's frame is pushed, so ((lambda (x) x) ((lambda (x) x) ... )) recurses through the Go evaluator while the physical stack height stays at zero — the exact shape WithMaximumPhysicalStackHeight exists to stop and the one shape it cannot see (issue #316). Nesting is bounded here instead.
A value of 0 selects DefaultMaxEvalNesting. A negative value disables the check, which re-exposes the host process to an unrecoverable "fatal error: stack overflow"; do that only when some other bound on expression depth is guaranteed.
func WithMaxMacroExpansionDepth ¶ added in v1.20.0
WithMaxMacroExpansionDepth returns a Config that limits the number of successive macro expansions during evaluation. This prevents infinite macro expansion from exhausting memory.
func WithMaxSleep ¶ added in v1.49.0
WithMaxSleep returns a Config that sets a HARD CEILING on how long a single (time:sleep d) may block, in the host's hands rather than the program's. The standard library no longer registers time:sleep (#757); the bound applies to a host that registers libtime.BuiltinSleep, written here as time:sleep.
This is the containment bound, and it is not the same knob as DefaultMaxSleep. A sleep with no explicit :max is capped at DefaultMaxSleep, which program source may raise per call with (time:sleep d :max m). That is a guard against accidents. The ceiling set here is what :max may not exceed, so a program cannot opt itself out of it — which matters when the program is untrusted, as customer-supplied phylum source is downstream in luthersystems/substrate.
Zero or negative means no ceiling: :max may name any duration. That is the default, because the interpreter cannot know what wall-clock budget the host is willing to spend.
Note what this does NOT do: it bounds one sleep call, not their sum. A loop of N sleeps each just under the ceiling still blocks for N times the ceiling. Bounding total elapsed time is what a context deadline is for (see WithContext) -- sleep observes that too, and refuses immediately rather than blocking to the deadline.
func WithMaxSteps ¶ added in v1.25.0
WithMaxSteps returns a Config that sets the maximum number of evaluation steps before evaluation returns a CondStepLimitExceeded error. A step is counted for each Eval entry, each TRO iteration, each macro re-expansion, each turn of a dotimes loop, and each value-passing callback, error handler or threading step (callValueFunction). A value of 0 means unlimited (the default).
The dotimes turn is counted because an empty-bodied loop evaluates nothing: (dotimes (i 2147483647)) consumed no budget and could not be interrupted at all. It costs exactly one extra step per turn, so a dotimes-heavy program now uses more budget than it did -- proportionally most for a small body (a constant body goes from 1 step per turn to 2; a three-form body from 12 to 13). A budget pinned tightly against a previously measured figure may need raising. opDoTimes carries the full measurement table.
The budget is per top-level evaluation: the counter is reset each time an exported entry point (Eval, EvalContext, EvalSExpr, FunCall, FunCallContext, SpecialOpCall, MacroCall, or any Load*) is entered from outside an evaluation. Nested evaluation — a builtin calling back into Eval, the tail-call loops, forms evaluated by load — shares the enclosing budget and does not refill it. Without the reset the limit would be a lifetime quota that permanently kills a long-lived Runtime once it was reached.
Runtime.Steps reports the current evaluation's usage; Runtime.TotalSteps reports the lifetime total. A native builtin that does work proportional to its input charges it to the same budget with LEnv.ChargeSteps.
A step budget is the only limit here that bounds an infinite loop which neither recurses nor tail-calls; the stack limits cannot see such a loop.
func WithMaxTailIterations ¶ added in v1.49.0
WithMaxTailIterations returns a Config that bounds the number of tail-call iterations a single stack frame may perform. Tail calls run in constant stack space, so neither stack-height limit can bound a runaway tail loop; this is the limit that does. Its unit is loop turns, independent of how many frames each turn elides.
A value of 0 disables the check. The default is DefaultMaxTailIterations, chosen as a runaway-loop backstop rather than a business limit.
func WithMaxValueDepth ¶ added in v1.62.0
WithMaxValueDepth sets the limit for iterative runtime value walkers, including copying, equality, JSON dumping, quasiquote and template admission. Values must be at least 1024. Raising the limit above the default MaxValueDepth is supported because these traversals use heap stacks, not Go recursion. APIs without a Runtime use MaxValueDepth; rendered text retains its separate fixed 1024-depth cap.
func WithMaximumLogicalStackHeight ¶
WithMaximumLogicalStackHeight returns a Config that will prevent an execution environment from allowing the logical stack height to exceed n. The logical height of the stack is the stack's physical height plus the number of stack frames which have been elided due to tail recursive call optimizations.
This limit is disabled by default (see DefaultMaxLogicalStackHeight). Its unit is elided frames, not loop turns: one turn of a tail loop adds the length of the elided terminal chain, which varies with the shape of the loop body. To bound how many turns a tail loop may take, use WithMaxTailIterations instead; to bound total evaluation work, use WithMaxSteps.
func WithMaximumPhysicalStackHeight ¶
WithMaximumPhysicalStackHeight returns a Config that will prevent an execution environment from allowing the physical stack height to exceed n. The physical stack height is the literal number of frames in the call stack and does not account for stack frames elided due to tail recursive call optimizations.
func WithReader ¶
WithReader returns a Config that makes environments use r to parse source streams. There is no default Reader for an environment.
func WithStderr ¶
WithStderr returns a Config that makes environments write debugging output to w instead of the default, os.Stderr.
type DebugAction ¶ added in v1.21.0
type DebugAction int
DebugAction represents the action the interpreter should take after the debugger resumes execution from a paused state.
const ( // DebugContinue resumes execution until the next breakpoint. DebugContinue DebugAction = iota // DebugStepInto pauses on the next OnEval call regardless of depth. DebugStepInto // DebugStepOver pauses on the next OnEval call at the same or // lesser stack depth (does not descend into function calls). DebugStepOver // DebugStepOut pauses on the next OnEval call at a lesser stack // depth (waits until the current function returns). DebugStepOut )
type Debugger ¶ added in v1.21.0
type Debugger interface {
// IsEnabled returns true when the debugger is actively debugging.
// A dormant debugger (attached but not yet activated) returns false,
// allowing the interpreter to skip all other hook calls.
IsEnabled() bool
// OnEval is called before evaluating any expression with a real source
// location (v has a non-nil source). Synthetic expressions from macro expansion
// are skipped.
// Returns true if the debugger wants execution to pause (breakpoint hit
// or step complete).
OnEval(env *LEnv, expr *LVal) bool
// WaitIfPaused blocks until the debugger allows execution to continue.
// Called when OnEval returns true. The eval goroutine blocks here while
// the DAP server goroutine processes user commands.
// Returns the action to take after resuming.
WaitIfPaused(env *LEnv, expr *LVal) DebugAction
// OnFunEntry is called when a function is entered, after formal
// parameters have been bound in the function's lexical environment.
// env is the caller's environment; fenv is the function's environment
// with parameter bindings in scope. For builtins (which have no lexical
// env), this hook is not called.
OnFunEntry(env *LEnv, fun *LVal, fenv *LEnv)
// OnFunReturn is called after a function returns.
// fun is the function value, result is the return value.
OnFunReturn(env *LEnv, fun, result *LVal)
// AfterFunCall is called in Eval after EvalSExpr returns, giving the
// debugger a chance to pause at the call-site expression when the
// stack depth has decreased (step-out from tail position). Returns
// true if execution should pause.
AfterFunCall(env *LEnv) bool
// OnError is called when an error condition is created (in
// ErrorCondition and ErrorConditionf). Returns true if the debugger
// wants execution to pause (exception breakpoint). When true, the
// interpreter calls WaitIfPaused with the error value.
// Recovered Go panics bypass this hook to avoid re-entering a failing
// debugger; their errors retain the Go stack for diagnosis.
OnError(env *LEnv, lerr *LVal) bool
}
Debugger is called by the interpreter at key execution points to support breakpoints, stepping, and variable inspection. When Runtime.Debugger is nil, no hook calls are made and there is zero overhead on the hot path.
Hook calls use a two-check gate pattern:
if d := env.Runtime.Debugger; d != nil && d.IsEnabled() { ... }
The nil check is free (branch-predicted not-taken). IsEnabled allows a debugger to remain attached but dormant, further reducing overhead when not actively debugging.
type DiagnosticRenderer ¶ added in v1.62.0
type DiagnosticRenderer struct {
// contains filtered or unexported fields
}
DiagnosticRenderer shares an output and traversal budget across the values in one response. It is not safe for concurrent use.
func (*DiagnosticRenderer) Exhausted ¶ added in v1.62.0
func (r *DiagnosticRenderer) Exhausted() bool
Exhausted reports whether the response has exhausted its rendering budget.
func (*DiagnosticRenderer) Render ¶ added in v1.62.0
func (r *DiagnosticRenderer) Render(v *LVal) string
Render appends a value's diagnostic representation to the response budget. Exhaustion emits a fitting truncation marker and stops subsequent rendering.
func (*DiagnosticRenderer) Text ¶ added in v1.62.0
func (r *DiagnosticRenderer) Text(parts ...string) string
Text charges already formatted text to the same response budget. Parts are checked before copying, so large names need no unbounded intermediate string.
type ErrorVal ¶
type ErrorVal LVal
ErrorVal implements the error interface so that errors can be first class lisp objects. Rendering honours the originating runtime output limit, using #<truncated> on exhaustion; cancellation comes from the context a caller passes to a *Context reader, never from one the error stores. The condition name is stored in Str, message/data in Cells, and the captured call stack in Native.
func (*ErrorVal) Condition ¶ added in v1.17.0
Condition returns the error condition name (e.g., "parse-error", "unmatched-syntax"). This is the programmatic error classification stored in the LVal.Str field for LError values.
func (*ErrorVal) Error ¶
Error implements the error interface. When the error condition is not “error” it will be printed preceding the error message. Otherwise, the name of the function that generated the error will be printed preceding the error, if the function can be determined.
Defensive: a nil receiver returns the nilErrorMessage sentinel rather than dereferencing. This matters because diagnostic code that renders errors may be invoked from a deferred recover handler where the LVal pointer can be stale or zeroed.
func (*ErrorVal) ErrorContext ¶ added in v1.78.0
ErrorContext returns the lines of context WrapError added to the error, outermost first, or nil when there are none. The slice is a copy.
func (*ErrorVal) ErrorMessage ¶
ErrorMessage returns the underlying message in the error.
Defensive: a downstream consumer reported a SIGSEGV inside the type switch over Cells[0].Native when the interface header was corrupted (stale itab → invalid pointer deref during the type assertion). The deferred recover ensures the diagnostic pipeline always produces a renderable string even when the underlying LVal is malformed; well-formed errors are unaffected. The recovered panic is logged so the operator sees that something is corrupting the error's Cells[0].Native — silently swallowing would hide a real bug.
func (*ErrorVal) ErrorMessageContext ¶ added in v1.62.0
ErrorMessageContext returns the underlying error message bounded by the originating output limit and ctx. A nil context, or one that is already cancelled, disables cancellation checks and leaves the byte limit alone to bound the render. Cancellation of a live ctx during the traversal, or exhaustion, substitutes a fitting #<truncated> marker.
func (*ErrorVal) FunName ¶
FunName returns the qualified name of function on the top of the call stack when the error occurred.
func (*ErrorVal) Source ¶ added in v1.52.0
Source returns a copy of the error's originating source location. It has the same semantics as (*LVal).Source: the boolean reports whether the error carries a location, the returned value is a private copy, and a nil receiver reports no location.
func (*ErrorVal) Unwrap ¶ added in v1.62.0
Unwrap returns the original Go error carried by this condition, if any. GoError still returns the ErrorVal so its condition and source remain available.
func (*ErrorVal) WriteDiagnosticContext ¶ added in v1.62.0
func (e *ErrorVal) WriteDiagnosticContext(ctx context.Context, w io.Writer, renderer *diagnostic.Renderer, hint ...string) (int, error)
WriteDiagnosticContext renders the complete CLI/REPL diagnostic under the originating runtime's byte/work cap and request context. Prefixes, messages, source snippets, stack notes, hints and writes all share one session. Hint fields are passed separately to avoid allocating concatenated strings. A nil context, or one that is already cancelled, disables cancellation checks so the whole diagnostic still renders under the byte cap.
func (*ErrorVal) WriteTrace ¶
WriteTrace writes the error and stack trace under the captured output limit, with no cancellation. The entire trace shares one budget, including any Go stack. A nil receiver writes the nilErrorMessage sentinel rather than panicking.
func (*ErrorVal) WriteTraceContext ¶ added in v1.62.0
WriteTraceContext writes the error, frames, and Go stack under one output budget and ctx. A nil context, or one that is already cancelled, disables cancellation checks so the whole trace still renders under the byte limit. Cancellation of a live ctx during the traversal, or exhaustion, emits a fitting #<truncated> marker.
type FSLibrary ¶ added in v1.16.14
FSLibrary implements SourceLibrary using an fs.FS. The fs.FS contract requires unrooted slash-separated paths without ".." elements, but does not guarantee confinement: os.DirFS follows symlinks outside its directory and is not a security boundary. Use an os.Root's FS method when filesystem loads must reject symlinks escaping a root directory at open time.
func (*FSLibrary) LoadSource ¶ added in v1.16.14
LoadSource cleans loc relative to the calling file, strips a leading slash, and reads it from FS. Access checks, including symlink handling, depend on FS.
type FunctionInfo ¶ added in v1.17.0
type FunctionInfo struct {
Source *token.Location
Name string // empty for lambda
Kind string // "defun", "defmacro", or "lambda"
DocString string
Params []ParamInfo
}
FunctionInfo holds metadata extracted from a defun, defmacro, or lambda s-expression.
func InspectFunction ¶ added in v1.17.0
func InspectFunction(node *LVal) *FunctionInfo
InspectFunction extracts metadata from a defun, defmacro, or lambda s-expression. Returns nil if node is not a recognized form.
type GenSyms ¶ added in v1.73.0
type GenSyms struct {
// contains filtered or unexported fields
}
GenSyms generates deterministic temporary symbols for one Go macro expansion. Its names contain @, which the ELPS reader cannot produce in a symbol, and have a level greater than any generated symbol in the argument forms. Names are not globally unique: independent expansions may reuse them. Use them for bindings scoped to the expansion, not persistent package names. See docs/internals/gensym.md for the capture argument and its limits.
func NewGenSyms ¶ added in v1.73.0
NewGenSyms creates a generator for args, the macro's argument list as received by its LBuiltin. It does not inspect args until the first Symbol call. Create one generator per expansion and keep args unchanged while using it.
func (*GenSyms) Symbol ¶ added in v1.73.0
Symbol returns a fresh, unquoted symbol named hint@level@index. The index starts at one and increases on every call, regardless of hint. A hint must not contain ':', which would turn the name into a package-qualified symbol. Symbol panics on such a hint or if a level or index would overflow uint64. It charges no evaluation steps and allocates only the symbol and its name.
type GoHelper ¶ added in v1.88.0
GoHelper names the Go code that returns what a language builtin returns, for a Go builtin that would otherwise call the builtin with CallBuiltin.
Method is the LEnv method, when there is one. It checks the evaluation's context first, as CallBuiltin does, and then runs the builtin's own body, so the value, the error and the allocation checks are the same. Note is the Go to write instead when there is no method, such as a type test for a predicate; it makes no context check.
func BuiltinGoHelper ¶ added in v1.88.0
BuiltinGoHelper returns the GoHelper for the language builtin name, and false when there is none.
type Key ¶ added in v1.77.0
Key is a type Lookup accepts as a sorted-map key. A string key also finds a symbol key with the same spelling, because a sorted-map treats the two alike. An int finds an int key. A MapKey (from Keys or All) and an *LVal find the key they hold. A named string type such as `type Field string` is not a Key; convert it with string(f).
type LBuiltin ¶
LBuiltin is a function that executes a lisp function.
When the evaluator or LEnv.CallBuiltin calls a builtin, args is a new list header, marked as data. The builtin may keep or return it. A builtin function must not write the cells of args: CallBuiltin passes its caller's slice as the cells. A special operator gets cells that nothing else reads.
func Func1 ¶ added in v1.73.0
func Func1[A any](da ArgDecoder[A], f func(env *LEnv, a A) *LVal) LBuiltin
Func1 returns an LBuiltin that decodes one argument and calls f.
func Func1E ¶ added in v1.77.0
Func1E returns an LBuiltin of one required argument whose body f returns (R, error). See the comment above Text for the argument decoders, and funcResult for the result. A non-nil error is returned as FuncE returns it.
func Func2 ¶ added in v1.73.0
func Func2[A, B any](da ArgDecoder[A], db ArgDecoder[B], f func(env *LEnv, a A, b B) *LVal) LBuiltin
Func2 returns an LBuiltin that decodes two arguments, in order, and calls f.
func Func2E ¶ added in v1.77.0
Func2E returns an LBuiltin of two required arguments whose body f returns (R, error). The arguments are decoded in order, and the first failure is returned.
func Func3 ¶ added in v1.77.0
func Func3[A, B, C any](da ArgDecoder[A], db ArgDecoder[B], dc ArgDecoder[C], f func(env *LEnv, a A, b B, c C) *LVal) LBuiltin
Func3 returns an LBuiltin that decodes three arguments, in order, and calls f.
func Func3E ¶ added in v1.77.0
Func3E returns an LBuiltin of three required arguments whose body f returns (R, error). The arguments are decoded in order, and the first failure is returned.
func FuncE ¶ added in v1.77.0
FuncE returns an LBuiltin whose body f is written in (value, error) form.
var builtinLoad = lisp.FuncE(func(env *lisp.LEnv, args *lisp.LVal) (*lisp.LVal, error) {
v, err := lisp.Result(env.MapKeys(args.Cells[0]))
if err != nil {
return nil, err
}
return v, nil
})
The builtin converts f's result to the *LVal a builtin returns:
- A non-nil error that is a *ErrorVal is returned as itself. It keeps its condition, data and stack, and the debugger is not told about it a second time.
- Any other non-nil error becomes env.Error(err), with condition "error" (see ConditionOf). The value is ignored.
- A nil value with a nil error becomes Nil().
elpsvet's elpsownpkg and elpsbuiltinstate rules check f like any other builtin body. FuncE adds one call and no allocation to the body's cost.
type LBuiltinDef ¶
LBuiltinDef is a built-in function
func DefaultBuiltins ¶
func DefaultBuiltins() []LBuiltinDef
DefaultBuiltins returns the default set of LBuiltinDefs added to LEnv objects when LEnv.AddBuiltins is called without arguments.
func DefaultMacros ¶
func DefaultMacros() []LBuiltinDef
DefaultMacros returns the default set of LBuiltinDef added to LEnv objects when LEnv.AddMacros is called without arguments.
func DefaultSpecialOps ¶
func DefaultSpecialOps() []LBuiltinDef
DefaultSpecialOps returns the default set of LBuiltinDef added to LEnv objects when LEnv.AddSpecialOps is called without arguments.
func FreeKeywords ¶ added in v1.73.0
func FreeKeywords(def LBuiltinDef) LBuiltinDef
FreeKeywords marks a Go builtin so that the keyword literals naming its &key arguments cost no evaluation step (luthersystems/elps#745). It is strictly opt-in, per builtin: nothing changes for a builtin registered without it, for any Lisp function, or for any existing program.
Today every argument of a call form is evaluated, and evaluating the keyword literal :k -- which just returns :k -- is one step, so (f x :a 1 :b 2) costs two steps more than a positional (f x 1 2). For a builtin wrapped with FreeKeywords, a keyword literal written directly in a call form at a key-name position of its &key section is passed as itself, unevaluated and uncharged. The value the builtin receives is identical (a keyword evaluates to itself), so only the step count differs.
Precisely, the discount applies only when all of these hold:
- the call form's head evaluates to a function value created by registering FreeKeywords(def) (AddBuiltins, BindBuiltins or an elpsutil package); the flag lives on that function value, so every binding, copy and template VM of it carries it identically;
- the argument expression is a bare, unquoted keyword symbol (:k), not an expression producing one (a variable bound to :k, ':k, a call);
- its position is a key-name position: index r, r+2, r+4, ... of the arguments, where r is the number of required formals.
Everything else is evaluated and charged as always: values in the key section, keywords in required positions, and every argument of a call that reaches the builtin through funcall or apply (those arguments belong to funcall's call form). A keyword literal the discount skips is also not seen by a debugger's per-expression hook.
def's formals must be required names followed by &key and at least one key name, with no &optional or &rest; AddBuiltins panics and BindBuiltins returns an error otherwise. Adopting FreeKeywords for an existing builtin changes the step count of every program calling it with keywords, so for a metered embedder it is a coordinated upgrade, like any step change; a NEW builtin can adopt it freely.
The returned definition wraps def: its dynamic type is not def's, so a type assertion to def's concrete type fails on it, and reflection-based tooling that walks definitions sees one more level (the wrapped def is its embedded LBuiltinDef field). Name, Formals, Eval and Docstring forward to def.
type LEnv ¶
LEnv is a lisp environment.
The binding state (scope), the lexical chain (parent) and the evaluator's current location (loc) are unexported — the issue #382 close applied one layer up from LVal. `env.scope[sym] = v` used to let any holder of an *LEnv rebind a symbol in a live environment — including a closure's captured environment, shared by every function value that closed over it — without going through Put, invisible to the runtime seal and to elpsvet; `env.loc = loc` aliased a caller's mutable location into every error and stack frame the evaluator stamped afterwards (the #362 aliasing class). Reads are mediated by Bindings, NumBindings, Parent and Source; the writes are kernel-only.
There used to be a fourth unexported field, `funName map[string]string`, marked for removal in elps2. It was dead the whole time: no site in the repository ever wrote or read it, so every environment ever created — one per function call, plus one per let/labels/dotimes scope — allocated an empty map that nothing could observe, and Fork rebuilt one per environment on top of that. It is gone (issue #440 follow-up); the live name lookup is Package.funNames, reached through GetFunName.
Field order is layout-sensitive: the pointer-bearing fields lead so the GC scan extent stops at 56 bytes, at the scope's bindings pointer. Keep scope last among them (its length and capacity are scalars) and the other scalars (ID and scopeHint) trailing.
func NewEnvRuntime ¶
NewEnvRuntime initializes a new LEnv, like NewEnv, but it explicitly specifies the runtime to use. NewEnvRuntime is only suitable for creating root LEnv object, so it does not take a parent argument. When rt is nil StandardRuntime() called to create a new Runtime for the returned LEnv. It is an error to use the same runtime object in multiple calls to NewEnvRuntime if the two envs are not in the same tree and doing so will have unspecified results.
func (*LEnv) Add ¶ added in v1.82.0
Add returns what the + builtin returns for (+ xs...): the sum of xs, 0 for no xs, or the same error from the same check. The sum is an int when every x is an int, and otherwise a float summed in float from the start, so (env.Add a b c) can differ from adding pairwise. It checks the evaluation's context first, as CallBuiltin does. It charges no step. It neither writes xs nor keeps the slice.
func (*LEnv) AddBuiltins ¶
func (env *LEnv) AddBuiltins(external bool, funs ...LBuiltinDef)
AddBuiltins binds the given funs to their names in env. When called with no arguments AddBuiltins adds the DefaultBuiltins to env.
A function's sealed formal argument list is shared with env by reference; an unsealed one is copied so environments never share mutable formals. See registrationFormals and issues #363, #379, #514.
func (*LEnv) AddMacros ¶
func (env *LEnv) AddMacros(external bool, macs ...LBuiltinDef)
AddMacros binds the given macros to their names in env. When called with no arguments AddMacros adds the DefaultMacros to env.
A macro's sealed formal argument list is shared with env by reference; an unsealed one is copied so environments never share mutable formals. See registrationFormals and issues #363, #379, #514.
CONTRACT FOR A GO MACRO'S EXPANSION. The value a Go macro returns must consist of nodes the macro constructed and its arguments (which it may splice in as they are, unevaluated). It must not embed a lisp value it looked up -- an unlocated runtime list reached through Get -- because, unless a debugger is attached, macroCall locates a Go macro's expansion IN PLACE (locateExpansionTree): every unlocated unsealed syntax node reachable from it, other than the arguments and what is under them, gets the macro call site as its source location, and a binding among them would keep that location for the rest of the process (the issue #582 shape, closed for lisp macros and for a Go macro's arguments by the copy-on-write stamp). Values -- functions, maps, vectors, bytes, natives -- are exempt: they are never written to. The in-place locate is what keeps a Go macro's expansion free of per-node copies; see the warning above stampMacroExpansion (lisp/macro.go), "who pays".
func (*LEnv) AddSpecialOps ¶
func (env *LEnv) AddSpecialOps(external bool, ops ...LBuiltinDef)
AddSpecialOps binds the given special operators to their names in env. When called with no arguments AddSpecialOps adds the DefaultSpecialOps to env.
An operator's sealed formal argument list is shared with env by reference; an unsealed one is copied so environments never share mutable formals. See registrationFormals and issues #363, #379, #514.
func (*LEnv) Append ¶ added in v1.88.0
Append returns what the append builtin returns for (append typespec seq vals...), where typespec is the type specifier kind stands for: a new sequence of that type that holds seq's elements and then vals, or the same error from the same check, the allocation check included. It does not change seq; AppendInPlace does, as append! does. It checks the evaluation's context first, as CallBuiltin does, and charges no step.
func (*LEnv) AppendInPlace ¶ added in v1.82.0
AppendInPlace returns what the append! builtin returns for (append! seq vals...): seq, a vector or bytes, after it appends vals to seq in place; or the same error from the same check, the allocation check included. A vector grows its capacity as append! grows it (GrowCap). It checks the evaluation's context first, as CallBuiltin does. It charges no step. It neither writes vals nor keeps the slice.
func (*LEnv) ApplyFunction ¶ added in v1.80.0
ApplyFunction returns what (apply fn args) returns for the list args (pass lisp.Nil() for no arguments; a Go nil raises apply's error): the function's value, or the same error from the same check, in apply's order (fn first, then a non-list args). It checks the evaluation's context first, as CallBuiltin does, and charges no step of its own. It neither writes nor keeps args.
Use it from a Go builtin where the Lisp called apply. Like CallFunction, it does not mark the calling frame terminal.
func (*LEnv) BindBuiltins ¶ added in v1.73.0
func (env *LEnv) BindBuiltins(opts BindOpts, defs ...LBuiltinDef) *LVal
BindBuiltins binds defs in env's current package (luthersystems/elps#745). It is AddBuiltins with two differences:
- With opts.Shadow a name the package already binds is rebound, as a defun would rebind it. A Go native replacing a Lisp defun that shadowed an imported name (statedb:get shadowing lisp:get) keeps that shadowing; AddBuiltins refuses the name by panicking.
- Every problem is returned as an error rather than a panic: a bound name without Shadow, a constant (true, false), a name repeated within defs, a nil definition, or formals the evaluator could never bind. All definitions are checked before any is bound, so an error leaves the package unchanged.
The bound values are exactly what AddBuiltins builds: the same function value shape, the definition's docstring (when it has a Docstring method), and its sealed formals shared or unsealed formals copied. Binding charges no step. Shadowing in the language package itself is refused once its bindings are sealed, as a Lisp set there is.
func (*LEnv) Bindings ¶ added in v1.52.0
Bindings iterates the symbol bindings in env's immediate scope; parent scopes are not included (walk Parent for those). Iteration order is unspecified.
Bindings is the read half of the scope table, which went unexported (as a map) in issue #382: enumerating an environment is a legitimate need (the debugger's variable panes), while the write that came free with an exported map — rebinding a symbol in an environment the writer does not own — is not. Use Put or PutGlobal to bind.
Binding into env while iterating (Put inside the loop) is unspecified: the loop may or may not see the new binding or an updated value.
Bindings is nil-receiver safe: a nil LEnv yields nothing.
func (*LEnv) CallBuiltin ¶ added in v1.73.0
func (env *LEnv) CallBuiltin(b BuiltinRef, args ...*LVal) (*LVal, error)
CallBuiltin calls the builtin b with already-evaluated args, as the evaluator does once a call form's arguments are evaluated, and returns its result:
- args are bound against the builtin's formals exactly as a call from Lisp binds them, so an omitted &optional or &key argument arrives as nil and a wrong argument count or a malformed keyword list fails with the evaluator's own error. The caller's args slice is never written.
- If the evaluation's context is done it returns the context-cancelled condition before calling, as the evaluator's call boundary does.
- A terminal expression the builtin returns (funcall, apply and the like hand one back for tail calls) is evaluated in its environment, so CallBuiltin always returns a value or an error, never an internal marker. That evaluation costs the steps it would cost from Lisp.
CallBuiltin charges no evaluation step of its own and pushes no stack frame: an error the builtin raises is attributed to the calling builtin's frame, as it was for the hand-written b.Eval(env, QExpr(args)) this replaces. It does not recover panics; call it from a builtin, which the evaluator already guards.
func (*LEnv) CallFunction ¶ added in v1.80.0
CallFunction returns what (funcall fn args...) returns: the function's value, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does. A symbol fn resolves to its global function in the current package, as funcall resolves it, and a special operator or a macro raises funcall's error. It charges no step of its own; the call costs what it costs from Lisp. It neither writes args nor keeps the slice: a &rest list in the callee is a copy.
Use it from a Go builtin where the Lisp called funcall. Unlike the funcall builtin, it does not mark the calling frame terminal: a Go frame cannot be unwound, so every call it makes is a plain call, whatever the callee's last form is (luthersystems/elps#852).
func (*LEnv) CallGlobal ¶ added in v1.73.0
CallGlobal calls the function bound to the global symbol named sym -- a qualified name such as "utils:set-exception-business" is the usual form -- with already-evaluated args, resolving the binding at call time exactly as a Lisp call site naming sym would, in the current package. It is for a Go builtin whose callee exists only in Lisp; to call a language builtin, use CallBuiltin, and call Go implementations directly.
CallGlobal charges no evaluation step of its own: the callee costs what its body evaluates (a Lisp call form would add a step for the form and one per argument expression). sym is a string, not a symbol LVal, so a caller keeps no package-level LVal. A special operator or macro is refused with "not a regular function".
func (*LEnv) CallValue ¶ added in v1.87.0
CallValue returns what the evaluator returns for the call form (f args...) when f is a variable that holds fn: a Go port calls it where the Lisp called a function it was handed, such as a callback argument or a closure read from a map. It differs from CallFunction, which is funcall, in one check: the evaluator calls only a function value, so a symbol fn raises instead of naming a global function.
It makes CheckCallable's check, the context check included, then calls fn as CallFunction does. It charges no step of its own. It neither writes args nor keeps the slice.
func (*LEnv) Car ¶ added in v1.88.0
Car returns what the car builtin returns for (car lis): the first element of a list, () for an empty list, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does, and charges no step. A stored error value comes back as the error, as the evaluator raises the builtin's result.
func (*LEnv) Cdr ¶ added in v1.88.0
Cdr returns what the cdr builtin returns for (cdr lis): a view of a list after its first element, () when it has fewer than two, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does, and charges no step. The view shares lis's elements, as the builtin's does.
func (*LEnv) ChargeSteps ¶ added in v1.69.0
ChargeSteps charges n evaluation steps to the current top-level evaluation on behalf of Go code, and reports whether the evaluation may continue. It exists for native builtins that do work the interpreter would otherwise have counted: the evaluator charges a builtin's call form but nothing for the work inside it, so a builtin that replaces a Lisp loop costs the same few steps however many elements it visits, which silently loosens a WithMaxSteps budget. Charging per element keeps the budget honest; the cost of a unit of work is the host's choice.
ChargeSteps returns nil when the evaluation may continue and the error, a *ErrorVal, otherwise, so a builtin returns the error unchanged:
if err := env.ChargeSteps(1); err != nil {
return lisp.Error(err)
}
The accounting is the evaluator's own, applied n times at once:
- When the runtime has neither a step limit nor a context (the default), the evaluator counts nothing, and neither does ChargeSteps: it returns nil without touching Steps or TotalSteps. The check is a couple of comparisons.
- Otherwise Steps (and so TotalSteps) grows by n, saturating at math.MaxInt64 rather than wrapping. The full n is recorded even when it overruns the budget, just as the evaluator records the step that overruns it. A count that saturates exceeds every budget, including one of math.MaxInt64. (LEnv.StepEach records only up to the step that overruns, as n single steps would.)
- If the step count now exceeds the WithMaxSteps budget, ChargeSteps returns the same step-limit-exceeded condition (CondStepLimitExceeded) with the same message the evaluator raises, so handler-bind, Go callers and errors.Is see no difference. Once over budget, every later charge and every later evaluation step in the same top-level evaluation fails the same way.
- Otherwise, if the evaluation's context is done, it returns the context-cancelled condition (CondContextCancelled), as the evaluator does between steps. A long native loop that charges per element is therefore also interruptible by a deadline.
A zero n is a no-op that returns nil and checks nothing. A negative n is a programming error in the builtin: ChargeSteps returns an ordinary error and changes no counter, whatever limits are configured, rather than refunding steps.
The charge applies to the runtime's current top-level evaluation, which is reset (and its count folded into TotalSteps) when the next exported entry point is entered from outside an evaluation, or by Runtime.ResetSteps. Called outside any evaluation it charges the most recent one's counter, which the next evaluation resets.
Like the rest of the evaluator, ChargeSteps is not safe for concurrent use: call it only from the goroutine evaluating on env's runtime. The counters live on that Runtime alone, so a VM instantiated from a Template charges only its own budget.
ChargeSteps bounds work, not time. It does not make a builtin that blocks inside a single charge responsive to cancellation.
func (*LEnv) CheckAlloc ¶ added in v1.77.0
CheckAlloc returns an error when n exceeds the runtime's allocation cap (Runtime.MaxAlloc), as a *ErrorVal, and nil otherwise. The error is the one builtins raise for that check: env.Errorf("%s", env.Runtime.CheckAlloc(n)), with condition "error" and the message "allocation size N exceeds maximum (M)". Runtime.CheckAlloc returns the message as a string instead. CheckAlloc makes no context check and charges no step.
if err := env.CheckAlloc(len(cells)); err != nil {
return lisp.Error(err)
}
func (*LEnv) CheckAllocs ¶ added in v1.95.0
CheckAllocs makes the CheckAlloc check of each size in sizes, in order, and returns the first error, or nil. A Go port of Lisp code that built several lists, with no other check between them, calls it with their sizes in the order the Lisp built them. The port then fails under an allocation cap with the error the Lisp raised first. Where the Lisp charged a step or made another check between two lists, the port checks each size at its own place. Like CheckAlloc, CheckAllocs makes no context check and charges no step.
func (*LEnv) CheckCallable ¶ added in v1.87.0
CheckCallable returns the error the evaluator returns for the call form (f args...) when f holds fn and fn is not a function, and Nil() otherwise. The evaluator makes this check before it evaluates the arguments, so a port whose arguments cost work (a decode, a ledger read) calls it first and CallValue after. It checks the evaluation's context first, as the evaluator does before each form, and charges no step.
An error value fn is returned as it is, as the evaluator returns the error its head evaluated to. For any other value the condition is "error". The message names fn's type, never fn, so its size is bounded; the evaluator's message prints fn. The evaluator expands a macro or special operator in the head over the unevaluated argument forms, which Go cannot do, so CheckCallable refuses one with "error".
func (*LEnv) CheckClosure ¶ added in v1.79.0
func (env *LEnv) CheckClosure(fn *LVal) ([]ClosureIssue, error)
CheckClosure checks the code of fn, a function value built by lambda, defun or a durable load, against env's current globals. It reports each global the code reads, calls, names with function or assigns with set! that is not defined; each call of a global bound to a value that is not a function; and each call of a global function or macro whose argument count its formals (required, &optional, &rest and &key) refuse. Thread steps count the threaded argument.
A symbol resolves as it would when fn runs: a local the code binds or a name fn's captured frames bind (in any frame up to the root environment) is a local; any other symbol is a global of fn's package, or of the package that qualifies it, in env's registry. The code is walked as macroexpand-all walks it, by the evaluator's own grammar of the builtin special forms (lisp.CodeWalker). A builtin macro (implemented in Go) is expanded and its expansion checked. A macro written in Lisp is not run: its call's argument count is checked, and its arguments are not. The arguments of an embedder's special operator, of a local macro, of a builtin macro whose expansion fails and of a form nested past the nesting limit are not checked either. It cannot see a name the code computes (funcall or apply of a value, eval, a symbol in quoted data).
The findings are sorted by name, kind and argument count, each once. The walk charges one step per list it walks and per macro it expands, as macroexpand-all does, so it stops at env's step budget, step limit or cancelled context with that error. It evaluates no Lisp code and does not change fn or any binding. Like macroexpand-all, it advances the runtime's gensym counter when a builtin macro it expands calls LEnv.GenSym (trace, curry-function and deftype do), so the names later gensym calls return move on. It returns an error for a value that is not such a function.
func (*LEnv) CheckContext ¶ added in v1.73.0
CheckContext returns the standard context-cancelled condition (CondContextCancelled, "context cancelled: <cause>") when the evaluation's context is done, as a *ErrorVal, and nil otherwise. It charges no step. It is the check the evaluator makes at every call boundary, for a builtin that does expensive work between charges.
func (*LEnv) Concat ¶ added in v1.82.0
Concat returns what the concat builtin returns for (concat typespec seqs...), where typespec is the type specifier kind stands for: one value of that type (SeqList, SeqVector, SeqString or SeqBytes) that holds the elements of seqs in order, or the same error from the same check, the allocation check included. SeqNil, which stands for (), returns "first argument is not a valid type specification: list" before any other check. Any other kind returns "type specifier is not valid: K" before it checks seqs (see SeqKind). It checks the evaluation's context first, as CallBuiltin does. It charges no step. It neither writes seqs nor keeps the slice.
func (*LEnv) Context ¶ added in v1.25.0
Context returns the context.Context currently associated with this environment. If no context has been set, context.Background() is returned.
func (*LEnv) DebugStack ¶ added in v1.82.0
DebugStack does what the debug-stack builtin does: it prints the call stack to the runtime's stderr and returns (), or returns the same error from the same check. The printing honours the allocation limit and stops when the context ends, as the builtin's does. It checks the evaluation's context first, as CallBuiltin does. It charges no step.
func (*LEnv) DeepCopy ¶ added in v1.82.0
DeepCopy returns what the copy builtin returns for (copy v): a deep copy of v that shares no storage with it, or the same error from the same check. It makes copy's allocation checks and honours the runtime's value-depth limit (WithMaxValueDepth), which LVal.Copy does not. It checks the evaluation's context first, as CallBuiltin does. It charges no step.
func (*LEnv) DefinePackage ¶
func (*LEnv) Error ¶
Error returns an LError value with an error message given by rendering msg.
Error may be called either with an error or with any number of *LVal values. It is invalid to pass an error argument with any other values and doing so returns a runtime error.
Unlike the exported function, the Error method returns LVal with a copy env.Runtime.Stack.
func (*LEnv) ErrorAssociate ¶
ErrorAssociate associates the LError value lerr with env's current call stack and source location. ErrorAssociate returns an LError if lerr is not an error value (indicating a bug in the caller), or nil on success.
func (*LEnv) ErrorCondition ¶
ErrorCondition returns an LError with the given condition type and an error message computed by rendering its arguments. Go errors become string data; an error wrapping an *ErrorVal propagates that original condition.
ErrorCondition may be called either with an error or with any number of *LVal values. It is invalid to pass ErrorCondition an error argument with any other values; doing so returns a runtime error.
Unlike the exported function, the ErrorCondition method returns an LVal with a copy env.Runtime.Stack.
func (*LEnv) ErrorConditionf ¶
ErrorConditionf returns an LError value with the given condition type and a a formatted error message rendered using fmt.Sprintf.
Unlike the exported function, the ErrorConditionf method returns an LVal with a copy env.Runtime.Stack.
func (*LEnv) Errorf ¶
Errorf returns an LError value with a formatted error message.
Unlike the exported function, the Errorf method returns an LVal with a copy env.Runtime.Stack.
func (*LEnv) ErrorfAt ¶ added in v1.73.0
ErrorfAt is Errorf located at form: the error carries form's source location (when form has one) instead of the evaluator's current location (the macro call).
func (*LEnv) EvalContext ¶ added in v1.25.0
EvalContext evaluates v with the given context. If ctx is cancelled or its deadline expires during evaluation, a CondContextCancelled error is returned.
func (*LEnv) First ¶ added in v1.80.0
First returns what the first builtin returns for (first seq): the first element of a list or a vector, () when it has none, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does, and charges no step. A stored error value comes back as the error, as the evaluator raises the builtin's result.
func (*LEnv) FormatString ¶ added in v1.77.0
FormatString returns the string the format-string builtin returns for the format f and the values fvals, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does. It bounds the result by MaxAlloc and stops when the context ends, as the builtin does. It charges no step and does not write fvals. The error is the builtin's own *ErrorVal.
msg, err := env.FormatString("unknown type: {}", k)
func (*LEnv) FunCallContext ¶ added in v1.25.0
FunCallContext invokes regular function fun with args under the given context. If ctx is cancelled or its deadline expires during the call, a CondContextCancelled error is returned.
func (*LEnv) GenSym ¶
GenSym returns a runtime-counter symbol named genNNNNNNNN. Its name can collide with user symbols and depends on runtime history. Go macros should use NewGenSyms for deterministic temporary bindings in a reserved namespace.
func (*LEnv) Get ¶
Get takes an LSymbol k and returns the LVal it is bound to in env.
For a function the result MAY BE THE STORED BINDING ITSELF, not a private copy: callers must treat it as read-only. Only when the binding does not already carry the looked-up name does Get hand back a FunRef header copy stamped with it.
func (*LEnv) GetFun ¶
GetFun returns a function referenced by the given LVal. If fun is already an LFun, then fun is returned. If fun is a symbol then GetFun looks for a function bound to the symbol. If fun does not reference a symbol then an error is returned.
GetFun is a suitable for backing an implementation of functional programing constructs, like funcall, map, reduce, etc.
func (*LEnv) GetFunGlobal ¶ added in v1.2.0
GetFunGlobal is like GetFun but only accesses the global package environment.
func (*LEnv) GetFunName ¶
GetFunName returns the function name (if any) known to be bound to the given function. If the function's FID is bound in its package then the global name of the function is returned. When the function is bound within a local scope then the local name used to reference the function (if any) is returned.
Safety: the error path in this function is cosmetic-only and does not mask data corruption. Every caller (MacroCall, SpecialOpCall, funCall, call, profiler) has already verified that f.Type == LFun before reaching here, so pkgFunName should never fail. The fallback to f.Str only affects the human-readable name shown in error messages and stack traces — it cannot influence evaluation, binding, or control flow. We log at BUG level so the issue is visible in diagnostics without changing the return type to an error that every caller would have to handle for an unreachable code path.
func (*LEnv) GetGlobal ¶
GetGlobal takes LSymbol k and returns the value it is bound to in the current package.
func (*LEnv) IsEmpty ¶ added in v1.80.0
IsEmpty returns what the empty? builtin returns for (empty? seq), as a Go bool, or the same error from the same check, as Length does.
func (*LEnv) Lambda ¶
Lambda returns a new Lambda with fun.Env and fun.Package set automatically.
This is the one constructor that stores a live environment into a function value, and let* relies on that: opLetSeq decides whether an initializer could have captured its scope by whether the runtime's closure counter moved while the initializer ran (see closuresCreated). A Go builtin or debugger hook that retained its env argument and evaluated in it after returning would sit outside that contract; nothing in the tree does, and any new holder of a live *LEnv must either go through Lambda or count itself the same way.
func (*LEnv) Length ¶ added in v1.80.0
Length returns what the length builtin returns for (length seq), as a Go int, or 0 and the same error from the same check: a value with no length (not a list, map, vector, bytes or string) raises the builtin's error. It checks the evaluation's context first, as CallBuiltin does, and charges no step. The error is the builtin's own *ErrorVal. LVal.Len makes no check and returns -1 for such a value.
func (*LEnv) Load
deprecated
Load reads LVals from r and evaluates them as if in a progn. The value returned by the last evaluated LVal will be retured. After evaluating expressions the current package is restored to the current package at the time Load was called, in case loaded source made calls to "in-package". If env.Runtime.Reader has not been set then an error will be returned by Load.
Deprecated: Use LoadContext for cancellation and timeout support.
func (*LEnv) LoadContext ¶ added in v1.25.0
LoadContext reads LVals from r and evaluates them with the given context.
func (*LEnv) LoadFile
deprecated
LoadFile attempts to use env.Runtime.Library to read a lisp source file and evaluate expressions it contains. Any error encountered will prevent execution of loaded source and be returned. After evaluating expressions the current package is restored to the current package at the time Load was called, in case loaded source made calls to "in-package". If env.Runtime.Reader has not been set then an error will be returned by Load.
Deprecated: Use LoadFileContext for cancellation and timeout support.
func (*LEnv) LoadFileContext ¶ added in v1.25.0
LoadFileContext loads and evaluates a source file with the given context.
func (*LEnv) LoadLocation
deprecated
LoadLocation attempts to use env.Runtime.Library to read a lisp source file, specifying its name and location explicity, and evaluate the expressions it contains. Because the name and location of the stream are specfied explicitly LoadLocation does not depend explicity on an env.Runtime.Library implementation. Any error encountered will prevent execution of loaded source and be returned. After evaluating expressions the current package is restored to the current package at the time Load was called, in case loaded source made calls to "in-package". If env.Runtime.Reader has not been set then an error will be returned by Load.
Deprecated: Use LoadLocationContext for cancellation and timeout support.
func (*LEnv) LoadLocationContext ¶ added in v1.25.0
LoadLocationContext loads and evaluates a source stream at a given location with the given context.
func (*LEnv) LoadProgram
deprecated
added in
v1.52.0
LoadProgram evaluates the program's expressions as if in a progn, exactly as Load evaluates the expressions returned by env.Runtime.Reader. The value of the last expression is returned. After evaluation the current package is restored, in case the program made calls to "in-package".
Deprecated: Use LoadProgramContext for cancellation and timeout support.
func (*LEnv) LoadProgramContext ¶ added in v1.52.0
LoadProgramContext evaluates the program's expressions with the given context. See LoadProgram.
func (*LEnv) LoadString
deprecated
func (*LEnv) LoadStringContext ¶ added in v1.25.0
LoadStringContext loads and evaluates a string with the given context.
func (*LEnv) Locals ¶ added in v1.73.0
Locals returns the local variables visible from env, sorted by name.
A local is a binding made by a function's parameters or by let, let*, flet, labels and the like, in env or any enclosing lexical scope up to (not including) the root environment. Package globals (set at top level, defun, defmacro) are not locals: they live in the package, not in the environment chain, and are read with Get. Local functions bound by flet and labels are locals too: they appear under their names with the function as the value. When an inner scope shadows a name, only the innermost binding is reported.
A Go builtin receives an environment whose parents are the caller's lexical scopes, so env.Locals() inside a builtin reports the variables visible at the call site, the way Python's locals() does. The debugger's variables pane and completion use the same walk.
The values are the bound values themselves, not copies: treat them as borrowed, never mutate them, and copy (LVal.Copy) any value kept past the builtin's return. The result reflects the bindings at the time of the call. Locals is nil-receiver safe and returns nil for an env with no locals.
func (*LEnv) MacroExpandAll ¶ added in v1.73.0
MacroExpandAll returns form with every macro call expanded, in env: the head, and every nested form in code position, recursively, until no macro call remains. It is the Go API behind macroexpand-all.
Heads resolve in env, so a package that shadows a builtin or defines its own macros is honored, and a lexical binding of a macro name (a let, flet, labels or lambda parameter inside form) suppresses expansion of that name within its scope. Local macros bound by macrolet inside form are expanded; each is built in env the way macrolet builds it at run time, so a local macro whose expansion reads a runtime local variable cannot be expanded statically and returns an error. defun and defmacro are kept as written, with their bodies expanded, because their own expansion embeds a compiled function rather than source. Quoted data is never entered.
form is not modified: see CodeWalker for what the result shares with it. A failed expansion returns the LError.
func (*LEnv) MakeSequence ¶ added in v1.88.0
MakeSequence returns what the make-sequence builtin returns for (make-sequence start stop step...): the list of numbers from start up to stop, or the same error from the same check, the allocation check included. It checks the evaluation's context first, as CallBuiltin does, and charges no step.
func (*LEnv) MapAssoc ¶ added in v1.82.0
MapAssoc returns what the assoc builtin returns for (assoc m k v): a new sorted-map that holds m's entries and k set to v, or the same error from the same check. It does not change m. A nil m gives a new map with the one entry. It checks the evaluation's context first, as CallBuiltin does, and makes assoc's allocation checks. It charges no step.
Use it where the Lisp called assoc. MapPut changes m in place, as assoc! does.
func (*LEnv) MapDelete ¶ added in v1.88.0
MapDelete returns what the dissoc! builtin returns for (dissoc! m k): m after it removes k, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does, then refuses a nil or non-map m and returns the map's own error for a key the map refuses. It charges no step.
Use it where the Lisp called dissoc!. MapDissoc returns a copy, as dissoc does.
func (*LEnv) MapDissoc ¶ added in v1.88.0
MapDissoc returns what the dissoc builtin returns for (dissoc m k): a new sorted-map that holds m's entries but k, or the same error from the same check. It does not change m. A nil m gives a new empty map. It checks the evaluation's context first, as CallBuiltin does, and makes dissoc's allocation check. It charges no step.
Use it where the Lisp called dissoc. MapDelete changes m in place, as dissoc! does.
func (*LEnv) MapHasKey ¶ added in v1.82.0
MapHasKey returns what the key? builtin returns for (key? m k): whether m has the key k, or the same error from the same check. A value that is not a sorted-map raises "first argument is not a map: <type>", and a key the map refuses returns the map's own error. The error is the builtin's own *ErrorVal, and nil when there is none. It checks the evaluation's context first, as CallBuiltin does. It charges no step.
func (*LEnv) MapKeys ¶ added in v1.80.0
MapKeys returns what the keys builtin returns for (keys m): the list of m's keys in key order, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does. A value that is not a sorted-map raises "first argument is not a map: <type>", and a map with more entries than the allocation limit raises the allocation error. It charges no step.
Use it where the Lisp called keys. LVal.MapKeys makes none of these checks.
func (*LEnv) MapLookup ¶ added in v1.77.0
MapLookup returns what the get builtin returns for (get m k): the value, () for a missing key or a nil m, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does. A non-map m raises "first argument is not a map: <type>", and a key the map refuses returns the map's own error. A stored error value comes back as the error, as the evaluator raises the builtin's result. It charges no step.
Use it where the Lisp called get. Lookup makes none of these checks.
func (*LEnv) MapOf ¶ added in v1.77.0
MapOf returns a new sorted map built from kv, which holds alternating Go keys and values. It converts each one and calls SortedMapOf, so it returns what the sorted-map builtin returns: the same map, or the same error from the same check (the context check, "uneven number of arguments: N" and the allocation check before each new key).
return lisp.Result(env.MapOf( "id", id, "type", typ, "description", desc))
A key is a string or an *LVal. A value is one of the Func*E result types: *LVal (a nil *LVal becomes ()), string, int, float64, bool, []byte (bytes), []*LVal or Cells (a list that uses the slice as its storage). Any other key or value type is a bug in the caller, and MapOf panics, as fmt does for a bad verb; elpsidiom reports it at build time. MapOf never turns a value into a native: use NativeOf for one.
MapOf uses a type switch, not reflection. A string key or value becomes a new string value, as lisp.String does. The converted keys and values sit in a fixed array on the stack for up to 16 of them, else in one allocated slice. MapOf charges no step. The package-level MapOf is the unchecked form, for Go code that fills a map it just built.
func (*LEnv) MapPut ¶ added in v1.77.0
MapPut returns what the assoc! builtin returns for (assoc! m k v): m after it sets k to v, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does. Then it refuses a nil or non-map m, makes assoc!'s allocation check for a new key, and returns the map's own error for a key the map refuses. It charges no step.
Use it where the Lisp called assoc!. A Go builtin that fills a map it just built keeps MapSetLVal, which makes none of these checks.
func (*LEnv) MapRange ¶ added in v1.73.0
MapRange calls fn for each entry of the sorted-map m in the map's documented order -- int keys first by value, then string and symbol keys by spelling, the order MapKeys and MapEntries use -- until fn returns false (luthersystems/elps#745). Keys arrive by value (MapKey) and values are the map's own, as MapEntries hands them out.
When (keys m) would fail, MapRange returns that error, with keys' own message, and calls fn for nothing: m not a sorted-map ("first argument is not a map: <type>") or m larger than the runtime's MaxAlloc. Otherwise it returns nil, or the error a custom backing's Entries method raises.
For the interpreter's own map backings MapRange allocates nothing in the steady state: it orders the entries in a recycled buffer rather than building the key or pair lists MapKeys and MapEntries return. A custom backing (NewMapData) is read through its Entries method, which allocates.
The entries are captured before the first call to fn, so fn may read the map but must not rely on seeing its own writes to it. MapRange charges no evaluation step and makes no context check; a builtin iterating a large map charges what its Lisp counterpart did (LEnv.Step per element, for example).
func (*LEnv) MapSeq ¶ added in v1.82.0
MapSeq returns what the map builtin returns for (map typespec fn seq), where typespec is the type specifier kind stands for: a list or a vector of fn's results for the elements of seq, or the same error from the same check. kind is SeqList or SeqVector, or SeqNil, which stands for (), to call fn for its effects and return (). Any other kind returns "type specifier is invalid: K" after the checks of fn and seq and the allocation check, as map does (see SeqKind). A symbol fn resolves to its global function, as map resolves it.
It checks the evaluation's context first, as CallBuiltin does. Each call of fn checks the context and charges one step, as map's calls do, and fn charges its own steps. MapSeq charges no other step. As CallBuiltin does, it keeps the calling frame non-terminal while fn runs, so no tail call in fn returns through the Go caller.
func (*LEnv) MapSeqCells ¶ added in v1.85.0
MapSeqCells returns the cells of seq as map reads its sequence argument: the cells of a list or of a one-dimensional vector, and a nil error. For any other value it returns nil and the error map returns for that value. The message names the value's type, never the value, so its size is bounded. A Go port of Lisp code that mapped over a value calls it, so the port refuses each value that map refused, with the same message.
The cells are the value's own storage, so treat them as read-only. MapSeqCells charges no step and makes no allocation check; map checks the allocation for its result after this check.
func (*LEnv) MapSeqFunc ¶ added in v1.87.0
MapSeqFunc returns what MapSeq returns when fn is the Go function f: a list or a vector of f's results for the elements of seq, or () for SeqNil, with map's check of seq (MapSeqCells) and its allocation check, in map's order. A Go port calls it where the Lisp mapped a lambda whose body the port writes in Go, so it need not repeat those checks.
It checks the evaluation's context first. Before each call of f it charges one step and checks the context, as each of map's calls does, and f charges its own steps. The first error f returns stops the map and is returned; lisp.Error(err) gives it to the map, so a *ErrorVal comes back as itself. On success f must return a value, never a Go nil. Any kind but SeqList, SeqVector and SeqNil returns "type specifier is invalid: K" after the checks of seq, as MapSeq does. As MapSeq does, it keeps the calling frame non-terminal while f runs, so no tail call that f evaluates returns through the Go caller.
func (*LEnv) Mod ¶ added in v1.88.0
Mod returns what the mod builtin returns for (mod a b): the remainder of the ints a and b, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does, and charges no step.
func (*LEnv) Mul ¶ added in v1.88.0
Mul returns what the * builtin returns for (* xs...): the product of xs, 1 for none, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does, and charges no step.
func (*LEnv) New ¶ added in v1.14.0
New takes a typedef along with a list of constructor arguments and returns an LTaggedValue containing the result of invoking the typedef's constructor with the given arguments. A typedef is an LTaggedVal itself that wraps a list holding the defined type name along with a constructor.
New requires that the system have typedef tagged-values. That is enabled by calling InitializeUserEnv when initializing the top-level environment. InitializeTypedef, which InitializeUserEnv calls, is a step inside it and not a substitute for it: it panics on an environment InitializeUserEnv has not already established. See its doc comment, and issue #433.
func (*LEnv) NewLambdaCode ¶ added in v1.75.0
func (env *LEnv) NewLambdaCode(formals *LVal, body []*LVal) (*LambdaCode, error)
NewLambdaCode validates a lambda's formals as Lambda does and returns its code for RestoreLambda. It evaluates nothing. A serializer that restores many closures over one body calls it once for that body.
func (*LEnv) NewRenderer ¶ added in v1.62.0
func (env *LEnv) NewRenderer(ctx context.Context) *DiagnosticRenderer
NewRenderer creates a response renderer using the runtime's output limit, floored at minRenderLimit: a response describes a failure, and MaxAlloc caps program data rather than diagnostics. Pass the request context explicitly when rendering after evaluation returns.
func (*LEnv) NewRendererWithLimit ¶ added in v1.62.0
func (env *LEnv) NewRendererWithLimit(ctx context.Context, limit int) *DiagnosticRenderer
NewRendererWithLimit reserves a smaller output budget for a response whose protocol adds framing, escaping or duplicate representations. The supplied limit can only tighten the diagnostic limit; zero permits no output.
func (*LEnv) Nth ¶ added in v1.82.0
Nth returns what the nth builtin returns for (nth seq n): the element at index n of a list or a vector, () when n is past its end, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does, and charges no step. A stored error value comes back as the error, as the evaluator raises the builtin's result.
func (*LEnv) NumBindings ¶ added in v1.52.0
NumBindings returns the number of symbols bound in env's immediate scope.
NumBindings is nil-receiver safe.
func (*LEnv) NumEqual ¶ added in v1.82.0
NumEqual returns what the = builtin returns for (= a b): whether the numbers a and b are equal, or the same error from the same check. An int and a float compare as floats. The error is the builtin's own *ErrorVal, and nil when there is none. It checks the evaluation's context first, as CallBuiltin does. It charges no step.
func (*LEnv) NumGreater ¶ added in v1.82.0
NumGreater returns what the > builtin returns for (> a b), as NumEqual does for =.
func (*LEnv) NumGreaterEqual ¶ added in v1.82.0
NumGreaterEqual returns what the >= builtin returns for (>= a b), as NumEqual does for =.
func (*LEnv) NumLess ¶ added in v1.82.0
NumLess returns what the < builtin returns for (< a b), as NumEqual does for =.
func (*LEnv) NumLessEqual ¶ added in v1.82.0
NumLessEqual returns what the <= builtin returns for (<= a b), as NumEqual does for =.
func (*LEnv) PanicError ¶ added in v1.95.0
PanicError returns the error the evaluator makes of a Go panic p that it recovers. p must be a non-nil value from recover: recover returns nil when nothing panicked, and PanicError would make an error of that nil. Since Go 1.21, panic(nil) recovers as a non-nil *runtime.PanicNilError. The error has condition internal-panic, the message "internal error (recovered panic): " followed by a description of p, and the Go stack of the caller attached, so IsInternalPanic reports it. An embedder calls it from its own recover, in Go code that the evaluator does not run, so that a host panic there fails exactly as a panic inside a builtin does.
The description calls no method of p other than a runtime error's Error, because p may refer to the object whose fault caused the panic. In an elpscheck build, PanicError panics again with an ownership or seal violation: those must stop the test that made them.
func (*LEnv) Parent ¶
Parent returns env's lexically enclosing environment, or nil when env is a root environment. The chain is read-only through this accessor: the field went unexported in issue #382 because re-parenting a live environment silently re-scopes every closure that captured it.
Parent is nil-receiver safe.
func (*LEnv) ParseProgram ¶ added in v1.52.0
ParseProgram parses the contents of r using env.Runtime.Reader and seals the result as a Program, without evaluating anything. Like LoadLocation, it uses ReadLocation when the runtime's reader supports locations and falls back to Read (with loc as the stream name) otherwise. An error is returned if env.Runtime.Reader has not been set.
func (*LEnv) Put ¶
Put takes an LSymbol k and binds it to v in env. If k is already bound to a value the binding is updated so that k is bound to v. It only writes the lexical scope, never package bindings, so Lisp callers may use it even when the current package is sealed.
func (*LEnv) PutGlobal ¶
PutGlobal takes an LSymbol k and binds it to v in the current or explicitly qualified package. This trusted Go registration API bypasses the core package seal; Lisp-facing writers must use PutGlobalFromLisp.
func (*LEnv) PutGlobalFromLisp ¶ added in v1.62.0
PutGlobalFromLisp binds k to v in the current or explicitly qualified package with the core package seal enforced. Builtins accepting Lisp-controlled binding names must use this instead of the trusted Go registration API.
func (*LEnv) QuasiquoteWith ¶ added in v1.93.0
QuasiquoteWith returns what (quasiquote form) evaluates to, except that the value of each unquoted expression is resolve(expr) where the quasiquote evaluated expr. An unquote-splicing splices the list resolve returns. The walk is quasiquote's own: its value depth checks, its allocation checks as each list adds a cell (a splice adds its length), its errors and their messages, and its step charges for duplicated work on a shared DAG. An error resolve returns stops the walk where an evaluation error stopped it.
A Go macro that replaces a defmacro whose body was a quasiquote calls it, so the expansion fails where the defmacro failed, with the same caught error (luthersystems/elps#894). resolve is not called for an unquote that the walk does not reach, and a nil it returns is an error. form is walked, not changed; the result shares form's leaves, as a quasiquote's result shares its template's, so a macro that must return fresh syntax passes a fresh form.
func (*LEnv) RegisterBuiltins ¶ added in v1.93.0
func (env *LEnv) RegisterBuiltins(funs ...LBuiltinDef) []*LVal
RegisterBuiltins registers funs as builtins of the current package without binding their names (luthersystems/elps#907), and returns the function values in the order of funs.
It exists for builtins that only Go calls: for example a helper that a Go macro's expansion calls. An expansion may hold the function value itself as the head of a call, so the helper needs no name that Lisp can reach. Before this, the only registration was a binding (AddBuiltins), so such a helper had to stay bound, visible and callable as pkg:name, only so that the registry knew it.
A builtin registered here is registered like one AddBuiltins binds, less the binding:
- PackageRegistry.RegisteredBuiltin and RegisteredBuiltinName answer for it, and a template publishes it with every other registered builtin, so a builtin policy approves it by identity (compare the value the policy gets with the value returned here).
- Stack frames and GetFunName show its name.
- Nothing binds the name: Lisp lookup (qualified or after use-package), lisp:builtin, the export list, elps doc and completion do not see it.
In a VM that a template mints, the function is the VM's own copy of the value returned here. A Go macro that runs in such VMs gets it with env.Runtime.Registry.RegisteredBuiltin(pkg, name), never by holding the returned value, which belongs to the runtime that registered it.
It panics when the current package binds a name in funs, or when a builtin is already registered under it, so a name never means two builtins at once. A later AddBuiltins, AddMacros or AddSpecialOps of the name replaces the registration, as a later registration always does. Like the other registration functions it is Go API that an embedder calls at setup, never Lisp.
func (*LEnv) Render ¶ added in v1.62.0
Render returns diagnostic text bounded by the environment's output limit and active evaluation context. After evaluation, use RenderContext to pass the request context explicitly. On exhaustion it substitutes #<truncated>; use builtins such as format-string when the program must receive an ordinary allocation error.
func (*LEnv) RenderContext ¶ added in v1.62.0
RenderContext returns diagnostic text bounded by the environment's output limit and ctx, including after EvalContext or LoadStringContext returns. Cancellation or exhaustion substitutes a fitting #<truncated> marker. A nil context disables cancellation checks.
func (*LEnv) Rest ¶ added in v1.88.0
Rest returns what the rest builtin returns for (rest seq): a view of a list or a vector after its first element, as a list, () when it has fewer than two, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does, and charges no step.
func (*LEnv) RestoreLambda ¶ added in v1.75.0
func (env *LEnv) RestoreLambda(pkg string, code *LambdaCode) *LVal
RestoreLambda rebuilds a lambda of package pkg that captures env, from code NewLambdaCode validated, and evaluates nothing. It is how a serializer restores a closure: the caller rebuilds the captured frames with NewEnv and Put, and pkg names the package the lambda was defined in, whose globals its body resolves when it is called. The lambda shares code's cells; it has a fresh FID and no source location. Code validated in a runtime with another LegacyKeywordFormals setting is validated again here, so RestoreLambda accepts exactly the formals this runtime's lambda accepts. RestoreLambda returns an error when no package pkg is registered.
func (*LEnv) Second ¶ added in v1.80.0
Second returns what the second builtin returns for (second seq), as First does for first.
func (*LEnv) SetPackageDoc ¶ added in v1.16.12
SetPackageDoc sets the documentation string for the current package.
func (*LEnv) SetSymbolDoc ¶ added in v1.16.12
SetSymbolDoc sets the documentation string for a symbol in the current package.
func (*LEnv) Slice ¶ added in v1.82.0
Slice returns what the slice builtin returns for (slice typespec seq start end), where typespec is the type specifier kind stands for: the elements of seq from index start up to end, as that type (SeqList, SeqVector, SeqString or SeqBytes), or the same error from the same check, the allocation check included. SeqNil, which stands for (), returns "first argument is not a valid type specifier: list" before any other check. Any other kind returns "type specifier is not valid: K" after the checks of seq, start and end (see SeqKind). For a list, a vector or bytes the result shares elements with seq, as the builtin's does. It checks the evaluation's context first, as CallBuiltin does. It charges no step.
func (*LEnv) SortedMapOf ¶ added in v1.77.0
SortedMapOf returns a new sorted map built from kv, which holds alternating keys and values. It returns what env.CallBuiltin(BuiltinFunc("sorted-map"), kv...) returns for every input: the same value, or the same error condition and message from the same check.
It checks the evaluation's context first, as CallBuiltin does. Then it refuses an odd number of arguments ("uneven number of arguments: N"). For each key that is not yet in the map it checks the allocation cap (Runtime.MaxAlloc) before the insert. A key the map refuses (a float, for example) returns the map's own error. A later duplicate key replaces the value and needs no new entry.
It charges no evaluation step, pushes no stack frame and does not write kv. The map stores the values in kv themselves, not copies, so a later in-place change to one of those values also changes the map's entry. Use it from a Go builtin in place of lisp.SortedMap and MapSet, which skip the allocation and context checks.
func (*LEnv) Source ¶ added in v1.52.0
Source returns a copy of the location the evaluator is currently stamping onto values produced in env, or nil when it has none. The location is copied because the evaluator rebinds and mutates its own (issue #382, closing the #362 aliasing class at the environment level): a caller holding the pointer would watch it change under evaluation.
Source is nil-receiver safe.
func (*LEnv) SpecialOpCall ¶
SpecialOpCall invokes special operator fun with the argument list args.
func (*LEnv) Step ¶ added in v1.73.0
Step charges env one evaluation step, the per-element charge of a native loop that replaces a Lisp loop. Like LEnv.ChargeSteps it also reports a done context, so a long native loop stays interruptible.
func (*LEnv) StepEach ¶ added in v1.85.0
StepEach charges env n steps as n calls to Step would, stopping at the first one that fails. Use it when Go code replaces n evaluated forms and a budget that runs out must stop at the usage those forms would have recorded (luthersystems/elps#863).
ChargeSteps(n) adds all n steps and then checks the limits once, so when a limit runs out part of the way it records more steps than the evaluator would: with 2 steps left, ChargeSteps(5) records 5 and StepEach(5) records 3, the step that overran the limit included, as the evaluator does. The error is the same either way, so the outcome of the evaluation is the same; only the recorded usage differs. A host that logs or bills the usage of a failed evaluation (substrate's step budget does) needs the evaluator's figure, so it needs StepEach. ChargeSteps stays right for a charge that stands for one unit of work, such as one step per KiB, where no evaluated form had a step of its own.
StepEach gives exactly what the loop gives: the same result, condition and message, and the same Steps, TotalSteps and StepBudget usage. Like Step, it reports a done context only after it charges the first step, and an overrun limit before a done context. It does not loop: it works out how many steps the loop would charge before it stopped, and charges them at once, so its cost does not grow with n. The one difference is a context that is cancelled by another goroutine while the call runs: the loop could see it at any step, and StepEach sees it at its last.
A zero or negative n behaves as in ChargeSteps: zero charges and checks nothing, and a negative n is an error that changes no counter.
func (*LEnv) StringEqual ¶ added in v1.82.0
StringEqual returns what the string= builtin returns for (string= a b): whether the strings a and b are equal, or the same error from the same check. The error is the builtin's own *ErrorVal, and nil when there is none. It checks the evaluation's context first, as CallBuiltin does. It charges no step.
func (*LEnv) StringGreater ¶ added in v1.88.0
StringGreater returns what the string> builtin returns for (string> a b), as StringLess does for string<.
func (*LEnv) StringLess ¶ added in v1.88.0
StringLess returns what the string< builtin returns for (string< a b): whether the string a sorts before b, or the same error from the same check. The error is the builtin's own *ErrorVal, and nil when there is none. It checks the evaluation's context first, as CallBuiltin does, and charges no step.
func (*LEnv) Sub ¶ added in v1.82.0
Sub returns what the - builtin returns for (- xs...): the negation of one x, the first x less the others, 0 for no xs, or the same error from the same check. As Add does, it works in float from the start when any x is a float. It checks the evaluation's context first, as CallBuiltin does. It charges no step. It neither writes xs nor keeps the slice.
func (*LEnv) TaggedValue ¶ added in v1.14.0
TaggedValue is a low-level function to create a tagged-value and should be used with great care and testing. The first argument must be a symbol and is used as the type of the returned tagged-value. The second argument is the value being tagged.
The type of a tagged-value should be a qualified symbol (e.g. 'lisp:mytype). Unqualified type names can clash with primitive type symbols (e.g. 'string) which can lead to program failures.
func (*LEnv) ToBytes ¶ added in v1.82.0
ToBytes returns the bytes the to-bytes builtin returns for val, or the same error from the same check, the allocation check included. It checks the evaluation's context first, as CallBuiltin does. It charges no step. For a bytes val the result is val's own storage, as the builtin returns val itself, so treat it as read-only. lisp.Bytes of it makes a second value over the same storage, which append! on either can change: return val itself, or a copy, when the result is a Lisp value. The error is the builtin's own *ErrorVal.
func (*LEnv) ToInt ¶ added in v1.82.0
ToInt returns the int the to-int builtin returns for val, or the same error from the same check. It checks the evaluation's context first, as CallBuiltin does. It charges no step. The error is the builtin's own *ErrorVal.
func (*LEnv) ToString ¶ added in v1.77.0
ToString returns the string the to-string builtin returns for val, or the same error from the same check, the allocation check included. It checks the evaluation's context first, as CallBuiltin does. It charges no step. The error is the builtin's own *ErrorVal, so a FuncE body that returns it raises the same condition.
s, err := env.ToString(v)
func (*LEnv) Update ¶
Update updates the binding of k to v within the scope of env. Update can update either lexical or global bindings. An unqualified k is resolved in env, an enclosing LEnv, then the current package; a package-qualified k (pkg:name) is resolved in package pkg only, bypassing lexical bindings. If k is not bound there an error condition is signaled. This is a trusted Go API that, like PutGlobal, bypasses the core package seal -- including for a qualified lisp:name; Lisp-facing writers must use UpdateFromLisp.
func (*LEnv) UpdateFromLisp ¶ added in v1.62.0
UpdateFromLisp updates an existing binding with the core package seal enforced. Use this for assignments with names controlled by Lisp code.
func (*LEnv) UsePackage ¶
func (*LEnv) Zip ¶ added in v1.82.0
Zip returns what the zip builtin returns for (zip typespec list lists...), where typespec is the type specifier kind (SeqList or SeqVector) stands for: a list or a vector of sequences of that type, where the i-th holds the i-th element of each input, cut to the shortest input; or the same error from the same check, the allocation check included. SeqNil, which stands for (), returns "first argument is not a valid type specifier: list" before any other check. Any other kind returns "type specifier is invalid: K" after the checks of the lists and the allocation check (see SeqKind). It checks the evaluation's context first, as CallBuiltin does. It charges no step. It neither writes lists nor keeps the slice.
type LFunType ¶
type LFunType uint8
LFunType denotes special functions, either macros or special operators.
LFunType constants. LFunNone indicates a normal function.
type LType ¶
type LType uint
LType is the type of an LVal
const ( // LInvalid (0) is not a valid lisp type. LInvalid LType = iota // LInt values store an int in the LVal.Int field. LInt // LFloat values store a float64 in the LVal.Float field. LFloat // LError values store the condition name in Str and error data in Cells. // Go errors become LString data, retaining the Go error in that string // cell's Native field for host diagnostics. // // In addition, LError values store a copy of the function call stack at // the time of their creation in the LVal.Native field. A handler-bind // handler reads it with the error-stack builtin (#734). LError // LSymbol values store a string representation of the symbol in the // LVal.Str field. LSymbol // LSExpr values are "list" values in lisp and store their values in // LVal.Cells. LSExpr // LFun values use the following fields in an LVal: // LVal.Str The local name used to reference the function (if any) // LVal.Native A funData object // // In addition to these fields, a function defined in lisp (with defun, // lambda, defmacro, etc) uses the LVal.Cells field to store the following // items: // [0] a list describing the function's arguments // [1:] body expressions of the function (potentially no expressions) // // NOTE: Native go functions (LBuiltin) don't have a lexical environment // by default. If a native function needs a lexical environment in order // to evaluate further expressions it is expected to create one. See the // implementation of the builtin “let”. // // NOTE: Cells[1] in an LFun may contain a string literal which contains a // docstring. To match common-lisp semantics and maintain backwards // compatibility a function with a body consisting of only a string literal // returns the string constant and is considered to have no documentation. // A builtin function may also include a docstring in Cells[1]. LFun // LQuote values are special values only used to represents two or more // levels of quoting (e.g. ”3 or ”””'()). The quoted value is stored // in LVals.Cells[0]. The first level of quoting takes places by setting // the LVal.quoted field on a value with a normal value in LVal.Type. // LQuote values must always have a true LVal.quoted field. LQuote // LString values store a string in the LVal.Str field. LString // LBytes values store a *[]byte in the LVal.Native field. LVal.Native, // and the contained pointer, must must never be nil (the slice being // pointed to may be nil though). LBytes // LSortMap value uses the LVal.Map field to store a map. LSortMap // LArray values use the LVal.Cells slice to store the following items: // [0] a list containing dimension cardinalities in index 0 // [1] a list containing row-major ordered array values LArray // LNative values store a Go value in the LVal.Native field and can be used // by builtin functions to store values of any type. LNative // LTaggedVal is a user-defined type that uses the following fields in an // LVal: // LVal.Str The user-defined type name // LVal.Cells[0] The user-data for the typed-value LTaggedVal // Mark LVals are used to trasmit information down the stack through return // values. Because the LEnv does not evaluate expressions using a stack // based virtual machine these Mark values, which often wrap other LVal // data in their Cells, are passed back from functions. Typically the // environment is solely responsible for managing mark values and // applications should never see them during calls to builtin functions. LMarkTerminal // LEnv marks the frame as terminal and evaluates tho contained expr LMarkTailRec // LEnv resumes a call a set number of frames down the stack. LMarkMacExpand // LEnv will evaluate the returned LVal a subsequent time. // LTypeMax is not a real type but represents a value numerically greater // than all valid LType values. It also can be used to determine the // number of valid LType values. LTypeMax )
Possible LValType values
type LVal ¶
type LVal struct {
// Native is generic storage for data which cannot be represented as an
// LVal (and thus can't be stored in Cells).
Native any
// Str used by LSymbol and LString values
Str string
// Cells used by many values as a storage space for lisp objects.
// Function formals/body vectors are read-only after construction; a
// template may share their backing when all entries are sealed code,
// even though the function header and lexical scope remain per VM.
//
Cells []*LVal
// Type is the native type for a value in lisp.
Type LType
// Fields used for numeric types.
Int int
Float float64
// FunType used to further classify LFun values.
FunType LFunType
// contains filtered or unexported fields
}
LVal is a lisp value
Field order is chosen so that every pointer-bearing word sits in the leading 64 bytes: the GC only scans up to the last pointer word, so grouping the pointers first and letting Str/Cells contribute their pointer word last leaves their len/cap tails (and all the scalars) outside the scan range. This cuts the GC scan extent from 112 bytes to 64 without changing the struct's overall size. LVal is allocated for every value in the interpreter, so keep the pointers first when adding fields — `govet`'s fieldalignment check (see .golangci.yml) enforces this.
func Array ¶
Array returns an LVal representing an array reference. The dims argument is be a list of integers sizes for each dimension of the array. If non-empty, cells provides the backing storage for the array. The dims argument may be nil, in which case a vector (one dimensional array) is returned. If dims is non-nil then cells must either be nil or have one element for every array element, in row-major order.
When no backing storage is supplied every element is initialized to Nil, the same value MakeVector gives an unset element. It used to be left as the zero value of the slice, a Go nil *LVal, which is not a value the rest of the interpreter can hold: reading one dereferences it. In-tree that was latent -- every caller here fills the cells it asked for before the array escapes -- but Array is exported and its documentation says cells may be nil, so an embedder following it built a value that panicked the host the first time lisp touched it:
env.PutGlobal(Symbol("a"), Array(QExpr([]*LVal{Int(3)}), nil))
(aref a 0) ; internal-panic: nil pointer dereference
A panic is the wrong answer twice over: the read is not an error at all (an unset element is nil, which is what the array now holds), and an internal-panic is deliberately not catchable by handler-bind, so the host had no way to contain it. See issue #367.
func Bool ¶
Bool returns an LVal with truthiness identical to b.
The returned value is a shared singleton — callers MUST NOT mutate it.
func Encode ¶ added in v1.88.0
Encode returns x as an ELPS value, the reverse of DecodeInto:
*LVal as is; a nil *LVal is ()
int and uint types an int; a value out of int's range is an error
float types a float
string a string
bool the symbol true or false
[]byte bytes (copied); a byte array is a vector
slice, array a vector; a nil slice is ()
map a sorted map; its keys must encode as strings,
symbols or ints
struct a sorted map with string keys, by the rules of
package elpstag (omitempty leaves a zero field out)
pointer, interface the value it holds; nil is ()
Any other kind (chan, func, complex) is an error, and so is a struct with fields but none that maps to a key, such as time.Time, whose state would be lost. A struct is never a native: wrap a value that should stay native in an *LVal field with NativeOf. Encode keeps no memo: a Go value that holds one pointer, slice or map along several paths encodes it once per path, and a cycle fails at the depth limit. Encode makes no check and charges no step, as the package-level MapOf; the values it builds are new, except an *LVal it is given.
func Error ¶
Error returns an LError representing err. Errors store their message in Cells and their condition type in Str. The error condition type must be a valid lisp symbol.
Errors generated during expression evaluation typically have a call stack. The LEnv.Error method captures that stack and is preferred during evaluation.
func ErrorCondition ¶
ErrorCondition returns an LError representing err and having the given condition type. Go errors become string data while their original value remains recoverable through GoError and errors.Unwrap. The condition type must be a valid Lisp symbol.
If err IS an *ErrorVal it is returned as itself: condition, data, stack and identity are preserved, which is what lets a handler rethrow the value it was given. If err merely WRAPS an *ErrorVal (fmt.Errorf with %w, or any Unwrap chain), the caller has asked for a new classification, and gets one: the result carries condition, the wrapper's full text as its message, and err as its Go error, so errors.As still reaches the inner value. The one exception is a wrapped internal panic: its marker is the non-forgeable evidence of a host fault and must survive a host wrapper unchanged, so it keeps its identity like a bare *ErrorVal.
Errors generated during expression evaluation typically have a call stack. The LEnv.Error method captures that stack and is preferred during evaluation.
func ErrorConditionf ¶
ErrorConditionf returns an LError with a formatted error message. Errors store their message in Cells and their condition type in Str. The condition type must be a valid symbol.
Errors generated during expression evaluation typically have a non-nil Stack field. The Env.ErrorConditionf() method is typically the preferred method for creating error LVal objects because it initializes Stack with an appropriate value.
func Errorf ¶
Errorf returns an LError with a formatted error message. Errors store their message in Cells and their condition type in Str. The condition type must be a valid symbol.
Errors generated during expression evaluation typically have a non-nil Stack field. The Env.Errorf() method is typically the preferred method for creating error LVal objects because it initializes Stack with an appropriate value.
func Formals ¶
Formals returns an LVal reprsenting a function's formal argument list containing symbols with the given names.
func Fun
deprecated
Fun returns an LVal representing a function. Package is left empty; callers MUST set the funData Package before the value is invoked, or GetFunName will log "BUG: ..." at every call site that observes a package-less LFun.
Deprecated: use FunInPackage, which sets Package atomically. See issue #271.
func FunInPackage ¶ added in v1.48.0
FunInPackage returns an LFun bound to the named package. Prefer this over Fun for code embedding ELPS: Fun leaves Package empty, and a package-less LFun reaching funCall / MacroCall / SpecialOpCall produces "BUG: GetFunName" log spam (issue #271).
func FunInPackageDoc ¶ added in v1.77.0
FunInPackageDoc is FunInPackage with the docstring doc. The function value holds its formals and docstring in the layout registered builtins use, so Docstring returns doc.
func InitializeTypedef ¶ added in v1.14.0
InitializeTypedef injects the meta-typedef object so `new` and `deftype` can be used to create user-defined types. The name of the injected typedef object is not exported and it should not be handled without abstraction in general because user error can break the type system.
InitializeTypedef is a step inside InitializeUserEnv, not an alternative to it. It requires an environment that InitializeUserEnv has already established: it reads env.Runtime.Package.Name, and it writes the typedef into the registry package named by env.Runtime.Registry.Lang. StandardRuntime leaves Runtime.Package nil and Registry.Lang "" (and Packages[""] is a nil *Package), so on a bare environment -- lisp.InitializeTypedef(lisp.NewEnv(nil)) -- this panics on the first of those two before doing anything.
The panics stay: under lisp/ a panic means a bug in the interpreter, or in code that had no business calling this (issue #361). Embedders establish a top-level environment with InitializeUserEnv, which calls this itself; there is no initialization order in which an embedder calls it directly. See issue #433.
See LEnv.TaggedValue for more information about creating tagged-values.
func InitializeUserEnv ¶
InitializeUserEnv creates the default user environment.
func Macro
deprecated
Macro returns an LVal representing a macro. Package is left empty; callers MUST set the funData Package before the value is invoked, or GetFunName will log "BUG: ..." at every call site that observes a package-less LFun.
Deprecated: use MacroInPackage, which sets Package atomically. See issue #271.
func MacroInPackage ¶ added in v1.48.0
MacroInPackage returns a macro LFun bound to the named package. Prefer this over Macro for the same reasons FunInPackage is preferred over Fun. See issue #271.
func MakeVector ¶ added in v1.14.0
MakeVector returns a vector with n cells initialized to Nil.
func MapOf ¶ added in v1.77.0
MapOf returns a new sorted map built from kv, which holds alternating Go keys and values. It makes no checks: no context check and no allocation check. LEnv.MapOf is the checked form; use it in a builtin where the Lisp called sorted-map.
m := lisp.MapOf("id", id, "type", typ)
MapOf converts the keys and values as LEnv.MapOf does, with the same panic on a type it does not accept. It panics on an odd count of arguments. The result is the result of lisp.SortedMap() followed by MapSetLVal for each pair, in order: a later duplicate key replaces an earlier one, and a key the map refuses is skipped, as the ignored result of MapSetLVal skips it. MapOf charges no step.
func MatchPatternNames ¶ added in v1.93.0
MatchPatternNames checks the shape of the match pattern p with the rules that match and a let binding pattern use before they try p. It returns the names p binds, in source order, or an error that describes a bad pattern. It evaluates nothing, so a static check such as lint can use it (luthersystems/elps#910).
func NativeOf ¶ added in v1.57.0
NativeOf is the typed constructor counterpart of Native: it wraps x in an LNative exactly as Native does, and is implemented as a call to it, so the two produce indistinguishable values.
What it buys is a compiler check at the write end of a boundary whose read end is NativeValue. Native takes an interface{}, so it accepts whatever the caller happens to be holding and any mismatch with what readers ask for surfaces later as a `false` from a type assertion, at a call site far from the mistake. Writing NativeOf[Handle](h) instead states the stored type in the source and makes storing something else a build failure — worth doing wherever the payload type is spelled out again in the NativeValue[Handle] that reads it back (issue #546).
Mutable native payloads must be created per VM, after Template instantiation. NativeOf stores the value as-is; it does not declare that the payload is immutable or suitable for a Template.
func NewCapturedFun ¶ added in v1.93.0
func NewCapturedFun(spec CapturedFun) *LVal
NewCapturedFun returns the function value that spec describes. It returns an error value when spec.Eval is nil.
func Nil ¶
func Nil() *LVal
Nil returns an LVal representing nil, an empty list, an absent value.
The returned value is a shared singleton — callers MUST NOT mutate it. If you need a mutable empty list (e.g., to append children), use SExpr(nil) directly.
func QExpr ¶
QExpr returns an LVal representing an Q-expression, a quoted expression, a list. Provided cells are used as backing storage for the returned list and are not copied.
func Result ¶ added in v1.77.0
Result splits v into the (value, error) pair of Go style code. When v is an LError, Result returns a nil value and v as a *ErrorVal. Otherwise it returns v and a nil error. The error is v itself, so returning it from a FuncE body raises the same condition, data and stack.
fields, err := lisp.Result(env.CallBuiltin(sortedMap, k, v))
if err != nil {
return nil, err
}
Result makes no check and allocates nothing. It is the bridge from a helper that returns *LVal to code that returns error; GoError is the bridge for a helper that only reports success or failure.
func SExpr ¶
SExpr returns an LVal representing an S-expression, a symbolic expression. Provided cells are used as backing storage for the returned expression and are not copied.
func SortedMapFromData ¶ added in v1.14.0
SortedMapFromData returns sorted-map with the given backing implementation. Applications calling this function must make ensure the Map implementation provided satisfies the semantics of Map methods.
func SortedMapSized ¶ added in v1.61.0
SortedMapSized returns an empty sorted-map whose backing is sized to receive n entries, as make(map, n) sizes a Go map. n is a capacity hint: the map is empty and it grows past n as SortedMap's would. A negative n is clamped to zero rather than passed to make, because the language requires make's size argument to be non-negative and gc's tolerance of a negative map hint is not something an exported constructor should oblige its callers to rely on. Use it where the entry count is known before the first Set, such as when copying a map.
func SpecialOp
deprecated
SpecialOp returns an LVal representing a special operator. Special operators are function which receive unevaluated results, like macros. However values returned by special operations do not require further evaluation, unlike macros.
Package is left empty; callers MUST set the funData Package before the value is invoked, or GetFunName will log "BUG: ..." at every call site that observes a package-less LFun.
Deprecated: use SpecialOpInPackage, which sets Package atomically. See issue #271.
func SpecialOpInPackage ¶ added in v1.48.0
SpecialOpInPackage returns a special-operator LFun bound to the named package. Prefer this over SpecialOp for the same reasons FunInPackage is preferred over Fun. See issue #271.
func Splice ¶
Splice is used in the implementation of quasiquote to insert a list into an outer slist.
func SplitSymbol ¶ added in v1.2.0
func StringList ¶ added in v1.77.0
StringList returns a fresh list of the strings ss. Each element is a new string value. StringList makes no check; a builtin that sizes the list from its input checks the allocation cap first (see LEnv.CheckAlloc).
func Value ¶
Value conveniently converts v to an LVal. Types which can be represented directly in lisp will be converted to the appropriate LVal. All other types will be turned into a Native LVal. Value is the inverse of the GoValue function.
func ValueOrError ¶ added in v1.91.0
ValueOrError joins the (value, error) pair of a Go form into the one value a builtin returns: lisp.Error(err) when err is not nil, which returns a *ErrorVal as itself, else v, or () for a nil v. It is the inverse of Result, for a builtin that returns a Go form's result as it is.
return lisp.ValueOrError(env.MapPut(m, k, v))
ValueOrError makes no check and allocates nothing for a *ErrorVal.
func Vector ¶ added in v1.14.0
Vector returns an LVal representing a vector, a 1-dimensional array. Provided cells are used as backing storage for the returned vector and are not copied. Vector(nil) is an empty vector: its length is 0 and its dimensions are (0), the same value as Array(nil, nil). A vector is an LArray with one dimension; the Array* methods (ArrayDims, ArrayIndex, ArrayParts, SetArrayData) work on it.
func WrapError ¶ added in v1.78.0
WrapError returns a copy of the error lerr with a line of context, built like fmt.Sprintf(format, v...), in front of its message (luthersystems/elps#831). It is Go's fmt.Errorf("...: %w", err) for a Lisp error: the copy renders as "context: message", and keeps lerr's condition, data, source location and call stack, so every handler-bind binding and condition-is? test that matched lerr matches the copy. A further wrap puts its line in front of the earlier ones. Lisp code wraps the error it is handling with (rethrow :context ...).
lerr is not changed. WrapError returns lerr itself when it is not an error or when the line is empty. An error with no call stack yet gets its stack when the evaluator associates it, as any error a builtin returns does, and keeps its context. A wrap costs the same however many lines the error has.
func (*LVal) All ¶ added in v1.77.0
All returns an iterator over the entries of the sorted-map v, in the order of MapRange: int keys first by value, then string and symbol keys by spelling. For any other value it yields nothing. Breaking out of the loop early is safe.
for k, val := range m.All() { ... }
All makes no check and charges no step. Use it after a check that v is a map, or where the Lisp made none; where the Lisp called keys, use LEnv.MapRange, which makes keys' checks. It shares MapRange's recycled buffer, and it loads every lazy value of a map a template built. A custom backing (NewMapData) that fails ends the loop.
func (*LVal) AppendMapKeyPairs ¶ added in v1.74.0
func (v *LVal) AppendMapKeyPairs(dst []MapKeyPair) ([]MapKeyPair, bool)
AppendMapKeyPairs appends the entries of the sorted-map v to dst, in no particular order, each with its key's type, and returns the extended slice. Unlike AppendSortedPairs it carries int keys and tells a string key from a symbol key, for a walk (the typed JSON encoder) that must write each key's type and orders the entries itself. Values are the map's own; nothing is allocated beyond growing dst.
ok is false when v's backing is not one of the built-in maps; dst is then returned unchanged and the caller must fall back to MapEntries. A map with no backing is empty. It panics if v.Type is not LSortMap.
func (*LVal) AppendSortedPairs ¶ added in v1.63.0
AppendSortedPairs appends the entries of the sorted-map v to dst, ordered by key spelling -- the order MapEntries yields -- and returns the extended slice. Only dst[len(dst):] is sorted; the prefix is left alone, so a caller can use one scratch slice as a stack across nested maps. Values are the map's own, exactly as MapEntries would hand them out.
It exists so a read-only walk (the JSON encoder) can visit a map without materialising a pair list: MapEntries builds a pair LVal, a key LVal and two cell slots per entry, all garbage the moment the walk moves on. Key kind (string or symbol) is not reported, which is why this is only for callers that treat both kinds alike.
ok is false when v's backing is not one of the built-in maps (the stock sortedmap or a decoded JSON map), or when it holds an int key, which a MapPair cannot carry; dst is then returned unchanged and the caller must fall back to MapEntries. A map with no backing is empty. AppendSortedPairs panics if v.Type is not LSortMap.
func (*LVal) ArrayDims ¶
ArrayDims returns the dimensions of an array. ArrayDims returns an error if v.Type is not LArray.
func (*LVal) ArrayIndex ¶
ArrayIndex returns the value at the given index in an array.
func (*LVal) ArrayParts ¶ added in v1.77.0
ArrayParts returns the dimension list (dims) and the data list (data) that the array v stores, in that order:
dims, data := v.ArrayParts()
It does not copy them, so a write to either list changes v; use ArrayDims for a copy of the dimension list. ArrayParts makes no check of the lists. For an LArray value that does not hold two cells (a malformed value built by hand), it returns nil, nil. ArrayParts panics if v.Type is not LArray, as Map and MapEntries panic for a value that is not a sorted-map.
func (*LVal) Builtin ¶
Builtin returns the native implementation of a builtin function value, or nil for user-defined functions and function values carrying no function data. It panics on non-function values.
func (*LVal) Bytes ¶
Bytes returns the []byte stored in v. Bytes panics if v.Type is not LBytes.
NOT LISP-REACHABLE (#367), and the guard belongs to the CALLER: every in-tree caller tests v.Type == LBytes (or reaches this from inside a `case LBytes`) before calling. `(append 'bytes 0)` was the counterexample -- the type SPECIFIER was validated and the sequence was not -- and it is now a seed in the eval fuzz corpus, replayed on every run.
Deliberately not softened to "return nil for a non-LBytes": nil is a perfectly good empty byte string, so a caller that skipped its type check would get a silent wrong answer instead of a loud one. That is the trade KeyArg's doc comment rejects. A caller that cannot vouch for the type must test it and raise its own error; TestBuiltinRegistryNeverPanics (lisp/lisplib) is what finds the caller that forgot.
func (*LVal) CallStack ¶
CallStack returns the call stack attached to the error v. CallStack panics if v.Type is not LError.
NOT LISP-REACHABLE (#367): the panic guards a Go type assertion, not a program's data. Every in-tree caller -- builtinLoad*/builtinIsKey, env.Error*, macroDefun/macroDefmacro, opLambda, libjson's attachStack, and the two diagnostic renderers -- tests v.Type == LError first, so lisp source has no way to route a non-error here. Reaching it means embedder Go code called the accessor on a value it had not classified, and that is the #351/#355 shape rather than this one. A caller that cannot classify the value should test v.Type == LError itself: the type IS the check, so an accessor that answered nil would be reporting "no stack recorded" for a value that can never have one.
func (*LVal) Copy ¶
Copy creates a deep copy of the receiver.
Copy has within-runtime semantics — a closure's environment, an LError's call stack and a native payload that is not a NativeCloner are shared with the receiver — so it is not a tool for transferring values between Runtimes; the in-kernel detach (lisp/detach.go, unexported until a real consumer appears) covers that.
The copy owns its positions. Every node Copy reaches gets its own *token.Location, so a write through the copy cannot move what the original reports, and the reverse. That holds for the Locations reachable THROUGH meta's comment tokens as well as for source on the node. Values Go constructed carry no location at all (source is nil and the accessor synthesizes one by value), so there is nothing left to share -- the process-wide "<native code>" singleton this used to except is deleted, see nativeLocation.
The copy also owns its per-node metadata: meta and macroExpansion. The one thing deliberately still shared is macroExpansionInfo's embedded *macroExpansionContext, which describes the macro CALL rather than the node -- see macroExpansionInfo.Copy.
The copy owns its payloads, once each. A sorted map, a bytes buffer or a NativeCloner payload reachable through several headers is rebuilt ONCE and shared by those headers in the copy exactly as it was in the source; a value reachable twice is copied once and a cycle closes onto the copy. A map's values, and an array's dims and data-list headers, are walked like a list's cells. What stays shared: a closure's environment, an LError's call stack, and a native payload that is not a NativeCloner. See copier in lisp/copier.go. Walks exceeding MaxValueDepth return an ordinary LError instead of a partial copy.
func (*LVal) Docstring ¶ added in v1.16.6
Docstring returns the docstring of the function reference v. If v is not a function Docstring returns the empty string. For user-defined functions, consecutive leading string expressions in the body are concatenated to form the docstring (the body must contain at least one non-string expression after the doc strings). Empty strings produce paragraph breaks.
func (*LVal) Equal ¶
Equal returns a non-nil value if v and other are logically equal, under the rules used by the "equal?" function.
Cyclic operands terminate. Comparison is co-inductive: when the walk reaches a pair of values it is already comparing further up the current path, it takes that pair to be equal and moves on. That is the greatest fixed point -- equality of the two values' infinite unfoldings, the same answer R7RS requires of equal? on circular structure -- and it is an answer rather than a guess: false is only ever returned for a difference actually found at a finite depth, so no equality is claimed that a longer walk could refute. See lisp/cycle.go and issue #390. Comparison descending beyond MaxValueDepth returns an ordinary LError.
Equal is the RUNTIME-LESS form and always uses MaxValueDepth: it takes no Runtime and an *LVal carries none, so it cannot see a WithMaxValueDepth override. Kernel code holding an environment calls EqualWithRuntime instead, which is what the equal? builtin does, so a configured limit bounds the comparison a program asks for. Equal remains for callers with no runtime in reach -- test support, embedder code comparing two values it owns -- where the default is the only answer available.
func (*LVal) EqualWithEnv ¶ added in v1.70.0
EqualWithEnv is EqualWithRuntime for a caller holding an environment -- the equal? builtin, and any native builtin comparing values a program supplied: env supplies the runtime and the context a long comparison polls (see equalIter). Polling never changes a result a comparison reaches; it only stops one whose deadline has already passed.
A nil env is EqualWithRuntime with a nil runtime.
func (*LVal) EqualWithRuntime ¶ added in v1.62.0
EqualWithRuntime is Equal with the runtime's configurable value depth limit. Deep comparisons use an explicit stack, so limits above MaxValueDepth are safe. A nil runtime is MaxValueDepth, which is what Equal passes.
func (*LVal) FID ¶
FID returns the function value's unique identifier, or "" for a function value carrying no function data. It panics on non-function values.
func (*LVal) InheritSeal ¶ added in v1.52.0
InheritSeal marks v sealed when src is. It is for a v that was just minted over storage BORROWED from src — a fresh LVal header wrapping a (sub)slice of src's backing array — where the seal has to travel with the storage, not with the header that happens to point at it.
The kernel already does this by hand at every such site, and the comment it repeats is the whole rule: "a two-index slice keeps the original backing array (and its spare capacity), so a sealed input's constraint travels with the intermediate value" (builtinSlice; builtinCdr and builtinRest say the same). Those sites live in package lisp and assign the unexported field directly. This method is that same assignment, exported, for the value-restructuring libraries the seal design expects to migrate into this module — libelpspath being the one named in the commentary above, and the one that shipped without it (issue #392).
It is deliberately NOT a general "seal this value" setter:
- Nothing happens unless src is already sealed. A caller cannot invent a constraint, only propagate one that exists.
- Only the node types SealAST itself marks are eligible. In particular an array is never marked, because the kernel's mutating vector builtins (append!, assoc!) do not consult the flag at all — a "sealed" vector would be a lie that reads as protection. Code wrapping sealed backing in a vector must copy instead, exactly as builtinSlice does for (slice 'vector …).
- Singletons are skipped: they are born sealed, and writing even a flag to a value shared process-wide would race.
Sealing is monotone, so this is idempotent and safe to call on a value that is already sealed.
func (*LVal) IsError ¶ added in v1.77.0
IsError reports whether v is an error value (LError). It is the same compare as v != nil && v.Type == LError. A nil v is not an error.
func (*LVal) IsMacro ¶
IsMacro returns true if v is a macro function. IsMacro doesn't actually check v.Type, only v.FunType.
func (*LVal) IsNil ¶
IsNil returns true if v represents a nil value.
Only the empty list is nil. Written as an expression rather than a switch: there is exactly one interesting type, so a switch would have to name the other seventeen LTypes to say nothing about them.
func (*LVal) IsNumeric ¶
IsNumeric returns true if v has a primitive numeric type (int, float64).
See IsNil for why this is an expression and not a switch.
func (*LVal) IsQuoted ¶ added in v1.52.0
IsQuoted reports whether v carries a single level of quoting — the flag behind the LQuote wrapper and the ['(...)/[...]] display forms. The underlying field is unexported (issue #382): quoting is established at construction time (Quote, Splice, QExpr, the parser) and removed only by the evaluator's own unquote step, so external packages get a read but never a write — an in-place external write to the flag on a shared value was exactly the #333/#334 singleton corruption.
IsQuoted is nil-receiver safe: a nil LVal reports false.
func (*LVal) IsSealed ¶ added in v1.52.0
IsSealed reports whether v is a sealed program-literal node (or a header sharing storage with one). Go code holding a value that reports true must not modify the value in place — modifying it could corrupt the program for every environment sharing the parse — and should either return an error ("cannot modify a program literal") or work on a Copy (whose fresh storage IsSealed reports false).
func (*LVal) IsSpecialFun ¶
IsSpecialFun returns true if v is a special function. IsSpecialFun doesn't actually check v.Type, only v.FunType.
func (*LVal) IsSpecialOp ¶
IsSpecialOp returns true if v is a special operator. IsMacro doesn't actually check v.Type, only v.FunType.
func (*LVal) IsSpliced ¶ added in v1.93.0
IsSpliced reports whether v carries the spliced flag that Splice sets. A quasiquote splices such a value into its list even in an unquote position, so a Go caller that builds what a quasiquote would build reads it (elpsutil.Template.ExpandChecked). Like quoted, the flag is read-only outside this package.
IsSpliced is nil-receiver safe: a nil LVal reports false.
func (*LVal) IsSymbol ¶ added in v1.77.0
IsSymbol reports whether v is the symbol name. It is the same compare as v != nil && v.Type == LSymbol && v.Str == name. A nil v is not a symbol.
if v.IsSymbol(lisp.TrueSymbol) { ... }
func (*LVal) IsVector ¶ added in v1.89.0
IsVector reports whether v is a vector: an array of one dimension, as the vector? builtin reads it.
func (*LVal) KeyArg ¶ added in v1.50.0
KeyArg returns the i'th cell of a builtin's argument list, or Nil if the list is shorter than that.
The evaluator always passes one cell per declared formal, so for a builtin reached from lisp this is just Cells[i]. It differs for the builtins this package exports for embedding: an embedder binds the Go function to formals of its own, and one that declares fewer formals than the builtin reads -- easily done for an &key argument, which is invisible at the call site -- would otherwise index past the end of Cells and panic on every call. The Go signature is identical either way, so nothing catches it at compile time.
An absent cell is reported as Nil, which is what the evaluator itself passes for an unsupplied &optional or &key argument. Builtins must therefore read those cells through KeyArg rather than indexing Cells directly.
Use it ONLY for optional and &key cells, never for a required argument. Reporting an absent cell as Nil conflates "the caller supplied nothing" with "the caller supplied nil", and that is only safe where nil already means "not supplied". For a required argument whose valid domain includes Nil it is actively harmful: json:dump-string would answer "null" for a binding that supplied no argument at all, turning a panic into a silent wrong answer. Required arguments use ReqArg instead.
func (*LVal) Keys ¶ added in v1.77.0
Keys returns an iterator over the keys of the sorted-map v, in the order of All. For any other value it yields nothing, where MapKeys panics.
for k := range m.Keys() {
if name, ok := k.Name(); ok && strings.HasPrefix(name, "$") { ... }
}
Keys reads keys only: it loads no lazy value and builds no list. It makes no check and charges no step. Breaking out of the loop early is safe. A custom backing (NewMapData) that fails ends the loop.
func (*LVal) MacroExpansion ¶ added in v1.23.0
func (v *LVal) MacroExpansion() (MacroExpansionMeta, bool)
MacroExpansion returns a snapshot of v's macro-expansion debug metadata and reports whether v carries any. Metadata exists only on nodes stamped during macro expansion while a debugger is attached (Runtime.Debugger != nil); in production runs every value reports false. The snapshot is a copy: mutating it does not touch v.
MacroExpansion is nil-receiver safe: a nil LVal reports false.
func (*LVal) Map ¶
Map returns the map data stored in v. Map panics if v.Type is not LSortMap.
NOT LISP-REACHABLE (#367), on the same terms as Bytes above: every in-tree caller -- the sorted-map builtins, Copy's LSortMap case, equal, libschema and libelpspath's path walkers -- tests LSortMap first. Returning nil for a non-map would only move the crash to the caller's next method call on the nil *MapData, so the type test stays the caller's job.
func (*LVal) MapEntries ¶ added in v1.14.0
MapEntries returns a list of key-value pairs in the map. MapEntries
func (*LVal) MapKeys ¶
MapKeys returns a list of keys in the map. MapKeys panics if v.Type is not LSortMap. The type of each map key is retained from the first type a value was set for that key. For example, if the MapSet(Symbol("a"), Int(1)) is called before MapSet(String("a"), Int(2)) then MapKey() will contain the symbol and not the string.
func (*LVal) MapSet
deprecated
MapSet sets k to val in v. MapSet returns an error if v.Type is not LSortMap. String and symbol keys are coerced to avoid programming errors causing symbol and string keys with equal string values from existing in the same map. A key that is neither *LVal nor string compiles but fails at run time.
Deprecated: use MapSetLVal or MapSetString, which check the key type at compile time.
func (*LVal) MapSetLVal ¶ added in v1.66.0
MapSetLVal sets k to val in v. MapSetLVal returns an error if v.Type is not LSortMap. String and symbol keys are coerced to avoid programming errors causing symbol and string keys with equal string values from existing in the same map.
func (*LVal) MapSetString ¶ added in v1.66.0
MapSetString is MapSetLVal with a string key.
func (*LVal) Package ¶
Package returns the name of the package a function value was defined in, or "" for a function value carrying no function data. It panics on non-function values.
func (*LVal) ReqArg ¶ added in v1.50.0
ReqArg returns the i'th cell of a builtin's argument list, or an error if the list is shorter than that.
This is the required-argument counterpart to KeyArg. Indexing Cells directly panics when an embedder binds the builtin to formals declaring fewer arguments than it reads, and a panic in an embedder's process is a far worse outcome than an error value -- the evaluator can only report it as an opaque internal-panic, and a host embedding elps has no way to recover context from it. Reporting the absent cell as Nil is not an option either, for the reason given on KeyArg. So: an error, naming the mismatch.
func (*LVal) SealAST ¶ added in v1.52.0
func (v *LVal) SealAST()
SealAST marks parser-producible nodes reachable through v.Cells as sealed program-literal nodes. The parser calls it on each completed top-level expression; embedders that build expression trees by hand and share them across environments may call it for the same protection.
Only parser-producible node types are marked: s-expressions, quote forms, symbols, strings and numbers. Any other type (functions, arrays, maps, bytes, errors, natives) was necessarily constructed at runtime; SealAST stops without descending rather than freeze storage the evaluator legitimately mutates. Singletons (Nil/true/false) are born sealed (lisp/singleton.go, issue #376), so the already-sealed check stops the walk before the write; the explicit isSingleton guard stays as a belt-and-braces statement of intent — writing even a flag to a shared singleton would race.
Sealing is idempotent, and an already-sealed node terminates the walk; the check also protects against cycles. A host-built mixed tree can contain a sealed parent above a mutable runtime value. A seal bit alone therefore does NOT prove transitive immutability. Program and Template admission validate the graph before sharing it; NewTemplate rejects mixed trees.
func (*LVal) SeqCells ¶ added in v1.77.0
SeqCells returns the cells of a list or of a one-dimensional vector and true. For any other value, a multi-dimensional array included, it returns nil and false. The cells are the value's own storage, so treat them as read-only. SeqCells makes no check and allocates nothing.
cells, ok := methodArgs.SeqCells()
if !ok {
return env.Errorf("argument is not a proper sequence: %v", methodArgs.Type)
}
func (*LVal) SetArrayCells ¶ added in v1.77.0
SetArrayCells makes cells the cells of the data list of the array v and sets its dimensions, in place, as SetArrayData does. It writes into v's own data list, so it builds no new list; a value that shares that list sees the new cells. cells is kept, not copied. SetArrayCells makes no check that the dimensions match len(cells). It panics if v.Type is not LArray.
A decoder that must give out an array before it reads the contents (a back-reference to the array from inside it) builds the empty array first and fills it last:
v := lisp.Vector(nil) define(v) cells := readElements() v.SetArrayCells(cells)
func (*LVal) SetArrayData ¶ added in v1.77.0
SetArrayData makes data the data list of the array v and sets its dimensions, in place. v keeps its pointer, so a value that holds v sees the new contents. With no dims, v becomes a vector of len(data.Cells) cells. With dims, they are v's dimensions. data is kept, not copied. SetArrayData makes no check that data is a list or that the dimensions match its length; Array is the checked form. When the new rank equals the old rank, SetArrayData writes the dimensions into v's own dimension list, as append! does. SetArrayData panics if v.Type is not LArray.
Use SetArrayData when data has an identity of its own (a list that another value shares). Use SetArrayCells to fill v's own data list.
func (*LVal) SetCallStack ¶
SetCallStack attaches a copy of stack to the error v. SetCallStack panics if v.Type is not LError.
NOT LISP-REACHABLE (#367): same argument as CallStack above -- every in-tree caller guards on v.Type == LError.
func (*LVal) SetSource ¶ added in v1.52.0
SetSource sets v's originating location in source code. A nil loc clears the location. The LVal stores the provided pointer, so a producer (e.g. a parser) may retain loc and continue to fix up its fields after the call — but once an LVal escapes to consumers the location must be treated as frozen, because the reference may be shared by many LVals (issue #362).
A sealed value (a parsed program node — see lisp/seal.go) keeps its parse-time location forever: SetSource on a sealed value is a no-op, because the node may be shared by every environment evaluating the same parse and restamping it would be a cross-environment write. The parser itself always stamps locations before sealing, so no in-repo caller is affected.
func (*LVal) Source ¶
Source returns a copy of v's originating location in source code. The boolean result reports whether v has a location at all — a false return means v carries no location (and the returned zero Location is meaningless), which is distinct from a real location whose fields happen to be zero.
The returned Location is a value copy: mutating it never affects v or any other LVal. The stored reference may be shared by many LVals, which is why no pointer accessor exists (issue #362).
Source is nil-receiver safe: a nil LVal reports no recorded location.
When v has no recorded location the boolean is false and the returned value is the synthetic "<native code>" location (File "<native code>", Pos -1) — the same location that values constructed by Go code have always reported — so the result is printable either way.
func (*LVal) String ¶
String renders v as lisp source.
A value that contains itself renders the marker "#<cycle>" at the point the walk reaches it a second time. Independently, rendering stops after 1024 nested values and replaces deeper subtrees with "#<depth-limit>", keeping even acyclic graphs from overflowing the goroutine stack. Scalars at the boundary still render in full. Output and work are bounded by DefaultMaxAlloc; exhausted output is replaced by #<truncated>. Use LEnv.Render for runtime limits. See lisp/render_bounded.go and lisp/cycle.go.
type LambdaCode ¶ added in v1.75.0
type LambdaCode struct {
// contains filtered or unexported fields
}
LambdaCode is a lambda's formals and body, validated once by NewLambdaCode, for RestoreLambda. Every lambda restored from one LambdaCode shares its cells, as the closures one lambda form makes share their formals and body forms: the code is immutable, so nothing is copied per lambda.
type LoadCache ¶ added in v1.54.0
type LoadCache interface {
// Load returns the cached parse stored under key.
Load(key string) (*CachedSource, bool)
// Store records src under key.
Store(key string, src *CachedSource)
}
LoadCache is an embedder-supplied cache of parsed source files. Install one on Runtime.LoadCache and every Load* entry point — which is to say `load-file`, and the Go-side Load/LoadString/LoadLocation family — will consult it before parsing.
The interface is deliberately policy-only. Implementations decide what to keep, how much, and for how long; they never see, construct or copy a *LVal. A minimal implementation is a map guarded by a mutex; a real one is usually size- or age-bounded.
Contract:
- Load returns the entry previously Stored under key, or ok == false. Returning an entry Stored under a DIFFERENT key is treated as a miss rather than trusted, so an implementation that mixes up its own keys degrades to "no cache" instead of running the wrong program.
- Store may drop the entry, keep it, or evict something else; elps makes no assumption that a Stored entry is later Loadable.
- Both methods must be safe for concurrent use when the cache is shared by Runtimes on more than one goroutine. The entries themselves are immutable, so no locking is needed around the values — only around the implementation's own bookkeeping.
- Load and Store must NOT re-enter the load path (they must not call Load*/LoadFile, directly or through a warmer). A re-entrant load is defended against — the guard treats it as a miss and parses without the cache (see (*LEnv).readCached) — but relying on that means giving up caching for the re-entrant load; do the warming outside the hook.
Reader custody on the fast path ¶
When a Reader hands back output already sealed throughout (the standard parser's path), admission stores the Reader's OWN nodes — it does not copy them. This is deliberate: it is what makes a cache miss zero-copy, and the standard and format-preserving parsers do not retain what they return. The contract that keeps it safe is therefore on the Reader: a Reader whose output feeds a LoadCache MUST NOT retain and later mutate the nodes it returned. This is the same residual the seal design already carries for all embedder Go code (seal.go: the flag cannot stop a direct v.Cells[0] = x), and its enforcement is the same — checked builds (-tags elpscheck) record each cached tree's fingerprint and re-verify it after every load, so a Reader that rewrites a stored tree is caught at the load that observed the change; production builds do not check and would be silently corrupted. (Copying on the fast path would close it unconditionally but would add a deep copy to every miss even though no compliant Reader needs it — the cost this hook exists to remove — so the contract is stated instead.)
Behaviour changes a cache can introduce ¶
- Installing a cache can change lisp SEMANTICS for a non-sealing Reader. Admission's copy path runs SealAST, so a guarded mutation site ((stable-sort < …), (append 'vector …), (slice 'vector …)) that succeeded cache-less against a Reader that did not seal begins raising modify-literal-error once a cache is installed. This is a migration hazard, not a doc nit — code that mutated program literals in place stops doing so. (The standard parser already seals, so its callers see no change; format-preserving and hand-written Readers are the ones affected.) A wrapping Reader that synthesizes even ONE node forces the whole file down the copy-and-seal path, so the zero-copy hit is conditional on the Reader sealing its entire output.
- Installing a cache drains the stream with io.ReadAll before parsing, so a streaming Reader that delivers a full program then a non-EOF error succeeds cache-less but fails with a cache (see (*LEnv).readCached).
A nil Runtime.LoadCache disables the hook entirely: the load path is then byte-identical to what it was before this hook existed, with no hashing, no buffering and no extra allocation (TestLoadCacheNilPathUnchanged).
Note the breadth: the hook sits at the read funnel, so it sees LoadString and Load as well as LoadFile — and, because `load-string`/`load-bytes` are builtins, GUEST lisp source can mint entries too (a phylum that load-strings N distinct programs adds N entries). A host that evaluates many distinct one-off strings through one unbounded cache will accumulate an entry per distinct string — retention is the implementation's job, and an implementation with no bound has no bound. (The embedder-side caches this hook replaces intercept Reader.Read and have exactly the same reach.)
type Loader ¶
func LoaderMust ¶
LoaderMust returns its first argument when err is nil. If err is nil LoaderMust panics.
NOT LISP-REACHABLE (#367): this is the Go `Must` idiom, for an embedder building a Loader from a source it controls (a //go:embed constant, a literal) at start-up. Nothing in the interpreter calls it, so no evaluated program can reach it; an embedder wrapping a source a program supplies should handle the error instead.
func TextLoader ¶
TextLoader parses a text stream using r and returns a Loader which evaluates the stream's expressions when called. The reader will be invoked only once.
TextLoader returns an error if r produces any reference type (bytes, map, array, native, etc), a nil node, or output that is not finite: a cycle, or nesting past loaderWalkMaxDepth. It does NOT reject node SHARING, imposes no node budget, and — unlike the Program constructors and the cache — tolerates a Native payload on a sealable type (see newTextLoaderWalk). A Reader that interns symbols or subexpressions, one that returns a single very large expression, and one that annotates nodes through Native all load exactly as they always have. Those rules exist for trees that are ALIASED between environments; every TextLoader load gets expr.Copy(), so none of them is a TextLoader concern.
type LocationReader ¶
type LocationReader interface {
// ReadLocation the contents of r, associated with physical location loc,
// and return the sequence of LVals that it contains. The returned LVals
// should be executed as if inside a progn.
ReadLocation(name string, loc string, r io.Reader) ([]*LVal, error)
}
LocationReader is like Reader but assigns physical locations to the tokens from r.
type LogicalStackOverflowError ¶
type LogicalStackOverflowError struct {
Height int
}
func (*LogicalStackOverflowError) Error ¶
func (e *LogicalStackOverflowError) Error() string
type MacroExpansionMeta ¶ added in v1.52.0
type MacroExpansionMeta struct {
// CallSite is a copy of the location where the macro was invoked.
CallSite *token.Location
// DefSite is a copy of the macro definition location (nil for builtins).
DefSite *token.Location
// Name is the qualified macro name (e.g. "lisp:defun").
Name string
// Args holds the unevaluated call-site arguments. The slice is a copy
// but the nodes are the shared originals — read-only by contract (they
// are typically sealed parse-tree nodes).
Args []*LVal
// ID is unique per stamped node, monotonically increasing within a
// runtime.
ID int64
}
MacroExpansionMeta is a read-only snapshot of the debug metadata attached to values produced by macro expansion while a debugger is attached. It is returned by (*LVal).MacroExpansion; the metadata itself lives in unexported storage (issue #382) because the historical corruption in #370 was a write of expansion metadata onto shared parser nodes — reads get a copy, and the in-kernel stamp is the only writer.
type Map ¶ added in v1.14.0
type Map interface {
Len() int
// Get returns the value associated with the given key and a bool signaling
// if the key was found in the map. The first value returned by Get may be
// an LError type if the implementation does not support the type of key
// given.
Get(key *LVal) (*LVal, bool)
// Set associates key with val in the map. Set may return an LError value
// if the
Set(key *LVal, val *LVal) *LVal
// Del removes any association it has with key. Del may return an LError
// value if key was not a supported type or if the map does not support
// dissociation.
Del(key *LVal) *LVal
// Keys returns a (sorted) list of keys with associated values in the map.
Keys() *LVal
// Entries copies its entries into the first Len() elements of buf.
// Entries are represented as lists with two elements. Entries returns the
// number of elements written (i.e. Len) or an error if any was encountered.
Entries(buf []*LVal) *LVal
}
Map is the backing of a sorted-map value. NewMapData is the extension point for an embedder that wants its own implementation.
KEYS ARE LInt, LString OR LSymbol. Every key the interpreter puts in a map is one of those three (issue #733 added LInt). An int key is its own identity: the int 1, the string "1" and the symbol 1 are three different keys, while a string and a symbol that spell the same name remain one key, as they always were. The documented order is every int key first, in numeric order, then the string and symbol keys by spelling. The walks that impose an order of their own on a map's entries -- the copier's generic arm and the detacher, which run host code per entry and so must run it in a defined order -- sort them with sortMapEntriesByKey, which implements that order and breaks a spelling tie by type tag. That is a total order over those three kinds and over nothing else: an implementation admitting keys of other types (an LFloat, say, whose Str is empty) leaves every such key comparing equal to every other non-int key with an empty spelling, and the stable sort then leaves them in the order Entries returned.
type MapData ¶ added in v1.14.0
type MapData struct {
// contains filtered or unexported fields
}
MapData is a concrete type to store in an interface as to avoid expensive runtime interface type checking. Construct it with NewMapData; the backing Map cannot be replaced after construction (issue #382).
func NewMapData ¶ added in v1.52.0
NewMapData returns a MapData backed by m. Together with SortedMapFromData it is the extension point for embedders that back a sorted-map with a custom Map implementation.
func (*MapData) Del ¶ added in v1.62.0
Del removes an entry. A map with no backing holds none to remove.
func (*MapData) Entries ¶ added in v1.62.0
Entries writes the entries into buf, none for a map with no backing.
type MapKey ¶ added in v1.73.0
MapKey is a sorted-map key presented by value, so MapRange needs no LVal per entry. Type is LInt, LString or LSymbol; Int holds an LInt key and Str the spelling of a string or symbol key.
type MapKeyPair ¶ added in v1.74.0
MapKeyPair is one sorted-map entry with its key's type: an LString or LSymbol key is spelled by Key, an LInt key's value is Int.
type MapLoadCache ¶ added in v1.95.0
type MapLoadCache struct {
// contains filtered or unexported fields
}
MapLoadCache is a LoadCache that keeps every entry until Clear. It is the cache an embedder installs when it wants no eviction policy of its own. It is safe for concurrent use: every Runtime of a process may share one. The zero value is an empty cache ready to use.
It retains every distinct source a Runtime loads, including source that guest code passes to load-string or load-bytes. A host that evaluates many distinct one-off programs calls Clear, or installs a bounded cache.
Clear drops the cache's references only. A Runtime that already loaded an entry keeps its program until the Runtime is collected, and the next load of that source parses it again.
func NewMapLoadCache ¶ added in v1.95.0
func NewMapLoadCache() *MapLoadCache
NewMapLoadCache returns an empty MapLoadCache. It is the same as &MapLoadCache{}.
func (*MapLoadCache) Clear ¶ added in v1.95.0
func (c *MapLoadCache) Clear()
Clear drops every entry.
func (*MapLoadCache) Len ¶ added in v1.95.0
func (c *MapLoadCache) Len() int
Len returns the number of entries.
func (*MapLoadCache) Load ¶ added in v1.95.0
func (c *MapLoadCache) Load(key string) (*CachedSource, bool)
Load implements LoadCache.
func (*MapLoadCache) Store ¶ added in v1.95.0
func (c *MapLoadCache) Store(key string, src *CachedSource)
Store implements LoadCache. It only writes the map: a Store that loaded source would re-enter the load path that called it.
type MapPair ¶ added in v1.63.0
MapPair is one sorted-map entry viewed as its key spelling and its value, with no LVal built for the key or the pair.
type MapView ¶ added in v1.77.0
type MapView struct {
// contains filtered or unexported fields
}
MapView is a sorted-map whose type has been checked. AsMap makes the one check; a function that takes a MapView needs none, because the compiler guarantees it holds a sorted-map. MapView holds one pointer, so passing it costs what passing the *LVal does.
m, ok := lisp.AsMap(v)
if !ok {
return env.Errorf("argument is not a sorted-map: %v", v.Type)
}
name, _ := lisp.Lookup[string](m, "name")
inner, ok := lisp.Lookup[lisp.MapView](m, "response")
The zero MapView holds no map: Lookup finds nothing in it, Keys and All yield nothing, Len is 0 and LVal is nil.
func AsMap ¶ added in v1.77.0
AsMap returns v as a MapView and true when v is a sorted-map, and the zero MapView and false otherwise (nil included). It makes no check beyond the type and charges no step.
type MatchFieldFunc ¶ added in v1.93.0
MatchFieldFunc reads the field name of v, a value that passed a type test. It returns the field and a nil error. A nil field means the pattern must not match, for a field the library treats as absent. The second result is an error when v has no field name that a pattern may read; it stops the match. Package s returns () for a field that is not set, as (v 'name) reads it. The field itself may be any value, an error value too.
type MatchTypeFunc ¶ added in v1.93.0
MatchTypeFunc reports whether v is of type typ: Bool(true or false), or an error, which stops the match.
type Name ¶ added in v1.77.0
type Name string
Name is the text of an argument that is a string or a symbol, such as a key or a collection name a builtin accepts in either form. As a Func*E argument type it accepts either and allocates nothing. Its message is "<what> is not a string or symbol: <type>".
type NativeCloner ¶ added in v1.52.0
type NativeCloner interface {
CloneNative() any
}
NativeCloner supplies an independent payload for Lisp copy and strict detach. CloneNative must not mutate its receiver. A detached clone must retain no reference into the source Runtime or LEnv tree; a within-VM copy may retain immutable references belonging to that same VM.
This is not a VM construction protocol. NewTemplate rejects mutable native payloads, including NativeCloner implementations. Approved immutable payloads are shared without invoking CloneNative. Create mutable services per VM.
type Package ¶
Package is a named set of bound symbols. A package is interpreted code and belongs to the LEnv that creates it.
func NewPackage ¶
NewPackage initializes and returns a package with the given name. Direct table initialization is safe here: the package is unfrozen and unpublished.
func (*Package) Export ¶ added in v1.52.0
Export appends names to the package's export list verbatim, preserving existing order and without deduplicating (matching historical append semantics on the package's export list). Use Exports for the deduplicating, sorting variant.
func (*Package) Exports ¶
Exports declares symbols exported by the package. The symbols are not required to be bound at the time Exports is called.
The result is always the sorted union of the package's existing export list with the names in sym that it did not already contain -- names repeated WITHIN one call are appended once each, matching the historical implementation, which tested membership against the pre-call list only.
The single-name case has its own path because it is the only one the interpreter reaches: `(export 'a 'b)` recurses one symbol at a time (exportArgs, lisp/builtins.go), so exporting n names used to copy and sort a one-element slice, scan the whole export list and re-sort it n times over. The fast path skips the copy and the input sort and splices the new name into its sorted position instead, keeping the list sorted for the next call.
func (*Package) Externals ¶
Externals returns the package's exported symbol names in declaration order. Externals allocates and returns a copy on every call so callers cannot modify the package's export list.
func (*Package) FirstNameOf ¶ added in v1.75.0
FirstNameOf returns the first name, in sorted order, under which pkg binds fn itself: a binding whose function data is fn's, so any header copy of fn (a FunRef or Copy) matches, and another function with the same FID does not. It returns false when no binding holds fn, or when fn is not a function. It also returns the number of bindings it read, so a caller can charge for the work.
It reads only the names under which pkg binds a function with fn's FID (luthersystems/elps#847), so the count does not grow with the package. Like FunNamesByFID it reads a binding a lazy template has not materialized from the template's plan, so it builds no value.
func (*Package) Frozen ¶ added in v1.65.0
Frozen reports whether pkg still reads a template-frozen package's shared tables. A frozen package is shared until its first write; that write thaws a private copy for this VM (see ensureWritable), after which Frozen is false.
func (*Package) FunNameByFID ¶ added in v1.81.0
FunNameByFID returns the first name, in sorted order, under which pkg binds a regular function that pkg defines and whose FID is fid: the name FunNamesByFID returns for fid, without reading the whole package. It returns false when no such binding exists. It reads at most the names pkg binds to a function with that FID, whatever the size of the package (luthersystems/elps#847).
It reads a binding a lazy template has not materialized from the template's plan, so it builds no value.
func (*Package) FunNamesByFID ¶ added in v1.75.0
FunNamesByFID returns the canonical global name of every regular function pkg defines and binds: for each FID, the first name in sorted order under which pkg binds a function whose Package is pkg.Name and whose FunType is LFunNone. It also returns the number of bindings it read, so a caller can charge for the work.
It reads a binding a lazy template has not materialized from the template's plan, so it builds no value. A rebinding (set, putSlot, a thawed table) is read as the binding it now holds. An FID is unique within its package, so the FID names one function of pkg.
func (*Package) Get ¶
Get takes an LSymbol k and returns the LVal it is bound to in pkg.
Get is a pure read. It used to record FunNames[fid] = k.Str on every successful function lookup, so that the name the caller used won over the name the binding was created with. That made a read method write a map that is shared by pointer across goroutines — embedders hand the same *Package to concurrent requests — with no synchronisation. Under -race it is a data race; without -race the Go runtime kills the process outright with "fatal error: concurrent map read and map write", which is a runtime throw that neither recover() nor handler-bind can intercept. See issue #397.
FunNames is maintained on the write path instead: put records the name for every LFun that enters Symbols, so the map is already populated by the time anything reads it. The one behaviour that goes away is the "last lookup wins" preference when a single function value is bound under several names in the same package: GetFunName now reports the name most recently *bound* rather than the name most recently *looked up*. That is cosmetic — it affects the function name rendered in stack traces and error messages only.
func (*Package) GetFunName ¶
GetFunName returns the function name (if any) known to be bound to the given FID.
func (*Package) NumBindings ¶ added in v1.75.0
NumBindings returns the number of names bound in pkg, without reading or materializing any binding.
func (*Package) NumExternals ¶ added in v1.52.0
NumExternals returns the number of exported symbol names without copying the export list.
func (*Package) Put ¶
Put takes an LSymbol k and binds it to v in pkg.
Put stores v as given: it takes no admission walk (see AddPackage and lisp/package_admit.go). It is the write path every `set` reaches through LEnv.PutGlobal, where the value being bound belongs to the environment that is already evaluating it and LEnv.Put/PutGlobal have already taken the checked-mode ownership sighting — so a per-binding walk here would tax the interpreter's hot path to guard a transfer that is not happening. A Go caller that reaches around the environment to Put into a package another Runtime is serving is doing the cross-Runtime sharing AddPackage's snapshot exists to prevent, and owns the consequences.
func (*Package) Seal ¶ added in v1.95.0
func (pkg *Package) Seal(policy SealPolicy) error
Seal seals pkg against Lisp writes from now on. With a nil policy every Lisp write to pkg fails with "cannot rebind sealed package binding: pkg:name"; otherwise policy decides each write (see SealPolicy). Writes before the call, and Go writes at any time, are not affected.
A host seals its packages after it has defined them and before it loads code it does not own. The seal applies to every Lisp write, the package's own Lisp functions included: a function defined in the package runs with it as the current package, so its (set 'counter ...) is refused too. A host package that keeps state that way must move the state to Go, or keep it in an unsealed package. The seal is part of the package: NewTemplate keeps it in every VM, and so does package admission. Seal returns ErrPackageSealed if pkg is already sealed, and so for the lisp package.
func (*Package) Symbol ¶ added in v1.52.0
Symbol returns the value bound to name in pkg and reports whether name is bound. Unlike Get, Symbol performs a raw table lookup: it does not resolve the true/false constants, does not record function names, and returns (nil, false) instead of an error LVal when name is unbound.
func (*Package) SymbolDoc ¶ added in v1.52.0
SymbolDoc returns the documentation string bound to name in pkg, or the empty string when name has no documentation.
func (*Package) SymbolNames ¶ added in v1.52.0
SymbolNames returns the names of all symbols bound in pkg in sorted order. SymbolNames allocates a new slice on every call.
type PackageRegistry ¶
type PackageRegistry struct {
Lang string // A default package used by all other packages
// contains filtered or unexported fields
}
PackageRegistry contains a set of packages.
func NewRegistry ¶
func NewRegistry() *PackageRegistry
NewRegistry initializes and returns a new PackageRegistry.
func (*PackageRegistry) AddPackage ¶ added in v1.52.0
func (r *PackageRegistry) AddPackage(p *Package) bool
AddPackage registers a private snapshot of p under p.Name if no package with that name exists already. AddPackage returns true when p was added and false when a package named p.Name was already registered (in which case the registry is unchanged).
AddPackage is an ADMISSION point, not a store (issue #524). A registry is the interpreter state of a Runtime, so a package built outside it — by an embedder, or by another Runtime whose registry is being merged into this one — arrives full of values the caller still holds pointers to. What gets registered is therefore a snapshot of p rather than p itself, and each bound value is admitted according to what the seal can promise about it (lisp/package_admit.go states the rule per value class):
- a value that is sealed throughout, and a singleton, are shared by reference — the sanctioned cross-environment share;
- a code-like tree that is NOT sealed (a runtime-built list, symbol, string or number: fresh mutable storage the caller still aliases) is copied privately and sealed, so neither side can write what the other evaluates;
- everything else — functions, natives, sorted-maps, arrays, byte strings, and trees holding one — is shared by reference, because no seal covers those classes. For them AddPackage transfers custody: the caller must stop mutating them, and evaluating a shared closure under two Runtimes remains the caller's problem (checked builds report it).
Two consequences worth stating for callers. The registry does not hold p, so binding into p afterwards does not change the registered package — bind through the environment (or add a finished package). And the snapshot reads p's maps on the calling goroutine, so, like every other read of a *Package, it requires that no other goroutine is writing p (issue #397).
func (*PackageRegistry) DefinePackage ¶
func (r *PackageRegistry) DefinePackage(name string) *Package
func (*PackageRegistry) Package ¶ added in v1.52.0
func (r *PackageRegistry) Package(name string) *Package
Package returns the package registered under name, or nil if no such package exists.
func (*PackageRegistry) PackageNames ¶ added in v1.52.0
func (r *PackageRegistry) PackageNames() []string
PackageNames returns the names of all registered packages in sorted order. PackageNames allocates a new slice on every call.
func (*PackageRegistry) RegisteredBuiltin ¶ added in v1.75.0
func (r *PackageRegistry) RegisteredBuiltin(pkg, name string) *LVal
RegisteredBuiltin returns the function, macro or special operator registered as pkg:name, or nil when nothing is registered under that name. Registration is LEnv.AddBuiltins, LEnv.BindBuiltins, LEnv.AddMacros, LEnv.AddSpecialOps or LEnv.RegisterBuiltins. The answer does not depend on what pkg binds name to now: set, set!, defun, Put and shadowing never change it. A later registration of the same package and name replaces it.
In a template VM the answer is the VM's own value of the builtin, the same value the package binds while the name is unchanged. In a lazy VM the first call for a name builds the value, so the single-goroutine rule of Template.NewVM applies.
func (*PackageRegistry) RegisteredBuiltinName ¶ added in v1.75.0
func (r *PackageRegistry) RegisteredBuiltinName(fn *LVal) (string, string, bool)
RegisteredBuiltinName returns the package and name under which fn was registered, and true, when fn is the function, macro or special operator that r's registration of that name created. A header that shares fn's function data (a FunRef or Copy of it) is the same function and answers the same.
The answer comes from the registration record elps stores with the function, never from its FID text. It is false for every other value: a lambda, a builtin made by FunInPackage, a captured builtin (a libschema validator, for one), a native, a non-function, and a builtin that a later registration of the same name replaced. Rebinding or shadowing the name does not change the answer. The record is shared by every VM a template mints, so the answer is the same in the source and in each VM.
type ParamInfo ¶ added in v1.17.0
ParamInfo describes a single parameter in a function signature.
func ParseFormals ¶ added in v1.17.0
ParseFormals extracts parameter info from a formals LVal. The formals list uses &optional, &rest, and &key markers to separate parameter kinds.
type PhysicalStackOverflowError ¶
type PhysicalStackOverflowError struct {
Height int
}
func (*PhysicalStackOverflowError) Error ¶
func (e *PhysicalStackOverflowError) Error() string
type Profiler ¶ added in v1.9.0
type Profiler interface {
// Start the process, and returns a function to stop.
Start(function *LVal) func()
}
Interface for a profiler
type Program ¶ added in v1.52.0
type Program struct {
// contains filtered or unexported fields
}
Program is an opaque sequence of parsed top-level expressions — the sealed form of a parser's []*LVal output. Its purpose is boundary control: code outside this module can hold, cache, and evaluate a Program but can never reach the raw AST nodes inside it, so an embedder's parse cache cannot leak *LVal pointers between environments by construction. The class of bug is eliminated at compile time (there is no accessor to misuse) and at zero runtime cost (Program is a slice header behind a struct; sealing copies nothing).
Producers live where the parse happens so raw-slice custody never leaves this package: ReadProgram, ReadLocationProgram, and (*LEnv).ParseProgram. The consumer is (*LEnv).LoadProgram / LoadProgramContext, which evaluates the sealed expressions exactly as (*LEnv).Load evaluates a Reader's output. There is no exported exit: the in-kernel detach machinery (lisp/detach.go) can produce hermetic deep copies of the sealed expressions, but it stays unexported until a real embedder consumer materializes. A reflection test (program_seal_test.go) guards the surface: no exported method may expose *LVal.
Scope of the guarantee: Program seals the parse/cache boundary in both directions. Outward, it stops raw AST nodes from ESCAPING to embedders (the compile-time seal above). Inward, every constructor establishes the hermetic seal (lisp/seal.go) on the expressions it admits — reader output that is not already sealed throughout is privately copied and sealed, and output the seal cannot cover is rejected (see newProgram, issue #394) — so the sharing a parse cache does is always the sanctioned kind: sealed nodes are frozen storage under the seal's write protection, and evaluating one Program from many environments cannot corrupt it for the others. Concurrency is unchanged by any of this: a Runtime serves one goroutine, so concurrent evaluation still means one environment per goroutine, all of them free to share the sealed Program.
The zero Program is valid, empty, and evaluates to nil.
func ReadLocationProgram ¶ added in v1.52.0
ReadLocationProgram is ReadProgram for a LocationReader, assigning physical location loc to the parsed tokens.
func ReadProgram ¶ added in v1.52.0
ReadProgram parses the contents of r using reader and seals the result as a Program. The parsed expression slice never leaves this package: it goes from the reader's return value through newProgram's seal admission into the sealed Program. Reader output that is not already hermetically sealed (a format-preserving parser, a caller-written Reader) is privately copied and sealed; output the seal cannot protect — reference types, function values — is rejected with an error. See newProgram.
type Reader ¶
type Reader interface {
// Read the contents of r and return the sequence of LVals that it
// contains. The returned LVals should be executed as if inside a progn.
Read(name string, r io.Reader) ([]*LVal, error)
}
Reader abstracts a parser implementation so that it may be implemented in a separate package as an optional/swappable component.
type ReaderIdentity ¶ added in v1.54.0
type ReaderIdentity interface {
// ReaderIdentity returns a stable token that differs between readers whose
// parse output differs. Two readers returning the same token are treated
// as interchangeable producers by the cache.
//
// The EMPTY string is not a valid token: it states nothing, and two
// readers returning it would be declared interchangeable. A reader that
// returns it disables the cache for its own loads (they parse every time)
// rather than risking a wrong-program serve.
// A panic also disables caching for that load and is reported best-effort
// to Stderr. Reentrant loads on the same Runtime bypass identity and cache
// hooks. Checked-build ownership violations remain hard failures.
ReaderIdentity() string
}
ReaderIdentity is an optional interface a Runtime.Reader may implement to tell the load cache which parses it produces.
The cache key binds every entry to the reader that produced it, not just to the source bytes (see loadCacheKey): two readers that parse the same bytes into DIFFERENT trees must never serve each other's entries. By default that binding is the reader's fully-qualified Go type, which is right for elps's own readers — the standard parser and the format-preserving parser are distinct types — and stable across instances, so many Runtimes each holding their own reader of the same type still share cache entries (the motivating warm-cache topology).
A reader whose parse output depends on configuration NOT reflected in its Go type — the same struct in two modes — is indistinguishable by type alone. Such a reader implements ReaderIdentity to return a token that differs whenever its parse would; the cache folds that token into the key instead of the type. Implementing it is optional: the default is safe without any embedder cooperation, and this is only for readers that multiplex parse behaviours behind one Go type.
type RelativeFileSystemLibrary ¶
type RelativeFileSystemLibrary struct {
// RootDir, when non-empty, confines all file access to this directory
// tree. Any attempt to load a file outside RootDir (including via ..
// path components) will return an error. When empty, no confinement
// is applied and the existing behavior is preserved.
RootDir string
}
RelativeFileSystemLibrary implements SourceLibrary and reads lisp source files from the filesystem, relative to the source context location.
In order to read filepaths relative to a source file's location the application's implementation of Runtime.Reader must implement LocationReader.
func (*RelativeFileSystemLibrary) LoadSource ¶
func (lib *RelativeFileSystemLibrary) LoadSource(ctx SourceContext, loc string) (string, string, []byte, error)
LoadSource attempts to open loc as a filepath.
type Runtime ¶
type Runtime struct {
Stderr io.Writer
Reader Reader
Library SourceLibrary
Profiler Profiler
Debugger Debugger // nil = disabled (zero overhead on hot path)
LoadCache LoadCache // nil = disabled (the load path is then byte-identical to having no hook); see lisp/loadcache.go
Registry *PackageRegistry
Package *Package
Stack *CallStack
MaxValueDepth int // Optional limit for iterative value walks (zero uses MaxValueDepth).
MaxAlloc int // Per-operation allocation size cap (0 = use default). Not cumulative.
MaxMacroExpansionDepth int // Maximum macro expansion iterations (0 = use default).
MaxEvalNesting int // Evaluator recursion depth cap (0 = use default, negative = disabled).
MaxSleep time.Duration // Hard ceiling on a single host sleep, libtime.BuiltinSleep (0 or negative = none). See MaxSleepCeiling.
LegacyKeywordFormals bool // Accept keyword parameter names with v1.61 semantics; see WithLegacyKeywordFormals.
// contains filtered or unexported fields
}
Runtime is an object underlying a family of tree of LEnv values. It is responsible for holding shared environment state, generating identifiers, and writing debugging output to a stream (typically os.Stderr).
Step Limits: Runtime supports optional instruction counting via MaxSteps. Context cancellation is handled per-evaluation via LEnv.evalCtx, which is set by the *Context methods on LEnv (e.g. EvalContext) or the WithContext Config option. When neither context nor step limits are configured, limit checks are two nil/zero comparisons with negligible overhead.
Concurrency: Runtime and its associated LEnv tree are NOT safe for concurrent use from multiple goroutines. All calls to Eval, Load, and any other methods that read or mutate Runtime or LEnv state must be serialized by the caller. To evaluate ELPS code concurrently, create a separate Runtime (and LEnv tree) per goroutine.
The only thread-safe operations are GenEnvID and GenSym, which use atomic counters internally. All other fields — including Registry, Package, Stack, conditionStack, and the LEnv Scope maps — are unprotected.
Field order is layout-sensitive: pointer-bearing fields lead so the GC scan extent stops at 160 bytes. Add scalars below conditionStack.
func StandardRuntime ¶
func StandardRuntime() *Runtime
StandardRuntime returns a new Runtime with an empty package registry and Stderr set to os.Stderr.
func (*Runtime) BeginLoad ¶ added in v1.93.0
func (r *Runtime) BeginLoad() func()
BeginLoad starts the program's load for a tool that reads the program's forms itself and evaluates them one at a time with Eval, such as a REPL: Loading reports true until the tool calls the function BeginLoad returns. Unlike a Load* call, it keeps each form's own step budget and the package a form switches to. An embedder must not call it around a transaction (luthersystems/elps#908). Like the rest of the runtime, the load depth is not synchronized: call BeginLoad and the returned function on the goroutine that evaluates the forms, while no other goroutine runs a load on the runtime. Calling the returned function again does nothing.
func (*Runtime) CheckAlloc ¶ added in v1.20.0
CheckAlloc returns a non-empty error message if n exceeds the per-operation allocation size cap. This is a point-in-time check for a single operation, not a cumulative memory tracker. Callers should use this before allocating buffers or sequences whose size is determined by user input.
func (*Runtime) ConditionIsA ¶ added in v1.78.0
ConditionIsA reports whether condition c is ancestor or a descendant of it in this runtime's hierarchy: elps's built-in parents and the ones DefineCondition added. It is the runtime's errors.Is. Like the package function ConditionIsA, it treats the catch-all condition as a name, not as the root: ConditionIsA(c, "condition") is false unless c is "condition".
func (*Runtime) ConditionParent ¶ added in v1.78.0
ConditionParent returns the parent of condition c in this runtime: its built-in parent (argument-error's is error), else the one DefineCondition gave it, else "".
func (*Runtime) CurrentCondition ¶ added in v1.16.13
CurrentCondition returns the condition currently being handled, or nil.
func (*Runtime) DefineCondition ¶ added in v1.78.0
DefineCondition makes parent the parent of condition child in this runtime (luthersystems/elps#831), so a handler-bind clause for parent, or for any ancestor of parent, catches child. Lisp code calls it through define-condition.
A condition has at most one parent, and gets it before its children: define a hierarchy from the root down. Defining the parent a condition already has does nothing. DefineCondition returns an error, and changes nothing, when:
- child or parent is empty, or child equals parent;
- child or parent is condition (the catch-all root) or internal-panic;
- child already has a different parent, built in (argument-error) or defined;
- child already has children (error has argument-error), which also rules out a cycle;
- child would have more than MaxConditionDepth ancestors.
The table belongs to the runtime. A template publishes a copy of it, and every VM forked from the template reads that copy until its first definition copies it. A definition in one VM is seen by no other VM, by the template, or by the source. A definition never changes how an error renders: an error whose condition has a defined parent renders with its condition name, as before.
func (*Runtime) DeleteSettingValue ¶ added in v1.74.0
DeleteSettingValue removes the value setting called name. Removing an unset name does nothing. A delete in one VM is seen by no other VM, by the template, or by the source.
func (*Runtime) DeleteVMValue ¶ added in v1.84.0
DeleteVMValue removes the VM value called name. Removing an unset name does nothing. A delete in one VM is seen by no other VM, by the template, or by the source.
func (*Runtime) EvalNesting ¶ added in v1.49.0
EvalNesting reports how deeply LEnv.eval is currently recursed into itself. It is zero outside of an evaluation. See DefaultMaxEvalNesting for what the quantity measures and why it is bounded separately from stack height.
func (*Runtime) GenSym ¶
GenSym returns the next genNNNNNNNN name from this runtime's counter. Go macros should use NewGenSyms for deterministic temporary bindings.
func (*Runtime) Loading ¶ added in v1.93.0
Loading reports whether the program's load is in progress: whether code runs inside one of the Go entry points Load, LoadFile, LoadString, LoadLocation, LoadProgram or their Context forms. A function called during the load runs inside it; one called after the load returns, such as a call through Eval or FunCall, does not. The Lisp functions load-file, load-string and load-bytes do not start a load: the code they load runs in their caller's state. So an embedder runs its program with Load* and each later call, such as a transaction, with Eval or FunCall. A template VM starts with no load in progress.
func (*Runtime) MaxAllocBytes ¶ added in v1.16.14
MaxAllocBytes returns the effective per-operation allocation size cap. Allocation-aware builtins check newly built buffer/sequence/map size against this limit independently — it is NOT a cumulative memory tracker. If MaxAlloc is non-positive, DefaultMaxAlloc is returned.
func (*Runtime) MaxEvalNestingDepth ¶ added in v1.49.0
MaxEvalNestingDepth returns the effective evaluator recursion-depth cap. Zero means "use DefaultMaxEvalNesting"; a negative value disables the check and is reported as zero.
func (*Runtime) MaxMacroExpansions ¶ added in v1.20.0
MaxMacroExpansions returns the effective maximum macro expansion depth. If MaxMacroExpansionDepth is zero, DefaultMaxMacroExpansionDepth is returned.
func (*Runtime) MaxSleepCeiling ¶ added in v1.49.0
MaxSleepCeiling returns the hard upper bound on a single sleep, or 0 when the embedder has set none. A negative Runtime.MaxSleep disables the bound explicitly and is also reported as 0.
This is the ceiling on what (time:sleep d :max m) may request, NOT the cap applied when :max is absent — that is DefaultMaxSleep. See its doc comment for why the two are separate.
func (*Runtime) PopCondition ¶ added in v1.16.13
PopCondition removes and returns the top condition from the stack.
func (*Runtime) PushCondition ¶ added in v1.16.13
PushCondition pushes an error onto the condition stack, making it available to rethrow within a handler-bind handler.
func (*Runtime) ResetStepBudget ¶ added in v1.70.0
func (r *Runtime) ResetStepBudget()
ResetStepBudget zeroes the usage of the shared step budget, leaving the budget itself in place. It does not affect Steps or TotalSteps.
func (*Runtime) ResetSteps ¶ added in v1.25.0
func (r *Runtime) ResetSteps()
ResetSteps resets the current evaluation's step counter to zero. It does not affect TotalSteps.
func (*Runtime) SetSetting ¶ added in v1.65.0
SetSetting sets a named per-VM library flag (see Setting).
func (*Runtime) SetSettingValue ¶ added in v1.74.0
SetSettingValue sets the value setting called name to v. Value settings are per-VM interpreter state, like Setting, in a separate namespace. A template publishes the source's value settings and every VM it mints starts from them. A write in one VM is seen by no other VM, by the template, or by the source. A cold runtime has no value settings.
VMs share stored values without copying them, so v must be immutable. SetSettingValue accepts the payloads that template publication shares without a host policy: a value of a Go scalar kind (bool, string, or an integer, float or complex kind, named types included), or a struct value that embeds the repository's internal immutability marker. It also accepts exactly []byte and stores a private copy. Any other type, including nil, a pointer, and a payload that only TemplateWithNativePolicy admits, returns an error and changes nothing.
A VM reads the template's value map until its first write copies it, so Template.NewVM does no per-VM work for value settings.
func (*Runtime) SetStepBudget ¶ added in v1.70.0
SetStepBudget installs a step budget shared by every top-level evaluation on this Runtime until it is changed or cleared, and resets its usage to zero. Unlike WithMaxSteps, which bounds each top-level evaluation (Eval, EvalContext, Load, ...) separately and refills on the next one, the shared budget does not refill between evaluations: a host that runs several top-level evaluations for one unit of work (a transaction, a batch of requests) bounds their total. n <= 0 clears the budget (unlimited, the default).
Steps are counted exactly as for WithMaxSteps -- the evaluator's own steps plus every LEnv.ChargeSteps charge -- so the count for a given program is deterministic and identical on a cold environment and on a VM from Template.NewVM. When the budget is exceeded the evaluation fails with the CondStepBudgetExceeded condition, and every later step or charge fails the same way until the budget is reset or changed. When both limits are set and one step exceeds both, CondStepLimitExceeded is reported.
The budget is per Runtime and is not published by NewTemplate; give a VM its own with VMWithStepBudget or by calling SetStepBudget on its Runtime.
func (*Runtime) SetVMValue ¶ added in v1.84.0
SetVMValue sets the VM value called name to v. A nil v stores nil, the empty list. VM values are per-VM interpreter state that holds Lisp values of any type: functions, closures and sorted maps included. Lisp code cannot reach them by a symbol. Only Go code that the embedder provides can read or write them.
NewTemplate publishes the source's VM values with the same admission and copy rules as package global values: a value that a template cannot publish as a global makes NewTemplate fail. Every VM that Template.NewVM mints starts with its own copy of each published value. A write, a delete or an in-place change (assoc! on a sorted map) in one VM is seen by no other VM, by the template, or by the source. A cold runtime has no VM values.
SetVMValue stores v itself, not a copy, as Package.Put does.
func (*Runtime) Setting ¶ added in v1.65.0
Setting reports the value of a named per-VM library flag and whether it has been set. Settings are interpreter state of this Runtime: a template publishes the source's settings and every VM it mints starts from a private copy, so SetSetting in one VM is invisible to every other. They let a library keep Lisp-controlled modes without writing a package, which may be frozen (TemplateWithFrozenPackages). SettingValue holds values of other types under the same rules.
func (*Runtime) SettingValue ¶ added in v1.74.0
SettingValue reports the value setting called name and whether it is set. A byte setting returns a fresh []byte on every call. Every other value keeps the dynamic type it was stored with. See SetSettingValue for the allowed types and the template rules.
func (*Runtime) StepBudget ¶ added in v1.70.0
StepBudget returns the shared step budget (0 = unlimited) and the steps charged against it since it was last set or reset. Usage saturates at math.MaxInt64.
func (*Runtime) Steps ¶ added in v1.25.0
Steps returns the number of steps consumed by the current top-level evaluation — or, if no evaluation is in progress, by the most recent one. Each call to Eval, tail-recursion iteration, macro re-expansion, dotimes turn, and value-passing callback, error handler or threading step (callValueFunction) increments the counter by one. Counting dotimes turns matters because an empty body evaluates nothing and would otherwise consume no budget at all -- see opDoTimes, which also records the measured per-turn cost. A native builtin adds to it with LEnv.ChargeSteps. Steps are counted only while a step limit or an evaluation context is configured; otherwise the counter stays at zero.
The counter is reset when a new top-level evaluation begins (see WithMaxSteps), so it is not a lifetime total. Use TotalSteps for that.
func (*Runtime) TotalSteps ¶ added in v1.49.0
TotalSteps returns the number of steps consumed over the lifetime of the Runtime, across every top-level evaluation. Unlike Steps it is not reset when a new evaluation begins.
Both counters saturate at math.MaxInt64 rather than wrapping; see LEnv.ChargeSteps, which can advance them by arbitrary amounts.
func (*Runtime) VMValue ¶ added in v1.84.0
VMValue returns the VM value called name and whether it is set. An unset name returns nil, the empty list, and false, as MapData.Get does. See SetVMValue. In a lazy template VM the first read of a published value builds this VM's copy, so the single-goroutine rule of Template.NewVM applies.
func (*Runtime) ValueDepthLimit ¶ added in v1.62.0
ValueDepthLimit returns the effective iterative value-walk limit. A nil runtime or unset/invalid field uses MaxValueDepth. WithMaxValueDepth rejects invalid settings; a valid override may exceed the default.
type RuntimeBound ¶ added in v1.57.0
type RuntimeBound interface {
BoundRuntime() *Runtime
}
RuntimeBound declares a native payload's runtime affinity. Checked builds (-tags elpscheck) reject its use from a different Runtime. BoundRuntime returns nil for an unbound value; that disables affinity checks, not template admission.
Checks run at three boundaries:
- Use: LNative values crossing Put, PutGlobal and evaluation are checked. This is shallow; values nested in containers are not traversed here.
- Template instantiation: all admitted native payloads are checked against the fresh Runtime. Publication admits only immutable natives; a bound service must instead be created separately in each VM.
- Strict detach: NativeCloner results must be unbound because the clone method does not know the destination Runtime. A within-VM copy may remain bound to its own Runtime.
RuntimeBound is diagnostic, not an immutability declaration. Neither this interface nor NativeCloner makes mutable payloads admissible in NewTemplate. Shared immutable declarations remain trusted host contracts; falsely declaring a runtime-bound handle immutable is a contract violation, checked here when that handle implements RuntimeBound.
type SealPolicy ¶ added in v1.95.0
type SealPolicy func(env *LEnv, v SealViolation) *LVal
SealPolicy decides a Lisp write to a sealed package. elps calls it before the write, so the package still holds its old binding (a host can tell an added name from a rebound one by looking it up). A nil result, or any result that is not an error, lets the write go ahead; an error result is returned to the Lisp caller in place of the write, which does not happen.
One policy serves every VM a template makes from the sealed package, and may run on several goroutines at once, so it must not write state of its own: per-VM state belongs in env.Runtime (SetVMValue, for example). It runs inside the write and charges no step.
type SealViolation ¶ added in v1.95.0
type SealViolation struct {
// Package is the sealed package's name.
Package string
// Symbol is the unqualified name the write changes.
Symbol string
// Write is the kind of write.
Write SealWrite
}
SealViolation describes one Lisp write to a sealed package.
type SealWrite ¶ added in v1.95.0
type SealWrite int
SealWrite names the kind of Lisp write that reached a sealed package.
const ( // SealBind is set, set!, defun or defmacro of a name in the package. SealBind SealWrite = iota // SealExport is an export of a name the package does not export yet. // A new export changes what every later use-package of the package // binds, even before the name has a value. SealExport // SealUse is a use-package into the package: it binds each export of // the used package in the sealed one. Symbol is that export. SealUse )
type SeqKind ¶ added in v1.83.0
type SeqKind uint8
SeqKind is a sequence type specifier for the Go forms of map, concat, zip, slice and append (LEnv.MapSeq, Concat, Zip, Slice and Append). SeqList, SeqVector, SeqString and SeqBytes stand for the symbols list, vector, string and bytes. SeqNil stands for the type specifier ().
A form given a kind its builtin refuses returns the error the builtin returns for the matching type specifier, at the same point in its checks:
- SeqNil to Concat, Zip, Slice or Append returns the builtin's first check for a type specifier that is not a symbol, "first argument is not a valid type specification: list" (concat and append) or "first argument is not a valid type specifier: list" (zip and slice).
- Any other refused kind returns the builtin's error for an unknown symbol, after the builtin's checks of the other arguments: "type specifier is invalid: K" (map, zip and append) or "type specifier is not valid: K" (concat and slice). K is the kind's String.
The zero SeqKind, and any value past the constants, is refused by every form, and K is then "SeqKind(n)".
const ( // SeqList is 'list. map, concat, zip, slice and append accept it. SeqList SeqKind = iota + 1 // SeqVector is 'vector. map, concat, zip, slice and append accept it. SeqVector // SeqString is 'string. concat and slice accept it. SeqString // SeqBytes is 'bytes. concat, slice and append accept it. SeqBytes // SeqNil is the type specifier (). map accepts it: map calls fn for // its effects and returns (). concat, zip, slice and append refuse it, // as they refuse a type specifier that is not a symbol. SeqNil )
type Shape ¶ added in v1.74.0
type Shape uint8
Shape is how a value of an LType holds other values.
const ( // ShapeLeaf holds no logical child values. ShapeLeaf Shape = iota + 1 // ShapeList holds list or quote cells. ShapeList // ShapeArray holds dimension and data lists in Cells[0] and Cells[1]. ShapeArray // ShapeMap holds map entries. ShapeMap // ShapeTagged holds a payload in Cells[0]. ShapeTagged // ShapeError holds a condition in Str and error data in Cells. ShapeError // ShapeFun is opaque to value walks. ShapeFun // ShapeNative is opaque to value walks. ShapeNative // ShapeMark holds internal evaluator cells. ShapeMark // ShapeInvalid denotes LInvalid and out-of-range type tags. ShapeInvalid )
type SingletonSnapshot ¶ added in v1.48.0
type SingletonSnapshot struct {
// contains filtered or unexported fields
}
SingletonSnapshot captures the current bit pattern of the three singleton LVals and of the shared native source Location. It is used by test infrastructure to detect inadvertent mutations to shared singletons — see TestMain in lisp_ and the per-test guard in elpstest.Runner.
func TakeSingletonSnapshot ¶ added in v1.48.0
func TakeSingletonSnapshot() SingletonSnapshot
TakeSingletonSnapshot captures the current state of the three singleton LVals so callers can later verify they were not mutated.
func (SingletonSnapshot) Verify ¶ added in v1.48.0
func (s SingletonSnapshot) Verify() string
Verify compares the current singleton state to the snapshot. If any singleton has drifted it returns the name of the offender; otherwise it returns the empty string.
Verify detects drift in VALUES. A write that stores the value a field already holds leaves no drift and Verify reports nothing, even though such a write still races with every concurrent reader — that was issue #333. Do not read an empty return as "no code mutated a singleton". See the "What this does NOT catch" section on checkSingleton in singleton_check_elpscheck.go for what covers that case.
type SourceContext ¶
type SourceContext interface {
// Name is the name of the current source stream being evaluated which
// caused the SourceLibrary LoadLocation operation. Name is like Location
// but is not necessarily tied to a physical location, or otherwise may be
// ambiguous.
//
// NOTE: Name may not be generated by the Runtime SourceLibrary due to
// functions being sourced externally (in particular, the core language and
// application standard library). As such, the Name of a SourceContext is
// meant only for informal use to assist humans and should not be relied
// upon by a SourceLibrary.
Name() string
// Location is the current source location (e.g. file path) being evaluated
// which caused the SourceLibrary LoadSource operation. This may be used
// in determining the location of relative target source locations. If
// executing code is not sourced from a lisp file then Location will return
// an empty string -- this includes LoadSource operations triggered from
// native Go functions and raw strings/[]bytes containing lisp code.
// SourceLibraries should interpret an empty Location string as the process
// working directory.
Location() string
}
SourceContext provides an execution context allowing SourceLibraries flexibility in determining how to interpret a path.
NOTE: SourceContext may be expanded with new methods yielding externally defined implementations incompatible.
func NewSourceContext ¶ added in v1.22.0
func NewSourceContext(name, loc string) SourceContext
NewSourceContext creates a SourceContext with the given name and location. This is useful for callers that need to construct a SourceContext for SourceLibrary.LoadSource calls outside of normal evaluation flow (e.g., DAP source request handlers).
type SourceLibrary ¶
type SourceLibrary interface {
// LoadSource returns the data contained in the source file specified by a
// location string obtained through a user call. For example, the call
// `(load-file "foo.txt")` would pass loc "foo.txt" to LoadSource).
// LoadSource also receives a SourceContext object which may be used to
// determine the physical path to the target location (e.g. what file is
// loading "foo.txt"?).
//
// LoadSource returns four values: a name and true-location unambiguously
// identifying the file, the file data, and any error that occurred while
// retrieving data. An interpreter must use trueloc as an identifier for
// the requested source file anywhere the SourceContext ctx is unavailable.
// The exported interface signature preserves embedder compatibility.
LoadSource(ctx SourceContext, loc string) (name, trueloc string, data []byte, err error)
}
SourceLibrary is responsible for loading source code from a given path. It is up to the SourceLibrary implementation how a source location should be interpreted. Depending on the application a SourceLibrary implementation may require a Runtime with Reader that implements LocationReader -- though the LocationReader implementation should not need to depend on the LocationReader implementation beyond that.
type StringKeyRanger ¶ added in v1.61.0
StringKeyRanger is an optional interface a Map implementation can provide when its Entries emits every key as an unquoted LString, never a symbol. RangeStringKeys calls fn exactly once per entry, in unspecified order, with the key string Entries would emit and the value exactly as Entries would emit it, and must not mutate the map while doing so. It returns nil, or the failure Entries would have reported; every caller discards a partial walk on error. Len is used only as a size hint.
A map whose Entries can emit a symbol key must not implement this: the callers store every key as a string, which is what Set does with an LString key, so a symbol flag the entries path would have recorded through Set(LSymbol) would be lost.
Fork and the copy behind assoc, dissoc and LVal.Copy use it to build the stock sorted-map copy of such a map without first boxing every entry into a pair list: the copy is the same stock map the Entries path produces for it, so nothing observable changes -- only the sort, the pair cells and the incremental growth go. Decoded JSON maps, which json:load returns, implement it.
type TailIterationLimitError ¶ added in v1.49.0
type TailIterationLimitError struct {
Iterations int
}
TailIterationLimitError reports that a single tail-recursive loop performed more iterations than the runtime allows. It is a runaway-loop backstop, not a stack-depth problem: tail calls run in constant stack space.
func (*TailIterationLimitError) Error ¶ added in v1.49.0
func (e *TailIterationLimitError) Error() string
type Template ¶ added in v1.61.0
type Template struct {
// contains filtered or unexported fields
}
Template is a privately owned construction plan for a loaded environment. Construct it with NewTemplate and instantiate independent VMs with NewVM. Mutable storage is copied once per backing object, including overlapping list and byte views. Transitively sealed program structure remains shared.
The source must be quiescent during NewTemplate: no active evaluation and no concurrent mutation of its reachable state (see NewTemplate). Afterwards its mutable values may change without changing the plan. Environment, runtime and package state is represented by owned descriptors, not source objects. Concurrent NewVM calls are supported.
Sharing immutable Go objects can retain their enclosing backing allocations, including inaccessible elements outside a slice's capacity. Thus Template does not guarantee garbage collection of the source VM when host-provided shared storage or services indirectly retain it (see the package documentation).
As with Program, embedders must not write exported fields of sealed values or function formals/body vectors that contain shared sealed code. Reader, SourceLibrary, LoadCache and Stderr retain their shared-service contracts. Approved Go callbacks and native implementations remain trusted code: arbitrary hidden Go references cannot be discovered or isolated by the interpreter.
Example ¶
package main
import (
"fmt"
"github.com/luthersystems/elps/lisp"
)
func main() {
source := lisp.NewEnv(nil)
for _, result := range []*lisp.LVal{
source.DefinePackage(lisp.Symbol("example")),
source.InPackage(lisp.Symbol("example")),
source.PutGlobal(lisp.Symbol("counter"), lisp.Int(1)),
} {
if result.Type == lisp.LError {
panic(result)
}
}
// Loading is complete. Nothing may evaluate or mutate source during this call.
plan, err := lisp.NewTemplate(source)
if err != nil {
panic(err)
}
first, err := plan.NewVM()
if err != nil {
panic(err)
}
second, err := plan.NewVM()
if err != nil {
panic(err)
}
if result := first.PutGlobal(lisp.Symbol("counter"), lisp.Int(2)); result.Type == lisp.LError {
panic(result)
}
fmt.Println(first.Get(lisp.Symbol("counter")).Int)
fmt.Println(second.Get(lisp.Symbol("counter")).Int)
fmt.Println(source.Get(lisp.Symbol("counter")).Int)
}
Output: 2 1 1
func NewTemplate ¶ added in v1.61.0
func NewTemplate(env *LEnv, opts ...TemplateOption) (*Template, error)
NewTemplate validates env and takes a private snapshot of its mutable state. It rejects partially sealed trees, unknown value types, and opaque Go state without an explicit immutability contract. In particular, a sealed ancestor cannot conceal a mutable descendant (issue #621). Retained CallStack or *CallStack payloads, including typed nil, are rejected under every value type and cannot be approved by a sharing policy. The live Runtime.Stack.GoStack must be nil, and no value may retain its byte-slice header, even while nil. Stack-free saved errors and errors generated by a VM after construction remain supported.
The source must remain quiescent for this entire call: no evaluation in progress, no active call frames or condition handlers, and no concurrent mutation of its reachable state by another goroutine or host callback. The caller provides this exclusion. Rejecting recorded active evaluation state does not lock the source or synchronize with concurrent writers.
Storage discovery uses numeric address arithmetic only at construction to identify overlapping backing ranges. It assumes Go's non-moving heap while retained source slices remain live. Numeric addresses are never converted back to pointers or dereferenced; memory is accessed through ordinary slices. The result stores ordinary Go slices and integer offsets, not source addresses.
Initialization must itself be suitable for replay: transaction context, time, randomness and external effects must be bound per VM when cold loads do so. Iterative admission is bounded by the source Runtime.ValueDepthLimit, including closure environments, visible cells and hidden capacity tails.
func (*Template) NewVM ¶ added in v1.61.0
NewVM constructs an independent VM from the template. Mutable values use independent allocations, so retaining one returned scalar or byte view does not retain unrelated VM storage. Context and stderr may be set per instance.
Unless the template was published with TemplateWithEagerInstantiation, the VM is built lazily: package bindings, sorted-map entries and what they reach are created on first use, once per VM. A VM must therefore be used by one goroutine at a time, including reads such as symbol lookups and map gets made by the host; hand it between goroutines only with a happens-before edge (a channel send, a mutex). Checked builds (-tags elpscheck) panic when two goroutines materialize in one VM concurrently. Any number of goroutines may call NewVM on one Template concurrently. A retained sorted map or package can keep the whole VM alive, as a retained function or environment always has, until each of its entries has been read or written. After that, it keeps alive only what its values refer to. A function among them still keeps the whole VM alive.
type TemplateOption ¶ added in v1.61.0
type TemplateOption func(*templateConfig)
TemplateOption configures admission of a loaded environment.
func TemplateWithBuiltinPolicy ¶ added in v1.61.0
func TemplateWithBuiltinPolicy(approve func(*LVal) bool) TemplateOption
TemplateWithBuiltinPolicy explicitly approves legacy Go builtin code for sharing. The predicate must attest that a callback captures no mutable VM data: it uses only immutable configuration, concurrency-safe host services, and the environment/arguments of its call. Shared services must not carry VM-local or request-bound state; callbacks obtain that state from each call's environment. ELPS's internal captured builtins expose VM state explicitly and need no such approval. Embedders attach mutable host state to each instance's context. A package name or a reflect function address is not evidence of this property. ELPS cannot inspect a Go closure or method receiver. Returning true for every builtin trusts every installed callback; it does not verify their captures. Embedders must audit their registrations and run ownership checks on host code. The policy runs during publication, not during each NewVM call.
func TemplateWithEagerInstantiation ¶ added in v1.65.0
func TemplateWithEagerInstantiation() TemplateOption
TemplateWithEagerInstantiation makes every NewVM build the template's whole value graph up front, as releases before lazy instantiation did. By default a VM materializes each package binding, sorted-map entry and everything they reach on first use, which is observably identical but costs only what a VM touches. Eager instantiation is the escape hatch for a host that reads one VM from several goroutines at once (lazy materialization turns reads into writes) or that wants every allocation paid at NewVM.
func TemplateWithFrozenPackages ¶ added in v1.65.0
func TemplateWithFrozenPackages(names ...string) TemplateOption
TemplateWithFrozenPackages freezes the named packages at publication: each is shared until its first write, and a write thaws a private copy for that VM. A frozen package's symbol, function-name, documentation and export tables are built once and shared by every VM the template mints, so NewVM does not copy them; each VM gets only its own values for the package's bindings. Rebinding a name the package already binds -- set, set! or defun of an existing global, including to a new function or closure -- writes only that VM's value for it and does not thaw; the function-name entry such a write records is kept per VM too. Any other write in a VM -- a new name, export, use-package into it, a docstring, or another Go Package mutator -- copies that one package's tables into the VM and then applies the write exactly as it would in an unfrozen package. Other packages, other VMs and the template are unaffected, so every program behaves as it does without the option. Values bound in a frozen package remain per-VM copies with the usual template semantics, and mutating a value never thaws its package. Reads, lookups and iteration are unchanged. Naming a package the source does not register makes NewTemplate fail. Options accumulate.
func TemplateWithNativePolicy ¶ added in v1.61.0
func TemplateWithNativePolicy(approve func(any) bool) TemplateOption
TemplateWithNativePolicy explicitly approves foreign immutable native payloads provided by the embedder. It is an assertion of transitive immutability, not a request to clone mutable data. A NativeCloner declaration does not make a payload admissible, and no clone callback is invoked. Mutable VM references belong in explicit builtin captures; host services are attached separately to each instance. Known diagnostic payloads (CallStack, *CallStack, and byte-header aliases into the live runtime stack) are always rejected, before this policy is consulted.
func TemplateWithThawHook ¶ added in v1.65.0
func TemplateWithThawHook(fn func(pkg string)) TemplateOption
TemplateWithThawHook installs fn to observe copy-on-write thaws. fn is called with the package name each time a VM minted by the template copies a package's shared tables into itself: the first write to a frozen package that is not a rebinding of an existing name (a new name, export, use-package, docstring), or the first such write to a package a lazy plan had not yet given private tables. It runs at most once per package per VM, synchronously on the VM's goroutine, before the write is applied; fn must be safe to call from every VM concurrently and must not evaluate in the VM. Unset, the thaw path costs one nil check.
type Text ¶ added in v1.77.0
type Text []byte
Text is the bytes of an argument that is a string or bytes. As a Func*E argument type it accepts either: a string is copied into a new slice (one allocation), and bytes are the argument's own storage, so treat them as read-only. Its message is "<what> is not a string or bytes: <type>".
type VMOption ¶ added in v1.61.0
type VMOption func(*vmConfig)
VMOption configures one VM constructed by Template.NewVM.
func VMWithContext ¶ added in v1.61.0
VMWithContext binds the new VM's evaluation context. The source context is never inherited. Per-call LEnv context methods remain available afterwards.
func VMWithPrewarm ¶ added in v1.65.0
func VMWithPrewarm() VMOption
VMWithPrewarm builds, during NewVM, the template values that at least half of the earlier VMs of the same template read, instead of on first use. A host that creates VMs ahead of demand (a pool filled by a background goroutine) uses it to move that work off the request path. The set is learned from use and follows it: a value read by few VMs is built on first use by the VMs that read it, so a VM does not build values it is not likely to read. The first 64 VMs of a template, and then about one VM in 16, chosen by a hash of the VM's number, count what they read; a VM that reads no template value does not count. Recent VMs weigh more: a VM's count halves for each 512 counting VMs after it, so when the VMs start to read other values, the set changes within about 512 counting VMs. Results are identical with or without it; it has no effect on a TemplateWithEagerInstantiation template.
func VMWithStderr ¶ added in v1.61.0
VMWithStderr overrides the new VM's diagnostic writer. Without an override, VMs share the publication-time writer, which must support concurrent use if multiple VMs write to it.
func VMWithStepBudget ¶ added in v1.70.0
VMWithStepBudget installs a shared step budget of n steps on the new VM's Runtime, as Runtime.SetStepBudget does: every top-level evaluation on the VM draws from it, and exhausting it raises CondStepBudgetExceeded. A template never carries a budget from its source runtime. n <= 0 leaves the VM unlimited.
type ValueDepthError ¶ added in v1.62.0
type ValueDepthError int
ValueDepthError reports an exhausted value walk. It is an ordinary error; Lisp builtins wrap it as an ELPS condition. Its value is the limit.
func (ValueDepthError) Error ¶ added in v1.62.0
func (e ValueDepthError) Error() string
type WalkEvent ¶ added in v1.73.0
type WalkEvent uint8
WalkEvent classifies a WalkNode.
const ( // WalkForm is a compound form in code position, after macro // expansion. Op names the special form ("" for a function call). WalkForm WalkEvent = iota // WalkRef is a symbol evaluated as a variable or function reference. WalkRef // WalkSet is a symbol assigned by set!. WalkSet // WalkBind is a name a binding form introduces into the current scope. WalkBind // WalkDefine is the global name a defun or defmacro defines. WalkDefine // WalkLiteral is a self-evaluating value in code position: a number, // string, keyword, (), or a value a macro spliced in. WalkLiteral // WalkData is an unevaluated datum: a quoted value, a condition type, // a test name. The walker never descends into data. WalkData // WalkEnter opens a scope. Node is the form (or function binding) // that creates it. WalkEnter // WalkLeave closes the scope the matching WalkEnter opened. WalkLeave )
Events a CodeWalker reports to its visitor.
type WalkNode ¶ added in v1.73.0
type WalkNode struct {
// Node is the value the event is about. For WalkForm it is the form
// after expansion; the walker never modifies it.
Node *LVal
// Op is the builtin form name for WalkForm, WalkDefine,
// WalkEnter, WalkLeave and WalkBind ("lambda", "let", ...), "" for a function call.
Op string
// Depth is the nesting depth, 0 for the walked form. Everything
// inside a form is deeper than the form, and everything inside a scope
// deeper than its WalkEnter, so a visitor can keep its own context
// stack by popping entries at least as deep as each new event.
Depth int
// Head marks a WalkRef that is the head of a function call.
Head bool
// Bound marks a WalkRef or WalkSet whose symbol is lexically bound
// inside the walked form (by a let, lambda, flet, ...). A reference
// that is not bound is free in the walked form: a global, or a local
// of code enclosing it.
Bound bool
// Function marks a WalkEnter/WalkLeave pair around a function body:
// lambda, defun, defmacro, one flet/labels/macrolet binding, and expr.
// The body of a let, flet, dotimes, test or benchmark is a scope but
// not a function.
Function bool
// Event is the kind of event.
Event WalkEvent
}
WalkNode is one event of a code walk.
Source Files
¶
- args.go
- bindbuiltins.go
- builtinhelpers.go
- builtinregistry.go
- builtins.go
- callbuiltin.go
- captured_names.go
- capturedfun.go
- cellreader.go
- cells.go
- charge.go
- closurecheck.go
- codewalk.go
- codewalk_source.go
- codewalk_syntax.go
- condition_hierarchy.go
- conditions.go
- config.go
- copier.go
- copy.go
- cycle.go
- debugger.go
- defformals.go
- deprecated.go
- detach.go
- diagnostic.go
- doc.go
- embed.go
- env.go
- error.go
- error_wrap.go
- fmtmeta.go
- freekeywords.go
- funraw.go
- gensym.go
- help.go
- host_panic.go
- inspect.go
- jsonmap.go
- jsonraw.go
- lang.go
- lazy_guard.go
- library.go
- lisp.go
- loadcache.go
- loadcache_keymemo.go
- loadcache_map.go
- loader.go
- locals.go
- macro.go
- maphelpers.go
- mapof.go
- maps.go
- mapview.go
- match.go
- native.go
- op.go
- ownership_check_default.go
- package.go
- package_admit.go
- package_base_default.go
- package_funnames.go
- package_seal.go
- profiler.go
- program.go
- render_bounded.go
- render_trace.go
- result.go
- resultas.go
- runtime.go
- runtime_bound.go
- scope_table.go
- seal.go
- seal_check_default.go
- sealfp.go
- seqkind.go
- shape.go
- sharing.go
- singleton.go
- singleton_check_default.go
- stack.go
- structcodec.go
- template.go
- template_check_default.go
- template_lazy.go
- template_plan.go
- template_runtime.go
- template_storage.go
- testdefs.go
- typedbind.go
- typedbinde.go
- value_depth.go
- vmvalues.go
- walkers.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package lisplib is used to conveniently load the standard library for the elps environment
|
Package lisplib is used to conveniently load the standard library for the elps environment |
|
libelpspath
elpspath — positional-arg path operations
|
elpspath — positional-arg path operations |
|
libregexp
Package libregexp provides regular expression compilation and matching.
|
Package libregexp provides regular expression compilation and matching. |
|
libtime
Package libtime provides immutable wall-clock timestamps and duration operations.
|
Package libtime provides immutable wall-clock timestamps and duration operations. |
|
x
|
|
|
debugger
Package debugger implements the ELPS debugger engine (Layer 1).
|
Package debugger implements the ELPS debugger engine (Layer 1). |
|
debugger/dapserver
Package dapserver implements a DAP (Debug Adapter Protocol) server for the ELPS debugger engine.
|
Package dapserver implements a DAP (Debug Adapter Protocol) server for the ELPS debugger engine. |
|
debugger/debugrepl
Package debugrepl provides an interactive CLI debug REPL built on top of the extensible repl.RunEnv function and the debugger engine.
|
Package debugrepl provides an interactive CLI debug REPL built on top of the extensible repl.RunEnv function and the debugger engine. |