Documentation
¶
Index ¶
- Constants
- Variables
- func DeepEqual(a, b Value, seen map[comparisonPair]bool) bool
- func PrimName(op PrimOp) string
- func ShowConst(v Value) string
- func StringifyValue(value Value) string
- func StringifyValueFull(value Value) string
- type Apply
- type Builtin
- type Closure
- type Composition
- type Cons
- type ListEnding
- type PC
- type PrimOp
- type Tag
- type Thunk
- type Tuple
- type Value
- func ApplyValue(fn, arg Value, pos source.SourcePos) Value
- func BuiltinValue(b *Builtin) Value
- func ClosureValue(c *Closure) Value
- func ConsValue(h, t Value) Value
- func EvalPrim(op PrimOp, args []Value) Value
- func EvalPrim1(op PrimOp, a float64) Value
- func EvalPrim2(op PrimOp, a, b float64) Value
- func EvalStructuralBuiltin(op PrimOp, args []Value) Value
- func FoldStringValue(s string) Value
- func FullNormalForm(value Value, seen map[*Thunk]bool) Value
- func NumberValue(n float64) Value
- func StdinBytes() Value
- func StdinCodePoints() Value
- func ThunkValue(t *Thunk) Value
- func TupleValue(t *Tuple) Value
Constants ¶
const ( ShowDefaultWidth = 100 ShowDefaultDepth = 50 )
Variables ¶
var EmptyTuple = Value{Tag: TupleTag, Ref: &Tuple{}}
EmptyTuple is the shared empty tuple / empty list. It is immutable, so a single instance is reused everywhere one is needed.
var Force func(v Value) Value
Force reduces v to weak head normal form.
var InitialBuiltins = map[string]*Builtin{}
InitialBuiltins maps each builtin name to its first-class Builtin value.
var PrimArity = [primOpCount]int{ PrimAdd: 2, PrimSub: 2, PrimMul: 2, PrimDiv: 2, PrimFdiv: 2, PrimMod: 2, PrimFmod: 2, PrimPow: 2, PrimSqrt: 1, PrimEq: 2, PrimLt: 2, PrimLte: 2, PrimGte: 2, PrimGt: 2, PrimNeq: 2, PrimEqual: 2, PrimEval: 1, PrimPeek: 1, PrimShow: 1, PrimWrite: 1, PrimBwrite: 1, PrimString: 1, PrimHash: 1, PrimSeq: 2, }
PrimArity gives each PrimOp's required number of arguments, indexed by the dense enum.
var PrimNames = [primOpCount]string{ PrimAdd: "add", PrimSub: "sub", PrimMul: "mul", PrimDiv: "div", PrimFdiv: "fdiv", PrimMod: "mod", PrimFmod: "fmod", PrimPow: "pow", PrimSqrt: "sqrt", PrimEq: "eq", PrimLt: "lt", PrimLte: "lte", PrimGte: "gte", PrimGt: "gt", PrimNeq: "neq", PrimEqual: "equal", PrimEval: "eval", PrimPeek: "peek", PrimShow: "show", PrimWrite: "write", PrimBwrite: "bwrite", PrimString: "string", PrimHash: "hash", PrimSeq: "seq", }
PrimNames gives the source-level name of each PrimOp, indexed by the dense enum. Used in error messages and bytecode/IR dumps.
var RaiseBuiltinError func(message string)
RaiseBuiltinError aborts execution from inside a structural builtin, locating the error at the active builtin's application span and tracing the reduction stack that was live when the builtin was entered.
Functions ¶
func DeepEqual ¶
DeepEqual compares two values by structure, forcing them as needed. It handles cyclic structures by tracking pairs of values it has already started comparing.
func ShowConst ¶
ShowConst renders a compile-time constant Value without forcing anything. Core/bytecode constants are already fully built (numbers, prebuilt code-point lists for string literals, the empty tuple, builtins), so this walks them directly rather than routing through Force, keeping the dumps independent of the machine.
func StringifyValue ¶
func StringifyValueFull ¶
Types ¶
type Apply ¶
An Apply is a runtime-synthesized application Fn Arg. The compiled bytecode never builds one — it pushes arguments and enters the head (the spine-less model). The only place an Apply is created is reducing a composition, which must turn (First ∘ Second) x into the application First (Second x). Pos locates a "cannot apply" failure.
type Builtin ¶
A Builtin is a primitive operation as a first-class value. It carries the operation, its arity, and the arguments gathered so far; applying it appends one argument, and when the count reaches Arity the machine runs the operation. This single representation is both the saturated and the partially-applied form: partial application is just a Builtin with fewer Args than Arity (see prims.go).
type Closure ¶
A Closure is a lambda (one or more pattern cases) paired with the minimal set of free variables it captured. Frame is the largest of its cases' frame sizes, allocated once per application. NoMatch is the source span of the whole pattern set, used to locate a "no pattern matched" error.
type Composition ¶
A Composition is an unreduced function composition. Applying (First ∘ Second) to x reduces to First (Second x); see the CompositionTag case of reduce.
type Cons ¶
A Cons is a head/tail pair. Thunky draws no distinction between a 2-tuple and a list cons cell (see LANGUAGE.md §11), so every arity-2 tuple — list literals, string code points, the stdin stream, a bare [a, b] pair — is a Cons. This packs the pair into one allocation and lets list code destructure cons cells directly.
type ListEnding ¶
type ListEnding int
const ( NotAList ListEnding = iota ProperList Truncated Cyclic )
type PC ¶
type PC = int32
PC is a program counter: an index into the compiled instruction array. It lives here because Thunk and Closure store entry points; the backend re-exports it.
const NoCode PC = -1
NoCode marks a thunk that has no compiled body: a graph-style indirection thunk (a pattern binding) whose deferred computation is simply "reduce Value". A real code thunk has Code >= 0.
type Tag ¶
type Tag uint8
const ( NumberTag Tag = iota // Num holds the value; Ref is nil ConsTag // Ref = *Cons TupleTag // Ref = *Tuple (arity 0, 1, 3, 4, …; never 2) ClosureTag // Ref = *Closure BuiltinTag // Ref = *Builtin CompositionTag // Ref = *Composition ApplyTag // Ref = *Apply (a runtime-synthesized application) ThunkTag // Ref = *Thunk (not yet in weak head normal form) )
type Thunk ¶
type Thunk struct {
Forced bool
Value Value
Name string
Code PC
Locals []Value
Upvalues []Value
Read func() Value
}
A Thunk is a deferred computation that memoises its result. It comes in three forms, distinguished without a tag field:
- Read != nil — a stdin stream cell; forcing it calls Read to obtain its weak head normal form (a cons cell or the empty list) and reads one input item.
- Code == NoCode — a graph indirection (a pattern binding); forcing it reduces the value already in Value and memoises the result. This keeps the bound name on the trace/show path without the name's owner having to re-evaluate.
- Code >= 0 — a compiled thunk; forcing it runs the body at Code over the captured frames (whole-frame capture, see below).
Code thunks capture the entire enclosing activation (Locals and Upvalues) by reference and address it with the enclosing slot numbers — no renumbering, and mutual recursion in a let group resolves because every binding thunk shares the frame, which is fully populated before any binding is forced.
Every thunk memoises (call-by-need): the first force runs the computation and writes the weak-head-normal-form result back into Value (setting Forced), so it is never recomputed. This is why re-forcing a thunk that embeds an output builtin (peek/show/write/bwrite) does not repeat the effect — the effect runs exactly once, when the thunk is first forced.
type Tuple ¶
type Tuple struct {
Fields []Value
}
A Tuple is an ordered sequence of any arity except 2 (arity 0, 1, 3, 4, …); the empty tuple is also the empty list. Arity-2 tuples are Cons cells instead.
type Value ¶
A Value is one runtime value, passed around by value (not by pointer). It is a small tagged union: a Number lives inline in Num, while every compound or heap-resident value keeps a pointer in Ref. This is the deliberate alternative to a Go interface for the runtime representation:
- A number never allocates. A float64 boxed into an interface{} heap-allocates on every arithmetic result; Value{Tag: NumberTag, Num: x} does not. Numeric programs allocate the most, so this is where the representation earns its keep.
- Ref holds a *pointer* (to Cons, Tuple, …). A pointer stored in an interface does NOT allocate in Go, so compound values pay only their own heap cost, never an extra box.
- The Tag makes the variant explicit and cheap to switch on, instead of a type switch on an interface's dynamic type.
func BuiltinValue ¶
func ClosureValue ¶
func EvalPrim ¶
EvalPrim executes a saturated numeric primitive. The machine forces every operand and verifies it is a number before calling (see finishBuiltin), so the kernels read .Num directly — re-checking the tag here would be pure duplication on the hot path.
Note the argument order: the comparison and subtraction builtins are threshold-first (`sub a b` is b - a, `lt a b` is b < a), so args[1] is the left operand of the arithmetic.
func EvalPrim2 ¶
EvalPrim2 runs a binary numeric kernel on already-forced operands, where a is the first source-level argument and b the second.
func EvalStructuralBuiltin ¶
EvalStructuralBuiltin executes structural primitives when saturated.
func FoldStringValue ¶
FoldStringValue decodes a string into the runtime representation of a string: a cons list of code points ending in the empty tuple. The lowerer calls it once per literal, building a shared immutable constant.
func FullNormalForm ¶
FullNormalForm forces a value and all its sub-values. It handles cycles by tracking Thunks it has already visited.
func NumberValue ¶
func StdinBytes ¶
func StdinBytes() Value
StdinBytes returns bstdin: the standard input as a lazy list of raw byte values.
func StdinCodePoints ¶
func StdinCodePoints() Value
StdinCodePoints returns stdin: the standard input decoded as a lazy list of Unicode code points. A byte sequence that is not valid UTF-8 is a runtime error.
func ThunkValue ¶
func TupleValue ¶
func (Value) Composition ¶
func (v Value) Composition() *Composition
func (Value) IsFunction ¶
IsFunction reports whether v can be applied to an argument. Used only for display and error wording.