Documentation
¶
Index ¶
- Constants
- func GoError(v *LVal) error
- func GoFloat64(v *LVal) (float64, bool)
- func GoInt(v *LVal) (int, bool)
- func GoMap(v *LVal) (map[interface{}]interface{}, bool)
- func GoSlice(v *LVal) ([]interface{}, bool)
- func GoString(v *LVal) (string, bool)
- func GoValue(v *LVal) interface{}
- func IsInternalPanic(v *LVal) bool
- func JoinDocStrings(parts []string) string
- 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 SymbolName(v *LVal) (string, bool)
- func True(v *LVal) bool
- 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) TerminalFID(fid string) int
- func (s *CallStack) Top() *CallFrame
- type Config
- func WithContext(ctx context.Context) Config
- func WithDebugger(d Debugger) 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 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 ErrorVal
- type FSLibrary
- type FunctionInfo
- type LBuiltin
- type LBuiltinDef
- type LEnv
- 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) Context() context.Context
- func (env *LEnv) Copy() *LEnv
- func (env *LEnv) DefinePackage(name *LVal) *LVal
- func (env *LEnv) Error(msg ...interface{}) *LVal
- func (env *LEnv) ErrorAssociate(lerr *LVal) *LVal
- func (env *LEnv) ErrorCondition(condition string, v ...interface{}) *LVal
- func (env *LEnv) ErrorConditionf(condition string, format string, v ...interface{}) *LVal
- func (env *LEnv) Errorf(format string, v ...interface{}) *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) 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) Lambda(formals *LVal, body []*LVal) *LVal
- 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) LoadString(name, exprs string) *LValdeprecated
- func (env *LEnv) LoadStringContext(ctx context.Context, name, exprs string) *LVal
- func (env *LEnv) MacroCall(fun, args *LVal) *LVal
- func (env *LEnv) New(typ *LVal, args *LVal) *LVal
- func (env *LEnv) Put(k, v *LVal) *LVal
- func (env *LEnv) PutGlobal(k, v *LVal) *LVal
- func (env *LEnv) SetPackageDoc(doc string)
- func (env *LEnv) SetSymbolDoc(name, doc string)
- func (env *LEnv) SpecialOpCall(fun, args *LVal) *LVal
- func (env *LEnv) TaggedValue(typ *LVal, val *LVal) *LVal
- func (env *LEnv) Terminal(expr *LVal) *LVal
- func (env *LEnv) Update(k, v *LVal) *LVal
- func (env *LEnv) UsePackage(name *LVal) *LVal
- type LFunData
- type LFunType
- type LType
- type LVal
- func Array(dims *LVal, cells []*LVal) *LVal
- func Bool(b bool) *LVal
- func Bytes(b []byte) *LVal
- func Error(err error) *LVal
- func ErrorCondition(condition string, err error) *LVal
- func ErrorConditionf(condition string, format string, v ...interface{}) *LVal
- func Errorf(format string, v ...interface{}) *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 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 Native(v interface{}) *LVal
- func Nil() *LVal
- func QExpr(cells []*LVal) *LVal
- func QSymbol(s string) *LVal
- func Quote(v *LVal) *LVal
- func SExpr(cells []*LVal) *LVal
- func SortedMap() *LVal
- func SortedMapFromData(data *MapData) *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 Symbol(s string) *LVal
- func Value(v interface{}) *LVal
- func Vector(cells []*LVal) *LVal
- func (v *LVal) ArrayDims() *LVal
- func (v *LVal) ArrayIndex(index ...*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) Env() *LEnv
- func (v *LVal) Equal(other *LVal) *LVal
- func (v *LVal) EqualNum(other *LVal) *LVal
- func (v *LVal) FID() string
- func (v *LVal) FunData() *LFunData
- func (v *LVal) IsMacro() bool
- func (v *LVal) IsNil() bool
- func (v *LVal) IsNumeric() bool
- func (v *LVal) IsSpecialFun() bool
- func (v *LVal) IsSpecialOp() bool
- func (v *LVal) KeyArg(i int) *LVal
- func (v *LVal) Len() int
- func (v *LVal) Map() *MapData
- func (v *LVal) MapEntries() *LVal
- func (v *LVal) MapGet(k interface{}) *LVal
- func (v *LVal) MapKeys() *LVal
- func (v *LVal) MapSet(k interface{}, val *LVal) *LVal
- func (v *LVal) Package() string
- func (v *LVal) ReqArg(env *LEnv, i int) *LVal
- func (v *LVal) SetCallStack(stack *CallStack)
- func (v *LVal) String() string
- func (v *LVal) UserData() *LVal
- type Loader
- type LocationReader
- type LogicalStackOverflowError
- type MacroExpansionContext
- type MacroExpansionInfo
- type Map
- type MapData
- type Package
- type PackageRegistry
- type ParamInfo
- type ParamKind
- type PhysicalStackOverflowError
- type Profiler
- type Reader
- type RelativeFileSystemLibrary
- type Runtime
- func (r *Runtime) CheckAlloc(n int) string
- func (r *Runtime) CurrentCondition() *LVal
- func (r *Runtime) EvalNesting() int
- func (r *Runtime) GenEnvID() uint
- func (r *Runtime) GenSym() string
- 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) ResetSteps()
- func (r *Runtime) Steps() int64
- func (r *Runtime) TotalSteps() int64
- type SingletonSnapshot
- type SourceContext
- type SourceLibrary
- type SourceMeta
- type TailIterationLimitError
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" // 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 how many turns a single tail-recursive loop may take. Its unit is loop turns, so it is a knob an operator can reason about.
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. 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 CondInternalPanic = "internal-panic"
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. 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 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, 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 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 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 ¶
This section is empty.
Functions ¶
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).
func GoSlice ¶
GoSlice returns the string that v represents and the value true. If v does not represent a string GoSlice returns a false second argument
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 ¶
func GoValue(v *LVal) interface{}
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.
NOTE: These semantics may change. It's unclear what the exact need is in corner cases.
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` and `handler-bind` use, 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 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 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
Types ¶
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
}
CallStack is a function call stack.
For errors produced by env.eval's recover() 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.
Field order is layout-sensitive: the two slice headers lead so the GC scan extent stops at 32 bytes instead of 48. Keep scalars trailing.
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
func (*CallStack) Pop ¶
Pop removes the top CallFrame from the stack and returns it. If the stack is empty Pop returns nil.
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 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 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, in elements for sequences). This limits the output size of any single builtin call, not cumulative memory usage.
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.
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, and each turn of a dotimes loop. 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 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 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.Source != nil). 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.
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 ErrorVal ¶
type ErrorVal LVal
ErrorVal implements the error interface so that errors can be first class lisp objects. The error message is stored in the Str field while contextual information (e.g. call stack) can be stored in the Cells slice.
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 wil 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) 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.
type FSLibrary ¶ added in v1.16.14
FSLibrary implements SourceLibrary using an fs.FS, providing natural confinement via the fs.FS contract (which rejects ".." path components and absolute paths). Use os.DirFS(dir) to create an fs.FS rooted at a directory.
func (*FSLibrary) LoadSource ¶ added in v1.16.14
LoadSource reads loc from the embedded fs.FS. The fs.FS contract inherently prevents path traversal — paths must be unrooted slash- separated sequences without ".." elements.
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 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.
type LEnv ¶
type LEnv struct {
Loc *token.Location
Scope map[string]*LVal
FunName map[string]string
Parent *LEnv
Runtime *Runtime
ID uint
// contains filtered or unexported fields
}
LEnv is a lisp environment.
Field order is layout-sensitive: the pointer-bearing fields lead so the GC scan extent stops at 56 bytes instead of 64. Keep scalars (ID) 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) 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.
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.
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.
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) Copy ¶
Copy returns a new LEnv with a copy of env.Scope but a shared parent and stack (not quite a deep copy).
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 will result in a runtime panic.
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 the given condition type and an error message computed by rendering msg.
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 and doing so will result in a runtime panic.
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) 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) 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) 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) 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) LoadString
deprecated
func (*LEnv) LoadStringContext ¶ added in v1.25.0
LoadStringContext loads and evaluates a string with the given context.
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) 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.
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) SpecialOpCall ¶
SpecialOpCall invokes special operator fun with the argument list args.
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) Update ¶
Update updates the binding of k to v within the scope of env. Update can update either lexical or global bindings. If k is not bound by env, an enclosing LEnv, or the current package an error condition is signaled.
func (*LEnv) UsePackage ¶
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 use the LVal.Cells slice to store the following items: // [0] a symbol representing the error "condition" (class name) // [1:] error data (of any type) // // In addition, LError values store a copy of the function call stack at // the time of their creation in the LVal.Native field. // // TODO: Make the stack a first class type (or some composite type) so // that it could be inspected during a condition handler. LError // LSymbol values store a string representation of the symbol in the // LVal.Str field. LSymbol LQSymbol // TODO: Remove this... I can't believe it actually has usages // 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 An LFunData 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. // // TODO: Use a tree-based map (that is potentially stored in Cells). A // tree based map would be capable of supporting integer keys. 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 interface{}
// Source is the values originating location in source code. Programs
// should not modify the contents of Source as the reference may be shared
// by multiple LVals.
Source *token.Location
// Meta holds formatting metadata, only populated in format-preserving mode.
Meta *SourceMeta
// MacroExpansion holds debug metadata for nodes produced by macro
// expansion. Only populated when a debugger is attached — nil in
// production (zero overhead: 8-byte nil pointer).
MacroExpansion *MacroExpansionInfo
// Str used by LSymbol and LString values
Str string
// Cells used by many values as a storage space for lisp objects.
//
// TODO: Consider making Cells' type []LVal instead of []*LVal to reduce
// the burden on the allocator/gc.
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
// Quoted is a flag indicating a single level of quoting.
Quoted bool
// Spliced denotes the value as needing to be spliced into a parent value.
Spliced bool
}
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.
func Bool ¶
Bool returns an LVal with truthiness identical to b.
The returned value is a shared singleton — callers MUST NOT mutate it.
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 non-nil Stack field. The Env.Error() method is typically the preferred method for creating error LVal objects because it initializes Stack with an appropriate value.
func ErrorCondition ¶
ErrorCondition returns an LError representing err and having the given condition type. Errors store their message/data in Cells and their condition type in Str. The condition type must be a valid lisp symbol.
Errors generated during expression evaluation typically have a non-nil Stack field. The Env.Error() method is typically the preferred method for creating error LVal objects because it initializes Stack with an appropriate value.
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 v.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 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 env.Runtime.Registry.Packages[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 v.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 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 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 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 v.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 Value ¶
func Value(v interface{}) *LVal
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 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.
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) Copy ¶
Copy creates a deep copy of the receiver.
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. The one Location left shared is nativeSource's process-wide singleton -- see the comment on the copy below.
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.
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.
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) 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) 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) MapEntries ¶ added in v1.14.0
MapEntries returns a list of key-value pairs in the map. MapEntries
func (*LVal) MapGet ¶
MapGet returns the value corresponding to k in v or an LError if k is not present in v. MapGet panics if v.Type is not LSortMap.
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 ¶
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.
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) SetCallStack ¶
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, so the result is finite and the walk cannot overflow the goroutine stack and kill the process. Rendering is otherwise unchanged: an acyclic value renders in full, at any nesting depth, exactly as it always did. See lisp/cycle.go and issue #390.
type Loader ¶
func LoaderMust ¶
LoaderMust returns its first argument when err is nil. If err is nil LoaderMust panics.
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 will return an error if r produces any reference types (bytes, map, array, native, etc).
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 MacroExpansionContext ¶ added in v1.23.0
type MacroExpansionContext struct {
CallSite *token.Location // where the macro was invoked
Name string // qualified macro name (e.g. "lisp:defun")
DefSite *token.Location // macro definition location (nil for builtins)
Args []*LVal // unevaluated call-site arguments (for debugger scope)
}
MacroExpansionContext is shared by all nodes in a single macro expansion. It records the macro call site, name, definition site, and unevaluated arguments for debugger inspection.
type MacroExpansionInfo ¶ added in v1.23.0
type MacroExpansionInfo struct {
*MacroExpansionContext // shared across all nodes in one expansion
// ID distinguishes one expansion node from another. stampMacroExpansion
// assigns it from Runtime.nextMacroExpID, monotonically increasing, so no
// two nodes an expansion stamps share a value.
//
// It is NOT unique per *LVal* in the wider sense, and this comment used
// to claim it was. LVal.Copy duplicates it, and cannot do otherwise:
// Copy takes no *Runtime, so it has no counter to draw a fresh value
// from, and there is no framing in which it could -- the value's whole
// purpose is to come from the runtime that did the expanding. A copy of
// an expansion node therefore carries the ID of the node it came from.
//
// The consumer to know about is lisp/x/debugger: exprStepLocation reads
// this into StepLocation.MacroID and stepper.go steps on `loc.MacroID !=
// s.start.MacroID`, so two distinct nodes carrying one ID read to the
// stepper as one node and it does not pause between them. Copying an
// expansion node under an attached debugger is what that would take; no
// in-tree path does it today (issue #466).
ID int64
}
MacroExpansionInfo is attached to LVal nodes produced by macro expansion. It is only allocated when a debugger is attached (Runtime.Debugger != nil), so production code pays zero allocation cost.
The embedded *MacroExpansionContext describes the macro CALL and is shared by every node of one expansion, by design. This struct is the per-node half, so LVal.Copy gives a copy its own -- see MacroExpansionInfo.Copy.
func (*MacroExpansionInfo) Copy ¶ added in v1.50.0
func (i *MacroExpansionInfo) Copy() *MacroExpansionInfo
Copy returns a pointer to an independent copy of i, or nil if i is nil.
The embedded *MacroExpansionContext is deliberately NOT copied. A copy separates two OWNERS, and the context has one owner -- the macro call -- which both nodes genuinely belong to. It is documented shared across every node of an expansion, and #456 already made CallSite an object the expansion owns rather than one borrowed from a live parse tree, so there is no third party to separate it from. Copying it would separate nothing and would make the "shared across all nodes in one expansion" comment above false for copied nodes.
What IS separated is this struct, which is per node. ID rides across unchanged -- see the field comment for why it cannot do otherwise.
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
}
type MapData ¶ added in v1.14.0
type MapData struct {
Map
}
MapData is a concrete type to store in an interface as to avoid expensive runtime interface type checking
type Package ¶
type Package struct {
Name string
Doc string
// Symbols holds the package's bindings. Write it through Put or
// Update rather than assigning to the map directly: those maintain
// FunNames alongside it, and nothing on the read path repairs a
// FunNames entry that a direct assignment skipped. A function bound
// by direct assignment still works; it just renders without its name
// in stack traces.
Symbols map[string]*LVal
SymbolDocs map[string]string
// FunNames maps a function's FID to the name it was most recently
// bound under in this package. It is populated exclusively by the
// write path (see put). Reads must not write it: a *Package is
// routinely shared by pointer across goroutines. See issue #397.
FunNames map[string]string
Externals []string
}
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.
func (*Package) Exports ¶
Exports declares symbols exported by the package. The symbols are not required to be bound at the time Exports is called.
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.
type PackageRegistry ¶
type PackageRegistry struct {
Packages map[string]*Package
Lang string // A default package used by all other packages
}
PackageRegistry contains a set of packages.
func NewRegistry ¶
func NewRegistry() *PackageRegistry
NewRegistry initializes and returns a new PackageRegistry.
func (*PackageRegistry) DefinePackage ¶
func (r *PackageRegistry) DefinePackage(name string) *Package
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 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 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 {
Registry *PackageRegistry
Package *Package
Stderr io.Writer
Stack *CallStack
Reader Reader
Library SourceLibrary
Profiler Profiler
Debugger Debugger // nil = disabled (zero overhead on hot path)
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 time:sleep (0 or negative = none). See MaxSleepCeiling.
// 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 112 bytes instead of 144. 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) 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) CurrentCondition ¶ added in v1.16.13
CurrentCondition returns the condition currently being handled, or nil.
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) MaxAllocBytes ¶ added in v1.16.14
MaxAllocBytes returns the effective per-operation allocation size cap. Each builtin that allocates a buffer or sequence checks its output size against this limit independently — it is NOT a cumulative memory tracker. If MaxAlloc is zero, 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) 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) 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. Four things increment the counter by one: each call to Eval, each tail-recursion iteration, each macro re-expansion, and each turn of a dotimes loop. The last of those exists because an empty-bodied dotimes evaluates nothing and would otherwise consume no budget at all -- see opDoTimes, which also records the measured per-turn cost.
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.
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, and of the shared native source Location, 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.
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 SourceMeta ¶ added in v1.16.10
type SourceMeta struct {
TrailingComment *token.Token // inline comment on same line after this node
OriginalText string // original token text for literals (preserves escapes, numeric bases)
LeadingComments []*token.Token // comment tokens preceding this node
InnerTrailingComments []*token.Token // comments between last child and closing bracket
BlankLinesBefore int // blank lines (newline count - 1) before this node (or before its leading comments)
BlankLinesAfterComments int // blank lines between last leading comment and the expression
PrecedingSpaces int // spaces before this token on the same line (for column alignment)
BracketType rune // '(' or '[' for LSExpr nodes
NewlineBefore bool // true if at least one newline preceded this node in source
ClosingBracketNewline bool // true if closing bracket was on its own line in source
}
SourceMeta holds formatting metadata for an LVal, populated only when parsing in format-preserving mode. Nil in normal parsing — zero cost.
It is per-node MUTABLE state rather than a shared description of one: the parser writes every field below in place on nodes it has just built, and rdparser.hoistOperandComments MOVES LeadingComments off one node onto another with an append plus a `= nil`. Two LVals holding one *SourceMeta is consequently an anomaly the parser has to special-case -- the `outer.Meta == inner.Meta` guard in that function -- which is why LVal.Copy gives a copy its own; see SourceMeta.Copy.
func (*SourceMeta) Copy ¶ added in v1.50.0
func (m *SourceMeta) Copy() *SourceMeta
Copy returns a pointer to a deep copy of m, or nil if m is nil.
Deep, not shallow, at both levels the struct has:
The two comment SLICES get their own backing arrays, because hoistOperandComments appends to LeadingComments and then nils the source out. A shared header is the state that makes that move visible through both nodes.
The comment TOKENS get their own objects, because each one holds a *token.Location. #446 gave a copied node its own position; leaving these shared would leave that guarantee true only for the node itself and false one level down, which is exactly what issue #466 reports.
Nil is preserved rather than materialised into a zero SourceMeta: a nil Meta means "not parsed in format-preserving mode" and every reader in the tree branches on it (rdparser, formatter, minifier). Materialising one would turn a copy of an ordinary parse tree into a format-preserving- looking one and put an allocation on the hot path for nothing.
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
Source Files
¶
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 |
|
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. |