kernel — API reference
156/183 types documented. Of 958 public methods, 610 carry their own description, 293 are covered by their type's, and 55 have neither. An entry with no prose below its signature is undocumented in the source, not undocumented here.
CLASS Array
IMPLEMENTS Cloneable, Collection
Opaque-size, ordered, indexable collection.
The foundation collection class. Stack, Queue, Deque, LinkedList, Set, and SortedList all compose on top of Array's storage; the hash-based collections (Dictionary) keep their own backing.
Element access is by integer index in 0..length-1. Out-of-bounds reads throw IndexOutOfBoundsError; out-of-bounds writes throw the same. Mutation methods come in MOVE and COPY pairs:
- append(value) — MOVE; consumes value
- appendCopy(value) — DEEP COPY; value stays valid
- setAt(i, value) — MOVE
- setAtCopy(i, v) — DEEP COPY
- insertAt(i, v) — MOVE
- insertAtCopy(i,v) — DEEP COPY
For class-typed T, COPY routes through Cloneable.clone(); T must implement Cloneable for any *Copy method to be reachable.
Removal:
- popFirst() / popLast() — return the removed element as T; EMPTY if empty; ownership transfers to the caller.
- remove(i) — destroys the element in place.
- clear() — destroys all elements.
Inspection without removal:
- peekFirst() / peekLast() — return REFERENCE T handles into the underlying storage along with a STATUS. The handle is valid only while no mutation occurs.
Iteration:
- iterator() returns a BidirectionalIterator OF T at position 0. Multiple iterators on the same Array are independent.
Sorting and search:
- sort() — in-place; requires T IMPLEMENTS Comparable[T].
- contains(v) — linear scan; requires T IMPLEMENTS Equatable[T].
Functional transforms (map / filter / reduce) are pure-Envzn loops over the element domain and return new Arrays.
The storage shorthand T[] is the same physical layout as
Array[T]; the difference is API surface. T[] exposes only the
minimal value-array surface (subscript read + HWM write proxy,
.length, .capacity, clear(), remove(i), iterator());
Array[T] does the richer work (append / insertAt / count / sort …)
on top of that surface and maintains a public length field. The
sole developer-visible difference between int32[] and an object
T[] is that object elements must relocate with := (a move),
never a plain =.
kernel/src/Array.ev:71
Fields
int64 length
Constructors
INIT()
Default constructor
INIT(int64 presized)
Presized constructor to reduce the malloc activity
Methods
METHOD iterator() RETURNS ArrayIterator[T]
Returns a fresh ArrayIterator[T] at position 0 — the FINAL
concrete iterator class (covariant against the inherited
Iterable.iterator() shape). Each call returns an independent
iterator; multiple may exist concurrently on the same Array.
The concrete return type lets the emitter inline hasNext() /
next() on FOR/IN loops over Array; callers binding to
BidirectionalIterator OF T still typecheck via class-to-
interface upcast.
METHOD size() RETURNS int64
Number of elements currently stored. Read-only, O(1).
METHOD isEmpty() RETURNS boolean
Countable — the other half of size(). Array carries the whole
Collection contract now (size, isEmpty, iterator), and none of
the three asks anything of T: that is what lets Array join Collection
while keeping its qualifier at Cloneable-or-primitive.
METHOD clone() RETURNS Array[T]
Deep copy. Each element is cloned via T->clone(); T must
implement Cloneable when T is a class type.
For T IMPLEMENTS Inoperative (opaque only today): returns
an empty Array — Inoperative values are opaque so we can't
preserve them on clone.
MODIFY METHOD append(MOVE T value) RETURNS STATUS
Append value to the end. Per §10.2 the bare-named form
consumes the source — value is moved into the array and
the caller's source is poisoned (post-call use is a compile
error). For a deep-copy variant that leaves the caller's source
valid, use appendCopy. O(1) amortised.
EMPTY-reject (class-typed T only): an EMPTY class-handle
value is refused with FAILURE before any state change — the
collection only ever holds real owned objects, so every
subsequent peek/pop/at sees a populated slot. Per §9.3 IS VALID
is meaningful only on class handles; the check is gated via
WHEN T IMPLEMENTS Cloneable (the kernel's proxy for
"class-shaped T") and elided for primitive / struct T.
MODIFY METHOD appendCopy(T value) RETURNS STATUS
Append a deep copy of value to the end. value stays fully
valid for the caller after the call. For class-typed T, the
copy goes through T->clone(); T must implement Cloneable. For
primitive/struct T, value-copy. O(1) amortised. Returns SUCCESS,
or FAILURE if backing storage rejected the insert.
METHOD __op_index__(int64 index) RETURNS MUTABLE REFERENCE T
Reference to element at index i. Throws IndexOutOfBoundsError if i is out
of range — bounds check lives in _EvArray<char32_t>::operator[]. A
borrow-propagating ("mirror") accessor: the result mirrors the receiver's
mutability (mutable array -> MUTABLE REFERENCE, const -> read-only REFERENCE).
METHOD subscript(int64 index) RETURNS | MUTABLE REFERENCE T
Bounds-checked reference into the element at index — the lowering
target for arr[idx] syntax on Array[T] receivers, and a borrow-
propagating ("mirror") accessor: SUCCESS populates result with a
reference that mirrors the receiver's mutability (a mutable array
yields a MUTABLE REFERENCE for in-place modification, a const array a
read-only REFERENCE); out-of-bounds populates s with FAILURE. Caller
pattern: IF arr[i] THEN { use($RETURNED) } ELSE { log($!) }. The
reference is valid only while the Array is not mutated (mutation lock).
Replaces the former at / subscript / mutableAt accessor set.
MODIFY METHOD setAt(int64 index, MOVE T value) RETURNS STATUS
Replace the element at index. Per §10.2 the bare-named form
consumes the source — value is moved into the array and
the caller's source is poisoned. The previous element is
destroyed. Returns FAILURE on out-of-bounds.
MODIFY METHOD setAtCopy(int64 index, T value) RETURNS STATUS
Replace the element at index with a deep copy of value.
value stays valid for the caller. Throws IndexOutOfBoundsError
on bad index.
MODIFY METHOD insertAt(int64 index, MOVE T value) RETURNS STATUS
Insert value at index, shifting subsequent elements right
by one. Per §10.2 the bare-named form consumes the source —
value is moved into the array and the caller's source is
poisoned. Valid index is 0..length inclusive (index == length
appends). Returns FAILURE on out-of-bounds. O(n) — shifts
everything to the right of the insertion point.
MODIFY METHOD insertAtCopy(int64 index, T value) RETURNS STATUS
Insert a deep copy of value at index. value stays valid.
Same index rules and complexity as insertAt.
MODIFY METHOD remove(int64 index) RETURNS | T
Remove the element at index and return it. Pipe-XOR:
SUCCESS populates removed with the value moved out of the
array; FAILURE populates status with the bounds violation.
Subsequent elements shift down. O(n). index is int64.
MODIFY METHOD clear() RETURNS void
Remove all elements; the array becomes empty.
METHOD map(LAMBDA func) RETURNS Array[T]
Apply func to every element; return a new Array with the
transformed values, in the same order. (Bug #59 / I.J.iii — unblocked
2026-06-26 when LAMBDA-param invocation landed.)
METHOD filter(LAMBDA pred) RETURNS Array[T]
Return a new Array containing only the elements for which
pred(elem) is TRUE, in their original order.
MODIFY METHOD reduce(T initial, LAMBDA acc) RETURNS T
Fold left: starting with initial, repeatedly apply
acc(accumulator, elem) for each element in order. Returns
the final accumulator value.
MODIFY METHOD shuffle(Shuffler rng) RETURNS void
Shuffleable OF T — in-place Fisher-Yates.
Lives on Array, not OrderedArray, so every subclass inherits it. It
costs Array nothing: the swap needs only DUPLICATE, which Array's
qualifier (Cloneable for a class T, PRIMITIVE otherwise) already
guarantees — no interface requirement is added by its being here. It
was also the one method whose presence on OrderedArray contradicted
that class: shuffling destroys exactly the ordering the name promises. Walks from the last index
down, swapping each element with a uniformly-chosen earlier-or-equal
slot drawn from rng. The element swap is split by T's kind (like
sort()): a class T (handle-stored) swaps via clones + the .data[k] :=
move-in idiom (a bare =/:= between handle slots would copy a
deleted _ev_unique); a primitive T swaps by plain value copy.
CLASS ArrayIterator
IMPLEMENTS BidirectionalIterator
Concrete BidirectionalIterator OF T for Array[T].
An Array[T]'s ->iterator() method yields a fresh ArrayIterator[T]
at position 0. Multiple iterators on the same Array are independent
(each has its own cursor); the source Array is locked against
mutation while any iterator is held (per §Iterator's mutation-lock
rule).
Notable:
The iterator holds a REFERENCE T[] source directly into
the source Array's data storage (collection storage shorthand) — validated under §REFERENCE structural condition 2
(mutation-locked at INIT). All element access goes through the
T[] implicit methods (->size(), [i]).
kernel/src/ArrayIterator.ev:35
Constructors
INIT(REFERENCE REFERENCE T[] source)
constructor that requires a REFERENCE
The iterator never owns the vector — .source is a
REFERENCE field, not
an owning copy — so a const reference is the correct shape
and accepts both const-source (Array's iterator() is non-
mutating) and non-const-source call sites.
Methods
METHOD hasNext() RETURNS boolean
returns TRUE if we've not reached the end
MODIFY METHOD next() RETURNS | REFERENCE T
Pipe-XOR: SUCCESS populates the value slot with a non-owning
reference; end-of-iteration populates STATUS with FAILURE.
a REFERENCE is non-owning, so referencing it is
harmless; only the by-value lift (popFirst/popLast) rejects opaque.
This matches Array.at, which has never guarded opaque.
METHOD peek() RETURNS | REFERENCE T
peek at the next item without advancing the iterator
MODIFY METHOD skip(uint64 n) RETURNS | REFERENCE T
skip(n) — moves cursor forward by n positions. Parameter is unsigned per spec §Iterator (forward-only contract). Pipe-XOR: SUCCESS with the element at the new position, or FAILURE if out of bounds. skip(0) is equivalent to peek().
METHOD hasPrevious() RETURNS boolean
returns TRUE if the iterator is not at the first element
MODIFY METHOD previous() RETURNS | REFERENCE T
mirror of next: on SUCCESS, it returns a REFERENCE to the value and decrements the iterator's cursor; on FAILURE it returns only the status message
MODIFY METHOD skipBack(uint64 n) RETURNS | REFERENCE T
skipBack(n) — moves cursor backward by n positions. Symmetric counterpart to skip(n) on BidirectionalIterator. Pipe-XOR: SUCCESS with the element at the new position, or FAILURE if start is reached. skipBack(0) is equivalent to peek().
CLASS AtomicBinary
INTERNAL kernel class wrapping a single lock-free atomic octet. Part of the concrete-types-per-T atomic family. See AtomicBool.ev for the design rationale.
Holds a binary — an opaque octet, NOT a number. AtomicUint8 is the
numeric counterpart; the two are distinct Envzn types over the same
underlying C shim, which is why the C entry points still spell
themselves ev_atomic_byte_*.
kernel/src/AtomicBinary.ev:27
Constructors
INIT(binary initialValue)
Methods
METHOD load() RETURNS binary
METHOD store(binary newValue) RETURNS void
METHOD compareExchange(binary expected, binary desired) RETURNS boolean
CLASS AtomicBoolean
INTERNAL kernel class wrapping a single lock-free atomic boolean.
Replaces the parametric Atomic[boolean]
instantiation. Brian's choice to ship a concrete-types-per-primitive
atomic family (mirroring Rust's core::sync::atomic::AtomicBoolean +
siblings) sidesteps the parametric FOREIGN BIND machinery and keeps
the C-target migration path open (each AtomicX has a single concrete
C11 _Atomic lowering, no templates).
Storage: UNSAFE.HANDLE native — a heap-allocated _Atomic bool
owned across the FFI boundary. Lock-free on every platform Envzn
supports (bool is the simplest lock-free atomic type).
Surface (all three lock-free patterns): - load() single-step indivisible read - store(v) single-step indivisible write - compareExchange(expected, desired) test-and-set; returns TRUE on successful swap
KERNEL-ONLY (INTERNAL). User code reaches atomics through higher- level patterns (Switchboard, Channel, future Future[T] / Pool / etc.) — never directly.
kernel/src/AtomicBoolean.ev:42
Constructors
INIT(boolean initialValue)
Methods
METHOD load() RETURNS boolean
METHOD store(boolean newValue) RETURNS void
METHOD compareExchange(boolean expected, boolean desired) RETURNS boolean
CLASS AtomicChar16
INTERNAL kernel class wrapping a single lock-free atomic char16 (UTF-16 code unit). Part of the V1 Part E Phase 8 concrete-types-per-T atomic family. See AtomicBool.ev for the design rationale.
FFI signatures use uint16 (E2090 forbids char16 at the C
boundary); the AtomicChar16 method surface uses char16 and the
conversion is a free bit-cast since char16 ≡ uint16 at storage.
kernel/src/AtomicChar16.ev:27
Constructors
INIT(char16 initialValue)
Methods
METHOD load() RETURNS char16
METHOD store(char16 newValue) RETURNS void
METHOD compareExchange(char16 expected, char16 desired) RETURNS boolean
CLASS AtomicChar32
INTERNAL kernel class wrapping a single lock-free atomic char32 (UTF-32 code point). Part of the V1 Part E Phase 8 concrete-types-per-T atomic family. See AtomicBool.ev for the design rationale.
FFI signatures use uint32 (E2090 forbids char32 at the C
boundary); the AtomicChar32 method surface uses char32 and the
conversion is a free bit-cast since char32 ≡ uint32 at storage.
kernel/src/AtomicChar32.ev:27
Constructors
INIT(char32 initialValue)
Methods
METHOD load() RETURNS char32
METHOD store(char32 newValue) RETURNS void
METHOD compareExchange(char32 expected, char32 desired) RETURNS boolean
CLASS AtomicChar8
INTERNAL kernel class wrapping a single lock-free atomic char8 (UTF-8 code unit). Part of the V1 Part E Phase 8 concrete-types-per-T atomic family. See AtomicBool.ev for the design rationale.
FFI signatures use uint8 (E2090 forbids char8 at the C
boundary); the AtomicChar8 method surface uses char8 and the
conversion is a free bit-cast since char8 ≡ uint8 at storage.
kernel/src/AtomicChar8.ev:27
Constructors
INIT(char8 initialValue)
Methods
METHOD load() RETURNS char8
METHOD store(char8 newValue) RETURNS void
METHOD compareExchange(char8 expected, char8 desired) RETURNS boolean
CLASS AtomicInt16
kernel/src/AtomicInt16.ev:21
Constructors
INIT(int16 initialValue)
Methods
METHOD load() RETURNS int16
METHOD store(int16 newValue) RETURNS void
METHOD compareExchange(int16 expected, int16 desired) RETURNS boolean
CLASS AtomicInt32
kernel/src/AtomicInt32.ev:21
Constructors
INIT(int32 initialValue)
Methods
METHOD load() RETURNS int32
METHOD store(int32 newValue) RETURNS void
METHOD compareExchange(int32 expected, int32 desired) RETURNS boolean
CLASS AtomicInt64
kernel/src/AtomicInt64.ev:21
Constructors
INIT(int64 initialValue)
Methods
METHOD load() RETURNS int64
METHOD store(int64 newValue) RETURNS void
METHOD compareExchange(int64 expected, int64 desired) RETURNS boolean
CLASS AtomicInt8
kernel/src/AtomicInt8.ev:22
Constructors
INIT(int8 initialValue)
Methods
METHOD load() RETURNS int8
METHOD store(int8 newValue) RETURNS void
METHOD compareExchange(int8 expected, int8 desired) RETURNS boolean
CLASS AtomicUint16
kernel/src/AtomicUint16.ev:21
Constructors
INIT(uint16 initialValue)
Methods
METHOD load() RETURNS uint16
METHOD store(uint16 newValue) RETURNS void
METHOD compareExchange(uint16 expected, uint16 desired) RETURNS boolean
CLASS AtomicUint32
INTERNAL kernel class wrapping a single lock-free atomic uint32. Part of the concrete-types-per-T atomic family. See AtomicBool.ev for the design rationale.
Shares the C ev_atomic_u32_* machinery with AtomicChar (char ≡
uint32 code point) — separate Envzn types, same underlying C shim.
kernel/src/AtomicUint32.ev:25
Constructors
INIT(uint32 initialValue)
Methods
METHOD load() RETURNS uint32
METHOD store(uint32 newValue) RETURNS void
METHOD compareExchange(uint32 expected, uint32 desired) RETURNS boolean
CLASS AtomicUint64
kernel/src/AtomicUint64.ev:21
Constructors
INIT(uint64 initialValue)
Methods
METHOD load() RETURNS uint64
METHOD store(uint64 newValue) RETURNS void
METHOD compareExchange(uint64 expected, uint64 desired) RETURNS boolean
CLASS AtomicUint8
INTERNAL kernel class wrapping a single lock-free atomic uint8. Part of the concrete-types-per-T atomic family. See AtomicBool.ev for the design rationale.
The NUMERIC octet. AtomicBinary is the opaque-octet counterpart; the two are distinct Envzn types over the same underlying C shim.
kernel/src/AtomicUint8.ev:25
Constructors
INIT(uint8 initialValue)
Methods
METHOD load() RETURNS uint8
METHOD store(uint8 newValue) RETURNS void
METHOD compareExchange(uint8 expected, uint8 desired) RETURNS boolean
CLASS Base64Codec
Base64Codec.ev — base64 text<->bytes encoding (RFC 4648 standard alphabet).
Parameterized, multi-form transforms — NOT AS/INTO operators — hosted in a
NAMESPACE of free functions (no instance), called as Base64Codec.toBase64(bb).
kernel/src/Base64Codec.ev:14
Methods
METHOD toBase64(ByteBuffer bb) RETURNS String
ByteBuffer -> base64 String (RFC 4648 standard alphabet). Padding ('=') is included so the output is always a multiple of 4 characters. No line wrapping.
METHOD fromBase64(String s) RETURNS | ByteBuffer
Base64 String -> ByteBuffer (RFC 4648 standard alphabet). Standard '+/' alphabet only (no URL-safe variant in V1). Padding required. Skips ASCII whitespace. Pipe-XOR — FAILURE on any invalid character or wrong padding.
CLASS BinaryWord
IMPLEMENTS Cloneable, Hashable, Equatable
A BORROWED window onto a run of bytes: the array, where the run starts, and
how long it is. Value-copied and inline — copying a BinaryWord copies three
machine words, never the bytes.
WHY IT EXISTS. A byte-keyed dictionary stores every key end-to-end in one
pool and remembers each key as an (offset, length) pair, because that is what
makes it fast: one allocation for all the keys instead of one per key. But a
key so stored is not a value the language can hand back — binary is a
single byte, and an iterator yielding bytes cannot say where one key ends and
the next begins. BinaryWord is that missing value: the pair the dictionary
already holds, given a type.
It BORROWS. The bytes belong to whatever pool the window was opened on, and
a BinaryWord is valid exactly as long as that pool is. It is the byte-axis
sibling of StringView/ByteBufferView, and differs from them in being a
VALUE class: a view is an object reached through a handle, while a word is
copied inline, which is what lets an iterator yield one per step without
allocating.
kernel/src/BinaryWord.ev:30
Constructors
INIT(REFERENCE REFERENCE binary[] src, int64 off, int64 len)
Methods
METHOD length() RETURNS int64
Bytes in the window.
METHOD offset() RETURNS int64
Where the window opens in the underlying pool. A caller holding the pool
can use this with length() to reach the bytes directly — which is how
the slice fast paths avoid going through the window at all.
METHOD at(int64 i) RETURNS | binary
The byte at i within the window, or FAILURE when i is outside it.
METHOD clone() RETURNS BinaryWord
METHOD hash() RETURNS uint64
METHOD equals(BinaryWord other) RETURNS boolean
Equal when the windows hold the same bytes — not when they name the same place. Two words over different pools are equal if their bytes match.
METHOD isLessThan(BinaryWord other) RETURNS boolean
Lexicographic by bytes, shorter-is-less on a shared prefix — the order a byte-keyed tree needs to stay total.
CLASS Box
IMPLEMENTS Cloneable
Defines the Box[T] class per ENVZN_CONSTITUTION §Box (L602–616).
Box[T] is a heap-allocating transparent wrapper around a single T. Its primary purpose is to break the type cycle that would otherwise make a self-recursive GROUP infinite-sized:
GROUP Expr {
Number n
BinaryExpr // contains Box[Expr] — the recursion
// through Box's heap pointer breaks
// the cycle for std::variant
}
CLASS BinaryExpr {
Box[Expr] left
Box[Expr] right
String op
}
At the C++ emission layer, Box[T] is std::unique_ptr<T> — single
owner, heap-indirected, fixed-size at the variant level. The
transparency promised by the spec (reading a Box field yields T,
not Box[T]; writing a T to a Box field auto-wraps) is compiler
sugar above this class, not anything the class itself implements.
The methods below are the explicit surface that the sugar
desugars to.
Compiler features (transparency sugar — not in this class):
- Auto-wrap on assignment. A class field declared
Box[T] foo accepts a T-typed RHS; the compiler emits the
equivalent of .foo = CREATE Box[T](rhs) for fresh INITs and
.foo->set(rhs) for reassignments.
- Auto-unwrap on read. Reading instance.foo where
.foo is a Box[T] field yields T (specifically a REFERENCE
T handle via the equivalent of .foo->value()); the user
never sees the Box wrapper.
- MATCH transparency. When a GROUP variant arm contains a
boxed type, MATCH dispatches on the inner T, not on Box[T].
None of those three features live inside this file — they're compiler-side rewrites. They reduce every user-facing interaction with Box to an Envzn-side method call on the surface declared below.
kernel/src/Box.ev:58
Constructors
INIT(MOVE T value)
The only constructor. value is consumed (MOVE). The
compiler's auto-wrap rewrite produces CREATE Box[T](rhs)
when initialising a Box[T] field from a T-typed expression.
Methods
METHOD clone() RETURNS Box[T]
Hand-written rather than AUTO: Box has no zero-arg INIT (the
user-facing model has no "empty box" state), so the cascade
synthesis path doesn't apply. T must implement Cloneable when
T is a class type; the explicit ->clone() call below
surfaces the requirement as a compile error on the Box[T]
instantiation that triggers it.
METHOD value() RETURNS REFERENCE T
── value — return a non-owning handle ────────────────────────
The auto-unwrap rewrite for reads of a Box[T] field. Returns
a non-optional REFERENCE T into SELF's owned .inner.
.inner is always populated (set by INIT and never cleared),
so the reference is always valid; the reference checker proves
it without runtime infrastructure.
METHOD valueClone() RETURNS T
── valueClone — return an owned copy ───────────────────────── The path callers take when they need an owning T (e.g. to store elsewhere, to mutate independently). T must be Cloneable; the cascade-failure rule fires at instantiation otherwise.
MODIFY METHOD set(MOVE T value) RETURNS void
── set — replace the wrapped value ───────────────────────────
Drops the previous inner and installs value (MOVE).
Bug #32: := to a populated non-Optional owning field
drops the previous owner via its destructor, then installs
the new owner from the RHS. Box never enters a "moved-out"
state observable to the user — the drop and the install
happen as a single operator step.
CLASS Broker
Typed broadcast pub/sub.
────────────────────────────────────────────────────────────────── USER-FACING API ──────────────────────────────────────────────────────────────────
Broker[T] fans a published value out to every current subscriber.
One broker per typed message. Each subscriber gets its own buffered
Channel[T], so a slow subscriber is governed by its own
BackpressurePolicy and does not block a fast one.
Broker[Match] broker := CREATE Broker[Match]()
CONCURRENT {
PARALLEL { // a subscriber task
Subscription[Match] sub := broker->subscribe(16, BackpressurePolicy.DROP_OLDEST)
WHILE sub->receive() DO { handle($RETURNED) }
}
PARALLEL { broker->publish(m) } // a publisher task
}
- publish(value) — clone the value out to every live subscriber's channel (fan-out).
- subscribe(capacity, policy) — register a new subscriber; returns a scope-bound Subscription[T].
────────────────────────────────────────────────────────────────── OWNERSHIP / LIFETIME ──────────────────────────────────────────────────────────────────
Each subscriber's Channel is co-owned: the broker holds one strong
(reference-counted) handle in an OwnedList[Channel[T]] (the move-only,
non-Cloneable collection — standard collections reject a non-Cloneable
Channel), and the returned Subscription holds a SHARED MUTABLE
REFERENCE that, because Channel is shared-eligible (§9.1), is a strong
handle too. Unsubscribe is lazy: the Subscription's CLEANUP closes its
channel, and publish skips closed channels. A channel is freed when its
last owner drops — so a Subscription may safely outlive the broker (it
keeps its channel alive and can still drain it), and the broker need not
outlive its subscriptions. This is the ARC guarantee that closes the
prior write-after-free.
The broker is itself shared across tasks by PARALLEL capture (like any
channel/mutex), not by a SHARED MUTABLE REFERENCE field — so it is an
ordinary class, internally synchronized by .lock.
kernel/src/Broker.ev:57
Constructors
INIT()
Methods
MODIFY METHOD publish(T value) RETURNS void
Fan value out to every live subscriber. Each subscriber gets its
own clone (class T) or copy (primitive T); a closed (unsubscribed)
channel is skipped. Walks the owned registry with a MUTABLE
REFERENCE local cursor — ownership grants mutable access, so no
SHARED carve-out is needed on the broker side.
MODIFY METHOD subscribe(int32 capacity, BackpressurePolicy policy) RETURNS Subscription[T]
Register a new subscriber with its own buffered channel. Returns a scope-bound Subscription holding a SHARED MUTABLE REFERENCE to the channel. The Subscription is created from the channel handle BEFORE the handle is moved into the registry — the heap channel object is not moved by the handle transfer, so the reference stays valid.
CLASS BuildInfo
Build-provenance runtime API.
Surfaces the AUTO-GENERATED EV_buildinfo_data.hpp constants (version,
git describe / commit / branch, build counter, dirty flag, build timestamp)
to an Envzn program. The data is computed at build time by the compiler
(compiler/buildinfo.py) and written to the OUTPUT side every build — never to
source — so a program can report its own provenance.
char8[], not String: the data header is #included early (global scope, before
namespace ENVZN), so this surface deliberately deals in char8[] byte
arrays (UTF-8) and primitives, and never touches String — a caller that
wants text converts the bytes itself (bytes INTO String, once String is in
scope) at the call site. A NAMESPACE (not a SINGLETON): BuildInfo is a
stateless provider of constants, it performs no behaviour.
The strings cross the FFI via the LOAD fill-buffer idiom — RETURNS char8[]
is not a legal FFI return (bind-spec), so each accessor fills a caller-owned
char8[] and returns the byte count.
kernel/src/BuildInfo.ev:37
Methods
METHOD version() RETURNS char8[]
METHOD gitDescribe() RETURNS char8[]
METHOD commit() RETURNS char8[]
METHOD branch() RETURNS char8[]
METHOD builtAt() RETURNS char8[]
METHOD isDirty() RETURNS boolean
METHOD buildCounter() RETURNS uint64
CLASS ByteBuffer
IMPLEMENTS Countable, Cloneable, Hashable, Equatable
Immutable opaque binary data container.
ByteBuffer is one of four sibling classes in the Envzn text/binary type system:
- String — heap, immutable, text (sibling)
- DynamicString — heap, mutable, text (sibling)
- ByteBuffer — heap, immutable, opaque binary (this file)
- DynamicByteBuffer — heap, mutable, binary (sibling)
PURPOSE A handle to a chunk of opaque binary data — bytes whose meaning the language does not interpret. Once constructed the contents never change; transformations like subBuffer or concat return a new ByteBuffer. Display in human-readable form goes through HexCodec.toHex(...) (default separator " ", e.g. "3F 40 6A").
STORAGE
A single INTERNAL binary[] field. At the C++ layer this lowers
to _ValueArray<uint8_t> — a contiguous, growable byte buffer
governed by the value-array storage shorthand of
ENVZN_CONSTITUTION.md §10.1. All positional accessors read via
.data[i]; appends are high-water-mark assignments at
.data[.data.length].
INDEXING SEMANTICS — BYTES, NOT CODE POINTS
Every positional method indexes in bytes. length() and size()
both exist and return the same number, because a byte IS the
storage unit; they differ only on the text axis. buf[i] returns a
binary primitive, not a char. 'binary' is an opaque char, aka
'unsigned char'.
ERROR-RETURN POLICY
One policy, shared with the text axis: [] throws
IndexOutOfBoundsError, the find family is pipe-XOR
(int64 index | STATUS) with no -1 sentinel, and everything else
is total.
This header used to say "No method throws", and that sentence was
the origin of most of the drift between the two axes: it is what
produced a second element accessor at(), a pipe-XOR subBuffer
against a throwing substring, and the -1 sentinel. It was also
already false about this class's own [], nine lines below where
it was written — [] has always delegated to
_ValueArray<uint8_t>::operator[], which throws. Phase 3 kept the
behaviour the code had and retired the sentence.
kernel/src/ByteBuffer.ev:62
Constructors
INIT()
CONSTRUCTORS / CLEANUP
INIT(REFERENCE REFERENCE binary[] origin)
Construct from a pre-built byte array, COPYING it. Every internal
transformation (subBuffer, concat, clone) constructs its result this
way: build a fresh binary[], hand it to a new ByteBuffer.
REFERENCE, not MOVE: the class allocates its own storage rather
than adopting the caller's. This was the last MOVE constructor in the
text/binary family and the only one that adopted caller memory — every
other kernel class allocates its own, so the outlier moved rather than
the pattern. It costs one copy per construction, measured at 8-21%
(E2, 2026-08-27), and buys a uniform rule: handing an array to a
constructor never takes it away from you.
It also cannot coexist with a MOVE companion. Both lower to a
one-argument constructor over the same EvArray<uint8_t> — one by
value, one by const reference — which clang rejects as ambiguous.
INIT(MOVE binary[] origin, int64 length)
Similar to String, we have a MOVE binary[] for performance
length IS REDUNDANT AND MUST NOT BE REMOVED.
INIT(REFERENCE REFERENCE ByteBuffer other)
Copy-construct from another ByteBuffer. One allocation, one bulk copy — the element loop this replaced wrote at the high-water mark, so the storage reallocated as it filled.
Methods
METHOD clone() RETURNS ByteBuffer
CLONE — hand-written. Hands the storage straight to the copying constructor, which reserves once and bulk-copies.
It used to build an intermediate binary[] byte by byte and hand THAT
over, which cost two full copies and an extra allocation: once into the
intermediate, once out of it. That shape made sense when the constructor
took MOVE and adopted the array; under REFERENCE the intermediate is
pure waste, because the constructor was going to copy anyway.
METHOD isEmpty() RETURNS boolean
INSPECTION
METHOD size() RETURNS int64
METHOD length() RETURNS int64
Byte count — the same number size() returns. On the byte axis the two
coincide, because a byte IS the storage unit; on the text axis they
differ (String.length() counts code points, String.size() counts
UTF-8 bytes). Both names exist on all six text/binary classes so a
caller never has to remember which axis they are holding.
METHOD __op_index__(int64 i) RETURNS binary
Byte at index i — buf[i] yields the raw binary. THE element
accessor: a pipe-XOR at() stood beside it until Phase 3, which left
the byte axis with two ways to read one byte and the text axis with one.
The bounds check lives in _ValueArray<uint8_t>::operator[], which
throws IndexOutOfBoundsError out of range, mirroring String's code-point
operator []. Delegating rather than range-checking here is deliberate:
the array's throw carries the offending index and the bound in its
message, and this class sits upstream of Formatter, so a PANIC written
here could only say "out of bounds" with no numbers in it.
METHOD equals(ByteBuffer other) RETURNS boolean
COMPARISON
Value equality, delegated to the value array's own equals. That is not
merely shorter: for an element type with a unique object representation
the array compares with a single length-checked memcmp (one SIMD
compare) where this method used to walk element by element. Measured
2026-08-27 at 6x faster on 64 elements and 11-14x from 1K up, with
identical results across an 8-cell differential matrix — including the
float guard, since the array deliberately does NOT memcmp an arithmetic
element type (-0.0 == +0.0, NaN != NaN would come out wrong).
METHOD isLessThan(ByteBuffer other) RETURNS boolean
Use the lexicographic comparision below
METHOD compareTo(REFERENCE REFERENCE ByteBuffer other) RETURNS int32
Lexicographic byte comparison: shorter buffer orders before a longer one when prefixes match. Returns -1 / 0 / 1.
METHOD find(REFERENCE REFERENCE ByteBuffer needle) RETURNS | int64
Searchable — first index at which needle's bytes occur in this
buffer, else FAILURE. Forwards to the one canonical binary scan in
ByteBufferView: a full-span view over this buffer searches for
needle's bytes.
METHOD lastIndexOf(REFERENCE REFERENCE ByteBuffer needle) RETURNS | int64
Index of the LAST occurrence of needle, pipe-XOR. The mirror of
find, forwarded through a full-span view the same way, so there is
exactly one byte scan in the kernel.
METHOD find_first_of(REFERENCE REFERENCE ByteBuffer set) RETURNS | int64
B1: index of the first byte that IS a member of set (the bytes of
set, treated as a set). Forwards through a full-span view.
METHOD find_first_not_of(REFERENCE REFERENCE ByteBuffer set) RETURNS | int64
B1: index of the first byte that is NOT a member of set.
METHOD view(int64 start, int64 length) RETURNS ByteBufferView
Viewable — a non-owning, bounded window over this buffer's bytes
without copying. The byte-side analogue of String.view.
METHOD hash() RETURNS uint64
HASHING — same FNV-1a as String, deterministic across runs FNV-1a 64-bit hash. Matches String.hash() and DynamicString.hash() byte-for-byte for identical content, so the three classes hash to the same uint64 when carrying the same bytes.
METHOD hash(uint64 seed) RETURNS uint64
Seeded FNV-1a, the peer of String.hash(uint64 seed). Chaining a seed
lets a composite key hash its parts in sequence without materialising a
concatenation.
METHOD contains(REFERENCE REFERENCE ByteBuffer needle) RETURNS boolean
CONTENT LOOKUP Empty needle returns true (matches String.contains).
METHOD startsWith(REFERENCE REFERENCE ByteBuffer prefix) RETURNS boolean
METHOD endsWith(REFERENCE REFERENCE ByteBuffer suffix) RETURNS boolean
METHOD find(REFERENCE REFERENCE ByteBuffer needle, int64 fromIndex) RETURNS | int64
First-occurrence index at or after fromIndex. Pipe-XOR:
FAILURE on out-of-range fromIndex or when needle does not
occur from that point onward.
METHOD count(REFERENCE REFERENCE ByteBuffer needle) RETURNS int64
Non-overlapping occurrence count. Empty needle returns 0 (avoids infinite-match scenario).
METHOD subBuffer(int64 start, int64 length) RETURNS ByteBuffer
SLICING / CONCATENATION
Copy a contiguous slice into a fresh ByteBuffer. start is inclusive,
length is a byte count. TOTAL, and the exact peer of
String.substring: a bad range is a precondition violation — a
programming error — so it THROWS, the same category as [].
length == 0 yields an empty buffer and is not an error.
This returned pipe-XOR (ByteBuffer | STATUS) until Phase 3, which made
a caller slicing a buffer write an IF where the same caller slicing a
String writes none. A recoverable outcome is a search that misses; a
caller asking for bytes that were never there is a bug in the caller.
METHOD concat(REFERENCE REFERENCE ByteBuffer other) RETURNS ByteBuffer
Concatenation. Returns a fresh ByteBuffer; receivers unchanged. operator + pending — see Bug #57.
METHOD iterator() RETURNS ByteBufferIterator
Yield a value-iterator over the bytes. The iterator copies the bytes into its own storage at construction — it is fully independent of this buffer for its whole lifetime.
METHOD data() RETURNS REFERENCE binary[]
Non-owning REFERENCE to the underlying binary[] storage. The
receiver retains ownership; the returned reference is valid
for the receiver's lifetime per §11.6 lifetime elision. The
canonical use is at a FOREIGN BIND call site that needs to
expose the byte storage to a C function without copying.
CLASS ByteBufferIterator
IMPLEMENTS ValueIterator
Value-form iterator over a ByteBuffer.
ByteBufferIterator is the value-iterator counterpart to the collection iterators (ArrayIterator and friends), and mirrors StringIterator on the text axis.
It has TWO constructors, and they differ in what they do with the
bytes. The BORROWING form takes a window [start, start+length) of
an array the caller keeps owning, and aliases it — ByteBuffer and
DynamicByteBuffer pass their own field, which outlives the
iterator. The OWNING form takes the array outright and lends itself
the borrow, which is what a view must use: a view's .source is a
REFERENCE field, so the only array it can offer is a local snapshot,
and lending a local is the gh #248 dangle.
The header used to say the bytes were "copied in — the iterator never aliases", which the single borrowing constructor had never done. Now one form aliases, one owns, and each says which.
IMPLEMENTS ValueIterator OF binary — a ByteBuffer's storage yields bytes by value, not by reference, so the iterator advances with nextValue() / peekValue() / skipValue() rather than the by-reference forms ReferenceIterator (the collection iterators) carries.
STORAGE
Five fields: snapshot storage used only by the owning constructor,
the borrowed payload, the window offset, a cached length, and a
cursor. Every read indexes .data[.start + .cursor], so a window
that does not begin at zero walks the right bytes. The compiler
emits this CLASS as a C++ struct (the value-class carve-out), so
allocation and teardown are deterministic and RAII.
kernel/src/ByteBufferIterator.ev:52
Constructors
INIT(REFERENCE REFERENCE binary[] source, int64 start, int64 length)
BORROWING form — built by ByteBuffer.iterator() and
DynamicByteBuffer.iterator(), which pass their own binary[] FIELD
plus the window [start, start+length). The iterator ALIASES that
storage, so the argument must outlive the iterator. Both owners pass the
whole array (0, .data.length).
INIT(MOVE binary[] snapshot)
OWNING form — the iterator takes the array and lends itself the borrow.
.data =@ .owned is SELF-rooted, so the referent lives exactly as long
as the iterator does and the gh #248 dangle cannot arise. This is the
form ByteBufferView.iterator() uses.
It is separable from the borrowing form ONLY because their arities
differ. Two one-argument constructors over the same EvArray<uint8_t>,
one MOVE and one REFERENCE, lower to by-value and const-reference
and clang rejects the pair as ambiguous — which is what blocked
ByteBufferView.iterator() through Phase 1.
Methods
METHOD hasNext() RETURNS boolean
MODIFY METHOD nextValue() RETURNS | binary
Value-form advance — copy out the byte at the cursor, then step the cursor forward. FAILURE at end-of-iteration.
METHOD peekValue() RETURNS | binary
The byte at the cursor without advancing. FAILURE at end.
MODIFY METHOD skipValue(uint64 n) RETURNS | binary
Move the cursor forward by n and return the byte there.
FAILURE if that would pass the end. skipValue(0) == peekValue().
MODIFY METHOD nextBytes(int64 numberOfBytes) RETURNS | binary[]
Block read: copy up to numberOfBytes bytes from the cursor into
a fresh binary[] and advance the cursor past them. Returns fewer
than requested when the buffer is exhausted first (the caller
checks .length); FAILURE only when the cursor is already at the
end, or numberOfBytes is not positive. The block-oriented
counterpart to byte-at-a-time nextValue(), mirroring the
(binary[] | STATUS) shape of File.read().
CLASS ByteBufferView
IMPLEMENTS View, Viewable
A non-owning, bounded window over a ByteBuffer's binary[] storage.
The byte-side mirror of StringView: it holds the one canonical binary
scan (find / contains / count) that ByteBuffer — and, via a ByteBuffer
snapshot, DynamicByteBuffer — forward into. Like StringView it is
ByteBuffer-free: it knows only binary[], so it introduces no
dependency cycle with the owning buffer types.
Bytes carry no case, so there is no case-fold variant — the scans are exact-byte only (the one structural difference from StringView).
kernel/src/ByteBufferView.ev:25
Constructors
INIT(REFERENCE REFERENCE binary[] source, int64 start, int64 length)
Construct a bounded view over [start, start+length) in source.
Throws (via the C-string helper) if start < 0 or length < 0 or
start + length > source.length(). A zero length is allowed.
Methods
METHOD length() RETURNS int64
Byte count of this view.
METHOD size() RETURNS int64
Countable — View EXTENDS Countable, so the view owes size() and
isEmpty() as well as its own length(). All three are the same
number here: the count of octets the window spans.
METHOD isEmpty() RETURNS boolean
METHOD __op_index__(int64 i) RETURNS binary
Byte at index i within this view. bv[i] is the only read syntax.
Throws (C-string helper) if i is out of [0, view.length).
METHOD find(REFERENCE REFERENCE binary[] needle) RETURNS | int64
First occurrence of needle's bytes within this view's whole range.
Pipe-XOR: SUCCESS yields the view-relative index of the match;
FAILURE means no match. To search a sub-range, narrow with
view(start, length) first.
METHOD startsWith(REFERENCE REFERENCE binary[] needle) RETURNS boolean
True iff this view begins with needle. An empty needle returns TRUE;
a needle longer than the view returns FALSE.
METHOD endsWith(REFERENCE REFERENCE binary[] needle) RETURNS boolean
True iff this view ends with needle. An empty needle returns TRUE;
a needle longer than the view returns FALSE.
METHOD lastIndexOf(REFERENCE REFERENCE binary[] needle) RETURNS | int64
Last occurrence of needle within this view. Pipe-XOR: SUCCESS yields
the view-relative index of the final match; FAILURE means no match. An
empty needle yields the view length. Scans backward from the last
candidate window, so the first hit found is the last occurrence.
METHOD contains(REFERENCE REFERENCE binary[] needle) RETURNS boolean
True iff needle occurs at least once in this view.
METHOD count(REFERENCE REFERENCE binary[] needle) RETURNS int64
Non-overlapping occurrence count of needle. Empty needle returns 0.
METHOD find_first_of(REFERENCE REFERENCE binary[] set) RETURNS | int64
B1: index of the first byte that IS a member of set. Pipe-XOR:
SUCCESS = the index; FAILURE = no byte in this view is in set.
METHOD find_first_not_of(REFERENCE REFERENCE binary[] set) RETURNS | int64
B1: index of the first byte that is NOT a member of set.
METHOD view(int64 start, int64 length) RETURNS ByteBufferView
Narrow to a sub-window [start, start+length) relative to THIS view
(composition — a sub-window of a window is a window). Bounds are
checked against this view's length, so a sub-view can never escape
its parent. Satisfies Viewable.
METHOD copy() RETURNS binary[]
Materialise this window's bytes into a fresh binary[] (the "keep this
slice" escape hatch — the view itself never escapes). Wrap with
CREATE ByteBuffer(view->copy()) for an owned ByteBuffer.
METHOD iterator() RETURNS ValueIterator[binary]
Walk this window's bytes in order. Mirrors StringView.iterator(),
which the byte axis had no counterpart to until now.
The window is materialised and GIVEN to the iterator. It cannot be
lent: .source is a REFERENCE field — a pointer — and does not bind to
a REFERENCE binary[] parameter, so the only array this view can offer
is a local snapshot, and lending a local returns an iterator over freed
storage (gh #248). The owning constructor takes the array, so the
referent lives exactly as long as the iterator does.
CLASS ByteOrderCodec
kernel/src/ByteOrderCodec.ev:32
Methods
METHOD toBytes(int32 n, Endianness endian) RETURNS ByteBuffer
METHOD toBytes(int64 n, Endianness endian) RETURNS ByteBuffer
METHOD toBytes(uint32 n, Endianness endian) RETURNS ByteBuffer
METHOD toBytes(uint64 n, Endianness endian) RETURNS ByteBuffer
METHOD toBytes(float32 n, Endianness endian) RETURNS ByteBuffer
Float -> bytes: IEEE-754 bit pattern via a bound shim, then packed by the same pure-Envzn endian helper the integer overloads use.
METHOD toBytes(float64 n, Endianness endian) RETURNS ByteBuffer
METHOD toInt32(ByteBuffer bb, Endianness endian) RETURNS | int32
METHOD toInt64(ByteBuffer bb, Endianness endian) RETURNS | int64
METHOD toUInt32(ByteBuffer bb, Endianness endian) RETURNS | uint32
METHOD toUInt64(ByteBuffer bb, Endianness endian) RETURNS | uint64
METHOD toFloat32(ByteBuffer bb, Endianness endian) RETURNS | float32
Bytes -> float: integer bit pattern assembled in pure Envzn, then a bound shim reinterprets it as IEEE-754.
METHOD toFloat64(ByteBuffer bb, Endianness endian) RETURNS | float64
CLASS CasualRandom
IMPLEMENTS Random
A fast, OS-seeded Random for games, sampling, jitter, and other non-reproducible, non-security uses. Seeds an xoshiro256** engine once at construction from operating-system entropy; thereafter it is a pure (fast) PRNG. NOT reproducible (the seed is fresh each time) and NOT for secrets — use SecureRandom for tokens/keys.
Thread-affine (see the Random interface note): not a SHARED CLASS.
kernel/src/CasualRandom.ev:26
Constructors
INIT()
Methods
MODIFY METHOD nextInt(int64 bound) RETURNS int64
MODIFY METHOD nextUInt128() RETURNS uint128
MODIFY METHOD nextFloat() RETURNS float64
MODIFY METHOD nextBoolean() RETURNS boolean
CLASS ChainedHashDictionary
IMPLEMENTS Dictionary
ChainedHashDictionary
STORAGE:
- buckets : HashBucket[K,V][] — a RAW value-array of lean value-class buckets,
one per slot. Each HashBucket stores its entries INLINE (2 CollisionNode slots
+ a lazy spill), so there is NO per-bucket heap object and NO per-entry heap
node — the LinkedList-per-bucket + ChainedHashEntry + per-lookup heap iterator
of the old design are gone (that iterator alloc was the measured 174 ns/lookup
S4 bug). An empty bucket is a zero-count HashBucket (no nullable-slot problem).
- In-place bucket mutation binds a MUTABLE REFERENCE into .buckets[idx] (reads
bind a plain REFERENCE); the bucket's own MODIFY methods do the work.
- count : int64; hasher : Hasher OF K (interface-typed, decision 5);
bucketLevel : int64 (power-of-two growth level); growth : GrowthHint
(advisory, stored-unused for V1).
BUCKET INDEX: hash & (bucketCount - 1) over a POWER-OF-TWO bucket count
(HashConstants.pow2CountAtLevel / indexForHash) — replaces the old prime %.
RESIZE: load factor > 0.79 (count100 > bucketCount79) → double the bucket count
(one pow2 level) and rehash every entry by its CACHED hash (no key re-hash).
IMPLEMENTS Dictionary[K, V, H] — Cloneable comes transitively (Dictionary EXTENDS
Cloneable); a redundant explicit Cloneable would create an ambiguous C++ double
base and break clone() covariance. H is phantom in the body; the stored hasher is
the Hasher OF K interface (no-arg INIT CREATEs the DefaultHasher).
ChainedHashDictionary[K, V, H] — separate-chaining hash table over lean inline
value-class buckets. HIDDEN: constructed via CREATE, held through the Dictionary
interface.
kernel/src/ChainedHashDictionary.ev:45
Constructors
INIT()
No hasher, default sizing — CREATE a DefaultHasher[K].
INIT(MOVE Hasher[K] h)
Caller-supplied hasher, default sizing.
INIT(GrowthHint hint, int64 initialCapacity)
Default hasher + sizing hints (initialCapacity ignored for V1 — the pow2 ladder governs bucket count; GrowthHint stored, unused).
INIT(MOVE Hasher[K] h, GrowthHint hint, int64 initialCapacity)
Caller hasher + sizing hints.
Methods
METHOD clone() RETURNS Dictionary[K, V, H]
METHOD iterator() RETURNS ReferenceIterator[K]
METHOD keys() RETURNS LinkedList[K]
METHOD isEmpty() RETURNS boolean
METHOD size() RETURNS int64
Number of key-value pairs currently stored. Read-only, O(1).
MODIFY METHOD clear() RETURNS void
MODIFY METHOD insert(MOVE K key, MOVE V value) RETURNS STATUS
MODIFY METHOD insertCopy(K key, V value) RETURNS STATUS
METHOD lookup(REFERENCE REFERENCE K key) RETURNS | MUTABLE REFERENCE V
METHOD lookupFast(REFERENCE REFERENCE K key) RETURNS boolean
Dictionary.lookupFast — same probe as lookup, without the STATUS that
lookup must allocate on a miss.
MODIFY METHOD remove(REFERENCE REFERENCE K key) RETURNS | K
METHOD contains(REFERENCE REFERENCE K key) RETURNS boolean
METHOD equals(Dictionary[K, V, H] other) RETURNS boolean
Two dictionaries are equal when they hold the SAME ASSOCIATIONS: the same keys, each mapping to an EQUAL VALUE.
Not merely the same KEYSET. That would rank {a:1} equal to {a:2}, so
two "equal" dictionaries would answer lookup(a) differently and neither
could stand in for the other — which is the whole point of an equivalence.
Keys-AND-values is what C++ (map and unordered_map), Rust, Swift,
Julia, Python and Java all specify. Keyset equality is a perfectly good
relation, but it is equality of the DOMAIN rather than of the dictionary,
and it deserves its own name (hasSameKeys) rather than this one.
Equal sizes plus a one-way walk is sufficient: with the counts already
equal, other cannot carry a key this one lacks unless this one also
carries a key other lacks — and the walk would have found that.
CLASS Channel
IMPLEMENTS Shareable
Typed, bounded, point-to-point message channel.
────────────────────────────────────────────────────────────────── USER-FACING API ──────────────────────────────────────────────────────────────────
Channel[T] is the blessed primitive for point-to-point handoff of
typed values between CONCURRENT-spawned tasks. It is bounded by
default — the constructor demands an explicit capacity and a
BackpressurePolicy, so "what happens when the consumer falls behind"
is a decision the author makes, never a silent unbounded-growth
default.
Channel[int32] work := CREATE Channel[int32](4, BackpressurePolicy.BLOCK)
CONCURRENT {
PARALLEL { // producer
work->send(1)
work->send(2)
work->close()
}
PARALLEL { // consumer
WHILE work->receive() DO {
printline($RETURNED INTO String)
}
}
}
- send(value) — enqueue; pipe-XOR STATUS. Behaviour at capacity is governed by the BackpressurePolicy (see below).
- receive() — pipe-XOR: blocks until a value is available, then returns it (SUCCESS); returns FAILURE once the channel is closed AND drained.
- close() — sticky shutdown. Wakes every blocked sender and receiver; subsequent send() fails.
- isClosed() — non-blocking state read.
────────────────────────────────────────────────────────────────── BACKPRESSURE ──────────────────────────────────────────────────────────────────
When the buffer is at capacity, send() consults the policy fixed at construction:
- BLOCK — sender blocks until a receiver frees a slot (or the channel closes). The safe default.
- DROP_NEWEST — the incoming value is discarded; send() returns SUCCESS. The backlog is preserved.
- DROP_OLDEST — the oldest buffered value is dropped to make room for the new one.
- CONFLATE — the buffer collapses to hold only the newest value.
────────────────────────────────────────────────────────────────── CROSS-THREAD SAFETY ──────────────────────────────────────────────────────────────────
Channel[T] is one of the kernel-managed exceptions to Envzn's
"user code cannot share instances across threads" rule (sibling to
Mutex / Broker / Future). Sharing IS the point — a producer task and
a consumer task hold the same Channel and coordinate through it. The
internal Lock serialises every queue mutation and provides the
happens-before ordering; the two partner-locked ThreadConditions
(notEmpty / notFull) handle the blocking waits without busy-polling.
────────────────────────────────────────────────────────────────── CLOSE SEMANTICS ──────────────────────────────────────────────────────────────────
close() is sticky. After close:
- receive() drains any buffered values (still SUCCESS), then returns
FAILURE once empty — so a WHILE ch->receive() DO { … } consumer
loop exits cleanly.
- send() returns FAILURE immediately.
A Channel also closes implicitly when its owning variable goes out of
scope (CLEANUP closes before tearing down the buffer).
────────────────────────────────────────────────────────────────── STATUS ──────────────────────────────────────────────────────────────────
Composes Queue[T] storage + Lock + ThreadCondition substrate. capacity is a count; callers pass a positive value.
kernel/src/Channel.ev:96
Constructors
INIT(int64 capacity, BackpressurePolicy policy)
Methods
MODIFY METHOD send(MOVE T value) RETURNS STATUS
PUBLIC API
Enqueue value. Pipe-XOR STATUS. The bare-named MOVE form
consumes the source — the value is moved into the buffer. At
capacity, behaviour follows the BackpressurePolicy. Returns
FAILURE if the channel is closed.
MODIFY METHOD receive() RETURNS | T
Remove and return the front value. Pipe-XOR: SUCCESS populates
value; blocks while the buffer is empty and the channel is
open. Returns FAILURE once the channel is closed AND drained, so
a WHILE ch->receive() DO { … } loop terminates on close.
MODIFY METHOD tryReceive() RETURNS | T
Non-blocking receive. Pipe-XOR: SUCCESS returns the front value if
one is buffered; FAILURE immediately if the buffer is empty (never
blocks). A WHILE ch->tryReceive() DO { … } loop drains whatever
is currently buffered and then exits.
MODIFY METHOD close() RETURNS void
Sticky shutdown. Wakes every blocked sender and receiver so they re-test their predicate and exit. Idempotent.
METHOD shareableKind() RETURNS String
Shareable marker — short type tag for debug printers / logs.
METHOD isClosed() RETURNS boolean
Non-blocking state read. TRUE once close() (or scope-exit CLEANUP) has run.
METHOD size() RETURNS int64
Current buffered count. Snapshot under the lock — advisory only (the value may change the instant the lock is released).
CLASS CharClassifier
kernel/src/CharClassifier.ev:17
Methods
METHOD isAlpha(char32 c) RETURNS boolean
METHOD isDigit(char32 c) RETURNS boolean
METHOD isAlnum(char32 c) RETURNS boolean
METHOD isSpace(char32 c) RETURNS boolean
METHOD isUpper(char32 c) RETURNS boolean
METHOD isLower(char32 c) RETURNS boolean
CLASS CharConverter
CONVERSIONS host for char→String.
A single code point rendered as a one-character String. char8/char16 widen to a char32 code point (via PrimitiveConversions) before encoding; char32 is already a code point. DynamicString.append(char32) UTF-8-encodes it and runs the Tier-1 text check — a non-text code point yields an empty String (the append FAILURE is benign here, matching the legacy behavior).
char8/char16/char32 INTO String — lossless, total char32[] INTO String — lossless, total String INTO char32 — exact-or-FAILURE (one code point)
Depends only on String + DynamicString, so it orders early. The char32[]→String
operator backs the compiler-inserted error-message auto-conversion (args.py
wraps a char[]-typed print/format argument as arg INTO String); the
conversion registry is array-aware so it coexists with the scalar char32→String
above rather than colliding on the key.
kernel/src/CharConverter.ev:28
CLASS CharWidthConverter
CONVERSIONS host for cross-char-width NARROWING. The AS surface over char16/char32 → a narrower char, and its implementation — the single-code-unit narrowing decision lives here, delegating only to UTFCodec's bulk transcoders (the UTF authority).
char16/char32 AS char8 — lossy, fallible (multi-byte UTF-8 → FAILURE) char32 AS char16 — lossy, fallible (UTF-16 surrogate pair → FAILURE)
UTFCodec emits the full encoded sequence; the narrowing succeeds only when the result is exactly one code unit. UTF validation (scalar range, surrogate rejection) is owned by UTFCodec — an invalid source scalar surfaces as STATUS.
Char-width WIDENING (char8 INTO char16/char32, char16 INTO char32) is lossless and lives in PrimitiveConversions; only the fallible narrowing direction needs UTFCodec, so this host depends on UTFCodec and orders AFTER it (later than the early PrimitiveConversions / CharConverter hosts).
kernel/src/CharWidthConverter.ev:27
CLASS CollisionNode
IMPLEMENTS Cloneable, Comparable
CollisionNode.ev — one (key, value, cachedHash) cell, as a VALUE CLASS so the
hash bucket stores it INLINE (no per-entry heap node). This is the Phase-1 dict-
rebuild substrate replacing the heap-allocated ChainedHashEntry: a HashBucket
holds CollisionNodes by value (two inline slots + a contiguous spill), and an
Array[CollisionNode[K,V]] lowers to inline storage — _ValueArray when K,V are
primitive (the trivially-copyable C-class path), _ObjectArray-by-value when K is
a class (e.g. a String key → owning handle field, the managed value-class form
the compiler deep-copies).
The key's hash is cached at insert (idea from libdict): a resize re-buckets via the cached hash without re-hashing keys, and a lookup rejects on the uint64 hash before the (possibly expensive) key compare.
K IS (Hashable AND Equatable); V is Cloneable / primitive. IMPLEMENTS Cloneable so
a node can be an Array[CollisionNode] element (whose clone() the bucket needs).
See: HashBucket.ev (the holder), ChainedHashDictionary.ev (the consumer),
ENVZN_CONSTITUTION.md §I.J.vi (VALUE CLASS). (Replaces the old heap-class
ChainedHashEntry, removed in the Phase-2 dict rebuild.)
CollisionNode[K, V, HASH] — a (key, value, cachedHash) bucket cell, value-typed.
HASH is the cached-hash width: uint64 for ChainedHashDictionary (FNV), uint128
for ProHashDictionary (SipHash-2-4-128). Parameterising the width keeps the common
int-key node tight (16 B at uint64 — 4 nodes/cache line) while letting the Pro
table keep its full 128-bit digest for the cheap pre-compare reject.
kernel/src/CollisionNode.ev:41
Constructors
INIT(MOVE K k, MOVE V v, HASH h)
Methods
METHOD hashValue() RETURNS HASH
The cached hash of this node's key (used by resize re-bucketing and the pre-compare reject in lookup).
METHOD matches(HASH h, REFERENCE REFERENCE K other) RETURNS boolean
TRUE if this node matches both the query hash and the query key. The cached
HASH is checked first (cheap reject); only on a hash hit is identity confirmed
via Equatable (class keys) or == (primitive keys) — the design doc's "compare
hash first, confirm key" step. Backs every lookup/insert/remove scan.
MODIFY METHOD setValue(MOVE V v) RETURNS void
Replace the stored value in place — backs insert-over-existing-key (the key and cached hash are unchanged).
METHOD valueClone() RETURNS V
A clone of the stored value — backs by-value lookup.
METHOD keyClone() RETURNS K
A clone of the stored key — backs keys().
MODIFY METHOD takeKey() RETURNS K
MOVE the stored key out — backs remove(). The node is being evicted, so
the key is handed to the caller rather than copied and destroyed
("move in => move out"). Reading .key afterward is a use-after-move;
the only caller evicts the node in the same operation.
MODIFY METHOD takeValue() RETURNS V
MOVE the stored value out — the twin of takeKey, same contract.
METHOD clone() RETURNS CollisionNode[K, V, HASH]
Deep copy — key and value cloned, cached hash carried across.
METHOD equals(CollisionNode[K, V, HASH] other) RETURNS boolean
METHOD isLessThan(CollisionNode[K, V, HASH] other) RETURNS boolean
CLASS CompileError
EXTENDS Error
Kernel error class. Thrown by tooling that performs compilation at runtime (the self-hosted Envzn compiler, opaque-evaluation helpers). Not expected to be thrown by ordinary application code, but reserved as a kernel type so compiler-using modules don't have to invent their own.
Subclass of Error; adds no new fields.
kernel/src/CompileError.ev:19
Constructors
INIT(String message)
CLASS Complex
The complex primitive's backing value-class.
complex is one complex-number type, backed by this compiler-known value-class
and NEVER dev-instantiated (no CREATE Complex); it is driven entirely by the
complex primitive surface and the real + coeff im literal form. Copy-by-value.
Internally it is a pair of numbers — the real and imaginary components — each
keeping its own int/float subtype, so a single complex spans both Gaussian-
integer complexes (3 + 4im, integer components) and float complexes
(3.0 + 4.0im), and even mixed ones (3 + 4.0im). Per-component arithmetic is
just number arithmetic, which is why complex is built ON number.
The field is named imag (not im) because im is the reserved imaginary-unit
token; the public accessors are .real / .imaginary (each a number), added
in a later cycle alongside .conjugate / .magnitude / .phase.
See number-and-complex-design.md Part 2 and ENVZN_CONSTITUTION.md I.D.i(g).
kernel/src/Complex.ev:38
Fields
number renumber imag
Constructors
INIT(number re, number imag)
Methods
METHOD equals(complex other) RETURNS boolean
Component-wise value-equality (the Equatable surface a == b lowers to).
Two complexes are equal iff both components are equal as numbers — so
(3 + 4im) == (3 + 4im). There is NO ordering on complex (</> are a
compile error): the complex field is not ordered.
METHOD __op_plus__(complex other) RETURNS complex
Addition / subtraction — component-wise, the reals with the reals and the
imaginaries with the imaginaries: (8 + 6im) = (3 + 4im) + (5 + 2im).
Each component op is ordinary number arithmetic; the result reassembles
through the rp + ip im literal form (fused at parse time, so this does
not recurse into the very operator being defined).
METHOD __op_minus__(complex other) RETURNS complex
METHOD __op_times__(complex other) RETURNS complex
Multiplication: (a + bi)(c + di) = (ac − bd) + (ad + bc)i. The cross terms
are what make im² = −1 fall out — (0 + 1im) * (0 + 1im) = (−1 + 0im).
METHOD real() RETURNS number
The real component (a in a + bi), as a number.
METHOD imaginary() RETURNS number
The imaginary component (b in a + bi), as a number.
METHOD conjugate() RETURNS complex
The complex conjugate — flips the sign of the imaginary part:
conjugate(a + bi) = a − bi. Multiplying a value by its conjugate gives a
real ((a+bi)(a−bi) = a² + b²), which is why division scales by it.
METHOD magnitude() RETURNS number
Magnitude (modulus) |a + bi| = √(a² + b²) — the distance from the origin,
always a non-negative real. Computed in float64 via Math.sqrt, so the
result is a float-subtype number. (Complex depends on Math here.)
METHOD phase() RETURNS number
Phase (argument) of a + bi = atan2(b, a) — the angle in radians from the
positive real axis, in (−π, π]. A float-subtype number.
METHOD negate() RETURNS complex
Negation — -c lowers to this: −(a + bi) = (−a) + (−b)i. (Unary minus on
a complex routes here; complex has no C++ unary operator-.)
METHOD __op_divide__(complex other) RETURNS complex
Division: (a + bi)/(c + di) = [(ac + bd) + (bc − ad)i] / (c² + d²).
Both components are scaled by the real denominator |other|². With integer
components this follows number's truncating integer /; float-component
complexes divide exactly — use those when an exact quotient is wanted.
CLASS ComplexConversions
kernel/src/ComplexConversions.ev:12
CLASS DataValue
IMPLEMENTS Cloneable, Serializable, Comparable
The kernel's neutral, format-independent value model.
A DataValue carries a DataKind discriminant plus the storage for
whichever shape it represents — the abstract value model shared by
JSON, BSON, CBOR, MessagePack, and DB rows (promoted from the Json
module's JsonValue so the model is a kernel citizen while wire
formats stay in their modules):
- NULL_KIND — absent value
- BOOL_KIND — a boolean
- NUMBER_KIND — a
number(int64 XOR uint64 XOR float64 — the full tower, so integer fidelity round-trips without the lossy-float64 gap JsonValue carried) - STRING_KIND — a String
- ARRAY_KIND —
Array[DataValue] - OBJECT_KIND —
Dictionary[String, DataValue, DefaultHasher[String]]
Recursive structure needs no Box[DataValue] — Array and Dictionary
heap-indirect their elements, so the recursion through collection
storage is finite.
Accessors, three flavors per kind:
- isXxx() RETURNS boolean — predicate
- asXxx() RETURNS (T | STATUS) — by-value; PRIMITIVE kinds only
- asXxxReference() RETURNS (REFERENCE T | STATUS) — handle (no clone), lifetime bound by SELF
- asStringView() RETURNS (StringView | STATUS) — bounded view, no clone, copies freely
No accessor clones. A by-value form exists only where the value IS the
datum (asBool, asNumber); for a class kind the by-value form would be
a deep copy on every read, which is what asString was and why it is
gone. asStringReference / asStringView replace it.
Constructors, one per kind: INIT() NULL, INIT(boolean),
INIT(number), INIT(String), INIT(Array[DataValue]) (MOVE),
INIT(Dictionary[String, DataValue, DefaultHasher[String]]) (MOVE).
This is a value model, not a format — it has no toString/serialize
of its own; a format module maps DataValue ↔ text (Json) or ↔ rows
(Data). Serialization rides the Serializable/Serializer interface.
kernel/src/DataValue.ev:51
Fields
DataKind kind
Constructors
INIT()
INIT(boolean v)
INIT(number v)
INIT(String v)
INIT(MOVE OrderedArray[DataValue] items)
INIT(MOVE Dictionary[String, DataValue, DefaultHasher[String]] entries)
Methods
METHOD clone() RETURNS DataValue
METHOD isNull() RETURNS boolean
METHOD isBool() RETURNS boolean
METHOD isNumber() RETURNS boolean
METHOD isString() RETURNS boolean
METHOD isArray() RETURNS boolean
METHOD isObject() RETURNS boolean
METHOD asBool() RETURNS | boolean
METHOD asNumber() RETURNS | number
METHOD asStringReference() RETURNS | REFERENCE String
METHOD asStringView() RETURNS | StringView
METHOD asArrayReference() RETURNS | REFERENCE OrderedArray[DataValue]
METHOD asObjectReference() RETURNS | REFERENCE Dictionary[String, DataValue, DefaultHasher[String]]
METHOD lookupReference(REFERENCE REFERENCE String key) RETURNS | REFERENCE DataValue
METHOD serialize(Serializer s) RETURNS STATUS
METHOD equals(DataValue other) RETURNS boolean
METHOD isLessThan(DataValue other) RETURNS boolean
CLASS DataValueBuilder
IMPLEMENTS Serializer
kernel/src/DataValueBuilder.ev:15
Constructors
INIT()
Methods
MODIFY METHOD putNull() RETURNS STATUS
MODIFY METHOD putBool(boolean v) RETURNS STATUS
MODIFY METHOD putNumber(number v) RETURNS STATUS
MODIFY METHOD putString(String v) RETURNS STATUS
MODIFY METHOD putKey(String key) RETURNS STATUS
MODIFY METHOD beginArray() RETURNS STATUS
MODIFY METHOD beginObject() RETURNS STATUS
MODIFY METHOD endArray() RETURNS STATUS
MODIFY METHOD endObject() RETURNS STATUS
METHOD result() RETURNS | DataValue
MODIFY METHOD takeResult() RETURNS | DataValue
CLASS DataValueReader
IMPLEMENTS Deserializer
The tree Deserializer backend.
A non-owning reader over a DataValue tree (the 2-layer-cake borrow
pattern, like StringView over char32[]): it holds a REFERENCE DataValue
and, on deserialize, re-emits that tree's content as a flat event stream
into the given Serializer sink. The walk already lives on the data
(DataValue.serialize), so the reader is a one-line adapter that delegates
to it — its job is only to present a borrowed tree through the Deserializer
interface (so a DataValue source and a JsonReader stream are interchangeable
sources for the same sink).
Short-lived / locals-only, by the same reference-checker rules StringView relies on: the source DataValue's lifetime must enclose the reader's.
kernel/src/DataValueReader.ev:25
Constructors
INIT(REFERENCE REFERENCE DataValue source)
Methods
MODIFY METHOD deserialize(Serializer sink) RETURNS STATUS
CLASS DateTime
IMPLEMENTS Comparable
UTC wall-clock instant, millisecond precision.
Storage shape (LOCKED): canonical int64 epochMs —
milliseconds since the Unix epoch — is the single source of truth.
The seven broken-down fields (year/month/day/hour/minute/second/
millisecond) are public read-only and derived once at construction
by the gmtime_r-backed ev_datetime_to_broken_down shim.
Construction (LOCKED, ISO 8601 range [year 1, year 9999]): - CREATE DateTime() — wall-clock now (always valid) - DateTimeFactory->newDateTime(...) — explicit components, pipe-XOR FAILURE on out-of-range. The factory lives on the DateTimeFactory singleton because fallible INIT isn't a V1 surface (parser has no INIT-RETURNS form; revisit when it does).
kernel/src/DateTime.ev:44
Fields
int32 yearint32 monthint32 dayint32 hourint32 minuteint32 secondint32 millisecond
Constructors
INIT()
Construct from the current wall-clock instant (UTC). Always valid — system time cannot be outside the representable range.
INIT(int64 epochMs)
Construct from a raw Unix epoch ms value. Primarily used by DateTimeFactory.newDateTime after it has validated components and computed epochMs via ev_datetime_from_broken_down — that is the supported path for explicit construction, because it surfaces a pipe-XOR FAILURE on out-of-range. Direct use is also fine if you already hold a known-valid epoch (e.g. from another DateTime, from a database column). Out-of-range epoch values surface as gmtime_r returning bogus broken-down fields; no FAILURE is raised here, so prefer DateTimeFactory.newDateTime for untrusted input.
Methods
METHOD getEpochMs() RETURNS int64
Wall-clock instant as milliseconds since the Unix epoch (UTC).
METHOD isLessThan(DateTime other) RETURNS boolean
Comparable[DateTime] — strict less-than on epochMs (UTC). The
ordering operators (<, <=, >, >=) are derived by the
compiler from this single method per Comparable's convention.
METHOD equals(DateTime other) RETURNS boolean
Equatable[DateTime] — Comparable's parent. Two DateTimes are equal iff their epochMs match; broken-down field equality is a derived consequence (both produced by gmtime_r from the same ms).
METHOD compareTo(DateTime other) RETURNS TimeComparison
Explicit three-way comparison — for callers that want the named
TimeComparison enum (BEFORE / EQUAL / AFTER) rather than
composing the Comparable primitives. Both surfaces are
intentional per Phase 0 #3 LOCKED.
METHOD __op_plus__(TimeDuration d) RETURNS DateTime
dt + td — shift forward by d. UTC.
METHOD __op_minus__(TimeDuration d) RETURNS DateTime
dt - td — shift back by d. UTC.
METHOD __op_minus__(DateTime other) RETURNS TimeDuration
dt - dt' — the TimeDuration FROM other TO self. Sign
follows the convention later - earlier > 0.
METHOD format(String pattern) RETURNS String
Render this DateTime against pattern, substituting each
recognised token with its zero-padded field value and copying
every other character verbatim.
METHOD hash() RETURNS uint64
FNV-1a 64-bit hash of epochMs, mixed as 8 little-endian bytes. epochMs is the canonical state — two DateTimes with equal epochMs are Equatable-equal and must hash identically.
METHOD clone() RETURNS DateTime
Field copy. Routes through INIT(int64) so the broken-down decomposition runs once on the clone; epochMs is the source of truth, the seven public fields are derived.
CLASS DateTimeFactory
Fallible DateTime construction surface.
Envzn has no static-method facility and fallible INIT is not a V1
parser surface, so the two DateTime factories that validate their
components and return pipe-XOR (DateTime | STATUS) live on a
dedicated stateless singleton rather than on the DateTime class
itself.
- newDateTime(year..millisecond) — explicit components, UTC.
- parseDateTime(text, pattern) — strict token-grammar parse.
Both delegate range / calendar validation to the same
ev_datetime_from_broken_down shim that DateTime.ev binds (FOREIGN
scope is per-file, §17), using NumericLimits.INT64_MIN as the failure sentinel.
kernel/src/DateTimeFactory.ev:32
Methods
METHOD parseDateTime(String text, String pattern) RETURNS | DateTime
Strict-parse a DateTime from text against a pattern using
the Phase 5 token grammar: YYYY (4-digit year), MM/DD/HH/
mm/ss (2-digit), SSS (3-digit milliseconds). Each token
consumes exactly its width in decimal digits; every other
pattern character must match the next text character exactly.
On any mismatch (width / non-digit / literal mismatch / trailing
text / out-of-range components / invalid calendar date such as
Feb 30), returns FAILURE rather than a partial DateTime.
METHOD newDateTime(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second, int32 millisecond) RETURNS | DateTime
Construct a DateTime from explicit year-through-millisecond components. UTC. Pipe-XOR FAILURE on out-of-range per Phase 0 #5: year must lie in [1, 9999] (ISO 8601 range), month in [1, 12], day in [1, 31] (timegm rejects e.g. Feb 30 → FAILURE too), hour in [0, 23], minute in [0, 59], second in [0, 60] (leap-second permissible), millisecond in [0, 999].
CLASS Decimal128
The decimal128 primitive's backing value-class (V1, DESIGN_QUEUE #40).
decimal128 is an IEEE 754-2008 exact base-10 numeric, backed by this
compiler-known value-class. NEVER dev-instantiated (no CREATE Decimal128 in
user code); driven by the decimal128 surface, literals, and AS/INTO. Copy-by-
value, blittable. Exact + cohort-normalized ⇒ Comparable AND Hashable.
Storage is { int128 coefficient; int32 exponent }: value = coefficient × 10^exponent. int128 holds ~38 decimal digits, ≥ the IEEE decimal128 34-significant-digit requirement. The hard decimal machinery lives here (north-star "relocate the cost inward"); the dev surface is a plain primitive.
ROUNDING CONTEXT: a RoundingMode enum + the single decideUp decision
funnel parametrize the reducers (reduced/mulReduce/divReduce) by (precision,
mode); the bare operators forward (34, HALF_EVEN) so their behavior is
unchanged. Public surface: roundTo(places[, mode]) (decimal-place rounding)
and add/subtract/multiply/divide(other, precision, mode) (explicit
significant-digit context). See decimal128-design.md.
kernel/src/Decimal128.ev:30
Fields
int128 coefficientint32 exponent
Constructors
INIT()
Default-initialize to zero (constitution I.D.i(b)).
INIT(int128 coeff, int32 exp)
Construct from a (coefficient, exponent) pair: value = coefficient × 10^exponent. The form the literal/conversion/arithmetic lowering targets.
Methods
METHOD powTen(int32 n) RETURNS int128
10^n as int128, for 0 ≤ n ≤ 38 (callers keep n in range). n < 0 → 1.
METHOD digitCount(int128 v) RETURNS int32
Decimal digit count of |v| (zero counts as 1 digit). Overflow-safe (uses ~/).
METHOD decideUp(int32 cmpHalf, boolean nonzeroDropped, boolean coeffOdd, boolean neg, RoundingMode mode) RETURNS boolean
Single source of truth for "round the retained magnitude UP by one?" across all seven RoundingMode values, given a cohort-free description of what was dropped below the retained coefficient: - cmpHalf — the dropped fraction vs ½ ULP of the last retained digit: +1 above ½, 0 exactly ½ (a tie), −1 below ½. - nonzeroDropped — TRUE iff ANY nonzero digit was dropped (the directed modes UP/CEILING/FLOOR round on any remainder, not just at the half). - coeffOdd — parity of the retained coefficient (HALF_EVEN tie-break). - neg — sign of the value (CEILING/FLOOR are sign-directed). Every reducing path (reduced / mulReduce / divReduce / roundedDrop) funnels its rounding decision through here, so the bare operators' fixed (34, HALF_EVEN) behavior and the explicit-mode surface share one rule and cannot drift.
METHOD roundedDrop(boolean neg, int128 mag, int32 drop, RoundingMode mode) RETURNS int128
Drop the drop lowest decimal digits (drop ≥ 1) from an EXACT magnitude
mag, rounding per mode via decideUp, and return the rounded
magnitude. The dropped part has no sticky tail (mag is exact), so the
tie test is just remainder-vs-half. Used by roundTo (decimal-place
rounding); the sig-digit reducers compute cmpHalf inline because they
also carry a sticky direction.
METHOD reduced(boolean neg, int128 mag, int32 exp, int32 stickyDir, int32 precision, RoundingMode mode) RETURNS decimal128
Build a decimal128 from a sign + magnitude + exponent, rounding the
magnitude to ≤ precision significant digits with the given mode.
stickyDir carries information about digits below mag already dropped:
+1 = the true value is slightly ABOVE mag (round up at a tie), −1 =
slightly BELOW (round down at a tie), 0 = mag is exact (tie → per mode).
Bare +/− pass (34, HALF_EVEN) so their behavior is unchanged.
METHOD addMagnitudes(int128 ca, int32 ea, int128 cb, int32 eb, int32 precision, RoundingMode mode) RETURNS decimal128
Sum of two decimals by sign+magnitude. Aligns the larger-exponent operand UP exactly (bounded so it fits int128), rounds the smaller operand DOWN to the working exponent capturing a tri-state sticky, combines by sign, then reduces to 34 digits. Subtraction's guard-borrow is handled by the sticky sign (a rounded-off subtrahend makes the true result slightly smaller).
METHOD pairDigits(int128 hPart, int128 lPart) RETURNS int32
Product of two magnitudes (each < 10^34) at the given exponent, reduced to ≤ 34 digits with round-half-even. Avoids a 256-bit binary intermediate by splitting each coefficient at 10^17 into halves whose pairwise products all fit int128 (< 10^34 < 2^113); the exact 68-digit product is carried as a (high, low) decimal pair and reduced digit-by-digit with a sticky tail. Significant-digit count of a value carried as a (high, low) decimal pair hPart·10^34 + lPart, with lPart < 10^34. When the high part is nonzero its lowest digit sits at decimal position 34, so the pair spans digitCount(hPart) + 34 digits; otherwise the count is just lPart's. The 34 here is the structural split exponent (10^34), independent of the target precision.
METHOD mulReduce(boolean neg, int128 ma, int128 mb, int32 exp, int32 precision, RoundingMode mode) RETURNS decimal128
METHOD divReduce(boolean neg, int128 ma, int128 mb, int32 exp, int32 precision, RoundingMode mode) RETURNS decimal128
Quotient of two magnitudes (mb ≠ 0) at the given exponent, to 34
significant digits with round-half-even. Schoolbook long division: take the
integer quotient, then emit fractional digits one at a time (each step
rem*10 < 10^35 stays in int128) until 34 significant digits or an exact
remainder, then one guard digit + sticky for the final rounding.
METHOD equals(decimal128 other) RETURNS boolean
Cohort-normalized VALUE equality (the Equatable surface a == b): two
decimals are equal iff their numeric values match, regardless of cohort —
1.0 == 1.00 == 1. Computed as (a − b) having a zero coefficient (the
subtraction aligns + reduces exactly), so different representations of the
same value compare equal.
METHOD isLessThan(decimal128 other) RETURNS boolean
Strict less-than (the Comparable surface): the sign of (a − b).
METHOD hash() RETURNS uint64
Cohort-invariant hash (the Hashable surface): hashes the CANONICAL form (coefficient with trailing zeros stripped + adjusted exponent), so equal values hash equal — the invariant that makes decimal128 a sound Dictionary/ Set key. FNV-1a over the canonical (coefficient, exponent). The int128 → uint64 folds are inline unchecked narrowings (Decimal128 is a floor primitive, emitted before NumericUtilities, so it can't call it).
METHOD __op_plus__(decimal128 other) RETURNS decimal128
METHOD __op_minus__(decimal128 other) RETURNS decimal128
METHOD checkedExp(int64 e) RETURNS int32
Narrow a working exponent (computed in int64 to dodge int32 overflow UB)
back to the int32 exponent field, raising a catchable MathError when the
scale overflows int32 — decision 2's "PANIC MathError on overflow".
Reachable: d = d * d doubles the exponent each step, so ~32 squarings
overflow. Decimal128 is emitted before the MathError class, so it cannot
PANIC MathError directly — it routes through the int128 floor-division
domain trap (INT64_MIN ~/ −1 → "floor division result out of range"), the
only MathError raiser reachable from a floor primitive. (The message is
the generic floor-div one; an exponent-specific message would need the
MathError class reordered ahead of Decimal128 — see decimal128-design.md.)
METHOD __op_times__(decimal128 other) RETURNS decimal128
METHOD __op_divide__(decimal128 other) RETURNS decimal128
METHOD clampPrecision(int32 p) RETURNS int32
Clamp a caller-supplied precision to the type's valid range [1, 34]: decimal128 carries at most 34 significant digits, and a precision below 1 is meaningless. Out-of-range requests are pinned to the nearest bound rather than rejected — the result is always a well-formed decimal128.
METHOD roundTo(int32 places, RoundingMode mode) RETURNS decimal128
Round to places digits after the decimal point, ties broken per mode.
Negative places rounds to tens / hundreds / … (place −2 → nearest 100).
A value already at this granularity or coarser is returned unchanged.
This is decimal-PLACE rounding (the everyday money/measurement operation);
significant-digit control is the precision arg on the arithmetic methods.
METHOD roundTo(int32 places) RETURNS decimal128
roundTo with the default HALF_EVEN (banker's) tie-break.
METHOD add(decimal128 other, int32 precision, RoundingMode mode) RETURNS decimal128
Sum reduced to precision significant digits, ties per mode — the
explicit-context form of +.
METHOD subtract(decimal128 other, int32 precision, RoundingMode mode) RETURNS decimal128
Difference reduced to precision significant digits, ties per mode —
the explicit-context form of −.
METHOD multiply(decimal128 other, int32 precision, RoundingMode mode) RETURNS decimal128
Product reduced to precision significant digits, ties per mode — the
explicit-context form of *.
METHOD divide(decimal128 other, int32 precision, RoundingMode mode) RETURNS decimal128
Quotient to precision significant digits, ties per mode — the
explicit-context form of /. Divide-by-zero raises MathError through
divReduce's floor-division, exactly as / does.
METHOD absOf(decimal128 v) RETURNS decimal128
|v| — magnitude as a decimal128 (negate the coefficient, keep the exponent). The cohort is irrelevant to closeTo (it compares values), so no normalization is needed.
METHOD closeTo(decimal128 other) RETURNS boolean
Approximate equality — the ≈ (U+2248) / ≉ (U+2249) surface. decimal128
is EXACT and 34 significant digits wide, so the number type's √machine-eps
epsilon (1.5e-8, sized for float64) is far too coarse here. closeTo is
RELATIVE (a fraction of the larger magnitude): the default tolerance agrees
to ~30 of 34 significant digits — it absorbs a few ULP of rounding noise
from /, AS, … without calling genuinely different values close. Use
->isClose(other, rtol, atol) for a caller-chosen tolerance.
METHOD isClose(decimal128 other, decimal128 rtol, decimal128 atol) RETURNS boolean
closeTo with caller-chosen relative + absolute tolerances (the principled-
tolerance companion, parallel to number->isClose): TRUE iff
|a − b| ≤ max(rtol·max(|a|,|b|), atol). rtol scales with the larger
operand magnitude (the relative term); atol is the absolute floor near
zero (where a relative test alone never matches a non-zero value).
CLASS DecimalCodec
The inbound half of the decimal128 IEEE 754-2008 BID (Binary Integer Decimal) 128-bit wire codec.
bidToParts decodes 16 big-endian BID bytes into a (coefficient, exponent)
DecimalParts — it returns only the PARTS, never a decimal128: the kernel
cannot construct a value-class-backed primitive, so the compiler's
ByteBuffer INTO decimal128 lowering calls this helper and assembles
Decimal128(coeff, exp) on success (the same parts→value pattern as
DecimalText.parseDecimalParts for String INTO decimal128). The outbound
encode is decimal128 INTO ByteBuffer (DecimalConversions, a plain total
OPERATOR INTO).
Simple form only: a canonical decimal128 holds ≤ 34 digits (< 10^34 < 2^113), so the coefficient always fits the 113-bit field and the "11" combination branch (large coefficient / Infinity / NaN) is never PRODUCED — but a hostile or foreign 16 bytes can present it, so decode REJECTS it (and any out-of-range exponent or > 34-digit coefficient) with a FAILURE: that is the fallible path.
Layout (big-endian): bit 127 = sign, bits 126..113 = biased exponent (exponent + 6176), bits 112..0 = the unsigned coefficient.
Pure Envzn, zero deps beyond ByteBuffer / DecimalParts / NumericUtilities. See decimal128-design.md §"IEEE BID wire codec".
kernel/src/DecimalCodec.ev:34
Methods
METHOD bidToParts(ByteBuffer bb) RETURNS | DecimalParts
Decode 16 big-endian IEEE BID bytes into (coefficient, exponent). Fallible: wrong length, the non-canonical "11" combination form, an out-of-range biased exponent, or a coefficient of more than 34 digits.
CLASS DecimalConversions
The AS/INTO conversion host for decimal128 (DESIGN_QUEUE #40, Phase 5). Mirrors NumberConversions / ComplexConversions.
Surface (decimal128-design.md §Conversions): - decimal128 INTO String — EXACT decimal text (total). The headline display feature: a decimal128 renders the precise value it holds, no float artifacts. - decimal128 AS int8…int64 / uint8…uint64 — fallible (non-integral or out-of-range → FAILURE). - decimal128 AS float32 / float64 — lossy, fallible. String → decimal128 (parse) and number ↔ decimal128 land alongside.
Widening INTO decimal128 (int* → decimal128) is implicit (no operator) — the compiler lowers it at the widen-in site (build_ir).
kernel/src/DecimalConversions.ev:25
CLASS DecimalText
kernel/src/DecimalText.ev:17
Methods
METHOD parseDecimalParts(String s) RETURNS | DecimalParts
Scan EXACT decimal text into its (coefficient, exponent) parts — an optional sign, integer digits, and an optional '.' with fractional digits ("19.99", "-0.01", "100", "+5.0"). The coefficient is every digit as a signed int128; the exponent is −(fractional digit count), so the text's scale is preserved ("1.00" → coefficient 100, exponent −2). Fallible: empty input, a stray character, a second '.', no digits, or int128-overflow of the coefficient.
CLASS DefaultHasher
IMPLEMENTS Hasher
DefaultHasher implements Hasher OF K (Hasher.ev) for every legal key type in one
class. A Dictionary / Set constructed without an explicit hasher
CREATEs one of these.
TWO HASHING PATHS, selected at compile time on K's kind:
-
K IMPLEMENTS Hashable (String + user classes that declare it): delegate to the key's own
hash(). TheWHEN K IMPLEMENTS Hashablegate narrows K to a class, sokey->hash()reaches THROUGH the_ev_unique<K>handle to the real method. (A bare ELSE would not narrow, andkey->hash()would hit the handle, which has nohash()member — this is why the class path MUST be guarded.) -
everything else (numeric primitives +
char): FNV-1a mix of the value reduced to a uint64, then FNV-1a mixed. That reduction is a WIDENING for integers andchar(exact — a char widens its code point) but it must NOT be one for a float: widening truncates the mantissa, so distinct values would collide. A float therefore takes its own arm and is hashed over its exact binary64 BITS, with -0.0 folded onto 0.0 so that IEEE-equal keys hash alike (NumericUtilities.floatHashBits). Primitives are NOTIMPLEMENTS Hashable(they are Hashable at the type level only — there is no callable.hash()), so they fall to this branch correctly.
Note: a specific-width gate like WHEN K IS float64 is illegal —
E1104 only admits IS PRIMITIVE / IS Numeric / IS <class> /
IS <group> as kinds, not individual primitive widths. Hence
WHEN K IS Floating, a GROUP over both float widths (interfaces.ev),
which is admitted where a bare width is not.
See: Hasher.ev (the interface), HashConstants.ev (FNV-1a constants),
interfaces.ev → Hashable.
DefaultHasher OF K — universal kernel hasher. Delegates to the key's
own hash() when K is Hashable (String, user classes); FNV-mixes the
widened value for numeric / char keys.
kernel/src/DefaultHasher.ev:49
Constructors
INIT()
Methods
METHOD clone() RETURNS DefaultHasher[K]
Cloneable — so a DefaultHasher can sit in a dictionary's concrete H slot (the
dictionaries DUPLICATE their hasher when cloned). Stateless, so a fresh instance
is an exact copy.
METHOD hash(REFERENCE REFERENCE K key) RETURNS uint64
CLASS Deque
IMPLEMENTS Cloneable, Collection
Defines the Deque[T] class per ENVZN_CONSTITUTION §Deque (L659–680).
Deque[T] is a double-ended queue: O(1) push and pop at either end, no random-access indexing, no mid-list operations. The contract is intentionally narrower than LinkedList[T] — Deque is a LinkedList with a constrained API surface that names the ends as "front" and "back" rather than "first" and "last."
Composes on LinkedList[T], which provides exactly Deque's contract:
- O(1) at both ends (LinkedList.prepend / append / popFirst /
popLast / peekFirst / peekLast).
- BidirectionalIterator OF T.
- Cascading clone() via Cloneable.
So Deque is pure delegation — no new storage, no new iterator,
just a renamed-and-narrowed view over LinkedList.
kernel/src/Deque.ev:29
Constructors
INIT()
Methods
METHOD clone() RETURNS Deque[T]
Hand-written clone() — AUTO refused for the same cascade reason as Queue / Stack / LinkedList: the synthesis walker can't see T's Cloneable status from this instantiation site through the LinkedList[T] composition. Walk the underlying LinkedList's iterator and pushBack a deep clone of each element — ordering preserved (front-to-back).
METHOD iterator() RETURNS LinkedListIterator[T]
METHOD isEmpty() RETURNS boolean
METHOD size() RETURNS int64
Number of elements currently stored. Read-only, O(1).
MODIFY METHOD clear() RETURNS void
MODIFY METHOD pushFront(MOVE T value) RETURNS STATUS
FRONT-SIDE OPERATIONS — pushFront / popFront / peekFront
MODIFY METHOD pushFrontCopy(T value) RETURNS STATUS
MODIFY METHOD popFront() RETURNS | T
METHOD peekFront() RETURNS | REFERENCE T
MODIFY METHOD pushBack(MOVE T value) RETURNS STATUS
BACK-SIDE OPERATIONS — pushBack / popBack / peekBack
MODIFY METHOD pushBackCopy(T value) RETURNS STATUS
MODIFY METHOD popBack() RETURNS | T
METHOD peekBack() RETURNS | REFERENCE T
INTERFACE Dictionary
Dictionary is a pure INTERFACE, not a concrete class, and is no longer special-cased by the compiler. It is an ordinary parametric interface the compiler treats like any other.
THREE TYPE PARAMETERS Dictionary[K, V, H]:
- K — key type. Hashable + Equatable (primitives, String, or a class
implementing both).
- V — value type. Cloneable / primitive (the clone()-bearing surface
requires it; see EXTENDS Cloneable below).
- H — the Hasher OF K used to hash keys. Callers name a concrete
hasher here (e.g. Dictionary[String, int32, StringHasher]); the
hidden impls store it as the Hasher OF K interface type and, when
constructed without one, CREATE a DefaultHasher[K].
IMPLEMENTATIONS (both HIDDEN, pure Envzn, no FOREIGN / std::): - ChainedHashDictionary[K,V,H] — bucket array + chaining; unordered. - SkipListDictionary[K,V,H] — skip list; sorted iteration (K also Comparable). (lands in a later #16 phase.)
Callers construct an impl and hold the interface: Dictionary[String, int32, StringHasher] scores := CREATE ChainedHashDictionaryString, int32, StringHasher
EXTENDS Cloneable — every Dictionary deep-copies.
ITERATION yields KEYS (forward-only), per spec §Iterator. Order is unspecified for the chained-hash impl; the skip-list impl iterates in sorted key order. Dictionary[K, V, H] — unordered key→value map interface. Keys are hashed by the H hasher; values are Cloneable. Construct a concrete impl (ChainedHashDictionary / SkipListDictionary) and hold this interface type.
kernel/src/Dictionary.ev:49
Methods
METHOD clone() RETURNS Dictionary[K, V, H]
Deep copy. (Cloneable contract; covariant return — an impl returns its own concrete type.)
METHOD iterator() RETURNS ReferenceIterator[K]
Forward-only iterator over the keys. FOR/IN over a Dictionary visits each key once.
METHOD keys() RETURNS LinkedList[K]
Snapshot of all keys in a fresh LinkedList (each key cloned).
METHOD isEmpty() RETURNS boolean
check if the dictionary is empty
METHOD size() RETURNS int64
Number of key→value pairs currently stored. All three impls already
provide it; declaring it here makes it callable through a Dictionary
interface handle (Bug #179).
MODIFY METHOD clear() RETURNS void
MODIFY METHOD insert(MOVE K key, MOVE V value) RETURNS STATUS
insert (key, value). BOTH are MOVEd in (consumed) — the dictionary
owns what it stores, so there is nothing for it to clone. Replacing an
existing key destroys the old value and leaves the count unchanged.
SUCCESS always (FAILURE only on an EMPTY key/value where the type is
Cloneable). Use insertCopy to keep your own key and value.
MODIFY METHOD insertCopy(K key, V value) RETURNS STATUS
Deep-copy insert — both key and value stay valid for the caller.
METHOD lookup(REFERENCE REFERENCE K key) RETURNS | MUTABLE REFERENCE V
Lookup — a reference into the stored value, or FAILURE if the key is absent. Gate on STATUS, not on the returned V. A borrow-propagating ("mirror") accessor: the returned reference mirrors the receiver's mutability — a mutable dictionary yields a MUTABLE REFERENCE (modify the value in place), a const one a read-only REFERENCE. Valid while the dictionary is not mutated (iterator mutation lock).
There is deliberately NO value-cloning lookup: borrowing is the default
and owning is explicit. To own a copy, clone the borrow; to own the
value itself, remove it.
METHOD lookupFast(REFERENCE REFERENCE K key) RETURNS boolean
Allocation-free lookup — found plus the value, no pipe and no STATUS.
This is not a faster lookup; it is a lookup with a different COST on
the miss. lookup builds a FAILURE("...key not found") when the key
is absent, so a loop that misses once per distinct key — a group-by, a
join, a dedupe — allocates a formatted String per miss, a cost the pipe
hides completely at the call site. Every implementation already knows
whether the key is present before it decides what to return, so making
this part of the interface costs nothing and is what lets a consumer
stay on the interface instead of reaching for the concrete class.
On a miss value is the type's absent form (EMPTY for a class V, zero
for a primitive) and MUST NOT be read: found is the only valid
discriminator. C# spells the same contract TryGetValue.
MODIFY METHOD remove(REFERENCE REFERENCE K key) RETURNS | K
remove — MOVES both halves out, or FAILURE if the key is absent. Nothing is cloned: the stored key and value are handed to the caller rather than copied and destroyed. The returned key is the STORED one, which is equal to — but not necessarily the same object as — the probe key, which is why it is worth returning.
METHOD contains(REFERENCE REFERENCE K key) RETURNS boolean
contains - like lookup() but only returns a boolean if the key-value pair is found
CLASS DurationText
ISO-8601 duration PARSE, the inverse of TimeDuration.format.
Reads [-]P[nD]T[nH][nM][nS[.mmm]] (millisecond precision) back into a
TimeDuration. Fallible: a malformed string is a FAILURE, never a silent zero —
which is exactly what serde's reconstruct needs at the untrusted document
boundary (I.P.i). A NAMESPACE (not a method on TimeDuration) because it is a
fallible construction with no receiver, mirroring DateTimeFactory.parseDateTime.
Bounds are checked explicitly before every index (the scan advances by a
variable amount), so the walk never indexes past the end regardless of AND
evaluation order.
kernel/src/DurationText.ev:22
Methods
METHOD parse(String text) RETURNS | TimeDuration
Parse an ISO-8601 duration string into a TimeDuration, or FAILURE.
CLASS DynamicByteBuffer
IMPLEMENTS Countable, Cloneable, Hashable, Equatable
Stack-resident mutable binary builder.
DynamicByteBuffer is one of four sibling classes in the Envzn text/binary type system:
- String — heap, immutable, text (sibling)
- DynamicString — heap, mutable, text (sibling)
- ByteBuffer — heap, immutable, opaque binary (sibling)
- DynamicByteBuffer — heap, mutable, binary (this file)
PURPOSE The binary builder. Mutate in place; snapshot to immutable ByteBuffer with toByteBuffer() when done. Mutating methods return STATUS only — no chaining.
ACCESS IDIOMS
Subscript read .data[i] returns binary. Subscript write at HWM
(.data[.data.length] = v) extends; in-range write replaces.
.data.length returns int32 (current logical length). .data.clear()
resets length to 0.
kernel/src/DynamicByteBuffer.ev:39
Constructors
INIT()
CONSTRUCTORS / CLEANUP
INIT(int64 reserve)
Pre-size the builder to reserve bytes — one allocation instead of the
growth schedule. that is
reserveExact, and the substrate's resize already handles the INLINE
spill. reserveExact and not reserve because this is a STATED size,
not the incremental hint append uses.
INIT(REFERENCE REFERENCE binary[] origin)
Construct from a raw byte array, COPYING it. DynamicString has had a raw-array constructor all along; the byte builder had none, so the only way in was append-in-a-loop.
REFERENCE, not MOVE: the class allocates its own storage rather than
adopting the caller's. That is what every kernel class does — the last
MOVE constructor in this family, ByteBuffer's, was retired in Phase 2.
INIT(REFERENCE REFERENCE DynamicByteBuffer other)
Copy-construct from another DynamicByteBuffer. Every owning class in the text/binary family now has a same-type copy constructor: String, ByteBuffer, DynamicString and DynamicByteBuffer.
REFERENCE, so the source is borrowed and this instance allocates its
own storage. It does NOT collide with the other constructors here — they
differ in parameter TYPE, which C++ overload resolution separates. What
it cannot separate is one type under MOVE against REFERENCE; that is
why there is no MOVE companion anywhere in this family.
INIT(REFERENCE REFERENCE ByteBuffer other)
Methods
METHOD clone() RETURNS DynamicByteBuffer
CLONE — hand-written. Constructs a fresh DynamicByteBuffer and re-appends every byte through the public append() method, so we stay on the same write path as ordinary growth (no cross- instance private-field access). Mirrors DynamicString.clone().
METHOD isEmpty() RETURNS boolean
INSPECTION
METHOD size() RETURNS int64
METHOD length() RETURNS int64
Byte count — the same number size() returns. On the byte axis the two
coincide, because a byte IS the storage unit; on the text axis they
differ (String.length() counts code points, String.size() counts
UTF-8 bytes). Both names exist on all six text/binary classes so a
caller never has to remember which axis they are holding.
METHOD data() RETURNS REFERENCE binary[]
Non-owning REFERENCE to the underlying binary[512+]
storage. Mirror of ByteBuffer.data() (§11.6 lifetime
elision applies); canonical use is at a FOREIGN BIND call
site that needs to expose the byte storage to a C function
without copying.
METHOD iterator() RETURNS ByteBufferIterator
Snapshot iterator over the current bytes. Returns the shared ByteBufferIterator (the value-form ValueIterator OF binary used by ByteBuffer too): it copies the bytes in at construction, so the walk stays stable even if this mutable buffer is appended to afterward.
METHOD equals(DynamicByteBuffer other) RETURNS boolean
COMPARISON
Value equality, delegated to the value array's own equals. That is not
merely shorter: for an element type with a unique object representation
the array compares with a single length-checked memcmp (one SIMD
compare) where this method used to walk element by element. Measured
2026-08-27 at 6x faster on 64 elements and 11-14x from 1K up, with
identical results across an 8-cell differential matrix — including the
float guard, since the array deliberately does NOT memcmp an arithmetic
element type (-0.0 == +0.0, NaN != NaN would come out wrong).
METHOD isLessThan(DynamicByteBuffer other) RETURNS boolean
Use the lexicographic comparison below.
METHOD compareTo(REFERENCE REFERENCE DynamicByteBuffer other) RETURNS int32
Lexicographic byte comparison. Shorter buffer orders before longer when prefixes match. Returns -1 / 0 / 1.
METHOD find(REFERENCE REFERENCE DynamicByteBuffer needle) RETURNS | int64
Searchable — first index at which needle's bytes occur in this
buffer, else FAILURE. Snapshots both sides to immutable ByteBuffers
and forwards to ByteBuffer.find — the one canonical byte scan.
METHOD hash() RETURNS uint64
HASHING FNV-1a 64-bit hash. Matches String.hash() / DynamicString.hash() / ByteBuffer.hash() byte-for-byte for identical content.
METHOD hash(uint64 seed) RETURNS uint64
METHOD contains(REFERENCE REFERENCE ByteBuffer needle) RETURNS boolean
CONTENT LOOKUP
METHOD startsWith(REFERENCE REFERENCE ByteBuffer prefix) RETURNS boolean
METHOD endsWith(REFERENCE REFERENCE ByteBuffer suffix) RETURNS boolean
METHOD find(REFERENCE REFERENCE ByteBuffer needle, int64 fromIndex) RETURNS | int64
First-occurrence index at or after fromIndex. Pipe-XOR:
FAILURE on out-of-range fromIndex or when needle does not
occur from that point onward.
METHOD count(REFERENCE REFERENCE ByteBuffer needle) RETURNS int64
Non-overlapping occurrence count. Empty needle returns 0 (avoids infinite-match scenario).
MODIFY METHOD append(binary b) RETURNS STATUS
MUTATIONS — return STATUS, mutate receiver in place
MODIFY METHOD append(REFERENCE REFERENCE ByteBuffer bb) RETURNS STATUS
MODIFY METHOD append(MOVE DynamicByteBuffer dbb) RETURNS STATUS
MODIFY METHOD prepend(binary b) RETURNS STATUS
MODIFY METHOD prepend(REFERENCE REFERENCE ByteBuffer bb) RETURNS STATUS
MODIFY METHOD setAt(int64 i, binary b) RETURNS STATUS
MODIFY METHOD clear() RETURNS STATUS
MODIFY METHOD truncate(int64 n) RETURNS STATUS
Drop bytes from the END until size() == n. FAILURE if n exceeds current size or n < 0 (no-grow contract).
MODIFY METHOD dropFirst(int64 n) RETURNS STATUS
Drop the first n bytes. O(size - n) shift left.
METHOD subBuffer(int64 start, int64 length) RETURNS ByteBuffer
A NEW ByteBuffer over [start, start+length). PURE — the receiver is
untouched. Same name, same meaning and same shape as
ByteBuffer.subBuffer, which is what the vertical rule requires — TOTAL
since Phase 3, throwing on a bad range like String.substring does.
METHOD __op_index__(int64 i) RETURNS binary
Byte at i. Throws IndexOutOfBoundsError out of range — the bounds
check lives in the value array's own subscript, exactly as ByteBuffer's
and String's do. The mutable class had no index operator at all, so
dbb[i] did not compile while bb[i] did.
METHOD view(int64 start, int64 length) RETURNS ByteBufferView
Viewable — a non-owning, bounded window over this builder's bytes
without copying. The mutable half of the family had no view() until
Phase 5, because a view into a GROWABLE buffer is only safe once the
analyzer refuses to mutate the source while the view is live.
That lock now exists: a view() on a named variable arms the same
per-source mutation lock an iterator() does (E5003), so appending to
or truncating this builder while a view over it is in scope is a
compile-time error rather than a stale window.
What the lock does NOT cover is the view outliving this object — that
is gh #248, it predates this method, and it already applies to
ByteBuffer.view().
METHOD lastIndexOf(REFERENCE REFERENCE ByteBuffer needle) RETURNS | int64
Index of the LAST occurrence of needle, pipe-XOR. Scans in place over
the backing rather than snapshotting to a ByteBuffer first — the same
choice find already makes here, and the one DynamicString.find still
does not. Once view() reaches the mutable classes this can forward to
ByteBufferView.lastIndexOf like the immutable peer does.
METHOD find_first_of(REFERENCE REFERENCE ByteBuffer set) RETURNS | int64
Index of the first byte that IS a member of set, pipe-XOR.
METHOD find_first_not_of(REFERENCE REFERENCE ByteBuffer set) RETURNS | int64
Index of the first byte that is NOT a member of set, pipe-XOR.
METHOD concat(REFERENCE REFERENCE ByteBuffer other) RETURNS ByteBuffer
A NEW ByteBuffer holding this buffer's bytes followed by other's.
Pure: this buffer is unchanged. The mutating sibling is append.
MODIFY METHOD padStart(int64 targetLength, binary padByte) RETURNS STATUS
Pad the FRONT with padByte until the buffer is targetLength long.
Already at or above the target is SUCCESS and a no-op. int64 rather
than the uint64 DynamicString still uses — the sign split across these
two classes is normalised in Phase 2, and a new method should not be
born on the wrong side of it.
MODIFY METHOD padEnd(int64 targetLength, binary padByte) RETURNS STATUS
Pad the END with padByte until the buffer is targetLength long.
MODIFY METHOD replace(REFERENCE REFERENCE ByteBuffer needle, REFERENCE REFERENCE ByteBuffer replacement) RETURNS STATUS
Replace every occurrence of needle with replacement. Empty
needle returns FAILURE (avoids infinite-substitution).
METHOD toByteBuffer() RETURNS ByteBuffer
BRIDGE OUT — explicit snapshot to immutable ByteBuffer
CLASS DynamicString
IMPLEMENTS Countable, Cloneable, Hashable, Equatable
Stack-resident mutable text builder.
DynamicString is one of four sibling classes in the Envzn text/binary type system:
- String — heap, immutable, text (sibling)
- DynamicString — heap, mutable, text (this file)
- ByteBuffer — heap, immutable, opaque binary (sibling)
- DynamicByteBuffer — heap, mutable, opaque binary (sibling)
PURPOSE The text builder. Mutate in place; snapshot to immutable String with toString() when done.
API SHAPE The non-modifying surface mirrors String exactly: length / isEmpty / at / equals / compare / hash / hash(seed) / find / contains / count / split / splitLines / concat / iterator / clone / format / formatWith / toString.
The modifying surface is DynamicString's reason for existing: MODIFY toUpper / toLower / trim / trimStart / trimEnd / substring (in-place slice) / append (String / DynamicString / char32) / prepend (String / DynamicString / char32) / replace / padStart / padEnd. Mutating methods return STATUS.
STRING ↔ DynamicString BRIDGE
Both classes now carry UTF-32 codepoint storage. Cross-class
operations (INIT(String), equals(String), find(String, …),
toString()) are direct codepoint copies — no UTF-8 encode/decode.
kernel/src/DynamicString.ev:52
Constructors
INIT()
INIT(int64 reserve)
Pre-size the builder to reserve code points, so a build of known
length performs ONE allocation instead of walking the growth schedule.
This was a NO-OP: it accepted the number and discarded it, on the
grounds that "pre-spilling to capacity N would need an storage reserve
method". reserveExact is that method — the substrate's resize already
takes INLINE and handles the spill, so the storage was never the
obstacle. Every caller that believed it was pre-sizing has been growing
from zero.
reserveExact, not reserve: this is a STATED size. reserve is the
incremental hint append uses and deliberately leaves 79% headroom.
INIT(REFERENCE REFERENCE String s)
Bridge ctor — start with the contents of an immutable String. Both storages are UTF-32 code points, so this is a direct copy with no encode step.
REFERENCE: the source is borrowed and survives construction. It used
to be taken by value, which handed the constructor a whole String it
only ever read from.
INIT(REFERENCE REFERENCE DynamicString other)
Copy-construct from another DynamicString. Every owning class in the text/binary family now has a same-type copy constructor: String, ByteBuffer, DynamicString and DynamicByteBuffer.
REFERENCE, so the source is borrowed and this instance allocates its
own storage. It does NOT collide with the other constructors here — they
differ in parameter TYPE, which C++ overload resolution separates. What
it cannot separate is one type under MOVE against REFERENCE; that is
why there is no MOVE companion anywhere in this family.
INIT(REFERENCE REFERENCE char32[] origin)
Construct from a raw code-point array, COPYING it. REFERENCE, so the
source is borrowed and the builder allocates its own storage — the same
rule the other three owning classes in this family follow.
Methods
METHOD clone() RETURNS DynamicString
Deep copy. Returns a fresh DynamicString with the same codepoints. Implements the Cloneable contract.
METHOD isEmpty() RETURNS boolean
O(1) emptiness test.
METHOD length() RETURNS int64
Code-point count, O(1). Storage is one codepoint per slot.
METHOD size() RETURNS int64
UTF-8 byte count of the stored text — distinct from length() (the code-point count). Storage is UTF-32 code points (Phase 8.7), so size() sums each code point's UTF-8 width per RFC 3629 (the same widths UTFCodec.encodeToUTF8 emits): U+0000–007F → 1 byte, U+0080–07FF → 2, U+0800–FFFF → 3, ≥ U+10000 → 4.
METHOD __op_index__(int64 i) RETURNS char32
Code point at index i. Declared as operator [] so the
only access syntax is s[i] — there is no public at()
method on DynamicString. Throws IndexOutOfBoundsError if i is out
of range — bounds check lives in _ValueArray<char32_t>::operator[].
METHOD equals(DynamicString other) RETURNS boolean
Compare codepoint content against another DynamicString.
Value equality, delegated to the value array's own equals. That is not
merely shorter: for an element type with a unique object representation
the array compares with a single length-checked memcmp (one SIMD
compare) where this method used to walk element by element. Measured
2026-08-27 at 6x faster on 64 elements and 11-14x from 1K up, with
identical results across an 8-cell differential matrix — including the
float guard, since the array deliberately does NOT memcmp an arithmetic
element type (-0.0 == +0.0, NaN != NaN would come out wrong).
METHOD equals(String other) RETURNS boolean
Compare codepoint content against a String. Direct compare — both storages are UTF-32 codepoints.
METHOD isLessThan(DynamicString other) RETURNS boolean
Lexicographic codepoint comparison against another DynamicString.
METHOD compareTo(String other) RETURNS int32
Lexicographic codepoint comparison against a String. Returns -1/0/+1.
METHOD compareTo(char32[] otherData) RETURNS int32
Lexicographic codepoint comparison against a String. Returns -1/0/+1.
METHOD hash() RETURNS uint64
FNV-1a 64-bit hash over the UTF-32 codepoints. Matches String.hash() for identical text content (both fold over the same codepoint sequence).
METHOD hash(uint64 seed) RETURNS uint64
FNV-1a 64-bit hash with caller-supplied seed.
METHOD find(REFERENCE REFERENCE DynamicString needle) RETURNS | int64
First occurrence of needle in this DynamicString. Pipe-XOR:
SUCCESS yields the codepoint index of the match; FAILURE means no
match. This is the Searchable contract (case-sensitive). The scan is
the one canonical char32 scan in StringView — DynamicString builds a
full-span view over its own codepoints and forwards. A String-needle
overload is provided for convenience.
METHOD find(REFERENCE REFERENCE DynamicString needle, boolean caseMatch) RETURNS | int64
As find, with ASCII case-fold when caseMatch == FALSE. Views both
sides in place and runs the one canonical char32 scan in StringView.
This method used to snapshot BOTH sides to immutable Strings — two full
copies per call — on the belief that a char32[128+] field could not
bind to a REFERENCE char32[] parameter. E3 (2026-08-27) disproved it:
since SBO was removed both spellings are the same EvArray<char32_t>.
METHOD find(REFERENCE REFERENCE String needle) RETURNS | int64
Convenience: search for a String needle.
METHOD find(REFERENCE REFERENCE String needle, boolean caseMatch) RETURNS | int64
As find, with ASCII case-fold when caseMatch == FALSE. Views the
storage in place — no snapshot — and forwards to the canonical scan,
which owns the native fast path and its bounds re-derivation (gh #197).
METHOD contains(REFERENCE REFERENCE String needle) RETURNS boolean
True iff needle occurs at least once (case-sensitive).
METHOD contains(REFERENCE REFERENCE String needle, boolean caseMatch) RETURNS boolean
METHOD count(REFERENCE REFERENCE String needle) RETURNS int64
Non-overlapping occurrence count of needle (case-sensitive).
Empty needle returns 0.
METHOD count(REFERENCE REFERENCE String needle, boolean caseMatch) RETURNS int64
METHOD view(int64 start, int64 len) RETURNS StringView
Viewable — a non-owning, bounded window over this builder's code points
without copying. The mutable half of the family had no view() until
Phase 5, because a view into a GROWABLE buffer is only safe once the
analyzer refuses to mutate the source while the view is live.
That lock now exists: a view() on a named variable arms the same
per-source mutation lock an iterator() does (E5003), so appending to
or truncating this builder while a view over it is in scope is a
compile-time error rather than a stale window.
What the lock does NOT cover is the view outliving this object — that
is gh #248, it predates this method, and it already applies to
String.view().
METHOD startsWith(REFERENCE REFERENCE String prefix) RETURNS boolean
True iff this builder begins with prefix.
Builds a StringView directly over .data — no snapshot. E3 (2026-08-27)
established that a char32[128+] field binds to a REFERENCE char32[]
parameter: both lower to EvArray<char32_t>, and have since SBO was
removed. The view is a local, consumed before this method returns, and
nothing mutates the builder in between — so it needs none of the
mutation-lock machinery that a PUBLIC view() will require, and it
costs no copy. find above uses the same shape.
METHOD endsWith(REFERENCE REFERENCE String suffix) RETURNS boolean
True iff this builder ends with suffix.
METHOD lastIndexOf(REFERENCE REFERENCE String needle) RETURNS | int64
Index of the LAST occurrence of needle, pipe-XOR.
METHOD find_first_of(REFERENCE REFERENCE String set) RETURNS | int64
Index of the first code point that IS a member of set, pipe-XOR.
METHOD find_first_not_of(REFERENCE REFERENCE String set) RETURNS | int64
Index of the first code point that is NOT a member of set, pipe-XOR.
METHOD split(REFERENCE REFERENCE String delim) RETURNS String[]
Split on delim into an array of Strings. Empty delim
returns a single-element array containing a snapshot of this
DynamicString. Adjacent delimiters produce empty pieces; a
trailing delimiter produces an empty trailing piece. Receiver
is not mutated.
METHOD splitLines() RETURNS String[]
Split into lines on any of the Unicode line boundaries (same set as String.splitLines): LF, VT, FF (0x0A-0x0C), FS, GS, RS (0x1C-0x1E), CR and CR-LF (one boundary), NEL (U+0085), LS (U+2028), PS (U+2029). Terminators are NOT included; a trailing terminator does not yield a trailing empty piece. Receiver not mutated.
METHOD concat(REFERENCE REFERENCE String other) RETURNS String
Build a fresh String from this DynamicString's content followed
by other's. Non-modifying — receiver and other are both
unchanged. (To append in place, use the modifying append(String)
below.)
METHOD iterator() RETURNS ValueIterator[char32]
Snapshot to a String first, then return its iterator. The snapshot is an O(n) codepoint copy; subsequent iteration is O(1) per codepoint against the contiguous char32[] storage.
METHOD format(opaque args...) RETURNS String
Snapshot to a String first, then delegate to String.format — keeps the format-engine source-of-truth on String and avoids duplicating template-walking logic across the two classes.
METHOD formatWith(BinaryMode mode, opaque[] args) RETURNS String
METHOD toString() RETURNS String
BRIDGE OUT — explicit snapshot to immutable String ----------- Direct codepoint copy into a fresh String. No decode step.
MODIFY METHOD toUpper() RETURNS STATUS
─── MODIFYING METHODS ───────────────────────────────────────── Mutate receiver in place; return STATUS. ASCII uppercase fold, in place. Codepoints >= 0x80 pass through. Full Unicode case mapping is V2.
MODIFY METHOD toLower() RETURNS STATUS
ASCII lowercase fold, in place.
MODIFY METHOD toTitleCase() RETURNS STATUS
ASCII title-case fold, in place: the first character of each word is
uppercased and the remainder of the word lowercased. Word boundaries are
ASCII whitespace, -, and ' (see isTitleBoundary). Codepoints >= 0x80
pass through uncased but do not break a word. Full Unicode case mapping is
V2 — the same limit toUpper / toLower carry.
MODIFY METHOD trim() RETURNS STATUS
Strip ASCII whitespace at both ends, in place.
MODIFY METHOD trimStart() RETURNS STATUS
MODIFY METHOD trimEnd() RETURNS STATUS
METHOD substring(int64 start, int64 len) RETURNS String
A NEW String over [start, start+length). PURE — the receiver is
untouched. Matches String.substring, which throws on a bad range
rather than returning a STATUS, so this does too.
MODIFY METHOD append(REFERENCE REFERENCE String s) RETURNS STATUS
BUILDER MUTATIONS — append ------------------------------------
MODIFY METHOD append(MOVE DynamicString d) RETURNS STATUS
MODIFY METHOD append(char32 c) RETURNS STATUS
Append one Unicode code point. Tier-1 text check rejects a noncharacter / surrogate / control code point with FAILURE.
MODIFY METHOD setAt(int64 i, char32 c) RETURNS STATUS
Assign the code point at i. Pipe-XOR on the index, and gated by the
same Tier-1 text check append(char32) applies — a DynamicString may
not be mutated into holding a surrogate, noncharacter or control.
int64, not the uint64 DynamicByteBuffer.setAt still takes; Phase 2
normalises that split and a new method should not join the wrong side.
MODIFY METHOD clear() RETURNS STATUS
Empty the builder, keeping its capacity. The mutable text class had no way to be cleared at all, while its byte peer did.
MODIFY METHOD truncate(int64 n) RETURNS STATUS
Drop code points from the END until length() == n. FAILURE if n
exceeds the current length or is negative — this never grows.
MODIFY METHOD dropFirst(int64 n) RETURNS STATUS
Drop the first n code points. O(length - n) shift left.
MODIFY METHOD prepend(REFERENCE REFERENCE String s) RETURNS STATUS
BUILDER MUTATIONS — prepend -----------------------------------
MODIFY METHOD prepend(MOVE DynamicString d) RETURNS STATUS
MODIFY METHOD prepend(char32 c) RETURNS STATUS
MODIFY METHOD replace(REFERENCE REFERENCE String needle, REFERENCE REFERENCE String replacement) RETURNS STATUS
BUILDER MUTATIONS — replace -----------------------------------
Replace every occurrence of needle with replacement, in
place. Empty needle returns FAILURE (avoids infinite-substitution).
MODIFY METHOD padStart(int64 targetLength, char32 padChar) RETURNS STATUS
BUILDER MUTATIONS — padStart / padEnd -------------------------
Left-pad with padChar until the receiver is targetLength
CODE POINTS long. Already-longer receivers are left alone.
MODIFY METHOD padEnd(int64 targetLength, char32 padChar) RETURNS STATUS
Right-pad with padChar until the receiver is targetLength
CODE POINTS long.
CLASS EnumReflection
The generic enum name<->value capability.
An enum's case names are per-enum compile-time data the compiler alone knows, so
the compiler emits them: IDENTITY(SomeEnum).cases yields an EnumKind[], one
pair per case (the "cases as instances" model). This namespace is the generic
LOGIC over that table — written once, reused by anything that needs an enum's
name<->value mapping (serde, logging, config/UI, round-tripping through any
format). No feature synthesizes its own per-enum lookups; they all call here.
EnumKind (a case's name + value) lives in structs.ev next to Identity: it is
referenced by both Identity.cases and this namespace, and a STRUCT may not
share a file with a NAMESPACE (E9008) — so the ">1 consumer -> structs.ev" rule
applies.
The lookups walk cases by index (FOR i = 0 UNTIL cases.length): FOR/IN over a
T[] value-array PARAM mis-emits cases->iterator() (arrow on a value) today, so
the index form is the working idiom here.
kernel/src/EnumReflection.ev:28
Methods
METHOD name(EnumKind[] cases, int32 value) RETURNS String
value -> case name; the empty String when no case has that value.
METHOD valueForName(EnumKind[] cases, String target) RETURNS | int32
case name -> value; FAILURE when no case carries that name.
METHOD checkValue(EnumKind[] cases, int32 candidate) RETURNS | int32
Validate a value names a live case; echoes it back, else FAILURE.
CLASS Error
Abstract base class for the kernel error hierarchy.
Every throwable kernel error class extends Error (directly or
transitively). User-defined error classes in consumer modules
also extend Error to participate in RECOVER dispatch. The base
class carries a single message field; subclasses add their own
fields as needed (see ErrorTypes.ev for the kernel-shipped
concrete classes).
────────────────────────────────────────────────────────────────── PANIC / RECOVER semantics ──────────────────────────────────────────────────────────────────
Every Error instance is THROWable. RECOVER clauses match by class type (and any subclass of the matched type — covariant). The base class is the catch-all:
TRY {
doWork()
} RECOVER (FileError fe) {
logError(fe.message)
} RECOVER (Error e) {
// any error not handled above
logError("unhandled: $1")->format(e.message)
}
The matching order is top-to-bottom; subclass clauses must precede their superclasses to be reachable.
────────────────────────────────────────────────────────────────── C++ INTEROP ──────────────────────────────────────────────────────────────────
At the C++ layer, Error extends std::exception so kernel
errors propagate cleanly through any C++ stack frame the runtime
traverses (FOREIGN-call-throwing, runtime panics, etc.).
kernel/src/Error.ev:49
Constructors
INIT()
INIT(MOVE char32[] message)
INIT(String message)
Bridge from a String arg — copy the source's code points
into this .message. The per-subclass INIT(String message)
pattern uses SUPER(message) which routes here.
INIT(String message, String cause)
Chaining INIT — set both the message and the triggering error's
message as the cause. Errors are chained by passing a triggering
error's message as the cause of a new one (I.M.ii). A subclass that
wants to expose chaining declares its own INIT(String, String) and
routes here with SUPER(message, cause).
INIT(String message, String cause, int32 code)
Chaining INIT with an explicit numeric code.
Methods
METHOD hasCause() RETURNS boolean
Whether a triggering cause is attached (I.M.ii).
METHOD getErrorCode() RETURNS int32
The numeric error code; 0 means "no code" (I.M.ii).
CLASS FastIntHasher
IMPLEMENTS Hasher
FastIntHasher implements Hasher OF K (Hasher.ev) with an IDENTITY hash: the key widened into
a uint64, no mixing. For integer keys this maps the benchmark's sequential keys
(0..N-1) to sequential slots under the pow2 & mask index, so every probe is a
cache-friendly prefetch — the ~1.5 ns/lookup path that C and C++ take (their
std::hash<int> is also identity). char widens its code point. The WordKey
qualifier admits no float, so nothing is reinterpreted here; a 128-bit key keeps
its low half, which collides only above bit 63 and is separated on lookup by
Equatable.
The tradeoff is adversarial weakness: an attacker who controls the keys can force
collisions (which is why Rust/Swift default to a slow keyed SipHash). FastIntHasher
is therefore OPT-IN — the default stays DefaultHasher (FNV), and untrusted keys
use SipHasher / ProHashDictionary. It slots into the existing H type
parameter, so it is zero dictionary-API change: a caller writes
Dictionary[int32, V, FastIntHasher[int32]] when the keys are trusted integers.
Constrained to WordKey — the primitives whose value IS an integer bit pattern
(identity on a class key is meaningless, and a float's bits are not its value): a
primitive is Hashable at the type level, so it satisfies the Hasher OF K bound
without the IMPLEMENTS Hashable class path that DefaultHasher needs.
See: Hasher.ev (the interface), DefaultHasher.ev (the FNV default), SipHasher.ev
(the keyed DoS-resistant hasher).
FastIntHasher OF K — identity hasher (widened key) for trusted primitive keys.
The ~1.5 ns C-class path; opt in via the dictionary's H slot.
kernel/src/FastIntHasher.ev:38
Constructors
INIT()
Methods
METHOD hash(REFERENCE REFERENCE K key) RETURNS uint64
METHOD clone() RETURNS FastIntHasher[K]
Stateless — a fresh instance behaves identically. Cloneable so a dictionary
that stores the hasher CONCRETELY (H hasher, monomorphised) can deep-copy
itself (clone()) by cloning its hasher.
CLASS File
IMPLEMENTS Readable, Writable
Full file-interaction class over the POSIX byte-I/O surface.
Pure Envzn making C system calls (no C++ shim). Two fd lifecycle modes on
ONE class via an optional fd field:
• Persistent — open() (or a named opener) stores the fd; subsequent reads/writes/append/seek reuse it; close() releases it (CLEANUP closes any still-open fd on scope exit). • Per-operation — never call open(): the convenience methods open their own fd, do the op, and close. They are ADAPTIVE: if a persistent fd is open they use it, otherwise they open-and-close.
Thread safety: a per-File Lock (gate) guards the shared OS fd offset in
persistent mode via raw acquire/release around the critical section
(SYNCHRONIZED cannot be used inside pipe-shaped methods — E6051). Positioned
I/O (readAt/writeAt → pread/pwrite) is offset-stable and needs no lock.
Failure model: open and every File-UNIQUE syscall carry SETS_ERRNO(-1), so
failures return a STATUS with the real strerror message + errno code.
read/write are co-bound (identically) in Stdio.ev, so they keep the plain
signature — their errors are detected by the (<0) return.
Pipe-XOR idioms (kernel-first use of SETS_ERRNO): a pipe-shaped
(X | STATUS status) method returns via the NAMED slot (status = FAILURE(…)
RETURN (status); slot = local RETURN (slot)), binds $RETURNED to a LOCAL
in the THEN arm (never slot = $RETURNED), and uses $!/$# only inside the
ELSE arm.
Text I/O is UTF-8: String is the decoded char32 form; bytes on disk are UTF-8, transcoded at the boundary via TextConverter (String↔ByteBuffer). Byte I/O is binary[] (Readable/Writable contract).
kernel/src/File.ev:85
Fields
String pathint32 fdLock gate
Constructors
INIT(String path)
Methods
MODIFY METHOD openRead() RETURNS STATUS
Open read-only (O_RDONLY).
MODIFY METHOD openWrite() RETURNS STATUS
Open for writing, creating + truncating (O_WRONLY|O_CREAT|O_TRUNC, 0644).
MODIFY METHOD openAppend() RETURNS STATUS
Open for appending, creating if absent (O_WRONLY|O_CREAT|O_APPEND, 0644).
MODIFY METHOD openReadWrite() RETURNS STATUS
Open for reading + writing, creating if absent (O_RDWR|O_CREAT, 0644).
MODIFY METHOD openExclusive() RETURNS STATUS
Open exclusively — fails if the file exists (O_WRONLY|O_CREAT|O_EXCL).
METHOD isOpen() RETURNS boolean
TRUE while a persistent fd is open.
MODIFY METHOD close() RETURNS STATUS
Close the persistent fd (no-op if not open).
MODIFY METHOD read() RETURNS | binary[]
Read the entire file's bytes (Readable). Adaptive: persistent fd (gate-guarded) if open, else opens O_RDONLY, drains, closes.
MODIFY METHOD write(binary[] content) RETURNS | int64
Write every byte of content, replacing contents (Writable). Adaptive.
The extent is content.length — see the Writable contract (gh #195).
MODIFY METHOD appendBytes(binary[] content) RETURNS | int64
Append length bytes to the end (creating if absent). Adaptive.
METHOD readAt(int64 offset, int32 max) RETURNS | binary[]
Read up to max bytes at absolute offset (pread). Persistent fd.
MODIFY METHOD writeAt(int64 offset, binary[] content) RETURNS | int64
Write length bytes at absolute offset (pwrite). Persistent fd.
MODIFY METHOD readText() RETURNS | String
Read the whole file as UTF-8 text. Invalid UTF-8 → FAILURE.
MODIFY METHOD writeText(String content) RETURNS STATUS
Write content as UTF-8 text, replacing the file's contents.
MODIFY METHOD appendText(String content) RETURNS STATUS
Append content as UTF-8 text to the end of the file.
MODIFY METHOD seekFromStart(int64 offset) RETURNS | int64
Move the cursor to offset bytes from the START of the file.
Returns the resulting absolute position.
MODIFY METHOD seekFromCurrent(int64 offset) RETURNS | int64
Move the cursor offset bytes from where it currently sits — negative
moves backward. Returns the resulting absolute position.
MODIFY METHOD seekFromEnd(int64 offset) RETURNS | int64
Move the cursor offset bytes from the END of the file — 0 is the end
itself and negative moves back into the content. Returns the resulting
absolute position. (A POSITIVE offset here is legal and seeks PAST the
end; writing there creates a hole. That is lseek's behaviour and this
wrapper does not change it.)
MODIFY METHOD tell() RETURNS | int64
Current cursor position (lseek SEEK_CUR, no move).
MODIFY METHOD size() RETURNS | int64
File size in bytes — seek to end then restore the cursor (no stat). Nested so no top-level statement follows a pipe consume.
MODIFY METHOD truncateTo(int64 length) RETURNS STATUS
Truncate (or extend) the file to length bytes (ftruncate).
MODIFY METHOD flush() RETURNS STATUS
Flush buffered writes to disk (fsync).
MODIFY METHOD lockExclusive(boolean blocking) RETURNS STATUS
Acquire an exclusive advisory lock. blocking FALSE → non-blocking.
MODIFY METHOD lockShared(boolean blocking) RETURNS STATUS
Acquire a shared advisory lock.
MODIFY METHOD unlockFile() RETURNS STATUS
Release any advisory lock held on the file (LOCK_UN).
METHOD exists() RETURNS boolean
TRUE if the path exists (access F_OK).
METHOD isReadable() RETURNS boolean
TRUE if readable by the caller (access R_OK).
METHOD isWritable() RETURNS boolean
TRUE if writable by the caller (access W_OK).
METHOD isExecutable() RETURNS boolean
TRUE if executable by the caller (access X_OK).
MODIFY METHOD deleteFile() RETURNS STATUS
Delete the file (unlink). Named deleteFile — delete is a C++ keyword.
MODIFY METHOD renameTo(String dest) RETURNS STATUS
Rename / move the file to dest (rename).
MODIFY METHOD setPermissions(int32 mode) RETURNS STATUS
Change the file's permission bits (chmod); mode is an octal value.
CLASS FileError
EXTENDS Error
Kernel error class. Thrown by File / Path methods on I/O failure. Examples: file not found, permission denied, disk full, directory creation failed, recursive remove encountered an undeletable entry.
Subclass of Error; adds no new fields — the type name itself is the discriminant for RECOVER dispatch.
kernel/src/FileError.ev:19
Constructors
INIT(String message)
CLASS FloatBigInt
kernel/src/FloatBigInt.ev:25
Fields
int32 MAX_LIMBS
Constructors
INIT()
Methods
METHOD isZero() RETURNS boolean
MODIFY METHOD setSmall(uint64 v) RETURNS void
Set to a value that fits in 64 bits.
MODIFY METHOD mulAddSmall(uint32 mul, uint32 add) RETURNS void
self := self * mul + add (mul, add each fit in 32 bits; mul >= 1).
METHOD pow10Small(int32 e) RETURNS uint32
10^e for e in 0..8 (fits uint32; e<=8 keeps it under 10^9 < 2^32).
MODIFY METHOD mulPow10(int32 k) RETURNS void
self := self * 10^k (k >= 0). Chunked by 10^9 per pass (the largest power of ten a uint32 multiplier holds), then a final <10^9 remainder.
MODIFY METHOD setPow10(int32 k) RETURNS void
self := 10^k (k >= 0). The denominator-construction path.
MODIFY METHOD shiftLeftBits(int32 n) RETURNS void
self := self * 2^n
MODIFY METHOD appendLimbHi(uint32 v) RETURNS void
Append a limb as the new most-significant limb (internal builder for cloning self into a scratch value without passing SELF).
METHOD bitLength() RETURNS int32
Number of significant bits (0 for zero). floor(log2(self)) + 1.
METHOD cloneBig() RETURNS FloatBigInt
A fresh independent copy of self (reads own limbs, builds the copy through its own appendLimbHi — never passes SELF).
METHOD cmp(FloatBigInt other) RETURNS int32
Ordering vs another big int: -1 (self < other), 0 (equal), 1 (self > other).
MODIFY METHOD addBig(FloatBigInt other) RETURNS void
self := self + other
MODIFY METHOD subBig(FloatBigInt other) RETURNS void
self := self - other (precondition: self >= other)
CLASS FloatConverter
CONVERSIONS host for float→String (V1 Part F piece 4).
Split out of NumberConverter so that NumberConverter (integer/boolean→String +
all String→number parsing) depends only on String/DynamicString and can be
ordered EARLY — before foundational classes like Array — letting them use
x INTO String. The float→decimal path is the one piece that needs the Ryu
engine (FloatFormat), so it lives here and orders after FloatFormat; its users
(Formatter, Math, MeasuringTimer) are all later classes.
float32 INTO String — lossless, total float64 INTO String — lossless, total
The float→bits reinterpret crosses the FOREIGN boundary (no pure-Envzn expression); everything past that is integer-only arithmetic in FloatFormat.
kernel/src/FloatConverter.ev:28
CLASS FloatFormat
Shortest-round-trippable float -> decimal string.
Portions derive from Ryu (Copyright 2018 Ulf Adams), used under the Boost Software License 1.0 — see LICENSE.md, Third-Party Notices.
Kernel-internal numeric formatter. Users never touch it directly;
FloatConverter's float→String operators delegate
here. Modelled on UTFCodec.ev — a self-contained numeric algorithm
in its own file behind a clean interface.
ALGORITHM
Implements the Ryu algorithm (Ulf Adams, "Ryū: fast float-to-string
conversion", PLDI 2018) — the shortest decimal string that round-
trips back to the exact input float. Pure Envzn: no FOREIGN, no
native shim. This replaces an earlier lossy std::to_string shim
(which gave a fixed 6 decimal places).
Re-implemented from the published algorithm rather than transcribed
from the reference C — with one exception: the precomputed constant
tables are copied verbatim, and carry per-table provenance notes in
the body. The portable RYU_OPTIMIZE_SIZE variant is the
model: it computes the pow5 / inverse-pow5 magic values at runtime
from a tiny base table rather than carrying ~1300 uint64 table
constants, and uses a hand-rolled 64x64->128 multiply (no uint128).
Upstream: github.com/ulfjack/ryu, offered under Apache-2.0 or Boost-1.0.
Envzn elects Boost-1.0 — see LICENSE.md, Third-Party Notices.
INTERFACE — bits in, String out
formatF64(uint64 bits) / formatF32(uint32 bits) take the raw
IEEE-754 bits of the float, never a float value. The float<->bits
reinterpret is the caller's job. Consequence: FloatFormat
is pure integer code with zero dependency on a float type or any
bitcast primitive.
kernel/src/FloatFormat.ev:53
Fields
uint32 LOG2_OF_5_MULTuint32 LOG2_OF_5_SHIFTint32 LOG2_POW5_MAX_Euint32 LOG10_OF_2_MULTuint32 LOG10_OF_2_SHIFTint32 LOG10_POW2_MAX_Euint32 LOG10_OF_5_MULTuint32 LOG10_OF_5_SHIFTint32 LOG10_POW5_MAX_Eint32 DOUBLE_MANTISSA_BITSint32 DOUBLE_EXPONENT_BITSint32 DOUBLE_BIASint32 FLOAT_MANTISSA_BITSint32 FLOAT_EXPONENT_BITSint32 FLOAT_BIASint32 DOUBLE_POW5_INV_BITCOUNTint32 DOUBLE_POW5_BITCOUNTint32 FLOAT_POW5_INV_BITCOUNTint32 FLOAT_POW5_BITCOUNTint32 POW5_TABLE_SIZE
Methods
METHOD float64toDecimal(uint64 bits) RETURNS FloatingDecimal64
Render the IEEE-754 double whose raw bits are bits as its
shortest round-trippable decimal String.
METHOD floatingDecimalToString(FloatingDecimal64 fd, boolean isNegative) RETURNS String
Turn a FloatingDecimal64 carrier into its final String, per the Phase 4 format-policy (floatformat-ryu-plan.md): Python-style threshold (sciExp < -4 OR >= 16), compact exponent (no '+', no leading zero), no trailing '.0', sign of zero dropped, special-case spellings "NaN"/"infinity"/"-infinity".
METHOD formatF64(uint64 bits) RETURNS String
One-shot: bits → FloatingDecimal64 → String. Convenience wrapper that chains float64toDecimal + floatingDecimalToString, extracting the sign bit from the raw bits.
METHOD float32toDecimal(uint32 bits) RETURNS FloatingDecimal32
Render the IEEE-754 float whose raw bits are bits as its
shortest round-trippable decimal. Same structural shape as
float64toDecimal above — re-read its STEP 1..5 comments
for the algorithm narrative; this body only flags the f32
specifics (narrower mantissa, narrower magic constants,
32-bit arithmetic throughout).
METHOD floatingDecimal32ToString(FloatingDecimal32 fd, boolean isNegative) RETURNS String
f32 sibling of floatingDecimalToString — identical logic; only the carrier type differs (FloatingDecimal32 vs FloatingDecimal64, uint32 vs uint64 mantissa). The shared digit-walking helpers (appendDigits, appendMantissaWithPoint, appendChars) take uint64 — uint32 mantissas widen at the call site.
METHOD formatF32(uint32 bits) RETURNS String
One-shot: bits → FloatingDecimal32 → String.
CLASS FloatParse
kernel/src/FloatParse.ev:14
Methods
METHOD parseF64(String s) RETURNS | float64
METHOD parseF32(String s) RETURNS | float32
CLASS FloatParseConverter
kernel/src/FloatParseConverter.ev:21
CLASS FloatParseResult
kernel/src/FloatParseResult.ev:27
Fields
int32 MAX_DIGITS
Constructors
INIT()
Methods
MODIFY METHOD pushDigit64(uint32 d) RETURNS void
Accumulate one decimal digit into the uint64 fast-path significand, flipping sig64Ovf the moment sig64·10+d would exceed uint64.
METHOD isValid() RETURNS boolean
Whether the last parse() produced a well-formed value (the FloatParse surface reads this to choose the success vs FAILURE arm).
METHOD charEqCI(char32 c, char32 lower) RETURNS boolean
ASCII case-insensitive char compare (lower is the lowercase letter).
METHOD isInfinityWord(String s, int64 start, int64 endp) RETURNS boolean
TRUE iff s[start, endp) spells "inf" or "infinity" (case-insensitive).
METHOD computeBitsN(int32 mantBits, int32 eMin, int32 biasAdd, int32 maxBiased, int32 signPos) RETURNS uint64
L4 — the exact float driver, generalized over target width. Produce the
correctly-rounded IEEE-754 bit pattern for the parsed (significand, exp10,
sign) via big-integer AlgorithmM: form V = sig·10^exp10 = N/D, scale to
[2^mantBits, 2^(mantBits+1)) tracking the binary exponent e2 (subnormal
floor at eMin), extract the mantissa by binary long-division, round-to-
nearest-even (with the truncated sticky bit), then handle rounding
overflow and the subnormal/normal/inf encodings. Parameters:
float64 → (52, -1074, 1075, 2047, 63) float32 → (23, -149, 150, 255, 31)
where biasAdd = mantBits + exponent-bias, maxBiased = 2^expBits − 1.
METHOD computeBits() RETURNS uint64
Correctly-rounded IEEE-754 binary64 / binary32 bit patterns (exact path).
METHOD computeBits32() RETURNS uint64
METHOD clingerEligible() RETURNS boolean
TRUE iff the Clinger fast path applies to the parsed value at float64.
METHOD eiselLemire64() RETURNS | uint64
Eisel-Lemire tier-2 fast path — value = sig64 · 10^exp10 with sig64 known exact (fits uint64). Returns the float64 MAGNITUDE bit pattern on a confidently-correct rounding, or FAILURE to fall back to the exact path. A faithful port of Rust dec2flt lemire.rs, validated in Python (0 mismatches over ~200k cases). It NEVER commits a wrong result — the only outcomes are the correctly-rounded value or a fall-back signal.
METHOD toFloat64() RETURNS float64
The parsed value as a real float64. Tiered: (L8) Clinger for the common exactly-representable case; then Eisel-Lemire for the wider fits-in-uint64 case; then the exact bignum computeBits() for everything else / EL fall-back.
METHOD clingerEligible32() RETURNS boolean
Clinger applies at float32 when sig ≤ 2^24 and |exp10| ≤ 10 (10^10 = 5^10· 2^10, 5^10 < 2^24, so it is an exact float32).
METHOD toFloat32() RETURNS float32
MODIFY METHOD parse(String s) RETURNS void
Parse s into this result's fields. Sets valid = FALSE (and stops) on any
character outside the strict finite-number grammar or on non-consumption.
CLASS FloatToDecimal
kernel/src/FloatToDecimal.ev:22
Methods
METHOD float64Parts(float64 v) RETURNS | DecimalParts
METHOD float32Parts(float32 v) RETURNS | DecimalParts
CLASS Formatter
Auto-conversion text formatter (stdio-spec.md §4).
Formatter is the kernel surface that powers the user-facing
("...$1")->format(args) form. The compiler rewrites that call to
Formatter->format(template, args) whenever at least one arg's
static type is not String — see stdio-spec.md §4.3.
A Formatter carries one piece of configuration: how it renders a
binary / ByteBuffer / DynamicByteBuffer placeholder. Construct
a Formatter once and reuse it — as a local, a CONSTANT field, or
held by a Writable for consistent output. The compiler's default
rewrite uses Formatter() (HEX_SPACED); call Formatter explicitly
to choose a different BinaryMode.
Architecture note: the template-walking + type cascade live here,
not on String. String is upstream of every renderable type's
conversion (the Converter and Codec classes, FloatFormat) in the kernel
.hpp DAG; placing
the cascade on String would create a cycle. Formatter is downstream
of both and can freely call into them. The compiler-rewrite from
stringExpr->format(args) to Formatter->format(stringExpr, args)
is what lets users keep the natural call shape while preserving
the topological invariant.
Format specifiers: a placeholder may carry a Python-style :spec
suffix (stdio-spec.md §4.4). The spec governs width / fill / align
and — for select types — base override and sign. The spec grammar
lives in parseSpec below; the rendering side dispatches in
renderArg / applyWidth.
kernel/src/Formatter.ev:47
Fields
BinaryMode binaryMode
Constructors
INIT()
Default Formatter — binary args render as spaced hex.
INIT(BinaryMode mode)
Formatter with an explicit binary-rendering mode.
Methods
METHOD format(String tmpl, opaque[] args) RETURNS String
Render tmpl against args, substituting $1..$9 and
$$ per stdio-spec.md §4.1. A placeholder may carry a
format spec :<spec> (§4.4) — width / fill / align /
sign / type-letter — that is parsed and applied to the
rendered value.
CLASS Future
Eventually-ready cell holding a worker's result.
────────────────────────────────────────────────────────────────── USER-FACING API ──────────────────────────────────────────────────────────────────
Future[T] is the handle returned by WorkerPool[J, R].submit(...)
. The pool spawns a Task that runs the supplied
function on the job; when the function returns, the kernel calls
future->resolve(result). The submitter holds the Future and
reads the result by calling future->await().
- future->isReady() — non-blocking: TRUE once resolved or failed
- future->await() — pipe-XOR: blocks until ready, then returns either the value (SUCCESS) or a FAILURE STATUS (worker threw, or enclosing CONCURRENT scope cancelled before the value was produced)
────────────────────────────────────────────────────────────────── NOT USER-CONSTRUCTIBLE ──────────────────────────────────────────────────────────────────
Future has no user-callable constructor. The only legitimate
construction site is the kernel: WorkerPool.submit allocates one
Future per submitted job, hands it to the worker Task, and returns
it to the submitter. The analyzer rejects bare CREATE Future[T](...)
in user .ev source (concurrent_scope_diagnostics.py R7, H.2.3
follow-up); the kernel construction path bypasses the analyzer via
emit-time new ENVZN::Future<T>(...) and isn't constrained by R7.
────────────────────────────────────────────────────────────────── CROSS-THREAD SAFETY ──────────────────────────────────────────────────────────────────
Future is one of the kernel-managed exceptions to Envzn's "user code cannot share instances across threads" rule (sibling to Mutex / Channel / Broker). The worker thread mutates the Future via resolve() / fail(); the submitter thread reads via isReady() / await(). All access goes through the internal Mutex, which provides the happens- before ordering, and the partner-locked ThreadCondition handles the blocking wait without busy-polling.
The kernel guarantees exactly one resolve() OR fail() call per Future — the WorkerPool / scope-guard pairs the construction with a single completion path. The Future itself doesn't enforce this; a second resolve() silently overwrites (harmless since notifyAll already fired on the first call). T-aliasing across threads is avoided because resolve() takes T by value (move at the C++ level) and await() returns a fresh clone (see "Value semantics" below).
────────────────────────────────────────────────────────────────── VALUE SEMANTICS ──────────────────────────────────────────────────────────────────
Future[T] retains the resolved value across multiple await() calls.
For class-T, await() returns a .value->clone() so the submitter
thread never aliases the worker's payload. For primitive T (int32
etc.), the := value-copy is sufficient and the WHEN T IMPLEMENTS
Cloneable split picks the ELSE branch automatically.
Per Constitution §12 paragraph 5: a resolved Future retains its
value if the enclosing CONCURRENT scope is later cancelled — only
unresolved Futures resolve to FAILURE("scope cancelled") on
cancellation. The scope guard arranges this by calling fail() only
on Futures whose isResolved field is still FALSE at cancel time.
────────────────────────────────────────────────────────────────── STATUS ──────────────────────────────────────────────────────────────────
Mutex + ThreadCondition substrate via the existing UNSAFE.MUTEX /
UNSAFE.CONDVAR FFI; no new _cxx* reserved types.
kernel/src/Future.ev:86
Constructors
INIT()
Methods
METHOD isReady() RETURNS boolean
PUBLIC API
Non-blocking check. Returns TRUE if the Future has been
resolved (with a value) OR failed (with a STATUS reason);
FALSE while the worker is still in flight. Takes the mutex
briefly to avoid a torn read of .isResolved.
MODIFY METHOD await() RETURNS | T
Blocking await. Returns the resolved value on success; returns a FAILURE STATUS if the worker threw or the enclosing CONCURRENT scope cancelled before resolution.
Pipe-XOR per Constitution §13.1: the caller MATCHes on the
return to dispatch the SUCCESS / FAILURE branches. Spurious
wakeups from pthread are tolerated by the WHILE-not-IF loop
around readyCond->wait().
Multiple await() calls on the same Future are safe — the value is preserved (cloned out for class T, value-copied for primitive T) and subsequent awaits return the same result without re-blocking.
CLASS HashBucket
IMPLEMENTS Cloneable, Comparable
PARAMETERISED OVER HASH: uint64 for ChainedHashDictionary (FNV), uint128 for
ProHashDictionary (SipHash-2-4-128). Every scan rejects on the cached HASH before the
key compare (CollisionNode.matches). Equal cached hashes form a contiguous run the
scans walk for the key match.
The lookup returns a TRIVIAL (boolean found, V value) multi-return — NOT a
(V|STATUS) pipe-XOR — so the hot path never constructs an _EvStatus (whose
_ValueArray member is non-trivially destructible). The dictionary builds the
interface-mandated (V|STATUS) once at its public boundary.
A method may not be chained off an indexed SELF field (.data[i]->m() is a parse
error), so an element is bound to a local reference before the call; counted
iteration uses WHILE.
See: CollisionNode.ev, HashConstants.ev, ChainedHashDictionary.ev / ProHashDictionary.ev, ENVZN_CONSTITUTION.md §I.J.vi (VALUE CLASS). HashBucket[K, V, HASH] — one sorted array of CollisionNodes (2 inline).
kernel/src/HashBucket.ev:35
Constructors
INIT()
Methods
METHOD size() RETURNS int64
Number of entries in this bucket.
MODIFY METHOD insert(MOVE K key, MOVE V value, HASH hash) RETURNS boolean
Insert key→value (with cached hash). TRUE if a new entry was added,
FALSE if an existing key's value was replaced in place.
METHOD lookup(HASH hash, REFERENCE REFERENCE K key) RETURNS boolean
Look up key (with cached hash) — TRIVIAL (found, value) multi-return, so
the hot path never builds an _EvStatus. value is the type default on a miss.
HYBRID (measured crossover at depth ~24, 2026-06-25): the common shallow bucket
(≤ LINEAR_SCAN_MAX entries) is a plain linear scan from 0 — no lowerBound
binary search, and one matches() per node (no redundant outer hash pre-check).
Only a deep/flooded bucket (> LINEAR_SCAN_MAX) pays the O(log n) sorted path.
The sorted invariant is kept by insert, so the deep path stays valid.
METHOD lookupRef(HASH hash, REFERENCE REFERENCE K key) RETURNS | MUTABLE REFERENCE V
Look up key — a non-owning reference into the stored value, or FAILURE.
A borrow-propagating ("mirror") accessor: the returned reference mirrors
the receiver's mutability (a mutable bucket yields a MUTABLE REFERENCE, a
const bucket a read-only REFERENCE). Binds directly to the matched node's
INTERNAL value field.
METHOD containsKey(HASH hash, REFERENCE REFERENCE K key) RETURNS boolean
TRUE if key is present.
METHOD keyCloneAt(int64 i) RETURNS K
A clone of the i-th node's key.
METHOD valueCloneAt(int64 i) RETURNS V
A clone of the i-th node's value.
METHOD hashAt(int64 i) RETURNS HASH
The cached hash of the i-th node — backs growAndRehash re-bucketing.
MODIFY METHOD removeEntry(HASH hash, REFERENCE REFERENCE K key) RETURNS | K
Remove key — returns the cloned-out key, or FAILURE. Shift-removes from the
sorted live region (the tail slides left, count--; the physical array is not
shrunk — the freed tail slot is reused by the next insert).
Evict the entry for key and hand BOTH halves back, moved — never
cloned. Replaces the former removeKey, which was written as
read-then-delete (keyCloneAt(pos) followed by the shift) and so
deep-copied the very key it was about to destroy — the Array.popLast
defect shape fixed in 3003ed5f, in its dictionary form.
The survivors still shift down and data's physical length is left
alone: it is a non-shrinking high-water mark and the vacated tail slot
is reused by the next insert (see the header note).
MODIFY METHOD placeNew(MOVE K key, MOVE V value, HASH hash) RETURNS void
Build a node from (key, value, hash) and sorted shift-insert it. No update scan; the caller guarantees the key is new (rehash re-place / known-fresh).
METHOD clone() RETURNS HashBucket[K, V, HASH]
Deep copy — every live node cloned, in sorted order (so the clone stays sorted).
METHOD equals(HashBucket[K, V, HASH] other) RETURNS boolean
METHOD isLessThan(HashBucket[K, V, HASH] other) RETURNS boolean
CLASS HashBucketIterator
IMPLEMENTS ReferenceIterator
HashBucketIterator walks the dictionary's HashBucket[K,V,HASH][] buckets (a raw value-array of inline
buckets) in bucket order, yielding a non-owning REFERENCE to each entry's key. Holds
a mutation-locked REFERENCE to the buckets array (set in INIT from a by-reference
parameter — §REFERENCE condition 2); the lock pins the source dictionary against
mutation for the iterator's declaring-block lifetime.
CURSOR MODEL: (bi, ei) is the NEXT position to read — bucket bi, entry ei
within that bucket (0→slot0, 1→slot1, k≥2→spill[k-2]). advanceToValid() normalises
the pair so it either names a live entry (bi < nbuckets, ei < bucket.size()) or
sits at end (bi >= nbuckets). hasNext is bi < nbuckets. Iteration order is
unspecified (bucket order, and within a bucket the 2 slots then the hash-sorted
spill) — the Dictionary interface promises no order for the hash impls.
The per-entry key is reached by binding directly to the bucket's / node's INTERNAL
fields (same ENVZN module) — =@ node.key — rather than via a value-class accessor
method, whose inlining into a =@ bind would drop the reference and copy the
move-only key (the long-standing key-bind gotcha). The bucket count is read through
size().
Parameterised over [K, V, HASH] — the dictionary's hasher type is not needed here (the iterator never hashes), so it is left off the iterator's contract; HASH is carried only to name the bucket/node types. HashBucketIterator[K, V, HASH] — forward-only key iterator over a bucket array.
kernel/src/HashBucketIterator.ev:43
Constructors
INIT(HashBucket[K, V, HASH][] buckets)
Methods
METHOD hasNext() RETURNS boolean
MODIFY METHOD next() RETURNS | REFERENCE K
METHOD peek() RETURNS | REFERENCE K
MODIFY METHOD skip(uint64 n) RETURNS | REFERENCE K
CLASS HashConstants
A stateless utility namespace (like Math) holding the
FNV-1a magic numbers that the Hashable kernel types reach into when
building their hash() implementations.
Lives in its own file because interfaces.ev may only hold INTERFACE
/ GROUP / STRUCT declarations (E9003), and a SINGLETON CLASS is
neither — so it cannot be co-located with the Hashable / Hasher
contracts it serves.
CONSUMERS
String, ByteBuffer, DynamicString, DynamicByteBuffer, DateTime,
TimeDuration — all reference these from their own hash() /
the 32-bit hash bodies that use them.
The 32-bit offset basis is stored in signed form because Envzn
int64 cannot hold the unsigned 0x811C9DC5. Callers BXOR with
byte values, so the bit pattern is what matters; the signed
interpretation is incidental.
See: http://www.isthe.com/chongo/tech/comp/fnv/
HashConstants — a NAMESPACE holding the FNV-1a hash-algorithm constants (offset basis + prime, in 64-bit and 32-bit forms) and the prime bucket-count ladder the chained hash dictionary grows through. Merged from the former HashConstants (FNV) + HashPrimes (ladder).
kernel/src/HashConstants.ev:39
Fields
uint64 FNV_OFFSET_BASIS_64uint64 FNV_PRIME_64int32 FNV_OFFSET_BASIS_32int32 FNV_PRIME_32int64 SIZES_LENint64 SIZESint64 POW2_MAX_LEVELint64 SHALLOW_PRIMES_LENint64 SHALLOW_PRIMES
Methods
METHOD initialBucketCount() RETURNS int64
Bucket count for growth level 0 (the initial table size).
METHOD bucketCountAtLevel(int64 level) RETURNS int64
Bucket count at level, clamped to the ends of the ladder. Past
the top prime the table stops growing (V1 cap ~196k buckets).
METHOD canGrow(int64 level) RETURNS boolean
TRUE while there is a larger prime to grow into.
METHOD pow2CountAtLevel(int64 level) RETURNS int64
Power-of-two bucket count at level — 16 << level, clamped to the cap.
METHOD indexForHash(uint64 hash, int64 count) RETURNS int64
Bucket index for hash given a power-of-two count: hash & (count - 1).
The low bits carry the masked index, so truncating the uint64 hash to int64
first (the mask < 2^31 keeps the result in [0, count)) avoids an int→uint
sign-cross while preserving every bit the mask reads.
METHOD mulShiftIndex(uint64 hash, uint64 mult, int64 shift) RETURNS int64
(mult * hash) >> shift — the top 64-shift bits of the product mix ALL
input bits (unlike the low-bit mask above), so it distributes a
well-mixed hash without a mod. mult is an odd 64-bit constant chosen
key-set-aware; shift = 64 - log2(capacity).
METHOD primeAtLeast(int64 target) RETURNS int64
Smallest ladder prime >= target (clamped to the top prime).
METHOD primeModIndex(uint64 hash, uint64 mult, uint64 p) RETURNS int64
(mult * hash) % p — the prime-mode index (fallback tier).
INTERFACE Hasher
Hasher
Distinct in shape from Hashable: Hashable is "this type knows how
to hash itself" (hash() on SELF); Hasher OF K is "this object
knows how to hash values of type K" (hash(K key) on a separate
hasher instance). The latter is the contract for pluggable hashing —
a Dictionary / Set may be constructed with a custom Hasher, and when
none is supplied the impl constructs a kernel default that delegates
to the key's own Hashable.hash().
Implementations should be:
- Deterministic: equal K values per Equatable[K] produce
equal hashes. Calling hash() twice with the same key yields
the same result.
- Pure: no observable side effects, no I/O, no mutation of
SELF or any reachable state.
- Well-distributed for the K-set actually used as keys.
See: interfaces.ev → Hashable; HashConstants.ev → FNV-1a constants.
Hasher OF K — pluggable hash-function interface for Dictionary
keys and Set elements. Supply a custom implementation at
construction to override the default (which hashes via the key's
own Hashable.hash()).
kernel/src/Hasher.ev:38
Methods
METHOD hash(REFERENCE REFERENCE K key) RETURNS uint64
The key is BORROWED, not taken. A bare K key parameter is a
type-parameter param, which emits by value — so every call site
materialises an owned K (_ev_duplicate) just to compute a hash.
Hashing only reads, so it borrows.
CLASS HexCodec
kernel/src/HexCodec.ev:13
Methods
METHOD toHex(ByteBuffer bb) RETURNS String
ByteBuffer -> hex string with " " between bytes. Empty buffer returns "". Each byte becomes two uppercase hex digits.
METHOD toHex(ByteBuffer bb, String separator) RETURNS String
ByteBuffer -> hex string with caller-chosen separator. Pass "" for compact ("3F406A"), " " for canonical ("3F 40 6A"), ":" for MAC-style.
METHOD toHex(binary b) RETURNS String
Single-byte hex form. Used by the kernel print/stringify dispatch as the
default formatter for the binary primitive (Bug #56). Returns exactly
two uppercase characters (e.g. "3F" for 0x3F, "00" for zero).
METHOD fromHex(String s) RETURNS | ByteBuffer
Hex string -> ByteBuffer. Accepts upper/lowercase hex digits; ignores ASCII whitespace and standard separators (" ", ":", "-") so canonical / MAC / UUID formats round-trip. Pipe-XOR — FAILURE on any non-hex non-separator character or an odd digit count.
CLASS IndexOutOfBoundsError
EXTENDS Error
Kernel error class. Thrown by every collection's bounds check (Array.at / Array.setAt / Array.insertAt / Array.remove, LinkedList.insertAt / removeAt, etc.). The message field typically encodes the offending index and the collection's length at throw time; format follows the pattern "index $1 out of bounds for length $2".
Subclass of Error; adds no new fields.
kernel/src/IndexOutOfBoundsError.ev:20
Constructors
INIT(String message)
CLASS InlineIntHasher
IMPLEMENTS Hasher
Identical hash to FastIntHasher (the key widened into uint64, no mixing), but a
VALUE CLASS rather than a regular CLASS. That single difference is the s04 speed
lever: a regular-class hasher field lowers to _ev_unique<H> (a HEAP handle), so
.hasher->hash(key) compiles to a real, non-inlined call through the pointer — the
exact overhead the dict-perf handoff measured. A VALUE CLASS stored as the concrete
type parameter H hasher lives INLINE in the dictionary, so -O3 inlines the
identity hash and the whole lookup chain collapses to C-class code.
Because a value class cannot be boxed behind a Hasher OF K interface handle in V1
(E1133), InlineIntHasher is used ONLY where the hasher is stored concretely (the H
type parameter) — i.e. IntKeyedDictionary. The interface-handle dictionaries
(ChainedHashDictionary / ProHashDictionary) keep using the regular-class FastIntHasher.
See: FastIntHasher.ev (the regular-class identity hasher), Hasher.ev (the interface), benchmarks/s04_dict/DICT_PERF_KERNEL_HANDOFF.md (recipe #1: store the hasher concretely).
InlineIntHasher OF K — VALUE-CLASS identity hasher (widened key). Stored inline as
a concrete H, so the hash inlines. The monomorphised C-class path for trusted
integer keys; opt in via IntKeyedDictionary's H slot. The K IS BLITTABLE
qualifier forces every instantiation blittable, so the value class earns its
ev_is_blittable partial-spec and lowers to inline (value-array) storage — the
blittable-qualifier rule (ENVZN_CONSTITUTION I.J: a generic value class whose
TEMPLATE qualifier forces every type param blittable earns the trait).
kernel/src/InlineIntHasher.ev:36
Constructors
INIT()
Methods
METHOD hash(REFERENCE REFERENCE K key) RETURNS uint64
CLASS IntKeyedDictionary
IMPLEMENTS Dictionary
HIDDEN concrete Dictionary impl specialised for INTEGER keys: linear-probe open-addressing over a STRUCT-OF-ARRAYS (K[] keys + V[] vals), with a hash & mask home index. The TRUSTED-KEY FAST TIER. Pure Envzn, zero-dep.
WHY it is fast (the s04 envzn_oa prototype — ~1.1x C for int32->int64, the fastest
s04 result; verified by benchmarks/s04_dict/envzn_intkeyed):
- STRUCT-OF-ARRAYS, not chained buckets: one flat array level, so the hot probe
is a direct keys[s] read (C's own layout) — no bucket->node indirection, no
per-node binary search, no redundant hash+key re-compare.
- hash & mask index (the LOW bits): with an identity hasher on dense/sequential
integer keys this maps sequential keys to SEQUENTIAL slots — cache-line locality
+ prefetch, the effect that made envzn_oa (1.04x C) beat multiply-shift's
scatter (envzn_lpint, 1.38x). Distribution is the pluggable HASHER's job:
identity for trusted/dense keys (fastest), a mixing hasher (DefaultHasher/
SipHasher) for adversarial keys. The key-set-aware multiply-shift + prime
fallback for the robust general case lives in ShallowDictionary, NOT here.
- A non-pipe-XOR fast read: operator[] (d[key]) and lookupFast return by
value / (found,value), skipping the (V | STATUS) tuple the interface lookup
builds per call — the s04 IntDict lever ("no pipe-XOR on the hot path"). Held
CONCRETELY (not behind the Dictionary interface) the whole lookup inlines to
C-class code (~1.1x C vs ~3x through the interface + pipe-XOR).
- ONE compare per probe: keys[s] == key (a primitive ==), then a field read of
vals[s]. No cached-hash pre-check (integer keys are their own cheap hash).
STORAGE — two parallel value-arrays. Empty/tombstone slots are marked in keys[]
by two RESERVED sentinel key values:
-1 = EMPTY (probe stops) -2 = TOMBSTONE (probe continues; slot reusable)
vals[] in a non-live slot holds 0 and is never read. This mirrors LpInt's -1
sentinel, extended with a tombstone so the full Dictionary remove surface works.
KEY DOMAIN (documented precondition): keys must not equal the two reserved sentinels (-1, -2). In practice IntKeyedDictionary is for NON-NEGATIVE / trusted integer keys — the fast opt-in tier (as FastIntHasher is the fast opt-in hasher). Full-range or untrusted keys → ChainedHashDictionary. A dev-build ASSERT guards the precondition.
HASHER — stored CONCRETELY as the type parameter H hasher (a VALUE-CLASS hasher such
as InlineIntHasher lives inline, no _ev_unique handle), so .hasher->hash(key) can
inline. (Measured: the hasher is a minor factor — the dominant lever is the shape, i.e.
concrete-hold + non-pipe-XOR read, not the hash call.) Because a type parameter cannot
be default-constructed generically (CREATE H() mis-codegens — envzn-internals defect),
the hasher is REQUIRED at construction: CREATE IntKeyedDictionary[K,V,H](CREATE
InlineIntHasher[K]()) — the caller-passes-the-hasher pattern ChainedHashDictionary uses.
IMPLEMENTS Dictionary[K, V, H] — Cloneable comes transitively (Dictionary EXTENDS
Cloneable). Full interface surface (rehash / tombstones / iterator / clone) PLUS the
concrete fast path (operator[] / lookupFast) for callers holding the concrete type.
kernel/src/IntKeyedDictionary.ev:62
Fields
int64 countint64 tombstonesint64 capuint64 maskK emptyKK tombK
Constructors
INIT(MOVE H h)
Methods
MODIFY METHOD insert(MOVE K key, MOVE V value) RETURNS STATUS
MODIFY METHOD insertCopy(K key, V value) RETURNS STATUS
METHOD lookup(REFERENCE REFERENCE K key) RETURNS | MUTABLE REFERENCE V
METHOD __op_index__(REFERENCE REFERENCE K key) RETURNS V
Direct value read — d[key]. Returns the stored value on a hit, or the V
default (0) on a miss. NO pipe-XOR (V | STATUS) tuple on the hot path: the
s04 experiments (IntDict) showed the pipe-XOR construction is a real per-lookup
cost, and operator[] returns V by value like ByteBuffer/String subscript.
Use contains/lookup when a miss must be distinguished from a stored 0.
METHOD lookupFast(REFERENCE REFERENCE K key) RETURNS boolean
Comma-return lookup — boolean f, V v := d->lookupFast(key). No pipe-XOR
(V | STATUS) tuple; the trivial (found, value) return the s04 prototypes
(IntDict/OADict) used to stay C-class. found distinguishes a miss from a
stored default.
METHOD contains(REFERENCE REFERENCE K key) RETURNS boolean
MODIFY METHOD remove(REFERENCE REFERENCE K key) RETURNS | K
Remove — hands back the stored key and value.
NOTE: unlike ChainedHashDictionary, this cannot MOVE the halves out. The
storage is a struct-of-arrays open-addressed table, and reading a slot
(.vals[s]) is a subscript READ, which by rule clones rather than moves
(cxx_emit stmt_assign.py:149 — "the subscript is a read accessor, never
a move-out"). HashBucket escapes this because it can evict a whole
CollisionNode and take its fields; an SoA table has no move-out-at-index
primitive to call. So this pays one DUPLICATE per half, and the census
pins it at that rather than at 0. K is an integer here, so the key half
is a bit copy; only a class-typed V costs a real clone.
METHOD isEmpty() RETURNS boolean
METHOD size() RETURNS int64
MODIFY METHOD clear() RETURNS void
METHOD iterator() RETURNS ReferenceIterator[K]
METHOD keys() RETURNS LinkedList[K]
METHOD clone() RETURNS Dictionary[K, V, H]
METHOD equals(Dictionary[K, V, H] other) RETURNS boolean
for Equatable Interface Dictionary is equal if all keys between the two dictionaries are equal Two dictionaries are equal when they hold the SAME ASSOCIATIONS: the same keys, each mapping to an EQUAL VALUE.
Not merely the same KEYSET. That would rank {a:1} equal to {a:2}, so
two "equal" dictionaries would answer lookup(a) differently and neither
could stand in for the other — which is the whole point of an equivalence.
Keys-AND-values is what C++ (map and unordered_map), Rust, Swift,
Julia, Python and Java all specify. Keyset equality is a perfectly good
relation, but it is equality of the DOMAIN rather than of the dictionary,
and it deserves its own name (hasSameKeys) rather than this one.
Equal sizes plus a one-way walk is sufficient: with the counts already
equal, other cannot carry a key this one lacks unless this one also
carries a key other lacks — and the walk would have found that.
CLASS IntKeyedDictionaryIterator
IMPLEMENTS ReferenceIterator
The ReferenceIterator OF K returned by IntKeyedDictionary.iterator(). Walks the struct-of-arrays K[] keys, yielding a REFERENCE K per live slot (a slot whose key is neither the EMPTY sentinel -1 nor the TOMBSTONE sentinel -2).
Holds a mutation-locking REFERENCE to the dictionary's keys value-array (bound
with =@ in INIT); the lock pins the source dictionary against mutation for the
iterator's declaring-block lifetime. Cursor pos is the next slot to examine;
advanceToValid() skips empty (-1) and tombstone (-2) slots.
Parameterised over [K, V] to match the dictionary's arity (the vals array is not
walked and V is otherwise unused, but keeping the pair mirrors ShallowDictionaryIterator
and leaves room for a key+value iterator later). The hasher H is not needed — the
iterator never hashes.
kernel/src/IntKeyedDictionaryIterator.ev:29
Constructors
INIT(K[] keyArr, int64 capacity)
Methods
METHOD hasNext() RETURNS boolean
MODIFY METHOD next() RETURNS | REFERENCE K
METHOD peek() RETURNS | REFERENCE K
MODIFY METHOD skip(uint64 n) RETURNS | REFERENCE K
CLASS LinkedList
IMPLEMENTS Cloneable, Collection
Defines the LinkedList[T] class per ENVZN_CONSTITUTION §LinkedList.
LinkedList[T] is a doubly-linked list with O(1) push/pop at both
ends and O(n) traversal/lookup. The
forward chain is owning; back-pointers are explicit REFERENCE, re-bound
with =@.
LinkedList is the foundation for Deque (composes on LinkedList for O(1) both-end ops) and SortedList (composes on LinkedList; insert walks to the sort spot via Comparable[T]). Together with Array, this reduces the kernel to two backing stores.
────────────────────────────────────────────────────────────────── STRUCTURE — sentinels + chain ──────────────────────────────────────────────────────────────────
Every list always contains two sentinel nodes — a "starter" at the head and an "ender" at the tail. They exist on construction and are never removed. User nodes live strictly between them.
_head (id=0) ⇄ user1 ⇄ user2 ⇄ ... ⇄ userN ⇄ _tail_ref (id=1)
Forward arrows (→ in the diagram, next field) are owning
unique_ptrs. Backward arrows (← in the diagram, prev_ref field)
are non-owning REFERENCEs.
The list class holds:
- _head — owning non-Optional sentinel; this owns the entire
forward chain transitively through .next.
- _tail_ref — non-owning REFERENCE to the ender sentinel; needed
for O(1) end-side operations without traversing the whole
chain. The ender is itself owned (via the forward chain) by
the node that immediately precedes it.
- _nextId — counter for new user-node IDs (starts at 2).
- length — public count of user nodes (excludes sentinels).
Empty list: _head → ender (head.next owns ender; head.prev_ref
is EMPTY; ender.prev_ref REFERENCEs head; ender.next is EMPTY).
────────────────────────────────────────────────────────────────── SPLICE PATTERN ──────────────────────────────────────────────────────────────────
Each insert/remove follows a consistent shape:
(a) MOVE owning links via := (always taking the existing
value out of an Optional owning field BEFORE overwriting
the field — otherwise the unique_ptr destructor frees the
value mid-splice);
(b) REBIND back-pointers via =@ (REFERENCE rewires;
structural conditions checked);
(c) MOVE the new owning link into the predecessor's .next
slot last, completing the splice.
Optional unwraps use IF X IS NOT VALID { PANIC } to convert
statically-unreachable EMPTY branches (e.g. _tail_ref.prev_ref
is always set after INIT) into explicit kernel-invariant THROWs
— making "this can't happen" auditable instead of hiding behind
silent fallbacks.
kernel/src/LinkedList.ev:72
Fields
int32 length
Constructors
INIT()
Constructs both sentinels and wires them. After this returns,
head → ender (head.next owns the ender), and the list is empty.
Methods
METHOD clone() RETURNS LinkedList[T]
Hand-written clone() — required because LinkedList carries a
REFERENCE field (.last_node_ref) that AUTO clone cannot rewire
onto the cloned topology. Builds a fresh empty list, walks the
current chain directly (no heap iterator), and appends a deep-clone
of each user value. The new list owns its own sentinel chain with
correctly-wired last_node_ref (set by append() on every push).
METHOD iterator() RETURNS LinkedListIterator[T]
Per spec L767, LinkedList exposes BidirectionalIterator OF T.
Concrete class is LinkedListIterator (separate file). Pass
.head by-& so the iterator's REFERENCE binding satisfies
§REFERENCE condition 2 (mutation-locked at INIT) without
moving ownership of .head out of SELF.
METHOD isEmpty() RETURNS boolean
METHOD size() RETURNS int64
Number of elements currently stored. Read-only, O(1).
MODIFY METHOD prepend(MOVE T value) RETURNS STATUS
FRONT-SIDE OPERATIONS (right after head sentinel)
Insert value at the front. value is cloned in via
LinkedListNode's INIT (const-ref param-passing, LOCKED 2026-05-12).
Splice ordering (Bug #32 move-out-before-overwrite): step 1: newNode.next := .head.next (MOVE old first into newNode.next; .head.next is now EMPTY) step 2: .head.next := newNode (MOVE newNode into .head.next; local poisoned) step 3: bump last_node_ref if list was empty
MODIFY METHOD prependCopy(T value) RETURNS STATUS
Deep-copy form of prepend. value stays valid for the caller;
the clone is the one moved into the new node.
MODIFY METHOD popFirst() RETURNS | T
Remove and return the front user value. EMPTY if empty.
Splice ordering (Bug #32 move-out-before-overwrite): step 1: popped := .head.next (MOVE; .head.next now EMPTY) step 2: .head.next := popped.next (MOVE the new-first into .head.next) step 3: if popped was the only user node, last_node_ref bumps back to .head
METHOD peekFirst() RETURNS | REFERENCE T
Read (without removing) the front user value. Pipe-XOR shape:
SUCCESS populates value with the reference; empty / invariant
violation populates s with FAILURE. Never throws.
METHOD at(int32 index) RETURNS | REFERENCE T
Non-owning reference to the value at index (0-based), or FAILURE
if out of bounds. O(index) forward walk from the first user node —
backs index-based external walkers that cannot hold a
LinkedListIterator field (iterator Rule A), e.g.
ChainedHashDictionaryIterator. Read-only; the source list stays
mutation-locked while the returned reference is held.
MODIFY METHOD append(MOVE T value) RETURNS STATUS
BACK-SIDE OPERATIONS (right before ender sentinel)
Insert value at the back. value is cloned in via
LinkedListNode's INIT (const-ref param-passing, LOCKED 2026-05-12).
O(n) — walks .head's owning chain to find the node whose .next is
the ender (id 1), then splices newNode in front of the ender.
MODIFY METHOD appendCopy(T value) RETURNS STATUS
Deep-copy form of append. value stays valid for the caller;
the clone is the one moved into the new node.
MODIFY METHOD popLast() RETURNS | T
Remove and return the back user value. EMPTY if empty. O(n) walk from .head to find the second-to-last user node so we can splice the ender into its .next slot. Single-element case short-circuits to popFirst().
Splice ordering (Bug #32 move-out-before-overwrite): step 1: popped := wasSecondToLast.next (MOVE popped out of the predecessor's next slot) step 2: wasSecondToLast.next := popped.next (MOVE ender into the predecessor's next slot)
METHOD peekLast() RETURNS | REFERENCE T
Read (without removing) the back user value. Pipe-XOR shape:
SUCCESS populates value with the reference; empty list
populates s with FAILURE. O(n) walk from .head.
MODIFY METHOD insertAt(int32 index, MOVE T value) RETURNS STATUS
MID-CHAIN OPERATIONS (insertAt / removeAt)
Used by SortedList[T] and any other consumer that needs to splice at an arbitrary position. O(index) walk to the target node, then O(1) splice via the same move-out-before-overwrite pattern as prepend/append/popFirst/popLast.
Index 0 and index .length (or .length - 1 for removeAt)
delegate to the existing front/back methods to avoid
duplicating splice logic.
Insert value at position index. Valid range is 0..length
(inclusive — index == length appends, equivalent to append).
Per §10.2 the bare-named form consumes value (poisoned
at the call site). STATUS == FAILURE on out-of-bounds index
or kernel-invariant violation; SUCCESS otherwise.
MODIFY METHOD insertAtCopy(int32 index, T value) RETURNS STATUS
Deep-copy form of insertAt. value stays valid for the caller;
the clone is the one moved into the new node.
MODIFY METHOD removeAt(int32 index) RETURNS | T
Remove and return the user value at position index. STATUS
== FAILURE on out-of-bounds index or kernel-invariant violation
(paired with EMPTY in the value slot); SUCCESS otherwise (paired
with the removed value).
CLASS LinkedListIterator
IMPLEMENTS ReferenceIterator
Concrete BidirectionalIterator OF T for LinkedList[T].
A LinkedList[T]'s ->iterator() method yields a fresh
LinkedListIterator[T] positioned at the first user node (or at
the ender sentinel if the list is empty). Multiple iterators on
the same list are independent (each has its own cursor REFERENCE);
the source list is locked against mutation while any iterator is
held (per §Iterator's mutation-lock rule).
────────────────────────────────────────────────────────────────── CURSOR MODEL ──────────────────────────────────────────────────────────────────
The cursor REFERENCEs the node "about to be read" by the next forward operation. Valid cursor positions:
- Any user node (id ≥ 2): peek/next reads this node's value.
- The ender sentinel (id = 1): the off-the-end position; hasNext is false, peek/next return EMPTY.
The cursor never sits at the head sentinel (id = 0). Stepping backward stops at the first user node — going further would land at head (which has no readable value), so hasPrevious returns false there and previous/skipBack return EMPTY without moving.
For an empty list, the cursor starts at the ender; both hasNext and hasPrevious are false.
────────────────────────────────────────────────────────────────── SAFETY ──────────────────────────────────────────────────────────────────
source_head_node_refis a REFERENCE to the source list's head sentinel. Set in INIT from a by-¶meter; satisfies §REFERENCE structural condition 2 (mutation-locked at INIT) — the mutation lock prevents append/prepend/pop on the source while any iterator exists.cursor_refis a REFERENCE to a node owned by the source list's_headchain. Safe under condition 1 (ownership- reachable from SELF via the validated source-head REFERENCE).- The mutation lock guarantees the cursor's referent cannot be deleted out from under the iterator — no dangling.
────────────────────────────────────────────────────────────────── SKIP / SKIP-BACK BOUNDS ──────────────────────────────────────────────────────────────────
skip(n)walks forward; on overshoot the cursor lands on the ender sentinel (a valid cursor position) and EMPTY is returned. No rollback needed — landing on the ender keeps the cursor model intact.skipBack(n)walks backward; on underflow the cursor stops at the first user node (the position whose_prev_ref.id == 0) and EMPTY is returned. The check is peek-before-step so the cursor never lands on the head sentinel — preserving the "cursor never at head" invariant.
No local REFERENCE-typed variables are used (REFERENCE is
restricted to class instance fields per Bug #29 V1; non-owning
local handles use Bug #30's =@ operator instead). The cursor
field is the walker.
kernel/src/LinkedListIterator.ev:75
Constructors
INIT(LinkedListNode[T] source_head_node)
Methods
METHOD hasNext() RETURNS boolean
MODIFY METHOD next() RETURNS | REFERENCE T
METHOD peek() RETURNS | REFERENCE T
MODIFY METHOD skip(uint64 n) RETURNS | REFERENCE T
skip(n) — moves cursor forward by n positions. skip(0) is equivalent to peek(). On overshoot, the cursor lands on the ender sentinel and FAILURE is returned.
CLASS Lock
Kernel mutual-exclusion primitive.
────────────────────────────────────────────────────────────────── PURE SYNCHRONIZATION ──────────────────────────────────────────────────────────────────
Lock is the bare OS-level mutual-exclusion primitive — a thin
Envzn wrapper over UNSAFE.MUTEX + pthread shims. It synchronises
access; it does not bundle protected data. The user-facing
Mutex[T] (see Mutex.ev) composes a Lock with a typed data
field for the "lock + protected state" pattern; kernel-internal
sites that need pure synchronisation (Channel's buffer lock,
Future.value, ThreadCondition partner) hold a Lock directly.
────────────────────────────────────────────────────────────────── USAGE ──────────────────────────────────────────────────────────────────
Use through the SYNCHRONIZED block. The block guarantees the lock is released when control leaves the block — even via RETURN, BREAK, or PANIC from inside:
SYNCHRONIZED .myLock {
// critical section
IF .someCondition THEN {
RETURN // lock still released — guaranteed by block scope
}
.field = newValue
}
The compiler emits an ENVZN::_ev_lock_guard (defined in
EV_unsafe_concurrency_native.hpp) over the Lock's .native
handle; release happens in the guard's destructor on every exit
path.
────────────────────────────────────────────────────────────────── RAW ACQUIRE / RELEASE — narrow uses only ──────────────────────────────────────────────────────────────────
The acquire() / release() / tryAcquire() methods are exposed for the rare cases where the lock-and-unlock paths can't be expressed as a single scope:
- ThreadCondition.wait() / waitFor() take ownership of the lock internally for the sleep window, then re-acquire it before returning.
- tryAcquire() for non-blocking optimistic locks, where the caller wants to bail out if contention is high rather than wait.
Outside those cases: prefer SYNCHRONIZED. Manual acquire/release is the C-mutex anti-pattern (forget release, leak the lock).
kernel/src/Lock.ev:70
Constructors
INIT()
Methods
MODIFY METHOD acquire() RETURNS void
Block until the lock is acquired. Used directly by ThreadCondition (and similar primitives that need explicit acquire/release pairing); everywhere else, prefer the SYNCHRONIZED block which guarantees release on every exit path.
MODIFY METHOD release() RETURNS void
Release the lock. Pairs with a previous acquire() on the same Lock from the same thread. Calling release() without first holding the lock is undefined behaviour at the OS level — pthread does not require the implementation to detect or report this misuse.
MODIFY METHOD tryAcquire() RETURNS boolean
Non-blocking attempt to acquire the lock. Returns TRUE if the caller now holds the lock (and is responsible for calling release() exactly once); FALSE if another thread currently holds it. Useful for optimistic-locking patterns where the caller would rather bail than wait.
CLASS Math
NAMESPACE of standard math functions and constants.
Math is a stateless NAMESPACE (the mirror of a STRUCT: functions +
CONSTANT data, no instance), accessed via Math.methodName(args)
and Math.PI. The namespace declares the float64 constants
(PI, E, …) plus a wide method surface
covering absolute value, roots/exponentiation, logarithms,
trigonometry, rounding, sign, min/max/clamp, angle conversion,
overflow-aware integer arithmetic, and geometry helpers.
The standard math functions are bound to their C symbols in
<math.h> (and abs in <stdlib.h>) via FOREIGN BIND (V1
Part E) — see the bind block below. Domain validation (negative
roots, non-positive logarithms, out-of-range inverse-trig
arguments) stays in Envzn; each bind performs only the raw
computation.
────────────────────────────────────────────────────────────────── OVERLOAD CONVENTIONS ──────────────────────────────────────────────────────────────────
Envzn has no user generics, so methods that the spec describes
generically over a numeric T (min(T, T), wrappingAdd(T, T),
etc.) appear here as one concrete overload per primitive type:
- For
min/max/clamp: int32, int64, float32, float64 overloads (4 per method). - For
saturatingAdd/checkedAdd: every integer primitive — int8..int64, uint8..uint64 (16 per method). Float overflow follows IEEE 754 semantics; no checked variant. (WRAPPING arithmetic is not a method — it is the operators&+ &- &*of I.F.ii(a.iv).) - For
abs/sign: int32, int64, float32, float64 overloads.
To keep this design preview reasonable in length, only the int32 and float64 overloads are spelled out below for the multi-overload families; production Math.ev expands every primitive variant. The expansion is mechanical.
────────────────────────────────────────────────────────────────── ERROR HANDLING ──────────────────────────────────────────────────────────────────
Domain errors PANIC MathError:
- sqrt, cbrt of a negative real
- log, log2, log10 of a non-positive value
- asin, acos of a value outside [-1, 1]
- pow(0, x) for x < 0
Integer overflow on plain + / - / * PANICs (MathError,
I.F.ii(a.iii)) — it never silently wraps. The explicit alternatives
are the wrapping operators &+ &- &* (modular), and the
saturating / checked method variants below.
kernel/src/Math.ev:96
Fields
float64 PIfloat64 TAUfloat64 Efloat64 PHIfloat64 EMfloat64 SQRT2float64 LN2
Methods
METHOD abs(int32 x) RETURNS int32
ABSOLUTE VALUE — overloads per primitive
METHOD abs(float64 x) RETURNS float64
METHOD isNaN(float64 x) RETURNS boolean
TRUE iff x is NaN — the only value not equal to itself. The language
forbids authoring NaN as a literal (a NaN/Infinity is a computation
RESULT, never developer-assignable, I.C), so this is how a value that
can ARISE is TESTED — retiring the hand-rolled v != v idiom.
METHOD isNaN(float32 x) RETURNS boolean
METHOD isFinite(float64 x) RETURNS boolean
TRUE iff x is finite (neither ±infinity nor NaN). x - x is 0 for any
finite x and NaN for ±inf/NaN, so (x - x) == 0 is the finiteness test —
retiring the hand-rolled (f - f) == 0 idiom.
METHOD isFinite(float32 x) RETURNS boolean
METHOD squareRoot(float64 x) RETURNS complex
Square root over the reals AND into the complex plane — RETURNS complex
so a negative argument yields a pure-imaginary result instead of failing:
squareRoot(9) = (3 + 0im) (x ≥ 0 → real component)
squareRoot(-4) = (0 + 2im) (x < 0 → imaginary component)
This is the Math↔complex integration: complex c = Math.squareRoot(-4)
is a plain complex assignment. (Math depends on the complex type here; the
edge is one-way — Complex's own magnitude/phase use the bare FOREIGN math
binds, not this namespace, so there is no Math↔Complex cycle.)
METHOD cubeRoot(float64 x) RETURNS float64
METHOD exponent(float64 base, float64 exp) RETURNS | float64
METHOD eulersExponent(float64 x) RETURNS float64
METHOD loge(float64 x) RETURNS | float64
LOGARITHMS — x must be > 0; FAILURE STATUS otherwise.
METHOD log2(float64 x) RETURNS | float64
METHOD log10(float64 x) RETURNS | float64
METHOD sin(float64 x) RETURNS float64
TRIGONOMETRY — radians; asin/acos domain-checked.
METHOD cos(float64 x) RETURNS float64
METHOD tan(float64 x) RETURNS float64
METHOD asin(float64 x) RETURNS | float64
METHOD acos(float64 x) RETURNS | float64
METHOD atan(float64 x) RETURNS float64
METHOD atan2(float64 y, float64 x) RETURNS float64
METHOD sinh(float64 x) RETURNS float64
METHOD cosh(float64 x) RETURNS float64
METHOD tanh(float64 x) RETURNS float64
METHOD floor(float64 x) RETURNS float64
ROUNDING — float64 in, float64 out
METHOD ceiling(float64 x) RETURNS float64
METHOD round(float64 x) RETURNS float64
METHOD truncate(float64 x) RETURNS float64
METHOD sign(int32 x) RETURNS int32
SIGN — returns -1, 0, or 1 as int32 regardless of input type
METHOD sign(float64 x) RETURNS int32
METHOD min(int32 a, int32 b) RETURNS int32
MIN / MAX / CLAMP — overloads per numeric primitive
Spelled out for int32 + float64 in this preview; production Math.ev expands int64 / float32 variants identically.
METHOD min(float64 a, float64 b) RETURNS float64
METHOD max(int32 a, int32 b) RETURNS int32
METHOD max(float64 a, float64 b) RETURNS float64
METHOD clamp(int32 v, int32 lo, int32 hi) RETURNS int32
METHOD clamp(float64 v, float64 lo, float64 hi) RETURNS float64
METHOD toRadians(float64 deg) RETURNS float64
ANGLE CONVERSION
METHOD toDegrees(float64 rad) RETURNS float64
METHOD saturatingAdd(int32 a, int32 b) RETURNS int32
OVERFLOW-AWARE INTEGER ARITHMETIC
Two flavors per integer primitive (the WRAPPING flavor is now the
operators &+ &- & of I.F.ii(a.iv), lowered to the ev_wrap_
int32 overloads spelled out; production Math.ev expands all 8 integer primitives.
METHOD checkedAdd(int32 a, int32 b) RETURNS | int32
METHOD hypotenuseLength(float64 x, float64 y) RETURNS float64
GEOMETRY
CLASS MathError
EXTENDS Error
Kernel error class. Thrown by Math singleton methods on domain errors and by the checked-arithmetic helpers (Math.checkedAdd, etc.) on overflow. Examples: square root of a negative real, log of a non-positive value, integer-overflow on a checked op.
Subclass of Error; adds no new fields.
kernel/src/MathError.ev:19
Constructors
INIT(String message)
CLASS MeasuringTimer
A stack-scoped RAII elapsed-time probe.
Construct one as a local; it captures a monotonic start instant. When it leaves scope its CLEANUP (destructor) reports the elapsed time — so the measured region is simply the local's lexical scope:
{
MeasuringTimer t := CREATE MeasuringTimer("parse phase")
... work to measure ...
} // → prints "parse phase: 1.234 ms"
Read the elapsed value mid-scope with elapsedNanos() / report(), or call silence() to suppress the automatic print (record-only).
Clock: a monotonic, nanosecond-resolution counter (ev_monotonic_nanos, EV_timer_native.hpp) — excludes system sleep, immune to wall-clock adjustments. The right source for timing code regions. (A direct FOREIGN BIND to libc's clock_gettime_nsec_np was rejected by the emitted C++: its clockid_t enum parameter won't implicitly construct from an Envzn uint32, so the one-line extern "C" wrapper takes the enum and exposes int64 nanos — and stays portable.)
kernel/src/MeasuringTimer.ev:37
Fields
int64 startNanosString labelboolean reportOnExit
Constructors
INIT(String label)
Start a labelled timer.
INIT()
Start an unlabelled timer (reports under "timer").
Methods
METHOD elapsedNanos() RETURNS int64
Elapsed monotonic nanoseconds since construction.
METHOD report() RETURNS String
Human-readable elapsed time, auto-scaled to ns / us / ms / s, prefixed with the label: e.g. "parse phase: 1.234 ms".
METHOD show() RETURNS void
Print the elapsed-time report line to stdout now. (Kept a normal method, not inlined into CLEANUP: the kernel's header-ordering scanner doesn't walk destructor bodies, so the Stdio dependency must surface from a scanned method to order EV_stdio.hpp ahead of this class.)
MODIFY METHOD silence() RETURNS void
Suppress the automatic CLEANUP print — use elapsedNanos()/report() to read the value yourself.
MODIFY METHOD unsilence() RETURNS void
Re-enable the automatic CLEANUP print.
CLASS Mutex
kernel/src/Mutex.ev:80
Fields
T data
Constructors
INIT(T initial)
Methods
MODIFY METHOD acquire() RETURNS void
Block until the lock is acquired. Mirrors Lock.acquire().
Use through SYNCHRONIZED in user code — manual acquire is
the C-mutex anti-pattern (forget release, leak the lock).
MODIFY METHOD release() RETURNS void
Release the lock. Mirrors Lock.release(). Pairs with a
previous acquire() on the same Mutex from the same thread.
MODIFY METHOD tryAcquire() RETURNS boolean
Non-blocking attempt to acquire. Mirrors Lock.tryAcquire().
Returns TRUE if the caller now holds the lock (and is
responsible for calling release() exactly once); FALSE if
another thread currently holds it.
CLASS NetworkError
EXTENDS Error
Historical kernel-side placeholder. The production NetworkError class lives in the Networking module (Networking/NetworkError.ev) where it belongs alongside the rest of the network-protocol error surface. This kernel-side declaration exists for C++ ABI symmetry with ENVZN.hpp.
Subclass of Error; adds no new fields.
kernel/src/NetworkError.ev:19
Constructors
INIT(String message)
CLASS NullPointerException
EXTENDS Error
Thrown when an UNSAFE.* primitive's safety-wrapper detects a null pointer at dereference time.
the kernel-wide exception class the
_UnsafeHandle<T>::operator->() and get() throw from. Caught by
user code via TRY { ... } RECOVER (NullPointerException npe) { ... },
matching the standard kernel Error hierarchy.
Subclass of Error; adds no new fields. The probe at
kernel_probe/unsafe_safety_wrapper/ validated that the throw
unwinds across the FFI boundary and Envzn TRY/RECOVER catches by
class identity.
kernel/src/NullPointerException.ev:24
Constructors
INIT()
UNION NumStore
number is an ergonomic unified numeric — an integer XOR a float, never
both and never neither — backed by this compiler-known value-class. It is
NEVER dev-instantiated (there is no CREATE Number); it is driven entirely
by the number primitive surface and by literals. It is copy-by-value (a
value-class, NOT opaque's move-only model).
Storage is a NumberKind tag (INTEGER | UNSIGNED | FLOAT) selecting one arm of
a raw NumStore UNION (int64 XOR uint64 XOR float64). The tag makes every access
safe — the unsafe union read is encapsulated entirely behind this value-class
surface (the north-star "relocate the cost inward" tiebreak). The UNSIGNED arm
(number-unsigned-arm-design.md, #38) is value-classified, not surface-typed: it
is reached only when an integer overflows the int64 arm into (int64_max,
uint64_max], giving full [int64_min, uint64_max] integer fidelity.
See number-and-complex-design.md and ENVZN_CONSTITUTION.md I.D.i(g). NumberKind (the INTEGER|FLOAT subtype tag) lives in enums.ev per the kernel convention that all ENUMs are declared there (E9031).
kernel/src/Number.ev:0
Fields
int64 iuint64 ufloat64 f
CLASS Number
kernel/src/Number.ev:41
Fields
NumberKind tagNumStore store
Constructors
INIT()
Default-initialize to zero, INTEGER subtype (constitution I.D.i(b): every
numeric primitive default-inits to zero). Without this the synthesized
zero-init would leave tag at 0 — not the INTEGER case — since kernel
enums start at 1.
INIT(int64 v)
INIT(uint64 v)
Widen-in from an unsigned 64-bit value. Value-classified by the ladder:
a magnitude that still fits the signed arm (v ≤ int64_max) is stored as
INTEGER so it compares/prints identically to the same int literal; only a
value in (int64_max, uint64_max] takes the UNSIGNED arm. This is the total
uint64 INTO number edge (no fallible AS needed — every uint64 fits).
INIT(float64 v)
Methods
METHOD kind() RETURNS NumberKind
The subtype this number currently holds — INTEGER, UNSIGNED, or FLOAT. The
V1 introspection surface (the typed, MATCH-able stand-in for V2 runtime
WHEN n IS int64). A pure tag read; never touches the union arm.
METHOD equals(number other) RETURNS boolean
Value-equality across subtypes (the Equatable surface a == b lowers to).
Any float-subtype operand compares by IEEE value (the integer arm promoted
to double) — so number(3) == number(3.0) is TRUE, number(0.1+0.2) ==
number(0.3) is FALSE. Two integer-kind operands are equal only when they
share an arm: INTEGER (≤ int64_max) and UNSIGNED ((int64_max, uint64_max])
hold DISJOINT value ranges, so a cross-arm compare is FALSE without ever
comparing signed to unsigned bits (no UB). Tolerance is ≈; bit-exact is
BEQUALS. (Decision 2026-06-23: float == = IEEE value.)
METHOD isLessThan(number other) RETURNS boolean
Ordering by mathematical value across arms (the a < b surface). Mirrors
equals: any float-subtype operand compares by IEEE value, so
number(2) < number(2.5) is TRUE. Two integer-kind operands compare in
the int128 domain, which unifies the INTEGER and UNSIGNED arms into one
signed-wide value — so a cross-arm compare is ORDERED (unlike equality,
where the disjoint ranges make it simply FALSE) and never compares signed
to unsigned bits.
NaN: the float path delegates to float64's own <, so every comparison
against a NaN is FALSE — the same answer float64 gives. That is why the
four operators derive by SWAPPING operands and never by negating: NOT (b
< a) would make NaN <= x TRUE. (I.D.g.v, ordering added 2026-09-02.)
METHOD isLessThanOrEqual(number other) RETURNS boolean
a <= b. A separate method rather than NOT isLessThan(b, a) for the
NaN reason in isLessThan above: negation flips NaN's correct FALSE into
a wrong TRUE. > and >= are the operand-swapped forms of these two.
METHOD __op_plus__(number other) RETURNS number
Arithmetic. Any float-subtype operand → float result (the integer arm
promoted to double — the common float path, unchanged). Otherwise both are
integer-kind: compute in int128 so int64 overflow is seen, then classify
down the ladder — so number overflow promotes (INTEGER→UNSIGNED→FLOAT)
instead of wrapping. */^ whose result exceeds int128 drop to FLOAT.
METHOD __op_minus__(number other) RETURNS number
METHOD __op_times__(number other) RETURNS number
Multiply. Integer-kind operands compute in int128; the one integer op whose
product can exceed int128 (near-uint64_max × near-uint64_max ≈ 2^128), so a
recover-and-check (a≠0 ∧ p/a≠b ⇒ the multiply wrapped) drops to FLOAT.
METHOD __op_divide__(number other) RETURNS number
METHOD __op_floordiv__(number other) RETURNS number
Floor division (~/) — floors toward negative infinity (vs / which
truncates). Integer-kind → int128 floor then classify; any float → float
floor. Division shrinks magnitude so it never overflows the int128 domain
(int64_min ~/ -1 lands at 2^63, which the ladder classifies UNSIGNED).
METHOD __op_power__(number other) RETURNS number
Power (^). Any float operand → float result. Integer-kind: a negative
exponent is fractional → FLOAT; otherwise iterate in int128, dropping to
FLOAT the moment a multiply would exceed int128. Bases 0/1/−1 are handled
directly so a huge exponent can't spin the loop (|base|≥2 overflows within
~127 steps).
METHOD isClose(number other, float64 rtol, float64 atol) RETURNS boolean
Approximate equality (the ≈ / ≉ operator lowers to this). Two
int-subtype operands compare EXACTLY — integers carry no representation
error, so ≈ adds nothing there. Otherwise (any float involved) the
principled tolerance applies: |a − b| ≤ max(rtol·max(|a|,|b|), atol).
The atol floor is what makes comparison to zero work. Operator defaults
come from the desugar; this method is also the explicit-tolerance surface.
CLASS NumberConversions
CONVERSIONS host for the number primitive's narrow-OUT surface. Widening INTO number (int / float -> number) is the implicit lattice edge handled by the compiler; this host owns the explicit, fallible extraction back to a fixed scalar.
number AS int8/16/32/64, uint8/16/32, uint64, char8 — lossy, fallible (T | STATUS)
Per CONVERSIONS.md: AS is lossy and RANGE-CHECKED at runtime — it fails
into the IF/ELSE pipe-XOR, it is NOT a silent truncation. Succeeds IFF the
number holds an integer subtype (INTEGER or the overflow-arm UNSIGNED, #38)
whose value fits the target (in range, ≥ 0 for unsigned). The UNSIGNED arm
is exactly a uint64, so AS uint64 on it is total; but UNSIGNED > int64_max,
so AS int64 (and any narrower signed/unsigned) FAILS its range check. A
float-subtype number always FAILS an integer extraction.
number INTO float64 — lossless-ish, total Every subtype lowers to float64 by the widening model (INTEGER via int64, UNSIGNED via uint64, both registered edges; float64 is identity), so this is a plain-value INTO, not a fallible AS (large magnitudes round, as any float).
The held value is read off the tagged union behind the value-class surface
(v.tag / v.store.i / v.store.u / v.store.f). The range-checked assignment to
the value slot is the sanctioned narrowing site (mirrors the int64 AS int32
row in PrimitiveConversions). number AS float32 is deferred (see its note
below — blocked on a writable float32-max bound).
kernel/src/NumberConversions.ev:35
CLASS NumberConverter
CONVERSIONS host for number↔text conversions The AS/INTO surface over the kernel's numeric conversions, and their implementation — the digit/parse logic lives here in the operators (and their shared helpers), not delegated elsewhere.
integer/boolean INTO String — lossless, total (every value has a text form) String INTO number — lossless, fallible (parse; bad text → FAILURE)
Scoped to depend on only String + DynamicString (NOT FloatFormat), so this
host orders EARLY — before foundational classes like Array — letting them use
x INTO String. The float→String path is the one piece that needs the Ryu
engine (FloatFormat); it lives in the separate FloatConverter host, which
orders later. Float PARSING (String→float) stays here — it uses the strtod/
strtof native shim, not FloatFormat (FOREIGN scope is per-file, §17, so the
parse binds are redeclared below).
kernel/src/NumberConverter.ev:26
CLASS NumberToDecimal
kernel/src/NumberToDecimal.ev:15
Methods
METHOD numberParts(number v) RETURNS | DecimalParts
number → decimal parts. An INTEGER subtype is exact (coefficient = store.i, exponent 0); a FLOAT subtype takes the Ryu shortest-decimal route (and so inherits its only failure mode — a non-finite float).
METHOD int128Parts(int128 v) RETURNS | DecimalParts
int128 → decimal parts (Bug #269). Exact when the value fits decimal128's
34 significant digits (|v| < 10^34); a wider value FAILS rather than
silently rounding — the exact-or-fail INTO contract that keeps decimal128
from ever losing precision. coefficient = v directly (int128 → int128).
METHOD uint128Parts(uint128 v) RETURNS | DecimalParts
uint128 → decimal parts (Bug #269). Exact when < 10^34; wider FAILS. The magnitude (< 2^113, so it fits a positive int128) is reassembled into the signed coefficient from its 64-bit halves — there is no uint128→int128 operator, and the low half is halved-then-doubled so the int64 reinterpret never goes negative (the DecimalCodec.bidToParts shape).
METHOD complexParts(complex v) RETURNS | DecimalParts
complex → decimal parts. A complex projects onto a real decimal128 only
when its imaginary part is zero (there is no ordering or real embedding of
a genuinely-complex value); otherwise FAILURE. The real part is a number,
so it routes through numberParts.
CLASS NumericLimits
NAMESPACE of the numeric limits and the layout constants the conversion hosts read.
A conversion body used to spell its bounds as literals — v > 127,
v > 0x7F, iv > 9223372036854775807 — and a literal's type was decided
twice, once by the analyzer and once by the emitter's magnitude ladder
(gh #268). A CONSTANT has one declared type, so a bound read from here
arrives typed exactly once. The four conversion hosts
(PrimitiveConversions, NumberConversions, DecimalConversions,
ComplexConversions) contain no numeric literal at all; every value they
compare against or count with is a member of this namespace, reached as
NumericLimits.INT8_MAX.
The <stdint.h> limits keep the names C gave them and are BOUND, not
declared: INT8_MIN is a macro, so a member of that name would be
rewritten by the preprocessor before clang saw a declaration. The bind
mechanism of §17 reads each macro once into a member of this namespace
(I.K.ii.d). Everything else here is an ordinary CONSTANT, typed as the
site that reads it needs — a binary mask for a byte extraction, an
int32 bias for an int32 exponent, a float64 bound for a float
range check — so no site casts.
kernel/src/NumericLimits.ev:33
Fields
int8 INT8_MINint8 INT8_MAXint16 INT16_MINint16 INT16_MAXint32 INT32_MINint32 INT32_MAXint64 INT64_MINint64 INT64_MAXuint8 UINT8_MAXuint16 UINT16_MAXuint32 UINT32_MAXuint64 UINT64_MAXint128 INT128_MINint128 INT128_MAXuint32 CODE_POINT_MAXuint16 SURROGATE_FIRSTuint16 SURROGATE_LASTfloat64 INT32_MIN_F64float64 INT32_MAX_F64float64 UINT8_MAX_F64float64 UINT16_MAX_F64float64 CODE_POINT_MAX_F64int32 INT32_ZEROint32 INT32_ONEint64 INT64_ZEROint64 INT64_ONEuint64 UINT64_ZEROuint64 UINT64_ONEint128 INT128_ZEROint128 INT128_ONEint128 INT128_TENfloat64 FLOAT64_ZEROfloat64 FLOAT64_TENbinary BYTE_MASKint64 BITS_PER_BYTEint64 BYTES_PER_HALFint64 LAST_BYTE_INDEXint64 HALF_WIDTH_BITSint32 DECIMAL128_EXPONENT_BIASint64 DECIMAL128_EXPONENT_SHIFTint64 DECIMAL128_SIGN_SHIFTchar32 DIGIT_ZEROchar32 DECIMAL_POINTchar32 MINUS_SIGN
CLASS NumericUtilities
kernel/src/NumericUtilities.ev:30
Methods
METHOD floatHashBits(float64 v) RETURNS uint64
The hash-stable bit pattern of a float — the value's IEEE-754 binary64 encoding, with one canonicalisation.
A hasher may not simply widen a float into a word: that truncates the mantissa, so two distinct values collide. It takes the BITS instead, which is exact and injective — a float32 key widens into binary64 losslessly first, so both widths share this one path.
-0.0 is the single case where bits and equality disagree: IEEE says
-0.0 == 0.0, so the two MUST hash alike or a dictionary loses a key it
was handed. Both encodings therefore map to 0.
NaN needs no special case and gets none: it is equal to nothing, itself included, so no equal pair can hash differently. The consequence is worth stating plainly — a NaN inserted as a key can never be looked up again. That is IEEE's rule, not this function's.
METHOD toInt32(uint32 v) RETURNS int32
Saturating cast uint32 -> int32 (clamps anything above INT32_MAX).
METHOD truncateToUint64(uint128 v) RETURNS uint64
Low 64 bits of a 128-bit unsigned value.
METHOD truncateToUint64(int128 v) RETURNS uint64
Low 64 bits of a 128-bit SIGNED value (bit-reinterpret; no range check).
The signed sibling of the uint128 form — used by decimal128's cohort hash
and by number's int64↔uint64 reinterpret paths.
METHOD toUint64(int64 v) RETURNS uint64
Reinterpret a signed int64 as uint64 (value-preserving for the non-negative
inputs callers guarantee; bit-reinterpret otherwise). The int64 sibling of
toUint64(int32) — number AS uint64 uses it instead of an inline narrow.
METHOD truncateToUint32(uint64 v) RETURNS uint32
Low 32 bits of a 64-bit unsigned value.
METHOD truncateToInt32(int64 v) RETURNS int32
Low 32 bits of a 64-bit signed value, WRAPPING into int32's range.
Spelled out rather than cast (2026-08-25). BAND states which bits are
kept and narrows the type to uint32; the sign is then applied by
arithmetic, because the top half of uint32 has no int32 counterpart and
AS correctly refuses it. Masking the sign bit off first makes the AS
total — it can never fail — and subtracting the bias reproduces two's
complement exactly, in Envzn, with nothing reinterpreted behind the
developer's back.
METHOD truncateToInt32(uint64 v) RETURNS int32
Low 32 bits of a 64-bit unsigned value, WRAPPING into int32's range. The unsigned-source sibling of the int64 form above; same construction.
METHOD truncateToInt32(float32 v) RETURNS int32
Truncate a float toward zero into int32 (no NaN / range check — the caller is responsible for validating the value first).
FOREIGN BIND, not an Envzn expression (Brian, 2026-08-24). There is no
total float->int spelling in the language and this host cannot reach the
one fallible form: NumericUtilities is emitted BEFORE
PrimitiveConversions, so AS/INTO are undeclared identifiers here.
TRUNCATE refuses a float source correctly — round-toward-zero is not a
bit chop. The shim sits beside ev_convert_f64_to_bits, which this file
already binds, so the pattern is the file's own.
METHOD truncateToInt32(float64 v) RETURNS int32
METHOD toUint64(int32 v) RETURNS uint64
A signed int32 read as uint64 — value-preserving for the non-negative inputs callers guarantee. A NEGATIVE input does not survive: it maps into the top of the unsigned range (-1 becomes 18446744073709551615), so the sign is lost rather than the bits reinterpreted.
METHOD toInt64(uint64 v) RETURNS int64
A uint64 read as int64 — value-preserving for values <= INT64_MAX, which
callers guarantee. Above that the value wraps negative. The inverse of
toUint64(int64). It also underpins the uint64 INTO int128 conversion
(PrimitiveConversions), which splits the magnitude across two toInt64 calls
to widen positively — the direct cross-sign assign is not itself a widen.
METHOD exactScaledInt128(int128 mantissa, int32 power) RETURNS | int128
Exact integer value of mantissa × 10^power as int128, or FAILURE when the
result is not an exact integer (power < 0 leaving a remainder) or its
magnitude exceeds int128. The shared extraction behind every decimal128 AS
int*/uint*; each caller range-checks the int128 against its own width.
CLASS OrderedArray
EXTENDS Array
Opaque-size, ordered, indexable collection.
The foundation collection class. Stack, Queue, Deque, LinkedList, Set, and SortedList all compose on top of Array's storage; the hash-based collections (Dictionary) keep their own backing.
Element access is by integer index in 0..length-1. Out-of-bounds reads throw IndexOutOfBoundsError; out-of-bounds writes throw the same. Mutation methods come in MOVE and COPY pairs:
- append(value) — MOVE; consumes value
- appendCopy(value) — DEEP COPY; value stays valid
- setAt(i, value) — MOVE
- setAtCopy(i, v) — DEEP COPY
- insertAt(i, v) — MOVE
- insertAtCopy(i,v) — DEEP COPY
For class-typed T, COPY routes through Cloneable.clone(); T must implement Cloneable for any *Copy method to be reachable.
Removal:
- popFirst() / popLast() — return the removed element as T; EMPTY if empty; ownership transfers to the caller.
- remove(i) — destroys the element in place.
- clear() — destroys all elements.
Inspection without removal:
- peekFirst() / peekLast() — return REFERENCE T handles into the underlying storage along with a STATUS. The handle is valid only while no mutation occurs.
Iteration:
- iterator() returns a BidirectionalIterator OF T at position 0. Multiple iterators on the same Array are independent.
Sorting and search:
- sort() — in-place; requires T IMPLEMENTS Comparable[T].
- contains(v) — linear scan; requires T IMPLEMENTS Equatable[T].
Functional transforms (map / filter / reduce) are pure-Envzn loops over the element domain and return new Arrays.
The storage shorthand T[] is the same physical layout as
Array[T]; the difference is API surface. T[] exposes only the
minimal value-array surface (subscript read + HWM write proxy,
.length, .capacity, clear(), remove(i), iterator());
Array[T] does the richer work (append / insertAt / count / sort …)
on top of that surface and maintains a public length field. The
sole developer-visible difference between int32[] and an object
T[] is that object elements must relocate with := (a move),
never a plain =.
kernel/src/OrderedArray.ev:71
Constructors
INIT()
Default constructor
INIT(int64 presized)
for presized Stack allocation
Methods
OVERRIDE METHOD clone() RETURNS OrderedArray[T]
Deep copy. Each element is cloned via T->clone(); T must
implement Cloneable when T is a class type.
For T IMPLEMENTS Inoperative (opaque only today): returns
an empty Array — Inoperative values are opaque so we can't
preserve them on clone.
MODIFY METHOD sort() RETURNS void
In-place insertion sort using T->isLessThan(other). T must implement Comparable[T] — the compiler emits a clear "T does not implement Comparable" diagnostic at instantiation otherwise. O(n^2) worst case; acceptable for the small-N collections this V1 kernel typically holds. A heap-or-merge-sort upgrade is a V2 candidate if profiling demands it.
METHOD contains(T value) RETURNS boolean
Linear scan for an element equal to value. T must implement
Equatable[T] for class T. O(n).
METHOD equals(OrderedArray[T] other) RETURNS boolean
Element-wise equality. Two arrays are equal when they are the same
length and every element at the same index is equal — which makes two
EMPTY arrays equal, the case the previous form got wrong: it seeded
result = FALSE and only ever set it TRUE from inside the element
loop, so a pair of empty arrays passed the length test, never entered
the loop, and were reported UNEQUAL.
METHOD isLessThan(OrderedArray[T] other) RETURNS boolean
LEXICOGRAPHIC, the same order String.isLessThan settled on and for the
same reason. The first differing element decides; if neither array
differs through the shorter one's length, the shorter is less — so a
prefix sorts before what extends it.
This returned .length < other.length. Length-first is a different
total order, not a cheaper route to this one: it ranks [9] below
[1,1] and calls every pair of same-length arrays equal-or-greater
regardless of content, which silently reorders any OrderedArray-keyed
RedBlackTreeDictionary and makes sort() over arrays-of-arrays wrong.
CLASS OwnedList
IMPLEMENTS Collection
Owning, move-only, non-Cloneable list.
────────────────────────────────────────────────────────────────── WHAT IT IS ──────────────────────────────────────────────────────────────────
OwnedList[T] is a singly-linked list that OWNS its elements and only
ever MOVES them — it never copies or clones. Because it is not itself
Cloneable, it imposes no Cloneable requirement on T; its bound is
T IS Shareable, so it can hold non-Cloneable kernel concurrency
primitives (Channel, Mutex, Future, …) that the standard collections
(Array / Dictionary / LinkedList / Set, all IMPLEMENTS Cloneable)
reject.
It is the registry behind Broker[T] (a list of per-subscriber
Channels), and is reusable for any "hold a set of owned, non-copyable
objects, walk them, add/remove by id" need.
────────────────────────────────────────────────────────────────── API (minimal) ──────────────────────────────────────────────────────────────────
- add(MOVE T value) RETURNS uint64 — take ownership, prepend, return a stable id for removal
- removeById(uint64 id) RETURNS boolean — splice out that node (close/drop its element)
- iterator() RETURNS OwnedListIterator[T] — forward walk by REFERENCE
- length (field) / isEmpty() — count of user nodes
Structure mirrors LinkedList: a head sentinel (id 0) and an ender
sentinel (id 1) bracket the user nodes (id ≥ 2). Splices use the
move-out-before-overwrite ordering. Singly-linked, owning-
forward; dropping .head cascade-frees the chain.
Built to back Broker[T].
kernel/src/OwnedList.ev:48
Fields
int32 length
Constructors
INIT()
Methods
MODIFY METHOD add(MOVE T value) RETURNS uint64
Take ownership of value and prepend it (right after the head
sentinel). O(1). Returns the new node's stable id, which the
caller passes to removeById later.
Splice ordering (Bug #32 move-out-before-overwrite): step 1: newNode.next := .head.next (MOVE old first into newNode.next) step 2: .head.next := newNode (MOVE newNode into .head.next)
MODIFY METHOD removeById(uint64 id) RETURNS boolean
Remove the user node with the given id. Returns TRUE if found and
removed; FALSE if no live node carries that id. Walks from the
head sentinel tracking the predecessor, then splices the target
out (its owned element is dropped as popped leaves scope).
METHOD findById(uint64 id) RETURNS | REFERENCE T
Return a REFERENCE to the element of the node with the given id. Pipe-XOR: SUCCESS yields the reference; FAILURE if no live node carries that id. Used by Broker.subscribe to hand a Subscription a non-owning handle to its just-added channel.
METHOD size() RETURNS int64
Countable — the count of user nodes (sentinels excluded). .length is
the int32 field; size() is the int64 contract, and int32 -> int64 is
a widening, so the assignment carries it with no stated conversion.
METHOD isEmpty() RETURNS boolean
METHOD iterator() RETURNS OwnedListIterator[T]
Fresh forward cursor positioned at the first user node (or the
ender if empty). Pass .head by-& so the iterator's REFERENCE
binding is mutation-locked at INIT without moving .head out.
CLASS OwnedListIterator
IMPLEMENTS ReferenceIterator
Forward cursor over OwnedList[T].
A fresh iterator from OwnedList.iterator() is positioned at the
first user node (or the ender sentinel if the list is empty). Drive
it with the pipe-XOR loop:
OwnedListIterator[T] it := list->iterator()
WHILE it->next() DO {
REFERENCE T value =@ $RETURNED
... use value in place (no copy) ...
}
Mirrors LinkedListIterator (the proven cursor model), bound to
T IS Shareable so it can walk a chain of non-Cloneable elements.
next() yields a REFERENCE to the element — the consumer operates on
it in place; the element is never copied out of the list.
Plain FINAL class (not IMPLEMENTS ReferenceIterator): OwnedList's
consumers drive next() directly, and the for-in / ReferenceIterator
protocol carries a Cloneable-shaped contract we don't want here.
kernel/src/OwnedListIterator.ev:33
Constructors
INIT(OwnedListNode[T] sourceHeadNode)
Methods
METHOD hasNext() RETURNS boolean
Iterator — TRUE while the cursor still sits on a user node. The ender
sentinel is id 1, which is exactly the condition next() refuses on,
so the two agree by construction.
This class implemented NO interface at all until now, which is why it
alone of the iterators could not satisfy Collection.iterator() —
ArrayIterator and the rest were already BidirectionalIterator OF T.
METHOD peek() RETURNS | REFERENCE T
ReferenceIterator — the reference at the current cursor WITHOUT
advancing. Same pipe-XOR shape as next() and the same refusal at the
ender sentinel; it simply does not move cursorRef.
MODIFY METHOD skip(uint64 n) RETURNS | REFERENCE T
ReferenceIterator — advance n positions and yield what lands under
the cursor. skip(0) is next(); running off the ender fails exactly
as next() does, so a short list refuses rather than walking past the
sentinel.
MODIFY METHOD next() RETURNS | REFERENCE T
Advance and yield the next element by REFERENCE. Pipe-XOR:
SUCCESS populates value; FAILURE once the cursor reaches the
ender sentinel (end of iteration).
CLASS Path
IMPLEMENTS Cloneable, Comparable
Instance class wrapping a filesystem path with rich operations.
Path is the comprehensive filesystem-path API in the kernel. Where File (File.ev) is minimal — just a path String + read/write — Path layers existence checks, directory operations, manipulation (parent, basename, stem, extension, join, withExtension, resolve), and tree traversal (listFiles, makeDir, remove).
Internally a Path is a value-type wrapper around a String. Path instances are cheap to construct and hand around; they don't hold OS resources beyond the String itself.
The filesystem-touching methods (exists / isDirectory / isFile / resolve / listFiles / makeDir / remove) are backed by the native shims in EV_path_native.hpp via FOREIGN BIND. The manipulation methods (parent / basename / stem / extension / join / withExtension / toString) are pure Envzn — they never touch the filesystem.
────────────────────────────────────────────────────────────────── ERROR HANDLING ──────────────────────────────────────────────────────────────────
- I/O failures (resolve, listFiles, makeDir, remove) surface as a
FAILURESTATUS in the method's multi-return — never throw. Per the kernel-wide error-handling convention, callers inspect the STATUS slot (IF p->resolve() THEN { ... $RETURNED ... } ELSE { ... $! }). - Pure-string operations (parent, basename, stem, extension, join, withExtension, toString) never fail — they manipulate the path String, not the filesystem.
- Existence checks (exists, isDirectory, isFile) return boolean; they do not fail on absence (that's the whole point of asking).
kernel/src/Path.ev:64
Fields
String value
Constructors
INIT(String value)
Methods
METHOD clone() RETURNS Cloneable
METHOD exists() RETURNS boolean
EXISTENCE CHECKS — never fail TRUE iff something exists at this path (file, directory, or other).
METHOD isDirectory() RETURNS boolean
TRUE iff this path exists and is a directory.
METHOD isFile() RETURNS boolean
TRUE iff this path exists and is a regular file.
METHOD parent() RETURNS Path
PURE-ENVZN PATH MANIPULATION
The six methods below — parent / basename / stem / extension /
withExtension / join — perform byte-level string manipulation
around the / separator. The filesystem is never touched, so
they are pure Envzn: cross-platform-consistent semantics
(forward-slash only), demagic-plan-aligned scrutability, and one
fewer dependency for the future LLVM-IR self-host.
Paths are treated as code-point strings: / is 0x2F and . is
0x2E (both ASCII, one code point each). Multi-byte codepoints
inside path segments pass through unchanged.
Returns the parent directory. For "/a/b/c" returns "/a/b".
For "/a/b/c/" returns "/a/b/c" (the trailing slash makes the
filename empty). For a relative single-segment path ("a"),
returns the empty path. For "/" returns "/" (root's own
parent — fixed point).
METHOD basename() RETURNS String
Filename including extension. For "/a/b/file.txt" returns "file.txt". For a path ending in "/" returns the empty String.
METHOD stem() RETURNS String
Filename without extension. For "/a/b/file.txt" returns "file". For "file.tar.gz" returns "file.tar" (only the last extension is stripped). For dotfiles like ".bashrc" returns ".bashrc" (no leading-dot extension stripped).
METHOD extension() RETURNS String
The file's extension including the leading dot. For "file.txt" returns ".txt". For an extensionless filename returns the empty String. For a leading-dot dotfile (".bashrc") returns the empty String — the leading dot is part of the stem, not an extension.
METHOD withExtension(String ext) RETURNS Path
Returns a new Path with the extension replaced. ext may
include or omit the leading dot. For withExtension(".cpp")
on "file.txt" returns Path "file.cpp". For withExtension("")
strips the extension entirely (returning the stem-form path).
METHOD join(String segment) RETURNS Path
Append segment as a child path component. The result is
value + "/" + segment, normalised so a trailing slash on
value or a leading slash on segment doesn't produce a
double "//". If segment is absolute (starts with '/') it
replaces value entirely — matches std::filesystem::path's
operator/ semantics.
METHOD resolve() RETURNS | Path
Resolve the path: if relative, prepend CWD; resolve any symbolic links; canonicalise '.' and '..' segments. Canonical resolution requires existence, so a missing path is FAILURE. Pipe-XOR: SUCCESS populates the resolved Path; FAILURE the STATUS.
METHOD listFiles() RETURNS | Array[Path]
DIRECTORY OPERATIONS — STATUS multi-return on failure
List the immediate children of this directory as Paths. Each
child is this joined with the entry's filename (forward-slash
semantics via join()). FAILURE if this path is not an existing
directory. An empty directory yields an empty Array (SUCCESS).
METHOD makeDir() RETURNS STATUS
Create the directory (and any missing parents). No-op if it already exists. FAILURE on permission error.
METHOD remove() RETURNS STATUS
Remove the file or directory tree. FAILURE on permission error or unreachable entries during traversal.
METHOD readText() RETURNS String
I/O — delegates to File
Convenience wrappers — for callers that already have a Path, it's natural to read/write directly without constructing a separate File instance. These are equivalent to: File f := CREATE File(.value); f->readText() / writeText
METHOD writeText(String content) RETURNS STATUS
METHOD modifiedAt() RETURNS | int64
Last modification time, in NANOSECONDS since the Unix epoch. FAILURE when the path cannot be stat'd — it does not exist, or a component of it is not searchable.
Nanoseconds rather than whole seconds because the caller is a build tool: a source edited in the same second as the build that consumed it must still read as newer, or the rebuild is silently skipped. An int64 of nanoseconds since the epoch runs to the year 2262.
IF p->modifiedAt() THEN { int64 ns := $= }
METHOD toString() RETURNS String
STRING REPRESENTATION
METHOD equals(Path other) RETURNS boolean
METHOD isLessThan(Path other) RETURNS boolean
CLASS PrimitiveConversions
CONVERSIONS host for primitive↔primitive numeric and character conversions. The AS/INTO surface over the number/char conversion matrix.
widening → OPERATOR INTO (lossless, total) e.g. int32 INTO int64 narrowing / → OPERATOR AS (lossy, fallible) e.g. int64 AS int32 sign-cross (range-checked → STATUS)
No class dependencies (only primitives + STATUS), so this host orders early in the kernel include topology and is reachable from every kernel class.
NOT here (they live on NumericUtilities): the deliberate UNCHECKED bit operations
(truncateTo*, the saturating toInt32(uint32), the reinterpreting
toUint64(int32)) — they would collide with the checked AS for the same
type pair; the char-transcoding narrowings (char16/char32 → char8,
char32 → char16) — they route through UTFCodec; and the character
classifiers (isAlpha/isDigit/…) — predicates, not conversions.
kernel/src/PrimitiveConversions.ev:28
CLASS ProHashDictionary
IMPLEMENTS Dictionary
The DoS-RESISTANT dictionary, rebuilt (2026-07-13) on the byte-keyed substrate that made StringKeyedDictionary beat C. Pure Envzn, zero-dep beyond the SipHash key shim.
WHY THE REBUILD (the measurement that forced it):
The old ProHash — Dictionary[BinaryWord,V,H] over char32 String keys, interface dispatch,
pipe-XOR returns, heap-node chains — ran the s05 word-dict at 1505 ms, 32x C.
The s05 experiment envzn_strkeyed_sip then ran the SAME SipHash-2-4 over the
byte-keyed open-addressing substrate at 49 ms — a 30x speedup at IDENTICAL
DoS-resistance. So ProHash's cost was never SipHash; it was the SHAPE. This class
keeps everything that made it "Pro" and rebuilds the shape underneath it.
WHAT MAKES IT "PRO" (both properties retained): 1. SipHash-2-4 keying. A process-global 16-byte secret (OS entropy, the same FOREIGN key shim SipHasher uses), so an attacker cannot predict bucket distribution — hash-flooding DoS defence by construction. 2. ADAPTIVE deep tier: chain -> RED-BLACK TREE. A bucket starts as a linear CHAIN (optimal when shallow — the overwhelmingly common case). If a bucket's depth exceeds TREEIFY_THRESHOLD (8) it is TREEIFIED into a red-black tree ordered by (cached hash, then key bytes) — a total order — so even a bucket an attacker somehow manages to overfill degrades to O(log n), never O(n). This is the belt-and-suspenders behind the SipHash keying: SipHash makes deep buckets unforceable; the tree bounds the damage if one ever occurs anyway.
THE SPEED LEVERS (carried over from StringKeyedDictionary):
- char8[] BYTE keys, not String. String stores char32 code points and hashes /
compares them one handle-indirected, bounds-checked element at a time — that was
the dominant cost. The caller transcodes ONCE (s INTO ByteBuffer -> bytes).
(Byte keys also mean this cannot be a Dictionary[BinaryWord,V,H] impl: the interface
cannot fix a type parameter (E2103) and an array key is not PRIMITIVE. It is a
standalone CLASS, as StringKeyedDictionary is.)
- System->memoryWord absorbs the SipHash message 8 BYTES PER ROUND (one wide
load) instead of eight bounds-checked byte reads.
- HOTLOOP hoists the per-byte bounds check out of the tail/compare loops.
- System->memoryCompare confirms a key with one std::memcmp, not a per-byte loop.
- No pipe-XOR on the hot path: lookupFast returns a trivial (found, value) and
operator[] returns V by value — neither builds the (V | STATUS) tuple. The
pipe-XOR lookup remains for the kernel idiom, off the hot path.
STORAGE — a flat byte pool + a flat ENTRY ARENA (SoA), no per-entry heap node:
- keyPool : char8[] — every key's bytes, contiguous.
- per-bucket: bucketList (the member CHAIN head — kept intact in BOTH modes, so
enumeration is always a chain walk), bucketRoot (the red-black index root when
treeified; -1 otherwise), bucketMode (0 chain / 1 tree), bucketDepth. The tree
is an ADDITIONAL lookup index over the same entries, never the sole structure —
that is what keeps rehash/remove/clone simple.
- per-entry: entHash, entOff/entLen (slice into keyPool), entVal,
entNext (chain link, doubles as the free-list link), entLeft/entRight/
entParent/entRed (red-black links, tree mode), entLive.
Freed entries are recycled through freeHead.
REMOVE — a deliberate, bounded simplification (labelled, not hidden): removing from a TREEIFIED bucket does not run the CLRS red-black delete-fixup. It collects the bucket's live entries, drops the target, and REBUILDS the bucket (re-chained if the remaining depth <= UNTREEIFY_THRESHOLD, else re-treeified). That is O(depth) — bounded, correct, and it keeps ~150 lines of the most bug-prone code in a red-black tree out of the kernel. Lookup and insert — the hot paths — use the real red-black tree.
kernel/src/ProHashDictionary.ev:78
Constructors
INIT()
Methods
MODIFY METHOD insertSlice(REFERENCE REFERENCE binary[] buf, int64 off, int64 len, MOVE V value) RETURNS STATUS
METHOD lookupFastSlice(REFERENCE REFERENCE binary[] buf, int64 off, int64 len) RETURNS boolean
Comma-return lookup — no pipe-XOR (V | STATUS) tuple on the hot path.
METHOD containsSlice(REFERENCE REFERENCE binary[] buf, int64 off, int64 len) RETURNS boolean
MODIFY METHOD removeSlice(REFERENCE REFERENCE binary[] buf, int64 off, int64 len) RETURNS boolean
Remove. Unlinks the entry from the bucket's member chain, then RE-INDEXES the bucket: a treeified bucket that has fallen to the untreeify threshold drops its red-black index and reverts to a plain chain (hysteresis: treeify at >8, untreeify at <=6, so churn around the boundary cannot thrash); one still above it rebuilds the index. Rebuilding rather than running the CLRS red-black delete-fixup is a deliberate, bounded simplification (O(depth)) — labelled, not hidden. It keeps the single most bug-prone routine in a red-black tree out of the kernel, and remove is not the hot path; lookup and insert use the real tree.
MODIFY METHOD insert(REFERENCE REFERENCE binary[] key, MOVE V value) RETURNS STATUS
METHOD lookupFast(REFERENCE REFERENCE binary[] key) RETURNS boolean
METHOD __op_index__(REFERENCE REFERENCE binary[] key) RETURNS V
Direct value read — d[key]. V default (0) on a miss. No pipe-XOR.
METHOD lookup(REFERENCE REFERENCE binary[] key) RETURNS | V
Pipe-XOR lookup — the kernel idiom, kept OFF the hot path.
METHOD lookupRef(REFERENCE REFERENCE binary[] key) RETURNS | MUTABLE REFERENCE V
METHOD contains(REFERENCE REFERENCE binary[] key) RETURNS boolean
MODIFY METHOD remove(REFERENCE REFERENCE binary[] key) RETURNS boolean
METHOD isEmpty() RETURNS boolean
METHOD size() RETURNS int64
MODIFY METHOD clear() RETURNS void
METHOD bucketIndexOf(REFERENCE REFERENCE binary[] key) RETURNS int64
The bucket a key currently maps to.
Exposed for TESTABILITY of the adaptive tier. SipHash keying means an attacker cannot force colliding keys — which also means a TEST cannot, so the chain->tree escalation would otherwise be unreachable, untested kernel code. This lets a test group keys that share a bucket and drive the escalation deterministically.
It does NOT weaken the DoS guarantee: hash-flooding's threat model is "the attacker supplies KEYS (data) that the victim hashes", not "the attacker calls methods on the victim's dictionary". Anyone able to invoke this already has code execution in-process, at which point the hash is not the weak link. Do not, however, echo the result across a trust boundary — that WOULD hand out a collision oracle.
METHOD maxBucketDepth() RETURNS int64
Deepest bucket in the table — the clustering metric the adaptive tier bounds.
METHOD treeifiedBuckets() RETURNS int64
How many buckets have escalated to a red-black tree. Zero under normal load — non-zero means collisions got deep (the adaptive tier is doing its job). Exposed so the escalation is TESTABLE and observable, not a silent internal.
METHOD iterator() RETURNS ReferenceIterator[BinaryWord]
One BinaryWord per live entry, borrowed from this dictionary's key
pool. Valid only while the dictionary is unmodified: an insert may grow
the pool and a rehash may move it.
METHOD keys() RETURNS LinkedList[BinaryWord]
MODIFY METHOD insertCopy(BinaryWord key, V value) RETURNS STATUS
MODIFY METHOD insert(MOVE BinaryWord key, MOVE V value) RETURNS STATUS
METHOD lookupFast(REFERENCE REFERENCE BinaryWord key) RETURNS boolean
Dictionary.lookupFast — the interface-shaped (BinaryWord-keyed) peer of
the binary[] slice form above. K is BinaryWord for this class, so this
is the overload that satisfies the interface; the slice form stays for
the zero-copy call sites that already hold a buffer and a range.
METHOD lookup(REFERENCE REFERENCE BinaryWord key) RETURNS | MUTABLE REFERENCE V
METHOD contains(REFERENCE REFERENCE BinaryWord key) RETURNS boolean
MODIFY METHOD remove(REFERENCE REFERENCE BinaryWord key) RETURNS | BinaryWord
The removed key is a word over this dictionary's own pool. A freed entry
is recycled through freeHead but its BYTES stay in the pool, so the
returned word reads correctly until the pool is next compacted.
METHOD equals(Dictionary[BinaryWord, V, H] other) RETURNS boolean
Equal when both hold the same keys mapped to the same values. Walks the smaller surface — this dictionary's entries — and asks the other by lookup, so the cost is one hash per key rather than a cross product. Two dictionaries are equal when they hold the SAME ASSOCIATIONS: the same keys, each mapping to an EQUAL VALUE.
Not merely the same KEYSET. That would rank {a:1} equal to {a:2}, so
two "equal" dictionaries would answer lookup(a) differently and neither
could stand in for the other — which is the whole point of an equivalence.
Keys-AND-values is what C++ (map and unordered_map), Rust, Swift,
Julia, Python and Java all specify. Keyset equality is a perfectly good
relation, but it is equality of the DOMAIN rather than of the dictionary,
and it deserves its own name (hasSameKeys) rather than this one.
Equal sizes plus a one-way walk is sufficient: with the counts already
equal, other cannot carry a key this one lacks unless this one also
carries a key other lacks — and the walk would have found that.
METHOD clone() RETURNS Dictionary[BinaryWord, V, H]
Deep copy. Returns the Dictionary INTERFACE, not the concrete class —
the class is HIDDEN, so its name may appear only as a CREATE target. The
copy goes in through the interface's own insert, since insertSlice
is this class's and not the interface's.
CLASS ProHashDictionaryIterator
IMPLEMENTS ReferenceIterator
Walks the ENTRY ARENA, yielding one BinaryWord per live entry.
WHY THE ARENA AND NOT THE BUCKETS. ProHash stores entries in flat parallel arrays and threads them into buckets — a chain when shallow, a red-black tree once a bucket passes the treeify threshold. Walking the buckets would mean two traversal shapes and a recursion for the tree tier; walking the arena is one linear pass that is correct for both, and it touches each array in order rather than chasing indices. Iteration order is arena order, which is neither insertion nor hash order — a dictionary promises no order, and this one says so rather than implying one it would have to keep.
Freed entries are recycled through freeHead, so a dead slot may sit
anywhere in the arena; entLive is the only thing that decides.
Like its StringKeyed sibling, the window is one VALUE field re-assigned per step — no allocation per key.
kernel/src/ProHashDictionaryIterator.ev:32
Constructors
INIT(REFERENCE REFERENCE binary[] pool, REFERENCE REFERENCE int64[] offs, REFERENCE REFERENCE int64[] lens, REFERENCE REFERENCE int8[] live, int64 arenaSize)
Methods
METHOD hasNext() RETURNS boolean
MODIFY METHOD next() RETURNS | REFERENCE BinaryWord
METHOD peek() RETURNS | REFERENCE BinaryWord
MODIFY METHOD skip(uint64 n) RETURNS | REFERENCE BinaryWord
CLASS Process
Process — kernel singleton for synchronous shell-out.
Stateless; never instantiated by user code. Always accessed
via Process->run(...).
kernel/src/Process.ev:46
Methods
METHOD run(String command, String[] args) RETURNS ProcessResult
Run command with args, capturing stdout, stderr, and
the exit code into a ProcessResult.
Blocks until the child process exits. PATH lookup follows
the standard execvp rules — passing a bare program name
(e.g. "tar") searches $PATH; an absolute or
directory-relative path (e.g. "/bin/sh", "./build") is
used directly.
Failure surfaces as exitCode == -1 (signal-killed); a
pipe() / fork() / waitpid() failure PANICs an Error.
execvp failure inside the child writes "execvp failed:
..." to stderr and exits with code 127.
CLASS Queue
EXTENDS Stack
Defines the Queue[T] class per ENVZN_CONSTITUTION §Queue.
Queue[T] is a FIFO collection implemented as a thin layer over
Array[T] (a.k.a. T[]). All storage and per-element ownership
semantics live in Array[T]; Queue contributes the FIFO discipline
and the public count field. Front is index 0, back is the last
element — enqueue appends, dequeue removes from index 0.
Performance note (Path B-pragmatic tradeoff):
- enqueue / enqueueCopy are O(1) amortised — same as Stack push
- dequeue is O(n) — removes from index 0; Array shifts elements down
- peek is O(1)
Callers who need O(1) dequeue should use Deque[T], which gets its
own std::deque backing (per Path B-pragmatic split — see
project_kernel_demagic_stage22.md memory). Queue-on-Array eats
the dequeue cost in exchange for a single backing storage shape
shared with Stack and SortedList.
kernel/src/Queue.ev:31
Constructors
INIT()
default constructor
INIT(int64 presized)
for presized Stack allocation
Methods
OVERRIDE METHOD clone() RETURNS Queue[T]
Deep copy. Each element is cloned via T->clone(); T must
implement Cloneable when T is a class type.
For T IMPLEMENTS Inoperative (opaque only today): returns
an empty Array — Inoperative values are opaque so we can't
preserve them on clone.
MODIFY METHOD enqueue(MOVE T value) RETURNS STATUS
Add value at the back of the queue. Per §10.2 the bare-named
form consumes the source — value is moved through Array's
MOVE append into the queue's backing.
MODIFY METHOD enqueueCopy(T value) RETURNS STATUS
Deep-copy form of enqueue. value stays valid for the caller;
the clone is what the backing array stores.
OVERRIDE METHOD peek() RETURNS | REFERENCE T
Remove and return the front element. Pipe-XOR: SUCCESS populates
value; FAILURE when the queue is empty. O(n) — depends on Array
providing popFirst() with move-out semantics for class T.
FIFO peek — the FRONT element, which is NOT what Stack.peek()
returns.
Queue EXTENDS Stack, and Stack's peek() is LIFO: it forwards to
peekLast(). Inherited unchanged, a Queue reported its most recently
ENQUEUED element as its front — dequeue() (which uses popFirst) and
peek() disagreed about which end the queue's head is. queueSmoke caught
it as "expected 11, got 22".
OVERRIDE is required here (W7020): this reimplements a CONCRETE inherited method rather than fulfilling an abstract contract.
MODIFY METHOD dequeue() RETURNS | T
CLASS RandomError
EXTENDS Error
Kernel error class for the Random surface. Thrown by SecureRandom when OS entropy is unavailable, and by the generators on a non-positive bound (nextInt(bound <= 0)).
Subclass of Error; adds no new fields.
kernel/src/RandomError.ev:17
Constructors
INIT(String message)
CLASS RedBlackTreeDictionary
IMPLEMENTS Dictionary
An ORDERED Dictionary[K, V, H] backed by a left-leaning red-black tree (Sedgewick LLRB), ARENA form. Guaranteed O(log n) lookup / insert / remove; iteration yields keys in Comparable order.
── Phase-4 arena rewrite (2026-06-24) ────────────────────────────────────────
Nodes live inline in ONE RBNode[K, V][] arena value-array; child links are
int64 arena indices (-1 = null), not owning handles. The owning-handle design's
per-node heap allocation (one _ev_unique<RBNode> per key) collapses to a
single growable array. rootIdx is a plain int64 (no RBRoot holder — index
assignment can't trip the move-out/EMPTY-consume rules that forced the holder).
A free-list (freeHead, threaded through a dead slot's left) reclaims slots
on delete. The iterator walks an explicit index stack (O(n) total), retiring
the subSize/nthKeyRef rank machinery.
THE ARENA DISCIPLINE (load-bearing — proved by kernel_probe/rbArenaProbe):
appending a node (alloc) can REALLOCATE the arena and dangle any live
reference into it. So: indices are stable, references are NOT. Every node
access is wrapped in a tiny index-helper (leftOf, setLeftAt, …) that binds
a fresh =@ .arena[i], touches the node, and drops the ref before returning —
so no reference is ever held across an alloc()/insertNode() that might grow the
arena. Insert allocs exactly one leaf at the bottom of the recursion; delete
never allocs (only frees), so the delete path is realloc-free.
(The helpers also sidestep a parser limitation: a bare { … } block whose
first statement declares a comma-bearing type — REFERENCE RBNode[K, V] n — is
misparsed as a set literal. Encapsulating each bind in a method avoids the bare
block entirely and reads better.)
K must additionally be Comparable (the tree orders by key). The hasher type H
is part of the Dictionary interface but UNUSED here — an ordered tree compares
keys, it does not hash — so no hasher is ever constructed (H bound satisfied
vacuously). Comparison goes through the WHEN K IMPLEMENTS Comparable[K] split
(the SortedList idiom): the compiler lowers built-in Comparable (primitives,
String) under that guard; a bare a->isLessThan(b) emits a literal method
clang rejects for primitives.
kernel/src/RedBlackTreeDictionary.ev:52
Fields
int64 rootIdxint64 countint64 freeHead
Constructors
INIT()
INIT(MOVE Hasher[K] h)
INIT(GrowthHint hint, int64 initialCapacity)
INIT(MOVE Hasher[K] h, GrowthHint hint, int64 initialCapacity)
Methods
MODIFY METHOD insert(MOVE K key, MOVE V value) RETURNS STATUS
MODIFY METHOD insertCopy(K key, V value) RETURNS STATUS
METHOD lookup(REFERENCE REFERENCE K key) RETURNS | MUTABLE REFERENCE V
METHOD lookupFast(REFERENCE REFERENCE K key) RETURNS boolean
Dictionary.lookupFast — the same descent as lookup, without the
STATUS that lookup must allocate on a miss.
MODIFY METHOD remove(REFERENCE REFERENCE K key) RETURNS | K
Remove — hands back the STORED key and value (not the probe key, which is what the old key-only remove returned).
Like the SoA dictionaries and unlike ChainedHashDictionary, the halves are cloned rather than moved: the arena node is reached by index and a subscript read clones by rule (stmt_assign.py:149), and the LLRB delete rebalances the tree, so the node cannot simply be evicted first. Census pins this at one DUPLICATE per half.
METHOD contains(REFERENCE REFERENCE K key) RETURNS boolean
METHOD isEmpty() RETURNS boolean
METHOD size() RETURNS int64
Number of key-value pairs currently stored. Read-only, O(1).
MODIFY METHOD clear() RETURNS void
METHOD keys() RETURNS LinkedList[K]
METHOD iterator() RETURNS ReferenceIterator[K]
METHOD clone() RETURNS Dictionary[K, V, H]
METHOD equals(Dictionary[K, V, H] other) RETURNS boolean
for Equatable Interface Dictionary is equal if all keys between the two dictionaries are equal Two dictionaries are equal when they hold the SAME ASSOCIATIONS: the same keys, each mapping to an EQUAL VALUE.
Not merely the same KEYSET. That would rank {a:1} equal to {a:2}, so
two "equal" dictionaries would answer lookup(a) differently and neither
could stand in for the other — which is the whole point of an equivalence.
Keys-AND-values is what C++ (map and unordered_map), Rust, Swift,
Julia, Python and Java all specify. Keyset equality is a perfectly good
relation, but it is equality of the DOMAIN rather than of the dictionary,
and it deserves its own name (hasSameKeys) rather than this one.
Equal sizes plus a one-way walk is sufficient: with the counts already
equal, other cannot carry a key this one lacks unless this one also
carries a key other lacks — and the walk would have found that.
CLASS RedBlackTreeDictionaryIterator
IMPLEMENTS ReferenceIterator
Forward, in-order key iterator for RedBlackTreeDictionary (ARENA form). Yields keys in Comparable order (the tree's natural order), each as a non-owning REFERENCE into the live arena.
This holds an explicit index stack of the un-visited left spine and advances ONE step per next(): pop the top (the next in-order node), then push the left spine of its right child. O(n) total, O(h) space.
Per the iterator rules it holds NEITHER an iterator-typed field (Rule A /
E1112) NOR an owning class field (Rule B / E1113); a mutation-locked REFERENCE
to the arena array plus primitive cursors and an int64[] stack (a value-typed
storage shorthand — exempt from Rule B) are all permitted. The per-node key is
bound DIRECTLY to the node's INTERNAL key field (value =@ nd.key), never via
an accessor (whose inlining into a =@ bind would copy the move-only key —
the key-bind gotcha shared with HashBucketIterator).
Parameterised over [K, V] (V needed to name RBNode[K, V]); H is irrelevant.
kernel/src/RedBlackTreeDictionaryIterator.ev:34
Constructors
INIT(RBNode[K, V][] arena, int64 root, int64 count)
Methods
METHOD hasNext() RETURNS boolean
MODIFY METHOD next() RETURNS | REFERENCE K
METHOD peek() RETURNS | REFERENCE K
MODIFY METHOD skip(uint64 n) RETURNS | REFERENCE K
CLASS SecureRandom
IMPLEMENTS Random
A cryptographically-secure Random drawing fresh operating-system entropy on every call (getentropy via the native shim). For tokens, session identifiers, salts, and keys. Not a PRNG: there is no seed and no reproducibility. INIT throws RandomError if the OS entropy source is unavailable.
Thread-affine (see the Random interface note): not a SHARED CLASS.
kernel/src/SecureRandom.ev:23
Constructors
INIT()
Methods
METHOD boundedSecure(uint64 bound) RETURNS uint64
Uniform unsigned in [0, bound) over fresh entropy — the same 128-bit Lemire rejection as the PRNG path, but each draw pulls new OS entropy rather than advancing a seeded engine. Assumes bound >= 1.
MODIFY METHOD nextInt(int64 bound) RETURNS int64
Uniform in [0, bound) over fresh entropy. bound <= 0 throws.
The former nextLong; see Xoshiro256.nextInt for why the two collapsed.
A checked r AS int64 was tried here and its ELSE branch had nothing to
put in it, because the draw cannot reach the upper half of uint64:
bound > 0 by the guard, so b <= INT64_MAX, and boundedSecure returns
Lemire-uniform in [0, b) — the value is bounded BY an int64 and is
therefore always one. An UNREACHABLE! in that branch would be honest but
would still be a branch written for a statically-impossible case, which is
what the unchecked, NAMED helper exists to avoid. toInt64 states the
crossing in one line and is exact for every value this can produce.
MODIFY METHOD nextUInt128() RETURNS uint128
MODIFY METHOD nextFloat() RETURNS float64
MODIFY METHOD nextBoolean() RETURNS boolean
CLASS SeededRandom
IMPLEMENTS Random
A deterministic Random seeded from a developer-supplied int64. Same seed → same sequence, across runs and platforms (xoshiro256** + splitmix64 are fixed algorithms). For reproducible simulations, tests, and property-based testing.
Thread-affine (see the Random interface note): not a SHARED CLASS, so it
cannot be shared across threads; move it if a task needs sole ownership.
kernel/src/SeededRandom.ev:19
Constructors
INIT(int64 seed)
Methods
MODIFY METHOD nextInt(int64 bound) RETURNS int64
MODIFY METHOD nextUInt128() RETURNS uint128
MODIFY METHOD nextFloat() RETURNS float64
MODIFY METHOD nextBoolean() RETURNS boolean
CLASS Set
IMPLEMENTS Cloneable, Collection
Set is a thin pure-Envzn wrapper over a Dictionary
STORAGE Set[V, H] holds Dictionary[V, int64, H] data (the interface),
backed by a ChainedHashDictionary. The element is the dictionary KEY
(collision-safe: distinct elements that hash equal land in the same
bucket and are told apart by Equatable, never merged); the value is an
unused int64 dummy (0). A boolean value is impossible — the Dictionary
qualifier bans boolean V — so a small int is the minimal placeholder.
TWO TYPE PARAMETERS Set[V, H]:
- V — element type. Hashable + Equatable (the dict's key contract).
- H — the Hasher OF V. Callers name a concrete hasher
(e.g. Set[int32, DefaultHasher[int32]]); the no-arg INIT lets the
backing dictionary CREATE a DefaultHasher[V], a hasher-taking INIT
threads a supplied one through. H is phantom in the body (the dict
stores the hasher as the Hasher OF V interface).
ITERATION yields the elements (the dict's keys), forward-only, order
unspecified. iterator() returns the backing dictionary's key iterator
directly — there is no separate SetIterator.
.length is kept in sync by hand: the Dictionary surface has no count(),
and insert() is replace-or-add (SUCCESS either way), so add/remove gate
on a prior contains to keep the count exact.
Set[V, H] — unordered set of unique elements over a Dictionary.
kernel/src/Set.ev:43
Fields
int64 length
Constructors
INIT()
No hasher — the backing dictionary CREATEs a DefaultHasher[V].
INIT(MOVE Hasher[V] h)
Caller-supplied hasher, threaded into the backing dictionary.
INIT(GrowthHint hint, int64 initialCapacity)
Default hasher + sizing hints (forwarded to the dictionary).
INIT(MOVE Hasher[V] h, GrowthHint hint, int64 initialCapacity)
Caller hasher + sizing hints.
Methods
METHOD clone() RETURNS Set[V, H]
Deep copy — the backing dictionary clones every key.
METHOD iterator() RETURNS ReferenceIterator[V]
ITERATOR — yields the elements (the dictionary's keys).
METHOD isEmpty() RETURNS boolean
METHOD size() RETURNS int64
Number of elements currently stored. Read-only, O(1).
MODIFY METHOD clear() RETURNS void
MODIFY METHOD add(MOVE V value) RETURNS STATUS
ADD / REMOVE / CONTAINS
Add value (consumed). If already present, the element stays and
the moved-in value is dropped; .length is unchanged.
MODIFY METHOD addCopy(V value) RETURNS STATUS
Deep-copy form of add. value stays valid for the caller.
METHOD contains(V value) RETURNS boolean
MODIFY METHOD remove(V value) RETURNS STATUS
Remove value. SUCCESS if it was a member, FAILURE otherwise.
METHOD unionWith(Set[V, H] other) RETURNS Set[V, H]
SET ALGEBRA — |+| / |&| / |-| bind to these.
Union — every element from either input (deduped).
METHOD intersectionWith(Set[V, H] other) RETURNS Set[V, H]
Intersection — elements present in BOTH inputs.
METHOD differenceWith(Set[V, H] other) RETURNS Set[V, H]
Difference — elements in SELF but NOT in other (asymmetric).
CLASS ShallowDictionary
IMPLEMENTS Dictionary
A Dictionary[K,V,H] with a WHOLE-KEY-SET-AWARE multiply-shift index and a prime-modulus fallback tier: linear-probe open-addressing over a STRUCT-OF-ARRAYS. Pure Envzn, zero-dep.
STATUS — PARKED as a narrow, documented option, NOT the default. A measurement-driven
finding (2026-07-13) settled its role: a dictionary lookup is memory-bound, and
cache-fast access requires either an IDENTITY hash on dense keys (IntKeyedDictionary's
key & mask, ~1.1x C) or INLINE byte-dense slots (StringKeyedDictionary). A MIXING
index — FNV, SipHash, OR this class's multiply-shift — scatters adjacent keys to
random slots by design, eating a cache miss per probe, which floors any such dict at
~3-4x C. Measured here: ~4.6x C for int keys, SLOWER than ChainedHashDictionary
(~3.3x). So multiply-shift is robust-but-slow; it does NOT unify "fast + robust" (the
two are physically opposed). Its one distinct property — structural (bit-pattern)
attack resistance without a keyed hasher — is better served by ProHashDictionary
(SipHasher, which also stops hash-collision attacks). Kept correct + tested as an
explicit option; use IntKeyedDictionary / StringKeyedDictionary for speed,
ChainedHashDictionary as the general default, ProHashDictionary for DoS.
WHY "SHALLOW": at each rehash it holds the whole live key set, SEARCHES a few
candidate 64-bit multipliers, and adopts the one that keeps home-slot depth
shallowest for THIS key set — a hash chosen for the actual keys, not blind. If no
pow2 multiplier can tame a pathological set (keys whose entropy is trapped in a few
low bits — e.g. all multiples of a power of two), it falls back to a PRIME-sized
table with mod-prime indexing, which is coprime to any 2^k stride and breaks those
structural collisions. This is the robustness IntKeyedDictionary's hash & mask
deliberately trades away for cache speed — ShallowDictionary is the safe default;
IntKeyedDictionary is the trusted-key fast tier.
INDEX — two composed layers (slotFor):
- pow2 (common): slot = (mult * hash) >> shift, probe (s+1) & mask.
- prime (fallback): slot = (mult * hash) % p, probe s+1 wrap-at-p.
The multiply-shift mixes ALL bits of the hasher output, so it rescues a weak/cheap
hasher (FastIntHasher's identity) and is harmless for a strong one (FNV/SipHash is
already uniform → the search converges on the first candidate). Full hasher
pluggability AND a whole-key-set-aware low-collision index, together.
STORAGE — four parallel value-arrays (SoA, blittable → flat _ValueArray; the hot
probe touches only the dense slotState/slotHash, then one keys[s]):
- slotState : int8[] — 0 empty / 1 full / 2 tombstone. Liveness lives here, so
ANY key value is valid (no reserved sentinels — unlike IntKeyedDictionary).
- slotHash : uint64[] — cached full hash per slot; the slotHash[s]==h
fast-reject skips the key compare on almost all non-matches (crucial once String
keys land — a mismatched hash never calls equals).
- keys : K[], vals : V[] — parallel per-slot key/value.
DoS: hash-flooding resistance comes from the pluggable hasher H — construct as
ShallowDictionary[K,V,SipHasher[K]] for a keyed, attacker-proof hash; the prime
fallback additionally bounds structural (bit-pattern) attacks. Default
(DefaultHasher / FNV) is the fast general-purpose choice.
V1 SCOPE: primitive K/V (the blittable fast path — SoA _ValueArray). String keys
(the design's 5x-over-chained headline) are the next step; they need object-array
slot handling (empty-slot placeholders + the cached-hash reject to gate equals),
which the cached-hash column above is already laid out for.
IMPLEMENTS Dictionary[K, V, H] — Cloneable comes transitively (Dictionary EXTENDS
Cloneable). H is the interface hasher slot; the stored hasher is the Hasher OF K
interface (no-arg INIT CREATEs DefaultHasher). Plus the non-pipe-XOR fast path
(operator[] / lookupFast) for callers holding the concrete type.
kernel/src/ShallowDictionary.ev:76
Fields
int64 countint64 tombstonesint64 capint64 shiftAmtuint64 multint64 maskboolean primeModeuint64 primeP
Constructors
INIT()
INIT(MOVE Hasher[K] h)
Methods
MODIFY METHOD insert(MOVE K key, MOVE V value) RETURNS STATUS
MODIFY METHOD insertCopy(K key, V value) RETURNS STATUS
METHOD lookup(REFERENCE REFERENCE K key) RETURNS | MUTABLE REFERENCE V
METHOD __op_index__(REFERENCE REFERENCE K key) RETURNS V
Direct value read — d[key]. Returns the stored value on a hit, or the V
default (0) on a miss. No pipe-XOR (V | STATUS) tuple on the hot path.
METHOD lookupFast(REFERENCE REFERENCE K key) RETURNS boolean
Comma-return lookup — boolean f, V v := d->lookupFast(key). No pipe-XOR;
found distinguishes a miss from a stored default.
METHOD contains(REFERENCE REFERENCE K key) RETURNS boolean
MODIFY METHOD remove(REFERENCE REFERENCE K key) RETURNS | K
Remove — hands back the stored key and value. Like IntKeyedDictionary and unlike ChainedHashDictionary, an SoA open-addressed table has no move-out-at-index primitive, so a subscript read clones rather than moves (stmt_assign.py:149). Census pins this at one DUPLICATE per half.
METHOD isEmpty() RETURNS boolean
METHOD size() RETURNS int64
MODIFY METHOD clear() RETURNS void
METHOD iterator() RETURNS ReferenceIterator[K]
METHOD keys() RETURNS LinkedList[K]
METHOD clone() RETURNS Dictionary[K, V, H]
METHOD equals(Dictionary[K, V, H] other) RETURNS boolean
for Equatable Interface Dictionary is equal if all keys between the two dictionaries are equal Two dictionaries are equal when they hold the SAME ASSOCIATIONS: the same keys, each mapping to an EQUAL VALUE.
Not merely the same KEYSET. That would rank {a:1} equal to {a:2}, so
two "equal" dictionaries would answer lookup(a) differently and neither
could stand in for the other — which is the whole point of an equivalence.
Keys-AND-values is what C++ (map and unordered_map), Rust, Swift,
Julia, Python and Java all specify. Keyset equality is a perfectly good
relation, but it is equality of the DOMAIN rather than of the dictionary,
and it deserves its own name (hasSameKeys) rather than this one.
Equal sizes plus a one-way walk is sufficient: with the counts already
equal, other cannot carry a key this one lacks unless this one also
carries a key other lacks — and the walk would have found that.
CLASS ShallowDictionaryIterator
IMPLEMENTS ReferenceIterator
The ReferenceIterator OF K returned by ShallowDictionary.iterator(). Walks the struct-of-arrays slots, yielding a REFERENCE K per live slot (slotState[pos] == 1).
Holds mutation-locking REFERENCEs to the dictionary's keys value-array and its
slotState liveness array (bound with =@ in INIT); the lock pins the source
dictionary against mutation for the iterator's declaring-block lifetime. Cursor
pos is the next slot to examine; advanceToValid() skips empty (0) and tombstone
(2) slots.
Parameterised over [K, V] to match the dictionary's arity; V is unused (the values are not walked), the hasher H is not needed (the iterator never hashes).
kernel/src/ShallowDictionaryIterator.ev:28
Constructors
INIT(K[] keyArr, int8[] stateArr, int64 capacity)
Methods
METHOD hasNext() RETURNS boolean
MODIFY METHOD next() RETURNS | REFERENCE K
METHOD peek() RETURNS | REFERENCE K
MODIFY METHOD skip(uint64 n) RETURNS | REFERENCE K
INTERFACE Shuffleable
The interface a shuffleable collection implements. shuffle(rng) reorders the collection in place into a uniformly-random permutation, drawing index choices from the supplied Shuffler. Array[T] implements it (Fisher-Yates); any future ordered collection can too.
Same element bound as Array: T is a Cloneable class or a non-boolean primitive (the swap clones / moves elements through the backing storage).
kernel/src/Shuffleable.ev:20
Methods
MODIFY METHOD shuffle(Shuffler rng) RETURNS void
Reorder in place into a uniformly-random permutation, drawing from
rng. MODIFY — mutates the collection.
CLASS SipHasher
IMPLEMENTS Hasher
A DoS-resistant Hasher OF K backed by SipHash-2-4-64. Keyed by a process-global 16-byte secret (EV_siphash_native.hpp, seeded once from OS entropy), so an attacker cannot predict bucket distribution (hash-flooding defense). The default hasher inside ProHashDictionary; opt-in elsewhere as Dictionary[K, V, SipHasher[K]] / Set[V, SipHasher[V]].
hash() — the 64-bit SipHash-2-4 digest. (Formerly this class also exposed a 128-bit digest; the 128-bit half was removed because nothing consumed it — ProHashDictionary stores and keys on the low 64 bits — so computing it was pure waste. See benchmarks/s04_dict/envzn_siphash_opt for the measurement.)
A String key is absorbed as its raw UTF-8 bytes (via a ByteBuffer). A primitive key is absorbed as the eight bytes of its widened value, mirroring DefaultHasher's primitive path.
kernel/src/SipHasher.ev:31
Constructors
INIT()
The DEFAULT — canonical SipHash-2-4, keyed from OS entropy.
INIT(SipVariant v)
Opt in to the SipHash-1-3 round schedule (a third fewer rounds; the schedule Rust ships). Still keyed from OS entropy. 1-3 is a WEAKER PRF than 2-4 — choosing it is a security decision, not a performance one. See SipVariant.
INIT(uint64 key0, uint64 key1)
Adopt an explicit key — used by clone(), and available when a caller needs a REPRODUCIBLE digest (a fixed key hashes deterministically across runs).
INIT(uint64 key0, uint64 key1, SipVariant v)
Methods
METHOD clone() RETURNS SipHasher[K]
Cloneable — so a SipHasher can sit in a dictionary's concrete H slot (the
dictionaries DUPLICATE their hasher when cloned). The clone MUST carry the SAME
key AND the same variant: re-seeding (or re-scheduling) here would make the clone
disagree with the entries the cloned dictionary copied over, and every lookup in
the copy would miss.
METHOD roundSchedule() RETURNS SipVariant
Which round schedule this hasher runs.
METHOD hashSlice(REFERENCE REFERENCE binary[] buf, int64 off, int64 len) RETURNS uint64
SipHash-64 over a RAW BYTE SLICE buf[off .. off+len) — the fast byte path, and
the one byte-keyed callers (StringKeyedDictionary / ProHashDictionary) should use.
This is what hashBuffer below cannot be. It absorbs each 64-bit message word with
ONE System->memoryWord wide load instead of assembling it from eight
bounds-checked per-byte accessor calls, and HOTLOOP hoists the trailing bytes'
per-element bounds check into a single up-front range check. Neither skips a check
— both move it — so the memory-safety floor is unchanged.
METHOD hashBuffer(REFERENCE REFERENCE ByteBuffer bytes) RETURNS uint64
SipHash-64 over a ByteBuffer — the byte-sequence core for the generic Hasher OF K
String path.
This used to assemble each 64-bit word from EIGHT bounds-checked, handle-indirected
bytes[i] accessor calls — the per-element dispatch the s05 study named as the real
bottleneck. It no longer does: ByteBuffer.data() hands back a non-owning REFERENCE
to the raw binary[] storage (zero copy — the buffer keeps ownership), which is
exactly the handle the bulk primitives need. So this now simply delegates to
hashSlice, and the ByteBuffer path gets System->memoryWord + HOTLOOP for free.
One implementation of SipHash over bytes, not two.
METHOD hashWord(uint64 m) RETURNS uint64
SipHash-64 over a single 8-byte word (primitive keys). No byte loop, so none of the bulk-memory levers apply here — this path is pure round arithmetic, and the only lever on it is the round SCHEDULE (SipVariant).
METHOD hash(REFERENCE REFERENCE K key) RETURNS uint64
Hasher OF K — the 64-bit SipHash-2-4 digest.
CLASS SipState
The SipHash-2-4 compression/finalization state: four 64-bit words mixed by the SipRound. INTERNAL engine driven by SipHasher; one transient instance per hash call.
A VALUE CLASS (§I.J.vi): value identity + inline storage, so a
SipState st := CREATE SipState(...) local lives on the stack with no
_ev_unique heap handle — no per-hash malloc/free. (It was formerly an
INTERNAL heap class, which cost a heap allocation on every hash call — the
dominant hasher overhead; see benchmarks/s04_dict/envzn_siphash_opt.)
Runs in 64-bit-output mode: SipHash-2-4-64 (the digest a Hasher.hash()
needs). The 128-bit second-half machinery was removed — nothing consumed it
(ProHashDictionary stores and keys on the low 64 bits), so computing it was
pure waste.
Reference: Aumasson & Bernstein, "SipHash: a fast short-input PRF" (2012), SipHash-2-4 (c = 2 compression rounds, d = 4 finalization rounds).
kernel/src/SipState.ev:29
Fields
uint64 v0uint64 v1uint64 v2uint64 v3
Constructors
INIT(uint64 k0, uint64 k1)
Methods
METHOD rotl(uint64 x, int32 b) RETURNS uint64
MODIFY METHOD round() RETURNS void
One SipRound.
MODIFY METHOD absorb(uint64 m) RETURNS void
Absorb one 64-bit message word (c = 2 compression rounds).
MODIFY METHOD finalize64() RETURNS uint64
The 64-bit digest (d = 4 finalization rounds), canonical SipHash-2-4-64.
MODIFY METHOD absorbFast(uint64 m) RETURNS void
Absorb one message word with c = 1 (SipHash-1-3).
MODIFY METHOD finalizeFast64() RETURNS uint64
The 64-bit digest with d = 3 (SipHash-1-3).
CLASS SortedList
IMPLEMENTS Cloneable, Collection
Defines the SortedList[T] class per ENVZN_CONSTITUTION §SortedList (L703–716).
SortedList[T] is a collection that maintains its elements in
ascending order (per T->isLessThan) at all times. Insertion
walks to the sort position and splices; the order is invariant
across the public API — there is no operation that produces an
out-of-order sequence.
Composes on LinkedList[T], as do Stack and Queue — one backing
store shared across the composed collections. LinkedList provides
mid-chain insert/remove via its insertAt / removeAt methods;
SortedList drives them with a Comparable-aware position search.
────────────────────────────────────────────────────────────────── ORDER INVARIANT ──────────────────────────────────────────────────────────────────
At every public-API boundary, for any two indices i < j in the
list, the element at i is isLessThan the element at j OR
isEqualTo the element at j. Duplicates (per Equatable.isEqualTo,
inherited via Comparable[T] EXTENDS Equatable[T]) are allowed and
stored adjacently.
────────────────────────────────────────────────────────────────── SHORT-CIRCUIT SEARCH ──────────────────────────────────────────────────────────────────
Because the list is always sorted, contains and remove walk
from the front and stop early when they cross the value's
expected position:
- For
contains(v): scan from the head; ifv->isLessThan(elem)becomes TRUE, the value is not present (any further elements are >= elem > v). - For
remove(v): same short-circuit; the first match is the one removed (matches duplicates' adjacent-storage rule).
Mean-case work is O(n/2) for both; worst-case is O(n). A real production version could replace LinkedList with a balanced tree (red-black, AVL) for O(log n), but that's a much larger implementation. This V1 form trades sorted-search complexity for implementation simplicity.
kernel/src/SortedList.ev:62
Fields
int32 length
Constructors
INIT()
Methods
METHOD clone() RETURNS SortedList[T]
Hand-written clone() — AUTO refused for the same cascade
reason as Queue / Stack / Deque / LinkedList: the synthesis
walker can't see T's Cloneable status from this instantiation
site through the LinkedList[T] composition. Walk the
underlying LinkedList's iterator and append a deep clone of
each element. Sort order is preserved because the source list
is already sorted and we visit front-to-back; we use
data->append (which adds to tail) rather than going through
SortedList.add's per-insert binary scan.
METHOD iterator() RETURNS LinkedListIterator[T]
ITERATOR — delegate to LinkedList's BidirectionalIterator
The element order is insertion order, which by the order
invariant is also ascending. Callers iterate front-to-back
for ascending traversal; back-to-front via previous() for
descending.
METHOD isEmpty() RETURNS boolean
METHOD size() RETURNS int64
Number of elements currently stored. Read-only, O(1).
MODIFY METHOD clear() RETURNS void
MODIFY METHOD insert(MOVE T value) RETURNS STATUS
INSERT
Walks the iterator to find the first position where
value->isLessThan(elem) becomes TRUE — that's where value
belongs in the sorted order. The iterator is held in a
nested block so it drops (releasing the mutation lock on
_data) before the call to _data->insertAt.
Insert value at the position that keeps the list sorted.
Per §10.2 the bare-named form consumes the source —
value is moved into the list. The walk reads value for
comparison (read-only, doesn't consume) before the final
.data->insertAt call which performs the move.
MODIFY METHOD insertCopy(T value) RETURNS STATUS
Deep-copy form of insert. value stays valid for the caller;
the clone is the one moved into the list.
METHOD contains(T value) RETURNS boolean
CONTAINS / REMOVE — short-circuit on Comparable order
MODIFY METHOD remove(T value) RETURNS boolean
Remove the first element equal to value. Returns TRUE if
a match was found and removed, FALSE otherwise.
METHOD peekFirst() RETURNS | REFERENCE T
FRONT / BACK ACCESS — convenience wrappers over LinkedList
peekFirst returns the smallest element; peekLast the largest. popFirst / popLast remove and return them. All four are O(1) by virtue of LinkedList's existing front/back operations.
METHOD peekLast() RETURNS | REFERENCE T
MODIFY METHOD popFirst() RETURNS | T
MODIFY METHOD popLast() RETURNS | T
CLASS Stack
EXTENDS Array
Defines the Stack[T] class per ENVZN_CONSTITUTION §Stack.
Stack[T] is a LIFO collection implemented as a thin layer over Array[T] (a.k.a. T[]). All storage and per-element ownership semantics live in Array[T]; Stack contributes the LIFO discipline and the public count field.
Design philosophy: by building Stack on Array[T], all the FOREIGN/T-substitution/ownership questions surfaced by the original Stack.ev preview are relocated to Array[T] — exactly once, instead of once per collection. See Array.ev (forthcoming) for those questions resolved.
kernel/src/Stack.ev:25
Constructors
INIT()
default constructor
INIT(int64 presized)
for presized Stack allocation
Methods
OVERRIDE METHOD clone() RETURNS Stack[T]
Deep copy. Each element is cloned via T->clone(); T must
implement Cloneable when T is a class type.
For T IMPLEMENTS Inoperative (opaque only today): returns
an empty Array — Inoperative values are opaque so we can't
preserve them on clone.
MODIFY METHOD push(MOVE T value) RETURNS STATUS
Push value onto the top. Per §10.2 the bare-named form
consumes the source — value is moved through Array's MOVE
append onto the stack's backing.
MODIFY METHOD pushCopy(T value) RETURNS STATUS
Deep-copy form of push. value stays valid for the caller;
the clone is what the backing array stores.
MODIFY METHOD pop() RETURNS | T
Remove and return the top element. Pipe-XOR: SUCCESS populates
value; FAILURE when the stack is empty. Depends on Array
providing popLast() with move-out semantics for class T.
METHOD peek() RETURNS | REFERENCE T
Return (without removing) the top element as a REFERENCE handle. Multi-return per the kernel peek contract — STATUS == FAILURE when empty, SUCCESS otherwise. Delegates to Array.peekLast.
METHOD isEmpty() RETURNS boolean
METHOD size() RETURNS int64
Number of elements currently stored. Read-only, O(1).
MODIFY METHOD popLast() RETURNS | T
Remove and return the last element. FAILURE if empty. Ownership
of the returned element transfers to the caller. O(1).
For T IMPLEMENTS Inoperative (opaque): always returns FAILURE —
the stored payload is opaque and can't be lifted out by-value.
MODIFY METHOD popFirst() RETURNS | T
Remove and return the first element. FAILURE if empty. O(n) — shifts every remaining element down by one. For T IMPLEMENTS Inoperative: always returns FAILURE (see popLast).
METHOD peekLast() RETURNS | REFERENCE T
Read the last element without removing it. STATUS is FAILURE
when the array is empty (value is EMPTY); SUCCESS otherwise
with value referencing the element in place. The REFERENCE
handle is valid only while no mutation occurs on the Array.
METHOD peekFirst() RETURNS | REFERENCE T
Read the first element without removing it. Same pipe-XOR shape as peekLast.
CLASS Stdio
The console: standard output, standard error, standard input. stdio-spec.md §2.
Stdio is a singleton. Twelve methods in three groups:
stdout — print · printline · formatPrint · formatPrintline · printBytes
stderr — the same five, each with an Err suffix
stdin — read(int32 length) · readline()
print writes its text verbatim; printline appends a newline.
The formatPrint* pair render a $1..$9 template (stdio-spec.md
§4) before writing. printBytes* write raw bytes with no formatting.
Writes return the pipe-XOR shape (int64 written | STATUS status) —
written is the byte count. Reads return the comma shape
(value, boolean atEof) — both slots are always populated, so an
end-of-input read still delivers the bytes it managed to read.
The I/O layer is pure Envzn: write / read are bound directly
from libc via FOREIGN BIND (no inline C++). <unistd.h> is listed
in the ENVZN manifest's foreign block. A ByteBuffer argument
crosses the boundary as a (pointer, length) pair (bind-spec §4); the
C side of read writes through it.
V1 scope: a write is a single write(2) call (a rare short write
under-reports the count, never corrupts); read / readline go
one byte at a time, and a line ends at \n (0x0A). Buffered I/O,
the full splitLines delimiter set, and EPIPE→FAILURE are V2.
kernel/src/Stdio.ev:50
Methods
METHOD print(String text) RETURNS | int64
Write text to stdout verbatim — no trailing newline.
METHOD printline(String text) RETURNS | int64
Write text to stdout followed by a newline.
METHOD formatPrint(String tmpl, opaque args...) RETURNS | int64
Render tmpl against args (stdio-spec.md §4) and write the
result to stdout — no trailing newline.
METHOD formatPrintline(String tmpl, opaque args...) RETURNS | int64
Render tmpl against args and write the result to stdout
followed by a newline.
METHOD printBytes(ByteBuffer data) RETURNS | int64
Write the raw bytes of data to stdout — no formatting.
METHOD printErr(String text) RETURNS | int64
Write text to stderr verbatim — no trailing newline.
METHOD printlineErr(String text) RETURNS | int64
Write text to stderr followed by a newline.
METHOD formatPrintErr(String tmpl, opaque args...) RETURNS | int64
Render tmpl against args and write the result to stderr.
METHOD formatPrintlineErr(String tmpl, opaque args...) RETURNS | int64
Render tmpl against args and write the result to stderr
followed by a newline.
METHOD printBytesErr(ByteBuffer data) RETURNS | int64
Write the raw bytes of data to stderr — no formatting.
METHOD read(int32 length) RETURNS ByteBuffer
Read from stdin. length > 0 reads up to that many bytes;
length == 0 reads one line, the \n delimiter included.
Comma shape: loaded always holds what was read; atEof is
TRUE when end-of-input was reached before the request was met.
METHOD readline() RETURNS String
Read one line from stdin, the trailing \n stripped. Comma
shape: at end-of-input line is empty and atEof is TRUE,
distinguishing EOF from a genuine blank line.
CLASS String
IMPLEMENTS Countable, Cloneable, Hashable, Equatable
Immutable readable/printable text.
Storage is char32[] (UTF-32 code points), one entry per code point.
The compiler lowers char32[] to ENVZN::_ValueArray<char32_t> so
every positional method is O(1) by code-point index. No UTF-8
decode lives in String — those conversions live in Converter and
Codec classes.
Storage rule: STRING IS IMMUTABLE. The .data field is populated
once in INIT and never mutated. Every transformation (substring,
toUpper, trim, concat, …) returns a fresh String.
Case fold: ASCII only. Code points >= 0x80 pass through unchanged
in toUpper / toLower / case-insensitive find / contains /
count. Full Unicode case folding waits for V2 Unicode tables.
kernel/src/String.ev:27
Constructors
INIT()
Empty String.
INIT(REFERENCE REFERENCE char32[] origin)
Take ownership of a pre-built code-point array. The literal-
lowering path in the emitter and every internal transformation
(substring, toUpper, concat, trim*, …) construct their result
this way: build a fresh char32[], hand it to a new String.
From a raw code-point array, COPYING it. REFERENCE is now explicit:
it always lowered to const EvArray<char32_t>&, so this states the
borrow the signature already had rather than changing it.
INIT(MOVE char32[] origin, int64 length)
THE LITERAL CONSTRUCTOR. The emitter's, not a developer's.
It could not be hidden. INTERNAL INIT is E1141 and PRIVATE INIT is a
parse error: a constructor carries no visibility modifier in Envzn,
because "INIT and CLEANUP are lifecycle, not module surface". So this is
public, and what keeps it honest is the word MOVE in its own
signature — a developer who calls it sees that the array is taken, and
the analyzer poisons the source so a later read is E3010. That is the
language's normal contract rather than a hidden trapdoor.
It exists to spend ONE allocation on a string literal instead of two.
String s = "bob" lowers to a _ValueArray<char32_t>(U"bob", 3)
handed to a String constructor. Through INIT(REFERENCE char32[]) above
that array is built, then COPIED into .data and destroyed — two heap
allocations and two memcpys for every literal in the program, of which
there are ~2,800. Taking it by MOVE makes the parameter a by-value
EvArray<char32_t>, so C++17's guaranteed copy elision constructs the
caller's prvalue DIRECTLY into it — nothing is materialised twice — and
:= then transfers the buffer pointer into .data. Measured 2 -> 1.
length IS REDUNDANT AND MUST NOT BE REMOVED. origin.length already
carries the count; this parameter exists solely to give this INIT a
different ARITY from INIT(REFERENCE char32[]). MOVE and REFERENCE of
the SAME type at the SAME arity both lower to a one-argument
constructor over EvArray<char32_t> — by value and by const reference —
and clang rejects the pair as ambiguous. Differing arity is the
established way round it; StringIterator carries both forms for
exactly this reason. Delete this parameter and the kernel stops building.
It is String-only on purpose: a character-sequence literal has exactly
one representation in the language, and it is String. The other five
text/binary classes do not get it, which
run_text_binary_consistency.sh records as an allow-listed asymmetry
rather than drift.
INIT(REFERENCE REFERENCE String other)
Copy-construct from another String. ByteBuffer has had
INIT(REFERENCE ByteBuffer) all along; the text axis had no equivalent,
so CREATE String(other) was rejected outright until Phase 1 added this.
This does NOT collide with the array form above: the two differ in
parameter TYPE (String against char32[]), which C++ overload
resolution separates cleanly. What it cannot separate is the same type
under MOVE versus REFERENCE — both lower to one-argument
constructors taking EvArray<char32_t> by value and by const reference,
and clang calls that ambiguous. That is why there is no MOVE companion.
Methods
METHOD clone() RETURNS String
Cloneable contract — deep copy. Returns a fresh String whose
.data is an independent code-point array with the same values.
METHOD length() RETURNS int64
Code-point count. O(1).
METHOD size() RETURNS int64
UTF-8 byte count of the text — distinct from length() (the code-point count). Storage is UTF-32 code points, so size() sums each code point's UTF-8 width per RFC 3629 (the same widths UTFCodec.encodeToUTF8 emits): U+0000–007F → 1 byte, U+0080–07FF → 2, U+0800–FFFF → 3, ≥ U+10000 → 4. Mirrors DynamicString.size().
METHOD isEmpty() RETURNS boolean
True iff the String has zero code points.
METHOD __op_index__(int64 i) RETURNS char32
Code point at index i. Declared as operator [] so the
only access syntax is s[i] — there is no public at()
method on String. Throws IndexOutOfBoundsError if i is out
of range — bounds check lives in _ValueArray<char32_t>::operator[].
METHOD equals(String other) RETURNS boolean
Code-point-by-code-point equality. UTF-32 storage means no
normalisation surprises; two Strings are equal iff their code-
point sequences match exactly.
Value equality, delegated to the value array's own equals. That is not
merely shorter: for an element type with a unique object representation
the array compares with a single length-checked memcmp (one SIMD
compare) where this method used to walk element by element. Measured
2026-08-27 at 6x faster on 64 elements and 11-14x from 1K up, with
identical results across an 8-cell differential matrix — including the
float guard, since the array deliberately does NOT memcmp an arithmetic
element type (-0.0 == +0.0, NaN != NaN would come out wrong).
METHOD isLessThan(String other) RETURNS boolean
Use the lexicographic comparision below
METHOD compareTo(REFERENCE REFERENCE String other) RETURNS int32
Lexicographic code-point comparison. Returns -1 / 0 / +1 in the usual three-way-comparison sense.
METHOD hash() RETURNS uint64
FNV-1a 64-bit hash over the code-point array. Satisfies the Hashable contract. Deterministic.
METHOD hash(uint64 seed) RETURNS uint64
FNV-1a 64-bit hash with caller-supplied seed. Each code point is mixed in as a 4-byte little-endian sequence: XOR then multiply per byte. Wrap on uint64 overflow is the algorithm.
METHOD substring(int64 start, int64 len) RETURNS String
Substring by code-point index. start is inclusive; length is
the count of code points. A bad range is a precondition violation
(programming error) — it THROWS, the same category as operator[].
Recoverable runtime outcomes (search misses) live on find/count
as the pipe-XOR (... | STATUS); a bad slice range is not one.
Also the lowering target of the s[start:length] slice sugar.
METHOD view(int64 start, int64 length) RETURNS StringView
Non-owning bounded view starting at start for length code
points. Suitable for searching / scanning without allocating a
copy. StringView's INIT validates the bounds; length == 0
yields an empty view; negative or out-of-range bounds throw.
METHOD find(REFERENCE REFERENCE String needle) RETURNS | int64
First occurrence of needle in this String (case-sensitive) — the
Searchable contract. Pipe-XOR: SUCCESS yields the absolute index
where the match begins; FAILURE means no match. To search a
sub-range, view(start, length) then search the window.
METHOD find(REFERENCE REFERENCE String needle, boolean caseMatch) RETURNS | int64
As find, with ASCII case-fold when caseMatch == FALSE. Forwards to
the one canonical char32 scan in StringView: a full-span view over this
String's codepoints searches for needle's codepoints.
METHOD contains(REFERENCE REFERENCE String needle) RETURNS boolean
True iff needle occurs at least once (case-sensitive).
METHOD contains(REFERENCE REFERENCE String needle, boolean caseMatch) RETURNS boolean
METHOD startsWith(REFERENCE REFERENCE String prefix) RETURNS boolean
True iff this String begins with prefix. Forwards to the one canonical
char32 scan in StringView, the same way find does — a full-span view
over this String's code points tests prefix's code points. An empty
prefix is a prefix of everything.
METHOD endsWith(REFERENCE REFERENCE String suffix) RETURNS boolean
True iff this String ends with suffix. Same delegation as startsWith.
METHOD lastIndexOf(REFERENCE REFERENCE String needle) RETURNS | int64
Index of the LAST occurrence of needle, pipe-XOR: SUCCESS populates
index, FAILURE when the needle does not occur. The forward search is
find; this is its mirror, and like find it forwards to StringView so
there is exactly one char32 scan in the kernel.
METHOD count(REFERENCE REFERENCE String needle) RETURNS int64
Non-overlapping occurrence count of needle (case-sensitive).
Empty needle returns 0.
METHOD count(REFERENCE REFERENCE String needle, boolean caseMatch) RETURNS int64
METHOD find_first_of(REFERENCE REFERENCE String set) RETURNS | int64
B1: index of the first code point that IS a member of set (the code
points of set, treated as a set). Pipe-XOR: SUCCESS = index, FAILURE =
none present. Forwards through a full-span view to the one canonical
char32 scan in StringView.
METHOD find_first_not_of(REFERENCE REFERENCE String set) RETURNS | int64
B1: index of the first code point that is NOT a member of set.
METHOD split(REFERENCE REFERENCE String delim) RETURNS String[]
Split on delim into an array of Strings. Adjacent delimiters
produce empty pieces; a trailing delimiter produces an empty
trailing piece. Empty receiver returns a single-element array
containing one empty String. Empty delim returns a single-
element array containing this String unchanged.
METHOD splitLines() RETURNS String[]
Split on any of the Unicode line-break code points: LF, CR, CRLF (one boundary), VT, FF, NEL (U+0085), LS (U+2028), PS (U+2029). The line terminators are NOT included in the pieces. A trailing terminator does NOT yield a trailing empty piece.
METHOD toUpper() RETURNS String
ASCII uppercase fold. Code points >= 0x80 unchanged.
METHOD toLower() RETURNS String
ASCII lowercase fold. Code points >= 0x80 unchanged.
METHOD trim() RETURNS String
TRIM (ASCII WHITESPACE: space, tab, LF, CR, VT, FF) -----------
METHOD trimStart() RETURNS String
METHOD trimEnd() RETURNS String
METHOD format(opaque args...) RETURNS String
Format this String as a template — substitute $1..$9 with
the corresponding argument and $$ with a literal $. The
everyday ("text $1")->format(a) form. stdio-spec.md §4.3 —
the String is the receiver of the formatting work; a Formatter
only supplies configuration, so format never instantiates one.
V1 scope: arguments must already be text — the compiler rewrite
at stdio-spec.md §4.3 redirects a .format(...) call whose args
include a non-String type to Formatter().format(...) so this
body only sees text args. A bare opaque holding a non-String
renders as ? (defensive fallback).
METHOD formatWith(BinaryMode mode, opaque[] args) RETURNS String
Template-substitution engine. mode is carried for the V2
binary-rendering cascade; V1 substitution is text-only and
does not consult it. Public so Formatter can delegate here.
Builds the result using only String methods (substring /
concat) so the kernel .hpp topology stays a clean DAG —
String never depends on another kernel class.
METHOD concat(REFERENCE REFERENCE String other) RETURNS String
String concatenation. Returns a fresh String; both receivers are unchanged.
METHOD iterator() RETURNS ValueIterator[char32]
ITERATOR — value-form (char32 is by-value). Wraps the bounded
walk in a sibling StringIterator class so the surface
matches the standard ValueIterator pipe-XOR shape (nextValue /
peekValue / skipValue). _ValueArray's raw iterator has a
different signature (no STATUS pipe-XOR), so the wrap is
necessary.
CLASS StringHasher
IMPLEMENTS Hasher
A non-parametric Hasher OF String. Functionally identical to DefaultHasher[String] (both delegate to String.hash()), but provided as a concrete named type so the canonical spelling
Dictionary[String, V, StringHasher]
reads naturally and the docs have a concrete hasher to point at.
See: Hasher.ev (the interface), DefaultHasher.ev (the universal hasher),
String.ev → hash().
StringHasher — Hasher OF String delegating to String's own FNV-1a
hash(). The default hasher for String-keyed Dictionary / Set.
kernel/src/StringHasher.ev:25
Constructors
INIT()
Methods
METHOD hash(REFERENCE REFERENCE String key) RETURNS uint64
CLASS StringKeyedDictionary
IMPLEMENTS Dictionary
A byte-keyed dictionary: linear-probe open-addressing over a STRUCT-OF-ARRAYS with BYTE-DENSE keys held in ONE FLAT binary[] pool, a byte-FNV hash, and a memcmp confirm. The STRING FAST TIER. Pure Envzn, zero-dep.
WHY the key is a byte array, NOT Dictionary[String, V, H]:
Two compiler realities, met head-on, shaped this class (2026-07-13):
1. The Dictionary[BinaryWord,V,H] INTERFACE cannot fix one of its type parameters — an
impl must be parameterised over the SAME type variables as the interface (a
... IMPLEMENTS Dictionary[String, V, H] is rejected E2103: the interface body
still references BinaryWord, unbound in the impl's scope). So there is no way to write a
String-SPECIALISED Dictionary impl; a generic [BinaryWord,V,H] one (ChainedHashDictionary)
is the only shape, and it must hash/compare keys through the pluggable Hasher
+ Equatable == — the SLOW char32 path (String stores char32 code points; each
hash/equals iterates them through handle-indirected, bounds-checked accessors).
2. The s05 word-dict study (FINDINGS.md #5/#9/#17) proved the fast form is
BYTE-DENSE keys read by DIRECT FLAT-ARRAY INDEXING — "byte-dense IS faster; the
slowness was ByteBuffer method dispatch, not byte-density."
So this class takes the KEY AS binary[] bytes (the caller transcodes its String
once, key INTO ByteBuffer → bytes, UNTIMED — exactly the s05 benchmark's contract),
sidestepping BOTH the interface-fix wall and the per-lookup String→bytes transcode.
It is a standalone CLASS, constructed directly. The "relocate cost inward" tier for
string/byte keys.
MODELLED ON benchmarks/s05_worddict/envzn_shimopt/native/native.hpp — the shim's
open-addressing map (byte-FNV + linear probe + stored-hash reject + length reject +
memcmp confirm), REBUILT PURE: what the inline C++ shim did with libc, the new
language performance levers do in-language —
- HOTLOOP → the byte-FNV hash loop (hoists the per-byte bounds check to
one up-front range check; the constitution I.H.iii(f) example
IS this hash).
- System->memoryCompare → the key confirm (std::memcmp, one bounds-checked call per
probe instead of a per-byte Envzn loop). The HOT-path lever.
- System->memoryWord → available for a word-wise long-key hash (not exercised by the
short Shakespeare words; a length-gated follow-up).
STORAGE — a FLAT byte pool + five parallel per-slot arrays (SoA; the hot probe touches
only the dense slotState/slotHash, then the pooled key bytes):
- keyBytes : binary[] — ONE flat, append-only pool; every key's bytes live here,
contiguous — the cache-dense form the s05 study crowned (#17). A removed key's
bytes linger until clear() (no compaction in V1 — a bounded, documented cost;
memoryCopy-based compaction on high-tombstone rehash is the follow-up).
- slotOff : int32[], slotLen : int32[] — each slot's key start + length in the pool.
- slotState : int8[] — 0 empty / 1 full / 2 tombstone (liveness; any byte content
is a valid key).
- slotHash : uint64[] — cached full FNV hash; the slotHash[s]==h fast-reject skips
the length + memcmp on almost every non-match (the crucial gate for byte keys).
- vals : V[] — parallel per-slot value.
INDEX — hash & mask (pow2), probe (s+1) & mask — native.hpp's index exactly. FNV
is a MIXING hash so this scatters, but for arbitrary string keys there is no dense-key
structure to exploit; the byte-dense pool + cached-hash reject is the cache lever here,
not index locality (the s05 finding, distinct from IntKeyedDictionary's key & mask).
SURFACE — a byte-array analogue of the Dictionary fast tier: insert/lookupFast/
lookup (pipe-XOR)/lookupRef/contains/remove/size/isEmpty/clear/clone.
Each comes in a whole-array form (insert(binary[] key, ...), key IS the array) and a
zero-copy SLICE form (insertSlice(buf, off, len, ...)) for the s05 flat-query loop
that never allocates per lookup. Iteration over pooled keys is a deferred follow-up.
kernel/src/StringKeyedDictionary.ev:73
Fields
uint64 primeuint64 h_maskint64 countint64 tombstonesint64 capuint64 mask
Constructors
INIT()
Methods
MODIFY METHOD insertSlice(REFERENCE REFERENCE binary[] buf, int64 off, int64 len, MOVE V value) RETURNS STATUS
Insert the key slice buf[off .. off+len) → value (value MOVEd in). New keys
are interned into the flat pool. Replacing an existing key leaves count unchanged.
METHOD lookupFastSlice(REFERENCE REFERENCE binary[] buf, int64 off, int64 len) RETURNS boolean
Comma-return lookup over a slice — boolean f, V v := d->lookupFastSlice(buf,off,len).
No pipe-XOR tuple on the hot path.
METHOD lookupSlice(REFERENCE REFERENCE binary[] buf, int64 off, int64 len) RETURNS | V
METHOD containsSlice(REFERENCE REFERENCE binary[] buf, int64 off, int64 len) RETURNS boolean
MODIFY METHOD removeSlice(REFERENCE REFERENCE binary[] buf, int64 off, int64 len) RETURNS boolean
MODIFY METHOD insert(REFERENCE REFERENCE binary[] key, MOVE V value) RETURNS STATUS
METHOD lookupFast(REFERENCE REFERENCE binary[] key) RETURNS boolean
METHOD lookup(REFERENCE REFERENCE binary[] key) RETURNS | V
METHOD lookupRef(REFERENCE REFERENCE binary[] key) RETURNS | MUTABLE REFERENCE V
METHOD contains(REFERENCE REFERENCE binary[] key) RETURNS boolean
MODIFY METHOD remove(REFERENCE REFERENCE binary[] key) RETURNS boolean
METHOD isEmpty() RETURNS boolean
METHOD size() RETURNS int64
MODIFY METHOD clear() RETURNS void
MODIFY METHOD insert(MOVE BinaryWord key, MOVE V value) RETURNS STATUS
METHOD lookupFast(REFERENCE REFERENCE BinaryWord key) RETURNS boolean
Dictionary.lookupFast — the interface-shaped (BinaryWord-keyed) peer of
the binary[] slice form above. K is BinaryWord for this class, so this
is the overload that satisfies the interface; the slice form stays for
the zero-copy call sites that already hold a buffer and a range.
METHOD lookup(REFERENCE REFERENCE BinaryWord key) RETURNS | MUTABLE REFERENCE V
METHOD contains(REFERENCE REFERENCE BinaryWord key) RETURNS boolean
MODIFY METHOD remove(REFERENCE REFERENCE BinaryWord key) RETURNS | BinaryWord
The removed key is returned as a word over THIS dictionary's pool, and the pool outlives the slot — a tombstone does not reclaim the bytes — so the returned word stays readable until the next rehash compacts them.
METHOD iterator() RETURNS ReferenceIterator[BinaryWord]
One BinaryWord per live key, borrowed from this dictionary's pool.
Valid only while the dictionary is: the words point INTO its storage,
and a rehash moves that storage.
METHOD keys() RETURNS LinkedList[BinaryWord]
Every live key, as borrowed words. Same lifetime caveat as iterator().
MODIFY METHOD insertCopy(BinaryWord key, V value) RETURNS STATUS
METHOD clone() RETURNS Dictionary[BinaryWord, V, H]
Returns the Dictionary INTERFACE, not the concrete class — the class
is HIDDEN, so its name may appear only as a CREATE target, and this is
the shape ProHashDictionary.clone() already had. The copy goes in
through the interface's own insert, since insertSlice is this
class's and not the interface's.
METHOD equals(Dictionary[BinaryWord, V, H] other) RETURNS boolean
for Equatable Interface Dictionary is equal if all keys between the two dictionaries are equal Two dictionaries are equal when they hold the SAME ASSOCIATIONS: the same keys, each mapping to an EQUAL VALUE.
Not merely the same KEYSET. That would rank {a:1} equal to {a:2}, so
two "equal" dictionaries would answer lookup(a) differently and neither
could stand in for the other — which is the whole point of an equivalence.
Keys-AND-values is what C++ (map and unordered_map), Rust, Swift,
Julia, Python and Java all specify. Keyset equality is a perfectly good
relation, but it is equality of the DOMAIN rather than of the dictionary,
and it deserves its own name (hasSameKeys) rather than this one.
Equal sizes plus a one-way walk is sufficient: with the counts already
equal, other cannot carry a key this one lacks unless this one also
carries a key other lacks — and the walk would have found that.
CLASS StringKeyedDictionaryIterator
IMPLEMENTS ReferenceIterator
Walks the open-addressed slot table, yielding one BinaryWord per OCCUPIED
slot — a borrowed window onto that key's bytes in the shared pool.
NO ALLOCATION PER KEY, and that is the whole reason this class exists rather
than materialising keys. The window is a VALUE class held in one field,
.cur, re-assigned at each step; next() hands back a reference to that
field. So a walk of a million keys performs one assignment per key and no
heap traffic at all — which is what keeps the dictionary's measured
advantage intact while it satisfies the Dictionary contract.
Slot states match the dictionary's own encoding: 0 empty, 1 occupied, 2 tombstone. Only 1 is yielded.
kernel/src/StringKeyedDictionaryIterator.ev:28
Constructors
INIT(REFERENCE REFERENCE binary[] pool, REFERENCE REFERENCE int64[] offs, REFERENCE REFERENCE int64[] lens, REFERENCE REFERENCE int8[] states, int64 capacity)
Methods
METHOD hasNext() RETURNS boolean
MODIFY METHOD next() RETURNS | REFERENCE BinaryWord
METHOD peek() RETURNS | REFERENCE BinaryWord
MODIFY METHOD skip(uint64 n) RETURNS | REFERENCE BinaryWord
CLASS StringView
IMPLEMENTS View, Viewable
Non-owning, bounded slice over a code-point array.
A StringView is a short-lived locals-only descriptor. It carries a
REFERENCE char32[] source plus start + length (code-point offsets
into the source array), and exposes a narrow read-only surface — find /
contains / count, length / operator[] / iterator(), plus view() (narrow
to a sub-window — composition) and copy() (materialise the window into a
fresh char32[]; wrap with CREATE String(view->copy()) for an owned
String). It IMPLEMENTS View and Viewable.
STRING-FREE BY DESIGN. StringView references only char32[] and itself —
never String. This breaks what would otherwise be a String↔StringView
dependency cycle, so String can implement Viewable with a covariant
view() returning a (complete, emitted-first) StringView. Consequences:
the search needle is a StringView (wrap a needle String with
needleStr->view(0, needleStr->length())), copy() yields char32[],
and bounds violations PANIC IndexOutOfBoundsError(...) directly (bodies
emit out-of-line).
The REFERENCE field is safe by the same reference-checker rules the rest
of the kernel relies on: the source's lifetime must enclose the view's;
the source must not be re-bound underneath a live view; the view is
non-storable into another class. An empty view (length == 0) is permitted
— find reports not-found and iterator() is immediately exhausted — so
String can route search / iteration uniformly through a full-span view.
kernel/src/StringView.ev:45
Constructors
INIT(REFERENCE REFERENCE char32[] source, int64 start, int64 length)
Construct a bounded view over [start, start+length) in source.
Throws (via the C-string helper) if start < 0 or length < 0 or
start + length > source.length(). A zero length is allowed.
Methods
METHOD length() RETURNS int64
Code-point count of this view.
METHOD size() RETURNS int64
Countable — View EXTENDS Countable, so the view owes size() and
isEmpty() as well as its own length(). All three are the same
number here: the count of code points the window spans.
METHOD isEmpty() RETURNS boolean
METHOD __op_index__(int64 i) RETURNS char32
Code point at index i within this view. sv[i] is the only read
syntax. Throws (C-string helper) if i is out of [0, view.length).
METHOD find(REFERENCE REFERENCE char32[] needle) RETURNS | int64
First occurrence of needle (itself a view) within this view's whole
code-point range. Pipe-XOR: SUCCESS yields the view-relative index of
the match; FAILURE means no match. Case-sensitive — to search a
sub-range, narrow with view(start, length) first.
METHOD find(REFERENCE REFERENCE char32[] needle, boolean caseMatch) RETURNS | int64
As find, with ASCII case-fold when caseMatch == FALSE.
METHOD startsWith(REFERENCE REFERENCE char32[] needle) RETURNS boolean
True iff this view begins with needle (case-sensitive). An empty
needle returns TRUE; a needle longer than the view returns FALSE.
METHOD endsWith(REFERENCE REFERENCE char32[] needle) RETURNS boolean
True iff this view ends with needle (case-sensitive). An empty
needle returns TRUE; a needle longer than the view returns FALSE.
METHOD lastIndexOf(REFERENCE REFERENCE char32[] needle) RETURNS | int64
Last occurrence of needle within this view (case-sensitive). Pipe-XOR:
SUCCESS yields the view-relative index of the final match; FAILURE means
no match. An empty needle yields the view length. Scans backward from the
last candidate window, so the first hit found is the last occurrence.
METHOD contains(REFERENCE REFERENCE char32[] needle) RETURNS boolean
True iff needle occurs at least once in this view (case-sensitive).
METHOD contains(REFERENCE REFERENCE char32[] needle, boolean caseMatch) RETURNS boolean
METHOD count(REFERENCE REFERENCE char32[] needle) RETURNS int64
Non-overlapping occurrence count of needle in this view
(case-sensitive). Empty needle returns 0.
METHOD count(REFERENCE REFERENCE char32[] needle, boolean caseMatch) RETURNS int64
METHOD find_first_of(REFERENCE REFERENCE char32[] set) RETURNS | int64
B1: index of the first code point that IS a member of set. Pipe-XOR:
SUCCESS = the index; FAILURE = no code point in this view is in set.
METHOD find_first_not_of(REFERENCE REFERENCE char32[] set) RETURNS | int64
B1: index of the first code point that is NOT a member of set.
METHOD view(int64 start, int64 length) RETURNS StringView
Narrow to a sub-window [start, start+length) relative to THIS view
(composition — a sub-window of a window is a window). Bounds are
checked against this view's length, so a sub-view can never escape
its parent. Satisfies Viewable.
METHOD copy() RETURNS char32[]
Materialise this window's code points into a fresh char32[] (the
"keep this slice" escape hatch — the view itself never escapes).
Wrap with CREATE String(view->copy()) for an owned String.
DEFECT FIXED 2026-08-27. This used .source->copyTo(piece, length),
which copies from the SOURCE'S index 0 and ignores .start — so any
view whose window did not begin at 0 returned the wrong code points.
_ValueArray::copyTo(other, n) takes no source offset, so it cannot
express a windowed copy at all; the element loop below (which was here,
commented out) is the only correct form. ByteBufferView.copy() never had
the bug — it always used this shape.
It stayed latent because every caller passed a FULL-SPAN view
(view(0, length())), where .start is 0 and the two agree. The first
caller with a non-zero start was DynamicString.substring, added in
Phase 2, which is what surfaced it.
METHOD iterator() RETURNS ValueIterator[char32]
Walk the view's code points in order. The iterator snapshots the
bounded slice into its own storage at construction, so it is
independent of this view and its source char32[] for its entire
lifetime (matching ByteBufferIterator's design). The snapshot is
materialised inline because the emit does not currently auto-deref a
REFERENCE self-field when passed to a value-typed ctor param.
CLASS Subscription
A scope-bound handle to a Broker subscription.
Returned by Broker[T].subscribe(...). It holds a SHARED MUTABLE
REFERENCE to its Channel[T] — the carve-out (§9.6) that lets the
subscription drive the channel's MODIFY methods (receive/tryReceive),
sound because Channel is a SHARED CLASS monitor. Because Channel
is shared-eligible (§9.1) the reference is a strong (reference-counted)
handle: the subscription co-owns the channel with the broker's registry.
Lifecycle: scope-bound. When the subscription's declaring scope ends, CLEANUP closes the channel; the broker skips closed channels on future publishes (lazy unsubscribe). The channel is freed when its last owner drops — so a subscription may outlive the broker and still drain its (closed) channel safely; there is no dangling reference.
kernel/src/Subscription.ev:27
Fields
MUTABLE REFERENCE Channel[T] channelRef
Constructors
INIT(Channel[T] channel)
Methods
MODIFY METHOD receive() RETURNS | T
Blocking receive of the next published value. Pipe-XOR: SUCCESS yields the value; FAILURE once the channel is closed and drained.
MODIFY METHOD tryReceive() RETURNS | T
Non-blocking receive. Pipe-XOR: SUCCESS if a value is buffered;
FAILURE immediately otherwise. A WHILE sub->tryReceive() DO { … }
loop drains whatever is currently buffered and exits.
CLASS System
Abstract base class for executable-module entry points.
Every executable Envzn module has exactly one class that
EXTENDS System and overrides start(String[] argv) — that's
the program's main entry point. The compiler resolves "which
class is the entry point" by finding the unique subclass of
System in the executable target.
Beyond the entry-point pattern, System exposes runtime services
every program tends to want: stdout / stderr printing, env-var
lookup, stdin line read, exit-code propagation. These are
instance methods on System (the entry-point class inherits them);
they are available to any code that has access to the module's
entry-point instance via the implicit machinery that drives
start().
EXTENDS System resolves through the standard parent-method walk
(no hardcoded shortcut).
────────────────────────────────────────────────────────────────── ENTRY-POINT CONTRACT ──────────────────────────────────────────────────────────────────
The entry-point class must:
- Extend System (
CLASS Main EXTENDS System { ... }). - Provide a no-arg INIT.
- Override
start(String[] argv) RETURNS int32. The returned value becomes the process exit code.
Example shape:
CLASS Main EXTENDS System {
INIT() { }
OVERRIDE METHOD start(String[] argv) RETURNS int32 {
SELF->print("Hello, world!")
RETURN (0)
}
}
The compiler synthesises a small int main(int argc, char**
argv) shim that constructs the Main class and invokes
start(). That shim lives in the compiler's emit path, not in
this file.
kernel/src/System.ev:87
Methods
METHOD getenv(String key) RETURNS | String
Look up an environment variable. Pipe-XOR: SUCCESS populates
value with the variable's value (empty String if set to the
empty value); FAILURE if the variable is unset.
METHOD exit(int32 code) RETURNS void
Exit the process immediately with the given code. Does NOT
return. CLEANUP on stack frames is skipped — call only when
the program genuinely cannot continue. For normal termination,
RETURN from start() instead and let the entry-point shim
propagate the exit code.
METHOD cloneCount() RETURNS int64
Deep copies performed so far. See the note above on enabling.
METHOD resetCloneCount() RETURNS void
Zero the counter, so a test can bracket one sequence.
METHOD memoryCompare(REFERENCE REFERENCE binary[] a, int64 aOff, REFERENCE REFERENCE binary[] b, int64 bOff, int64 n) RETURNS int32
Lexicographic byte comparison of the n-byte run a[aOff .. aOff+n)
against b[bOff .. bOff+n). Returns a negative int32 when the first
differing byte is smaller in a, positive when larger, and 0 when the
two runs are byte-equal — the std::memcmp result contract, so one
primitive serves both equality (== 0) and ordering. Each offset+run
is bounds-checked once against its array's length.
METHOD memoryCopy(MUTABLE REFERENCE MUTABLE REFERENCE binary[] dst, int64 dstOff, REFERENCE REFERENCE binary[] src, int64 srcOff, int64 n) RETURNS void
Copy the n-byte run src[srcOff .. srcOff+n) into dst[dstOff ..
dstOff+n) in place. PRECONDITION: both ranges are live — dstOff+n
must not exceed dst.length (this is an in-place copy into existing
slots, never a growing append) and srcOff+n must not exceed
src.length; the compiler enforces both with one bounds check per run
(IndexOutOfBoundsError on violation). The source and destination runs
must not overlap. The compiler lowers each call site to one std::memcpy.
METHOD memoryWord(REFERENCE REFERENCE binary[] a, int64 off) RETURNS uint64
Read the 8 consecutive bytes a[off .. off+8) as one uint64 — a WIDE
load, for word-wise hashing/scanning that processes eight bytes per
operation instead of one. PRECONDITION: off+8 must not exceed
a.length (the compiler checks it once and throws IndexOutOfBoundsError
otherwise). The compiler lowers each call site to a single native-endian
8-byte load; this body is the readable little-endian spec, which
coincides with the load on the little-endian V1 targets. Byte order is an
internal detail — a word-wise hash only needs the same bytes to map to
the same word, which both forms guarantee.
CLASS Task
User-visible handle for a CONCURRENT-spawned task.
────────────────────────────────────────────────────────────────── USER-FACING API ──────────────────────────────────────────────────────────────────
Task is the per-task handle carried as the currentTask reference
inside a CONCURRENT block. User code can only obtain a Task through
the compiler-injected currentTask identifier — Tasks cannot be
stored in fields, assigned to variables, or passed as arguments
(analyzer-enforced; see concurrent_scope_diagnostics.py rules R1–
R5, with R6 rejecting bare CREATE Task(...)).
- currentTask->name() — assigned task name
- currentTask->isCancelled() — cooperative cancellation flag
- currentTask->sleep(int64 millis) — sleep the calling OS thread
────────────────────────────────────────────────────────────────── NOT USER-CONSTRUCTIBLE ──────────────────────────────────────────────────────────────────
Task has no public INIT. The only constructor is INTERNAL and is
invoked by the CONCURRENT-block lowering (_ev_task_scope::spawn
in EV_task_native.hpp): one Task per spawned child, installed
as the new OS thread's thread_local _ev_current_task slot before
the body runs and cleared on body exit.
────────────────────────────────────────────────────────────────── CANCELLATION ──────────────────────────────────────────────────────────────────
Cancellation is cooperative (Decision #2, threads-research §6.8).
The enclosing CONCURRENT scope calls task->cancel() (INTERNAL) on
every spawned Task whenever any sibling throws or the parent scope
cancels. Body code polls currentTask->isCancelled() in long-
running loops and returns at its own pace. There is no async tear-
down at V1; the _ev_task_scope destructor still joins each spawned
OS thread before unwinding.
────────────────────────────────────────────────────────────────── LIFETIME ──────────────────────────────────────────────────────────────────
The Task instance is heap-allocated by _ev_task_scope::spawn and
lives until the enclosing scope's destructor runs. Task does NOT
own its RawThread — the scope guard does, so the join order is
driven by scope unwinding, not by Task CLEANUP.
kernel/src/Task.ev:62
Constructors
INIT(String name)
Methods
METHOD name() RETURNS String
PUBLIC API
METHOD isCancelled() RETURNS boolean
Returns TRUE once the enclosing CONCURRENT scope has flipped this task's cancellation flag. Body code is expected to call this in any long-running loop and exit cooperatively.
METHOD sleep(int64 millis) RETURNS void
Sleep the calling OS thread for millis milliseconds. Lowers
to FOREIGN::ev_thread_sleep_ms(millis) → POSIX nanosleep in
EV_unsafe_concurrency_native.cpp.
The receiver MUST be the current task: the analyzer rules
against storing/passing Task handles ensure currentTask is
the only reachable receiver. sleep does NOT observe the
cancellation flag — callers that want cancellable sleep check
isCancelled() before and after the call.
CLASS TextConverter
CONVERSIONS host for the String ↔ ByteBuffer hard line. The AS/INTO surface over the kernel's text/binary boundary, and its implementation — the UTF-8 encode/decode logic lives here in the operators (and the shared encoder helper), not delegated elsewhere.
Asymmetry (by design): String INTO ByteBuffer — lossless, total (every code point UTF-8 encodes) ByteBuffer INTO String — lossless, fallible (arbitrary bytes may not be valid UTF-8 → FAILURE; each decoded code point also passes the Tier-1 text check)
INTO here assumes UTF-8 — the canonical text encoding. Other encodings, and
the hex/base64 transforms, are parameterized and stay as named functions on
the Converter and Codec classes (out of scope for single-source/single-target AS/INTO operators).
kernel/src/TextConverter.ev:26
CLASS TimeConstants
The fixed factors between time units, in one place.
These are not arbitrary tuning numbers — every one is a definition, fixed by
the units themselves and unchanging. They lived as bare literals in
TimeDuration, DurationText and MeasuringTimer, which meant the same
86400000 appeared in two files with nothing connecting them and nothing
saying which unit pair it converted. A reader met mag / 86400000 and had to
count the zeroes to learn it was days.
Sibling of HashConstants and NumericLimits — a namespace whose whole
content is named constants, reached as TimeConstants.MS_PER_DAY.
kernel/src/TimeConstants.ev:23
Fields
int64 MS_PER_SECOND— 1_000 — milliseconds in a second.int64 MS_PER_MINUTE— 60_000 — milliseconds in a minute (60 x MS_PER_SECOND).int64 MS_PER_HOUR— 3_600_000 — milliseconds in an hour (60 x MS_PER_MINUTE).int64 MS_PER_DAY— 86_400_000 — milliseconds in a day (24 x MS_PER_HOUR). A NOMINAL day: this is duration arithmetic, which knows nothing of leap seconds or of the 23- and 25-hour days a DST transition produces. Calendar-aware arithmetic belongs toDateTime, not here.int64 NS_PER_MICROSECOND— 1_000 — nanoseconds in a microsecond.int64 NS_PER_MILLISECOND— 1_000_000 — nanoseconds in a millisecond.int64 NS_PER_SECOND— 1_000_000_000 — nanoseconds in a second.
CLASS TimeDuration
IMPLEMENTS Cloneable, Hashable, Equatable
Signed wall-clock duration with millisecond precision.
Storage: canonical int64 totalMs (signed) — single source of truth.
The five broken-down accessor fields (days/hours/minutes/seconds/millis)
are public read-only and decomposed once at construction; for negative
totalMs each carries the same sign as totalMs (mirrors java.time.Duration's
sign convention).
Construction: - CREATE TimeDuration() — zero duration - CREATE TimeDuration(d, h, m, s, ms) — explicit components; the INIT composes totalMs and re-decomposes the fields, so callers reading .days / .hours / ... always see the normalised values (1 day 25 hours → 2 days 1 hour). - CREATE TimeDuration(int64 totalMs) — direct totalMs (used by arithmetic operator results — e.g., DateTime operator -(DateTime)).
Arithmetic / comparison: TimeDuration implements Comparable (and the
parent Equatable), Hashable, Cloneable, plus operator + / operator -
over TimeDuration → TimeDuration and a named negate() (V1 leaves
unary - on classes out of scope per Phase 0 #6 LOCKED).
kernel/src/TimeDuration.ev:43
Fields
int64 daysint64 hoursint64 minutesint64 secondsint64 millis
Constructors
INIT()
Zero duration.
INIT(int32 days, int32 hours, int32 minutes, int32 seconds, int32 millis)
Explicit-component constructor. Each parameter contributes
linearly to totalMs (1 day = 86_400_000 ms; 1 hour = 3_600_000 ms;
1 minute = 60_000 ms; 1 second = 1_000 ms; 1 ms = 1 ms). The
broken-down fields below are then derived from totalMs, so
(1 day, 25 hours, 0, 0, 0) yields .days = 2, .hours = 1.
INIT(int64 totalMs)
Direct totalMs constructor. Used by arithmetic-operator results (DateTime->operator-(DateTime), TimeDuration±TimeDuration) where the int64 difference is already known and re-running the component composition would just round-trip.
Methods
METHOD getTotalMs() RETURNS int64
Signed milliseconds — the canonical underlying value.
METHOD __op_plus__(TimeDuration other) RETURNS TimeDuration
td1 + td2 lowering. Returns a new TimeDuration whose totalMs
is the int64 sum; no overflow check (int64 ms range is ~±292M
years, well past anything practical).
METHOD __op_minus__(TimeDuration other) RETURNS TimeDuration
td1 - td2 lowering.
METHOD negate() RETURNS TimeDuration
Named unary negation. Unary - on classes is out of V1 scope
(Phase 0 #6 LOCKED); callers compose td->negate() instead.
METHOD isLessThan(TimeDuration other) RETURNS boolean
Strict less-than on totalMs. Drives derived </<=/>/>=.
METHOD equals(TimeDuration other) RETURNS boolean
Two TimeDurations are equal iff their totalMs match.
METHOD hash() RETURNS uint64
FNV-1a 64-bit hash of totalMs, mixed as 8 little-endian bytes. Mirrors the per-byte mixing String.hash() uses; equal totalMs → equal hash, the Equatable contract.
METHOD clone() RETURNS TimeDuration
Field copy. Routes through INIT(int64) so the decomposition runs once on the clone; totalMs is the source of truth, the public fields are derived.
METHOD format() RETURNS String
ISO-8601 duration text — [-]P[nD]T[nH][nM][nS[.mmm]], millisecond
precision: PT0S for zero, a leading - for a negative duration, and
fractional seconds (PT1.500S) only when there are sub-second millis.
TOTAL — every duration has a text form. The inverse is
DurationText.parse (DurationText.ev); serde carries a TimeDuration
field through this pair (I.P.i).
CLASS UTFCodec
Unicode Transformation Format codec + scalar validity.
UTFCodec is a stateless NAMESPACE: never instantiated, reached via
UTFCodec.methodName(args). It hosts free functions plus the private
CONSTANT lookup tables (and the CharClass / State enums + FirstUnitInfo
struct they are built from) the transcoders walk.
PURPOSE
The single home for transcoding between the three UTF encodings
(UTF-8 / UTF-16 / UTF-32), plus the predicates that decide whether
a code point is a legal Unicode scalar and whether it is admissible
as text. The Converter and Codec classes and DynamicString (via
DynamicString.append) are the user-facing bridges to the String / ByteBuffer types; they
marshal char[] arrays in/out and call UTFCodec for the actual
transcoding. UTFCodec itself has no dependency on String,
DynamicString, ByteBuffer, or DynamicByteBuffer — it works
purely in terms of char8[] / char16[] / char32[] and the two
scalar-validity predicates over char. That keeps UTFCodec below
every text/byte class in the kernel dependency order.
The class is named for the UTF family (UTF-8 / UTF-16 / UTF-32).
Every transcoder is named encodeTo<TARGET>(<SOURCE>[] source) ->
(<TARGET>[] | STATUS) — the destination encoding is in the method
name; the source is in the argument width. The full 3×3 matrix
minus the identity diagonal is implemented (6 methods).
CODE POINT vs CODE UNIT
char32 is a Unicode code point (U+0000..U+10FFFF, lowering to
char32_t). char8 is a UTF-8 code unit (8-bit, char8_t). char16
is a UTF-16 code unit (16-bit, char16_t). Code-unit arrays in
char8[] / char16[] form may need multi-unit grouping to recover a
code point — UTF-8 lead+continuation bytes for encodeToUTF32(char8[]),
surrogate pairs for encodeToUTF32(char16[]).
LOOKUP TABLES (utf_utils-style DFA)
Four PRIVATE CONSTANT tables back a single-table DFA decoder
(currently dormant — the linear decode below is what runs today).
The DFA shape follows Bob Steagall's utf_utils paper:
firstUnitTable[256] — for each possible first byte, the masked code-point bits + the next DFA state. octetCategory[256] — CharClass tag for every byte value, used by continuation-byte transition lookup. transitions[108] — 9 states × 12 CharClass categories → next State. Index = (state + category). firstOctetMask[12] — per-CharClass mask of the first byte's code-point bits (e.g. 0x1F for the 5-bit lead in a 2-byte sequence).
VALIDITY — TWO PREDICATES
- isValid(char) — is the code point a legal Unicode
scalar value (<= U+10FFFF, not a
UTF-16 surrogate).
- isStringSafeCodePoint(char) — stricter: a scalar that is also
admissible as text. Rejects the 66
noncharacters and the non-whitespace
C0/C1 control bytes. This is the
invariant DynamicString enforces on
every append, so a text value can
never accumulate non-text content.
STATUS: first version, written 2026-05-18 as part of the four-form
text/binary dig-out. Carries the UTF-8 encode/decode that previously
lived on Convert (encodeUtf8 / decodeUtf8), now with the scalar-
validity checks (overlong / surrogate / out-of-range) folded into
decode. The DFA lookup tables landed 2026-05-25 — they are dormant
storage until a DFA-based decode rewrite replaces the current linear
form.
kernel/src/UTFCodec.ev:84
Methods
METHOD encodeToUTF32(char8[] source) RETURNS | char32[]
DFA-BASED BULK DECODE — UTF-8 code units -> UTF-32 code points Decode a full UTF-8 buffer (char8[] code units) into a char32[] of code points using the four LOOKUP TABLES above (utf_utils- style single-DFA decoder). Pipe-XOR: SUCCESS yields the codepoints buffer; FAILURE on the first malformed lead unit, truncated sequence, bad continuation unit, or DFA-detected illegal form (overlong, surrogate, beyond U+10FFFF — all baked into the transitions[] mapping).
Input / output are both bare value-array storage shorthand — no String / ByteBuffer / DynamicString / Array[T] dependency. UTFCodec sits below the String family in the kernel dependency order.
Algorithm:
1. firstUnitTable[u] -> (firstOctet bits, nextState).
ASCII units (0x00..0x7F) return nextState=BEGIN with
cp == u, so the ASCII path is one table read + one write.
2. For multi-unit leads, nextState moves into a CONTINUEn /
PARTIAL_SEQUENCE state. The continuation loop reads
successive code units, accumulates 6 bits per unit into
cp, and advances state via
transitions[currentState + octetCategory[contUnit]].
Loop exits when state returns to BEGIN/END (= 0; code
point complete) or ERROR (malformed).
3. Result built via the value-array HWM-write proxy —
result[outCount] = cp grows the buffer when outCount
equals result.length.
METHOD encodeToUTF8(char32[] source) RETURNS | char8[]
BULK TRANSCODE — char32[] → char8[] (codepoints → UTF-8 bytes) Encode an array of UTF-32 codepoints into a UTF-8 byte stream. Pipe-XOR: SUCCESS yields the byte buffer; FAILURE on the first codepoint that's beyond U+10FFFF or in the UTF-16 surrogate block. The byte width per codepoint follows RFC 3629: < 0x80 → 1 byte (0xxxxxxx) < 0x800 → 2 bytes (110xxxxx 10xxxxxx) < 0x10000 → 3 bytes (1110xxxx 10xxxxxx 10xxxxxx) else → 4 bytes (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
METHOD encodeToUTF16(char32[] source) RETURNS | char16[]
BULK TRANSCODE — char32[] → char16[] (codepoints → UTF-16 units) Encode an array of UTF-32 codepoints into a UTF-16 code unit stream. BMP codepoints (< U+10000) emit one char16. Supplementary plane codepoints (U+10000..U+10FFFF) emit a surrogate pair — high in [0xD800, 0xDBFF], low in [0xDC00, 0xDFFF]. Pipe-XOR: FAILURE on a codepoint that's beyond U+10FFFF or in the surrogate block (raw surrogate codepoints are not legal scalar values).
METHOD encodeToUTF32(char16[] source) RETURNS | char32[]
BULK TRANSCODE — char16[] → char32[] (UTF-16 units → codepoints) Decode a UTF-16 code unit stream into UTF-32 codepoints, pairing surrogate halves into supplementary-plane codepoints. Pipe-XOR: FAILURE on an unpaired surrogate (low surrogate without a preceding high, or high surrogate without a following low).
METHOD encodeToUTF8(char16[] source) RETURNS | char8[]
BULK TRANSCODE — char16[] → char8[] (UTF-16 → UTF-8)
Composition: decode UTF-16 → codepoints → encode codepoints as UTF-8. The chain propagates STATUS from either step.
METHOD encodeToUTF16(char8[] source) RETURNS | char16[]
BULK TRANSCODE — char8[] → char16[] (UTF-8 → UTF-16)
Composition: decode UTF-8 → codepoints → encode codepoints as UTF-16. The chain propagates STATUS from either step.
METHOD isValid(char32 c) RETURNS boolean
VALIDITY PREDICATES
TRUE when c is a legal Unicode scalar value: in range
U+0000..U+10FFFF and not a UTF-16 surrogate half.
METHOD isStringSafeCodePoint(char32 c) RETURNS boolean
TRUE when c is admissible as text — a legal scalar value that
is also not a noncharacter and not a junk control byte. This is
the Tier-1 text rule: DynamicString.append(char) rejects
every code point that fails it, so no text value can carry
non-text content.
Allowed control bytes are the text whitespace set only — tab (U+0009), LF (U+000A), VT (U+000B), FF (U+000C), CR (U+000D), and NEL (U+0085); every other C0/C1 control and U+007F (DEL) is rejected. VT/FF/NEL are kept because they are line separators (see splitLines).
CLASS WorkerPool
V1 Part H.4.1: a pool of worker threads that run submitted jobs.
────────────────────────────────────────────────────────────────── USER-FACING API ──────────────────────────────────────────────────────────────────
A WorkerPool owns a fixed set of worker threads that drain a shared,
bounded job queue. A job is any TaskStarter — a self-contained unit of
work whose start() runs on a worker thread. Jobs are moved in (the pool
takes ownership), so a job carries its inputs as value fields and reports
results by writing to an output destination it holds — typically a
SHARED MUTABLE REFERENCE Channel[R] the caller owns and drains. The
pool is non-parametric: it is polymorphic over TaskStarter, not
WorkerPool[T].
WorkerPool pool := CREATE WorkerPool(4, 64, BackpressurePolicy.BLOCK)
pool->submit(CREATE RenderJob(tile, resultChannel))
pool->submit(CREATE RenderJob(tile2, resultChannel))
pool->awaitIdle() // block until both jobs have finished
pool->shutdown() // stop the workers (also runs at scope exit)
- submit(MOVE TaskStarter job) — enqueue a job; pipe-XOR STATUS. At queue capacity, behaviour follows the BackpressurePolicy. Fire-and-forget: the job's own STATUS is not surfaced here (a job reports results through its held output channel).
- awaitIdle() — block until the queue is empty AND no job is running. Does not stop the pool.
- shutdown() — sticky stop: drains queued jobs, then joins every worker. Idempotent; also runs from CLEANUP.
────────────────────────────────────────────────────────────────── STRUCTURE ──────────────────────────────────────────────────────────────────
The pool owns a WorkerPoolCore (the thread-safe engine + job queue) and
one shared WorkerBody (the drain loop), and spawns N OS threads via
RawThread. Each thread runs the shared body, which loops on the core.
The threads are OS-detached at spawn; clean shutdown is driven by the
core's live-worker count and exitCond, not by joining std::thread
handles — so the pool keeps no RawThread handles after spawning.
The body holds a SHARED MUTABLE REFERENCE to the core (not to the pool), which is why the engine is a separate owned sub-object: the pool hands out a reference to something it owns, never to itself.
────────────────────────────────────────────────────────────────── V1 SCOPE (DETACHED + LONG_LIVED) ──────────────────────────────────────────────────────────────────
- Attachment is fixed to DETACHED and lifetime to LONG_LIVED — these are reserved concurrency keywords (siblings of DETACHED), not options, so V1 takes no attachment/lifetime argument. ATTACHED / TEMPORARY are reserved for the eventual modifier grammar.
- Worker count is an explicit constructor argument (a cores-default awaits a CPU-count primitive).
- submit() is thread-safe (it forwards into the SHARED CLASS core); pool management — construction and shutdown() — is owner-thread work.
- Tail-append in the core's queue is O(queue-depth); fine for a bounded pool. A constant-time tail or a chunked ring is a perf backlog item.
STATUS: V1 Part H.4.1 (2026-05-29). First real client of RawThread.
kernel/src/WorkerPool.ev:75
Constructors
INIT(int32 workerCount, int32 capacity, BackpressurePolicy policy)
Methods
MODIFY METHOD submit(MOVE TaskStarter job) RETURNS STATUS
Enqueue a job for a worker to run. Pipe-XOR STATUS. The bare-named MOVE form consumes the source — the pool takes ownership of the job. At queue capacity, behaviour follows the BackpressurePolicy fixed at construction. Returns FAILURE if the pool has been shut down.
MODIFY METHOD awaitIdle() RETURNS void
Block until the pool is idle — the queue is empty and no job is running. Does not stop the pool; the workers stay alive for more work. Useful as a batch barrier between submit waves.
MODIFY METHOD shutdown() RETURNS void
Sticky shutdown. Queued-but-unstarted jobs still drain; once the queue empties, every worker leaves its loop and this returns. Idempotent — a second call is a no-op.
METHOD size() RETURNS int64
Number of worker threads in this pool.
CLASS WorkerPoolCore
IMPLEMENTS Shareable
WorkerPoolCore is the thread-safe heart of a WorkerPool: a bounded,
move-only job queue plus the synchronisation that lets N worker threads
drain it concurrently while submitters hand work in. It is a
SHARED CLASS (monitor) — the carve-out (§9.6) that lets the worker
threads and the submitting thread drive its MODIFY methods through
shared references without owning it.
The public WorkerPool façade owns one WorkerPoolCore and hands a
SHARED MUTABLE REFERENCE to it to the worker bodies. Splitting the
engine out of the façade is what avoids a self-reference: the façade
passes a reference to an owned sub-object (the same idiom Channel uses
to hand its bufferLock to its ThreadConditions), never to itself.
THE JOB QUEUE
Jobs are TaskStarter instances, moved in (never cloned), so the queue
cannot be Channel[T] (which requires T IS Cloneable). It is a
singly-linked chain of WorkerJobNodes behind a head sentinel: submit
appends at the tail, processNext splices off the front (FIFO). Append is
O(n) in queue depth — acceptable for a bounded pool; see the perf note
in WorkerPool.ev.
LIFECYCLE COUNTERS
- count — jobs currently queued (front .. tail).
- active — jobs currently running on a worker (dequeued, not yet
finished).
idleCondfires when count == 0 AND active == 0 — the signal awaitIdle() waits on. - liveWorkers — workers still in their drain loop. Bumped once per
worker at spawn time by registerWorker() (synchronously,
from WorkerPool.INIT, so it reaches N before any
shutdown can race), decremented by workerExited() when a
worker leaves its loop.
exitCondfires when it hits 0 — the signal awaitWorkersExit() waits on for clean teardown.
STATUS: V1 Part H.4.1 (2026-05-29). Composes WorkerJobNode storage + Lock + ThreadCondition substrate. DETACHED + LONG_LIVED behaviour.
kernel/src/WorkerPoolCore.ev:49
Constructors
INIT(int32 capacity, BackpressurePolicy policy)
Methods
MODIFY METHOD registerWorker() RETURNS void
Register a worker about to be spawned. Called synchronously from WorkerPool.INIT, once per worker, BEFORE any shutdown can be requested — so liveWorkers is exactly the worker count before the first awaitWorkersExit() can observe it.
MODIFY METHOD workerExited() RETURNS void
A worker has left its drain loop. Decrements liveWorkers and, when the last worker exits, wakes awaitWorkersExit().
MODIFY METHOD submit(MOVE TaskStarter job) RETURNS STATUS
Submit a job. Pipe-XOR STATUS. The bare-named MOVE form consumes the source — the job is moved into the queue. At capacity, behaviour follows the BackpressurePolicy. Returns FAILURE if the pool is shut down.
MODIFY METHOD processNext() RETURNS boolean
Run one job, blocking until work is available. Returns TRUE after running a job (the caller should loop and call again); FALSE once the pool is shut down AND the queue is drained (the worker should leave its loop). The job runs OUTSIDE the lock so other workers can dequeue and submit() does not stall behind a long-running job.
MODIFY METHOD beginShutdown() RETURNS void
Begin a sticky shutdown. Wakes every blocked worker and submitter so they re-test their predicate; queued-but-unstarted jobs still drain (workers exit only once the queue is empty). Idempotent.
MODIFY METHOD awaitWorkersExit() RETURNS void
Block until every worker has left its drain loop. Called by WorkerPool teardown after beginShutdown(), so the worker bodies and this core are guaranteed untouched by any thread before they are destroyed.
MODIFY METHOD awaitIdle() RETURNS void
Block until the pool is idle — no queued jobs and none running. Does NOT shut the pool down; the workers stay alive for more work.
METHOD shareableKind() RETURNS String
Shareable marker — short type tag for debug printers / logs.
ENUM BackpressurePolicy
BackpressurePolicy — what a Channel[T] / Broker[T] subscriber does when its bounded buffer is full at send/publish time. V1 Part H (ENVZN_CONSTITUTION.md §12). The Channel constructor and the Broker.subscribe call both take an explicit policy; there is no default — every site declares its choice so the failure mode is visible.
- BLOCK — sender blocks until a slot is available. The safe choice for correctness-critical streams; propagates backpressure upstream.
- DROP_OLDEST — drop the oldest queued value, append the new one. The Kotlin SharedFlow default; friendly to telemetry-style streams where freshness wins.
- DROP_NEWEST — drop the incoming value, keep the buffered backlog. Used when older messages have priority (e.g. ordered event replay).
- CONFLATE — collapse to a single most-recent value; the buffer holds only the latest. State-update streams where intermediate values don't matter.
kernel/src/enums.ev:152
| Case | Description |
|---|---|
? |
— |
? |
— |
? |
— |
? |
— |
ENUM BinaryMode
BinaryMode — how a Formatter renders a binary placeholder
argument. Selected at Formatter construction; the default
Formatter uses HEX_SPACED.
- HEX_SPACED — uppercase hex, byte pairs space-separated: "DE AD BE EF". The default.
- HEX_PACKED — uppercase hex, no separators: "DEADBEEF".
- BASE64 — standard base64 with
=padding: "3q2+7w==".
Spec reference: stdio-spec.md §4.
kernel/src/enums.ev:171
| Case | Description |
|---|---|
? |
— |
? |
— |
? |
— |
ENUM Build
Build — compile-time build flavor. Available to user code via
the WHEN Build IS DEBUG/RELEASE conditional-compilation construct
(analogous to #ifdef DEBUG in C/C++; #5, shipped 2026-06-26 — the
unmatched arm is removed before semantic analysis, never emitted):
WHEN Build IS DEBUG {
log("debug-only state: $1")->format(detail)
} ELSE {
// release path
}
- DEBUG — assertions on, optimizations relaxed, debug output enabled.
- RELEASE — assertions stripped, optimizations on.
Spec reference: §Control Flow > Conditional Compilation.
kernel/src/enums.ev:64
| Case | Description |
|---|---|
? |
— |
? |
— |
ENUM DataKind
DataKind — the discriminant of DataValue, the kernel's neutral,
format-independent value model (the abstract shape shared by JSON,
BSON, CBOR, MessagePack, and DB rows). A DataValue carries one of
these kinds plus the storage for that shape. Promoted from the Json
module's Kind so the value model is a kernel citizen while wire
formats stay in their modules (Json maps DataValue ↔ text).
kernel/src/enums.ev:258
| Case | Description |
|---|---|
? |
— |
? |
— |
? |
— |
? |
— |
? |
— |
? |
— |
ENUM Encoding
Encoding — text-encoding tag used by Convert text↔bytes bridges. Consumed by the four-form String/ByteBuffer type system (added 2026-05-03 in the String/Binary rework).
- UTF8 — Unicode 8-bit variable-length, the standard text encoding for new files / network payloads / source code. The kernel's String class stores text as UTF-8 internally; UTF8 is the natural round-trip.
- UTF16LE — Unicode 16-bit little-endian (Windows native, .NET).
- UTF16BE — Unicode 16-bit big-endian (Java DataOutput, BMP).
- ASCII — 7-bit ASCII subset; bytes ≥ 0x80 fail decode.
- LATIN1 — ISO-8859-1; round-trips all byte values 0–255 to code points 0–255 without failure.
Used by: - Convert.toBytes(String, Encoding) → ByteBuffer - Convert.toString(ByteBuffer, Encoding) → (String, STATUS)
V1 implementation: UTF-8 round-trip is end-to-end; the other four variants are stubbed as STATUS-FAILURE returns until needed.
kernel/src/enums.ev:91
| Case | Description |
|---|---|
? |
— |
? |
— |
? |
— |
? |
— |
? |
— |
? |
— |
ENUM Endianness
Endianness — byte-order tag used by Convert numeric↔binary bridges. Consumed by the four-form String/ByteBuffer type system (added 2026-05-03 in the String/Binary rework).
- LITTLE — least-significant byte first. x86 / ARM64 native; the dominant choice for new wire formats.
- BIG — most-significant byte first. Network byte order (RFC 1700), JVM / .NET BinaryWriter big-endian methods, many older protocols.
- NATIVE — host CPU's native order; resolved at call site. Use only when the bytes will not leave the process — otherwise always specify LITTLE or BIG.
Used by: - Convert.toBytes(int32, Endianness) → ByteBuffer - Convert.toInt32(ByteBuffer, Endianness) → (int32, STATUS) - …and every other Convert numeric↔binary bridge.
kernel/src/enums.ev:119
| Case | Description |
|---|---|
? |
— |
? |
— |
? |
— |
ENUM GrowthHint
GrowthHint — advisory sizing hint for hash-based collections (Dictionary impls, Set). Passed at construction alongside an initial capacity so an implementation can tune its backing storage.
- STABLE — size stays roughly constant after fill; size the backing store once and avoid rehash churn.
- GROW_ONLY — size only increases; pre-grow aggressively, never shrink.
- DYNAMIC — size moves up and down frequently; keep headroom
and tolerate rehash. (
VOLATILEis a reserved concurrency keyword, so the churn case is named DYNAMIC.)
V1: the hint is accepted and stored but only initialCapacity
drives behavior (ChainedHashDictionary pre-sizes its bucket array).
GrowthHint-driven tuning (shrink policy, load-factor selection) is
a future performance pass.
kernel/src/enums.ev:207
| Case | Description |
|---|---|
? |
— |
? |
— |
? |
— |
ENUM NumberKind
NumberKind — the subtype tag carried by a number primitive: a signed
integer (int64) XOR an unsigned integer (uint64, reached only when a value
overflows the int64 arm into (int64_max, uint64_max]) XOR a float (float64).
number->kind() returns this (a typed, MATCH-able surface). Backs the
Number value-class (Number.ev, V1 Part D). UNSIGNED is appended last so the
shipped INTEGER/FLOAT ordinals are unchanged. See number-and-complex-design.md,
number-unsigned-arm-design.md / ENVZN_CONSTITUTION I.D.i(g).
kernel/src/enums.ev:245
| Case | Description |
|---|---|
? |
— |
? |
— |
? |
— |
ENUM RoundingMode
RoundingMode — how a decimal128 operation breaks at the boundary where
it must drop digits. The bare operators + - * / always round to 34
significant digits with HALF_EVEN; the explicit-context methods
(roundTo, and add/subtract/multiply/divide with an explicit
precision) take a mode. There is NO ambient/global rounding context —
every mode-taking call names its choice (the readable north-star).
The set is Java's RoundingMode minus UNNECESSARY; a superset of
IEEE 754's five rounding-direction attributes. HALF_EVEN is the
zero-default (it is listed first), matching the bare-operator behavior.
Backs the Decimal128 value-class (Decimal128.ev, V1 Part #40).
- HALF_EVEN — ties to the nearest even digit (banker's rounding). The IEEE default; minimizes cumulative bias. DEFAULT.
- HALF_UP — ties away from zero (0.5 → 1, −0.5 → −1). The rounding taught in schools; IEEE roundTiesToAway.
- HALF_DOWN — ties toward zero (0.5 → 0, −0.5 → 0).
- UP — always away from zero (round magnitude up on any nonzero dropped part).
- DOWN — always toward zero (truncate). IEEE roundTowardZero.
- CEILING — toward +∞ (round up for positives, truncate for negatives). IEEE roundTowardPositive.
- FLOOR — toward −∞ (truncate for positives, round up for negatives). IEEE roundTowardNegative.
kernel/src/enums.ev:291
| Case | Description |
|---|---|
? |
— |
? |
— |
? |
— |
? |
— |
? |
— |
? |
— |
? |
— |
ENUM Severity
Per the ENVZN module convention, this single file holds the constitution's reserved enums. They're small (2–3 cases each) so grouping them in one file matches the pattern used for STRUCTs and INTERFACEs.
Each enum is referenced by name from the constitution and/or
standard library:
- DeliveryMode — used by CHANNEL[T]->send_message() to
specify how a message reaches subscribers.
- Severity — used by logging APIs and the constitution's
Compiler-Error-Strategy spec to grade diagnostic levels.
- Build — used by WHEN_BUILD conditional compilation
to switch on debug-vs-release at compile time.
Severity — diagnostic and logging level. Ordered (informally)
from least to most serious. Consumers can compare ordinally
via the implicit enum order.
- INFO — routine operational message.
- WARNING — something unexpected but recoverable.
- ERROR — operation failed; caller should handle.
- FATAL — unrecoverable; typical follow-up is exit() or PANIC.
Spec reference: §17 mentions ENVZN::Severity.WARNING for
compiler-driven diagnostics; user-side logging follows the
same enum.
kernel/src/enums.ev:39
| Case | Description |
|---|---|
? |
— |
? |
— |
? |
— |
? |
— |
ENUM SipVariant
SipVariant — which SipHash round schedule a SipHasher runs.
STRONG_2_4 (the DEFAULT) is canonical SipHash-2-4: two compression rounds per message word, four finalization rounds. It is the conservative choice and what SipHash's authors specify for general keyed hashing.
FAST_1_3 is SipHash-1-3: one compression round, three finalization — a third fewer rounds, and the schedule Rust ships in its default HashMap. It is still considered adequate against hash-flooding, but it IS a weaker PRF than 2-4, so it is opt-in and never the default: choosing it is a security decision, not a performance one.
kernel/src/enums.ev:224
| Case | Description |
|---|---|
? |
— |
? |
— |
ENUM TimeComparison
Three-way comparison result for DateTime->compareTo. Co-exists with Comparable[DateTime]'s isLessThan / equals — both surfaces are intentional per the constitution §28.5 reservation. Comparable derives the ordering operators (<, <=, >, >=); compareTo names the result for callers that want the explicit enum.
kernel/src/enums.ev:183
| Case | Description |
|---|---|
? |
— |
? |
— |
? |
— |
INTERFACE Arithmetic
Arithmetic[T] — the contract that T is additive: an add(T),
subtract(T), and negate(), each closed over T. It is a named
constraint (e.g. for generic bounds), NOT an operator-derivation
mechanism.
The binary OPERATORS are a separate, explicit surface: a class
overloads + / - by declaring METHOD operator +(T) RETURNS T /
METHOD operator -(T) RETURNS T (mangled __op_plus__ /
__op_minus__), exactly as number and decimal128 do. Declaring
IMPLEMENTS Arithmetic[T] does NOT by itself make a + b compile —
the operator method is what the compiler dispatches. There is no
unary operator -; expose negation as the named negate().
Implementations should be closed (add(b) returns a T),
associative, and have subtraction undo add. Built-in for the
primitive numerics. A fully-numeric custom type typically declares
both this contract and the matching operator methods, and pairs
with Comparable[T] / Multiplier[T].
kernel/src/interfaces.ev:541
Methods
METHOD add(T other) RETURNS T
METHOD subtract(T other) RETURNS T
METHOD negate() RETURNS T
INTERFACE BidirectionalIterator
BidirectionalIterator OF T — extends ReferenceIterator OF T with reverse traversal. Used by Array, LinkedList, SortedList, Deque per spec L763-768.
- hasPrevious() — TRUE if cursor has not reached the start
- previous() — moves cursor backward, returns the element; STATUS = FAILURE at start
- skipBack(n) — moves cursor backward by n positions; returns the element at the new position, or STATUS = FAILURE if start is reached. Symmetric to skip(n). skipBack(0) is equivalent to peek().
kernel/src/interfaces.ev:417
Methods
METHOD hasPrevious() RETURNS boolean
MODIFY METHOD previous() RETURNS | REFERENCE T
MODIFY METHOD skipBack(uint64 n) RETURNS | REFERENCE T
INTERFACE Cloneable
Cloneable — per Bug #23. A class implementing Cloneable opts in
to deep-copy semantics: := on instances calls clone(); the
*Copy family of collection methods clones each class-typed
element through this interface.
Implementations declare their own concrete return type — Envzn's interface satisfaction rule accepts any return type that itself implements Cloneable (covariant-return-via-subtype), so:
CLASS Person IMPLEMENTS Cloneable { AUTO METHOD clone() RETURNS Person { } }
...satisfies the contract without any cast.
kernel/src/interfaces.ev:163
Methods
METHOD clone() RETURNS Cloneable
GROUP Collectable
kernel/src/interfaces.ev:144
INTERFACE Collection
The minimum definition of a Collection container — what every container in the standard library can answer, so that membership of this interface is what marks a type AS a collection.
3 METHODS: iterator() declared here size() and isEmpty() inherited from Countable
contains() is deliberately NOT here, and the reason is the whole shape of
the interface. Containment needs EQUALITY on T, and Array[T] requires only
Cloneable of its elements — so putting contains in the minimum would force
Equatable onto every element type in the language the moment Array joined.
That is the split the industry already divides on: where equality is
UNIVERSAL (Java's Object.equals, Python's eq) a Collection can require
contains, and where equality is OPT-IN (Rust, Swift, C++) it cannot —
Swift's Collection requires iteration and offers contains only as an
extension where Element: Equatable. Envzn is the second kind. Containment
belongs with Searchable OF T, whose find needs exactly the same equality
and of which contains is the boolean shadow.
ITERATION is also the more primitive notion: contains is DERIVABLE from
iterating and comparing, while iteration cannot be derived from containment.
A minimum should be the irreducible core.
The qualifier is deliberately the WIDEST of any container's: iterator(),
size() and isEmpty() ask NOTHING of T, so nothing here should either.
Shareable is listed beside Cloneable because the OwnedList family
constrains its element that way, and refusing it would exclude a container
from the interface over a contract the interface never uses.
kernel/src/interfaces.ev:480
Methods
METHOD iterator() RETURNS ReferenceIterator[T]
GROUP Collectors
kernel/src/interfaces.ev:132
INTERFACE Comparable
Comparable[T] — type defines a strict less-than ordering. Extends Equatable[T]: a type that is comparable is necessarily equatable (otherwise sorted-and-equal-to checks can't compose).
Required for any T used in:
- SortedList[T] (insert position determined by isLessThan)
- Array[T]->sort() (insertion-sort or quicksort comparator)
- any future ordered collection
The implementation MUST satisfy a strict total order:
- Irreflexive — a.isLessThan(a) is always FALSE.
- Antisymmetric — if a.isLessThan(b) then NOT b.isLessThan(a).
- Transitive — if a.isLessThan(b) and b.isLessThan(c),
then a.isLessThan(c).
- Trichotomous (consistent with Equatable) — for every
pair, exactly one of a.isLessThan(b), b.isLessThan(a),
or a.isEqualTo(b) is TRUE.
The kernel provides built-in Comparable behaviour for primitive
numerics (int8..int64, uint8..uint64, float32, float64,
byte, char) and String (lexicographic). boolean is also
comparable (FALSE < TRUE). Class types must declare
IMPLEMENTS Comparable[K] and provide isLessThan(K) and
isEqualTo(K) methods explicitly.
isLessThanOrEqual, isGreaterThan, isGreaterThanOrEqual are
derivable and not part of this interface; the canonical method
is isLessThan. Callers compose: NOT a.isLessThan(b) is
"a >= b", b.isLessThan(a) is "a > b", etc.
kernel/src/interfaces.ev:516
Methods
METHOD isLessThan(T other) RETURNS boolean
GROUP Concurrency
kernel/src/interfaces.ev:140
INTERFACE Countable
Countable is an opt-in interface for any class that enumerates how many elements it holds.
kernel/src/interfaces.ev:209
Methods
METHOD size() RETURNS int64
METHOD isEmpty() RETURNS boolean
INTERFACE Deserializer
Deserializer — a read source. deserialize reads its source (a JSON
stream, a DataValue tree, …) and re-emits the content as a flat event
stream into a Serializer sink, recursing INTERNALLY through nested
containers. This is the shape Envzn's memory floor admits: the sink is a
plain interface param (its MODIFY methods mutate the underlying object,
so no stored mutable borrow — E1115 — and no SELF-as-argument — E1085);
a streaming reader owns its cursor and threads it down its own recursion,
a tree reader recurses over its (immutable) child nodes. There is no
separate visitor abstraction — a reconstruct event stream IS a serialize
event stream, so Serializer is the one universal sink. The sink backend
decides what the events become: a DataValueBuilder builds a tree, a
JsonWriter transcodes to text.
kernel/src/interfaces.ev:655
Methods
MODIFY METHOD deserialize(Serializer sink) RETURNS STATUS
INTERFACE Equatable
Equatable[T] — type can compare itself to another instance of the same type for value equality. Parameterised over T to pin the comparison type at the implementation site:
CLASS Token IMPLEMENTS Equatable[Token] {
METHOD isEqualTo(Token other) RETURNS boolean { ... }
}
Equatable is the contract Dictionary uses to resolve hash collisions and Set uses for membership. Required alongside Hashable for any class type used as a Dictionary key.
Implementations must be:
- Reflexive — a.isEqualTo(a) is always TRUE.
- Symmetric — a.isEqualTo(b) iff b.isEqualTo(a).
- Transitive — if a.isEqualTo(b) and b.isEqualTo(c),
then a.isEqualTo(c).
- Consistent with Hashable — if a.isEqualTo(b), then
a.hash() == b.hash(). (The reverse need not hold; hash
collisions on equal-by-isEqualTo objects break Dictionary.)
Built-in for primitives and String, same as Hashable.
kernel/src/interfaces.ev:271
Methods
METHOD equals(T other) RETURNS boolean
GROUP Floating
GROUP HashKey — the primitives admissible as a hash key: every WordKey,
plus decimal128.
decimal128 is NOT a WordKey — uint64 v = key is forbidden for it (E2121,
decimal never mixes) — but it does not need to be. It carries its own exact
hash() (Decimal128.ev), so it takes a hasher's WHEN K IMPLEMENTS Hashable
arm and never reaches the widening. That is why the general hashers admit it
and FastIntHasher/InlineIntHasher, whose bodies ARE the widening, do not.
kernel/src/interfaces.ev:122
GROUP HashKey
kernel/src/interfaces.ev:127
INTERFACE Hashable
Hashable — type can produce a stable hash code of itself.
Returned value is uint64 per spec §Hashable and KeyHasher
(CONSTITUTION L3611). Implementing classes are eligible for use
as Dictionary[K: V] keys (paired with Equatable[K], below) and
Set[T] elements.
The hash MUST be: - Deterministic — equal objects (per Equatable.isEqualTo) must produce equal hashes. Calling hash() on the same value twice must yield the same result. - Pure — no observable side effects, no I/O, no mutation of SELF or any reachable state.
The kernel provides built-in Hashable behaviour for primitives
(int8..int64, uint8..uint64, float32, float64, byte,
char, boolean) and String — those types do not need to
declare IMPLEMENTS Hashable explicitly. Dictionary[K: V] uses
the built-in path automatically when K is one of those.
kernel/src/interfaces.ev:443
Methods
METHOD hash() RETURNS uint64
INTERFACE Inoperative
Inoperative — marker for types whose values can't be operated on
in generic code. Today's sole member is the Part-G opaque
primitive: type-erased payload whose static type is opaque to
callers, so no method dispatch is well-defined. Generic kernel
containers (Array, Dictionary, Set, etc.) use
WHEN T IMPLEMENTS Inoperative { /* no-op stub */ } ELSE { ... }
to opt out of clone, copy, and iteration paths that would
instantiate to deleted-ctor / dangling-handle errors for opaque.
The inoperativeReason() method is the sole operation valid on an
Inoperative value — it returns a fixed-text human-readable
description of why this type is opaque. Used by debug printers,
error formatters, and kernel logs that need to render "what is
this thing I got" without unwrapping.
Compile-time WHEN T IMPLEMENTS Inoperative lowers to a special
_is_inoperative_v<T> trait check (see EV_handles.hpp)
rather than the usual std::is_base_of_v<> path, because
opaque is a runtime struct (not a class that can inherit from
an interface). The dispatch of inoperativeReason() itself is
hardcoded in the emitter for the opaque keyword type.
kernel/src/interfaces.ev:327
Methods
METHOD inoperativeReason() RETURNS String
INTERFACE Iterator
Iterator OF T — forward-traversal cursor over a collection. Per spec §Iterator (L730+).
- hasNext() — TRUE if cursor has not reached the end
- next() — advances cursor and returns the new element; EMPTY when end is reached
- peek() — returns what next() would return, without advancing. Calling twice in a row returns the same value
- skip(n) — moves cursor forward by n positions; returns
the element at the new position, or EMPTY if end
is reached. skip(0) is equivalent to peek().
Parameter is unsigned: backward movement is
unavailable on Iterator OF T (forward-only
iterators have no notion of "previous"). Use
BidirectionalIterator OF T's skipBack(n) for
reverse traversal.
Iterator OF T — the iteration root. The only operation universal
to every iterator is hasNext(): no single advance form fits both
referenceable storage and by-value storage, so the advance methods
live on the two sub-interfaces below. The FOR / WHILE loop
machinery holds an Iterator and dispatches on which sub-interface
the concrete iterator implements — ReferenceIterator (collections,
referenceable storage) or ValueIterator (buffers, by-value storage).
The element is what is ITERATED, and
hasNext()uses it not at all — the qualifier saidT IS Countable, which asked the ELEMENT to have a size.
kernel/src/interfaces.ev:243
Methods
METHOD hasNext() RETURNS boolean
INTERFACE Multiplier
Multiplier[T] — the contract that T supports multiply(T) and
divide(T). Sister to Arithmetic[T]; separated because not every
additive type multiplies (e.g. Date types add a Duration but have no
meaningful multiplication). Like Arithmetic, this is a constraint,
NOT operator-derivation.
The * / / OPERATORS are declared explicitly: METHOD operator
*(T) RETURNS T / METHOD operator /(T) RETURNS T (mangled
__op_times__ / __op_divide__). A class may overload *// this
way (e.g. Matrix * Matrix); % is not overloadable.
Division by a zero-equivalent value should PANIC MathError if
undefined. Built-in for the primitive numerics. Arithmetic[T] +
Multiplier[T] + Comparable[T] is what makes a custom type satisfy
the implicit Numeric constraint.
kernel/src/interfaces.ev:565
Methods
METHOD multiply(T other) RETURNS T
METHOD divide(T other) RETURNS T
GROUP Numeric
Per the ENVZN module convention (CLAUDE.md), this file contains INTERFACEs (and may contain GROUPs / STRUCTs). Each interface here is "reserved" — recognized by the compiler for built-in cascade rules, operator dispatch, or similar.
This file currently contains the interfaces needed to support String, Array, Stack, and ArrayIterator. Expansion as more kernel collections land (Hashable for Set/Dictionary, Equatable / Comparable fully spec'd alongside the existing Operator Overloading interfaces, etc.) — out of scope for this initial draft. GROUP Numeric — sum type spanning every primitive numeric kind.
The constitution's reserved numeric category. Used by:
- Methods that accept "any number" without committing to a
specific width (Math.isLessThan(Numeric a, Numeric b)).
- Generic numeric algorithms expressed at the kernel layer.
- Operator dispatch on Numeric-typed variables (the four
arithmetic operators are spec-defined on Numeric; the
compiler dispatches to the underlying primitive at runtime).
Includes byte because byte is structurally an unsigned 8-bit
integer and participates in numeric arithmetic in the kernel
(e.g. byte arrays for binary protocols). Excludes char
because char represents a Unicode code point — comparison /
ordering apply, but arithmetic does not.
kernel/src/interfaces.ev:37
INTERFACE ObjectIterator
ObjectIterator OF T — extends Iterator with by-reference navigation
over a HEAP-CLASS element type. The object peer of ReferenceIterator:
where ReferenceIterator's bound admits only Cloneable primitives,
ObjectIterator admits any class, so a collection whose logical
element is a synthesized class instance (a per-position VIEW — e.g.
a DataFrame yielding a Row built from its stored columns) can be
FOR e IN coll-iterable. Because the source may not STORE the
element (it synthesizes it), the concrete iterator OWNS the current
element in an internal slot and hands back a non-owning REFERENCE
into that slot; the reference is valid until the next advance, on
which the slot is refreshed. The FOR-IN loop borrows it for the
iteration exactly as it borrows a ReferenceIterator's referent —
so next() lowers to REFERENCE T (a T*) on both this interface
and its implementers, with none of the value-form covariance seam.
kernel/src/interfaces.ev:393
Methods
MODIFY METHOD next() RETURNS | REFERENCE T
2.0 pipe-XOR shape: a successful navigation refreshes the
iterator's owned slot and populates value with a non-owning
reference into it; end-of-iteration populates s with FAILURE.
METHOD peek() RETURNS | REFERENCE T
peek is non-consuming: the reference to the current slot without advancing. Same pipe-XOR shape as next().
INTERFACE Printable
Printable — type can render itself directly to stdout.
Distinct from Readable — Readable produces a String for the caller to do something with; Printable is the convenience for types that own their own output side effects (e.g. a complex renderer that writes structured output to multiple lines).
Most types should implement Readable and let callers route the output. Printable is for cases where producing the intermediate String would be wasteful (e.g. large structured dumps).
kernel/src/interfaces.ev:603
Methods
METHOD print() RETURNS void
INTERFACE Random
Random — the full random / pseudo-random surface, extending
Shuffler with wider-range, 128-bit, float, and boolean draws.
Implemented by CasualRandom (fast, OS-seeded), SeededRandom
(deterministic from a developer seed), and SecureRandom (OS
entropy). A Random instance is thread-affine: it is not a
SHARED CLASS, so it cannot be shared across a thread boundary — a
non-owning capture into a PARALLEL body is rejected, while a
sole-ownership MOVE transfer is permitted and data-race-safe.
kernel/src/interfaces.ev:759
Methods
MODIFY METHOD nextUInt128() RETURNS uint128
A full-width 128-bit random value (two engine draws).
MODIFY METHOD nextFloat() RETURNS float64
Uniform random float64 in [0, 1) (53-bit mantissa).
MODIFY METHOD nextBoolean() RETURNS boolean
Uniform random boolean (the engine's top bit).
INTERFACE Readable
Readable — a byte source: the counterpart of Writable. The single
operation is read — drain the source into an storage char8[512+]
of whatever it had. File, Socket, and the Stdio stdin source
implement it, so code can ask of any of them "can I read this?"
the same way Writable answers "can I write it?".
read returns the bytes it managed to produce as a char8[512+]
storage value — empty when the source has none, populated through
to end-of-input otherwise. The FAILURE status is reserved for a
genuine read error, not for a short or empty read. The caller can
build a ByteBuffer or DynamicByteBuffer from the returned
storage when a wrapper is needed (§21).
Spec reference: stdio-spec.md §6.
kernel/src/interfaces.ev:586
Methods
MODIFY METHOD read() RETURNS | binary[]
INTERFACE ReferenceIterator
ReferenceIterator OF T — extends Iterator with by-reference
navigation. Implemented by the collection iterators, whose backing
storage is referenceable: next() / peek() / skip() hand back a
non-owning reference into the source rather than a copy. Buffer
iterators (over by-value _cxx* storage) implement only the
value-form Iterator.
next/peek/skip hand back a REFERENCE and never clone, so Cloneable
was never used; Shareable joins it so the OwnedList family qualifies.
kernel/src/interfaces.ev:343
Methods
MODIFY METHOD next() RETURNS | REFERENCE T
2.0 pipe-XOR shape: a successful navigation populates value
with a non-owning reference into the source; end-of-iteration
or invariant violation populates s with FAILURE. The caller's
IF iter->next() THEN { use($RETURNED) } ELSE { ... } consumes the
populated slot.
METHOD peek() RETURNS | REFERENCE T
peek is non-throwing and non-consuming: returns the reference at the current cursor without advancing. Same pipe-XOR shape as next().
MODIFY METHOD skip(uint64 n) RETURNS | REFERENCE T
INTERFACE Searchable
Searchable OF T — contents can be searched for a needle of the same
Text kind. find returns the match position; pipe-XOR FAILURE means
no match. Implemented by String and StringView — the view holds the
one canonical scan, and String forwards through a full-span view.
kernel/src/interfaces.ev:280
Methods
METHOD find(REFERENCE REFERENCE T needle) RETURNS | int64
INTERFACE Serializable
Serializable — the type-side contract. serialize pushes the type's
shape into the serializer (read-only on SELF; the plain param lets
the sink's MODIFY methods be called). The compiler synthesizes the
body per type (derive-by-use); kernel types implement it by hand.
kernel/src/interfaces.ev:639
Methods
METHOD serialize(Serializer s) RETURNS STATUS
INTERFACE Serializer
The serialization surface (serde-style, format-neutral) — three
interfaces that let the compiler synthesize a type's codec
(derive-by-use) against a format-agnostic value model. A format
module supplies the backends: the Json module gives a JsonWriter
(Serializer) + JsonReader (Deserializer) over text; the kernel
gives DataValueBuilder/DataValueReader over the DataValue tree.
See docs/specifications-implemented/serde_design.md.
(Supersedes the earlier unused binary Serializable {encode()} /
JSON_Serializer {toJson()} stubs — no code implemented either.)
Serializer — a write sink. A serializable type describes its shape
by pushing a flat event stream into the serializer; the backend
decides whether that becomes bytes (streaming) or a DataValue
tree. All methods mutate the sink.
kernel/src/interfaces.ev:623
Methods
MODIFY METHOD putNull() RETURNS STATUS
MODIFY METHOD putBool(boolean v) RETURNS STATUS
MODIFY METHOD putNumber(number v) RETURNS STATUS
MODIFY METHOD putString(String v) RETURNS STATUS
MODIFY METHOD beginObject() RETURNS STATUS
MODIFY METHOD putKey(String key) RETURNS STATUS
MODIFY METHOD endObject() RETURNS STATUS
MODIFY METHOD beginArray() RETURNS STATUS
MODIFY METHOD endArray() RETURNS STATUS
INTERFACE Shareable
Shareable — the marker interface for a SHARED CLASS monitor: an
interior-mutable, self-synchronizing object that may be aliased by
reference across concurrent tasks. Per Constitution §771, a
SHARED CLASS MUST IMPLEMENTS Shareable (omitting it is E2115),
and Shareable is the marker that admits a type into the two places
the borrow model otherwise forbids aliasing:
- a SHARED MUTABLE REFERENCE field (§9.6), and
- the move-only OwnedList[T] collection (owned and moved, never
cloned — so it needs no Cloneable bound; Shareable is the
explicit constraint atom that admits the non-Cloneable monitor).
The kernel's SHARED CLASS monitors are Channel[T] (the canonical
one) and WorkerPoolCore. These are the only kernel types that
implement Shareable, and the only element type used in an
OwnedList is Channel (the Broker's subscriber registry).
NOT Shareable — the other concurrency types are ordinary classes,
not monitors, and deliberately do NOT implement this marker:
- Broker / Mutex — shared across tasks by PARALLEL capture
plus an internal Lock, not by a SHARED MUTABLE REFERENCE
field, so they need no carve-out.
- Subscription — a scope-bound handle that merely HOLDS a
SHARED MUTABLE REFERENCE Channel; it rides on Channel's
shareability rather than being a monitor itself.
- Future — an ordinary result cell, not aliased mutably.
A type opts in via IMPLEMENTS Shareable. Envzn forbids empty
interfaces (E6001), so the marker carries one descriptive method —
shareableKind() returns a short human-readable tag for the
concrete type (used by debug printers / kernel logs), mirroring the
Inoperative.inoperativeReason() idiom. OwnedList itself never
calls it; the interface exists for the type-bound.
kernel/src/interfaces.ev:201
Methods
METHOD shareableKind() RETURNS String
INTERFACE Shuffler
Shuffler — the narrow random-source capability: a single bounded
integer draw. Consumers that only need to pick an index (e.g.
Array.shuffle) depend on this, not the full Random surface.
kernel/src/interfaces.ev:743
Methods
MODIFY METHOD nextInt(int64 bound) RETURNS int64
Uniform random integer in [0, bound). bound <= 0 throws
RandomError. MODIFY because drawing advances generator state.
INTERFACE TaskStarter
TaskStarter — body interface for spawnable threads.
A class that IMPLEMENTS TaskStarter can be passed to
CREATE Thread(...). When the user calls Thread.start() (the outer
kickoff), the runtime spawns an OS thread, sets up that thread's
Channel and current-thread accessors, and then invokes
body->start() (this interface's method) on the new thread. The
dev never calls TaskStarter.start() directly — the symmetric
naming mirrors CREATE → INIT.
Example:
CLASS Worker IMPLEMENTS TaskStarter { PRIVATE int32 jobId INIT(int32 jobId) { .jobId = jobId } METHOD start() RETURNS STATUS { Channel->current()->subscribe("jobs") // ... body runs on the spawned OS thread ... RETURN (SUCCESS) } }
Thread t := CREATE Thread("worker", CREATE Worker(42)) t->start() // outer kickoff — runtime calls Worker.start() inside
V1 ban: CREATE Thread(LAMBDA { ... }) is a parse error (Bug #43); thread bodies must be a TaskStarter class. V2 will add the LAMBDA shortcut as a strict superset (move-semantic captures only, REFERENCE captures rejected); existing V1 TaskStarter code continues to work unchanged.
kernel/src/interfaces.ev:693
Methods
METHOD start() RETURNS STATUS
GROUP Text
kernel/src/interfaces.ev:136
INTERFACE ValueIterator
ValueIterator OF T — extends Iterator OF T with by-value navigation. Implemented by the buffer iterators, whose backing storage yields elements by value, not by reference: nextValue() / peekValue() / skipValue() hand back a copy of the element. The value-form mirror of ReferenceIterator.
kernel/src/interfaces.ev:365
Methods
MODIFY METHOD nextValue() RETURNS | T
2.0 pipe-XOR shape: a successful navigation populates value
with a copy of the element at the new cursor; end-of-iteration
populates s with FAILURE.
METHOD peekValue() RETURNS | T
peekValue is non-consuming: the value at the current cursor without advancing. Same pipe-XOR shape as nextValue().
MODIFY METHOD skipValue(uint64 n) RETURNS | T
GROUP ValuePrimitive
GROUP Collections — sum type spanning every kernel collection class.
The compiler recognises this group as the canonical "is this a
collection?" check. Every member is parametric (Foo[T] or
Dictionary[K: V]) and is guaranteed by the kernel to:
- implement Cloneable with an AUTO METHOD clone() generated body
- default-construct to an empty container via
EMPTYorCREATE() - support
:=ownership transfer (per the canonical move semantics)
Drives Bug #23's AUTO clone synthesis when a class has a collection-
typed field: the body emits field->clone() without requiring the
element type to itself be Cloneable when the collection's clone()
internally handles its own elements.
Used by template qualifier expressions as the constraint for "this
type param accepts any kernel collection" (e.g. Dictionary's V).
GROUP ValuePrimitive — the POSITIVE form of PRIMITIVE(EXCEPT opaque,
boolean, number, complex). A negative list has to be remembered; a positive
one cannot be forgotten. The EXCEPT form was also silently WRONG: it still
admitted the four C.* boundary types, legal only inside a FOREIGN
declaration (I.D.x).
⚠ IT IS NOT A DROP-IN FOR THE ATOM, and the reason is structural. A comma in
a template qualifier is a UNION, not an intersection: V IS Cloneable,
PRIMITIVE(EXCEPT boolean) means "a Cloneable class OR such a primitive".
Writing V IS Cloneable, ValuePrimitive therefore ADDS an arm rather than
narrowing one, and PRIMITIVE is a reserved meta-category atom carrying
value semantics the analyzer reads — dropping it changed ownership analysis
and raised E3023 on CollisionNode. So this group replaces an EXCEPT list
only where the primitive constraint stands ALONE (GIVEN TYPE K IS
PRIMITIVE(EXCEPT …)), which is the hasher shape. Narrowing an atom BY a
group at a union site needs compiler support that does not exist yet.
kernel/src/interfaces.ev:79
INTERFACE View
View — the non-owning window itself. Minimal base contract; the element-typed operations (operator[], iterator, find via Searchable, copy) live on the concrete view (StringView). Kept deliberately minimal so a future N-D view tier is not constrained by 1-D shape. Declared before Viewable because Viewable.view() references it.
kernel/src/interfaces.ev:289
Methods
METHOD length() RETURNS int64
INTERFACE Viewable
Viewable — a source that yields a non-owning, bounded View into its
own storage without copying. view(start, length) windows over
[start, start+length). The return type is the base View; an
implementer narrows it covariantly to its concrete view (String
returns StringView). This is the 1-D / linear tier (String now;
ArrayView / byte-buffer views later) — the N-D strided views the
Data module needs are a separate, related contract.
kernel/src/interfaces.ev:300
Methods
METHOD view(int64 start, int64 length) RETURNS View
GROUP WordKey
GROUP WordKey — a primitive whose value IS an integer bit-pattern, so a
hasher body may reduce it to a word with uint64 v = key and nothing about
the value domain changes.
FastIntHasher and InlineIntHasher were qualified PRIMITIVE(EXCEPT
opaque, boolean, number, complex) while their bodies are a bare
uint64 v = key — which TRUNCATES for float32/float64, is forbidden outright
for decimal128 (E2121 — decimal never mixes), and names a FOREIGN-only type
for the C.* four. The constraint admitted types the body could not honour.
This is the set it can.
int128/uint128 ARE members, and they are the one case where the reduction is
lossy: the high 64 bits are dropped, so two keys differing only above bit 63
hash alike. That is a WEAK hash, not a wrong one — Equatable still
separates them on lookup, and taking low bits is an ordinary hash fold. A
float is excluded for the different reason that its bits are not a value:
the same number has several encodings, so uint64 v = key is not a fold but
a reinterpretation. (Brian, 2026-08-24.)
kernel/src/interfaces.ev:106
INTERFACE Writable
Writable — a byte sink. The single operation is write: hand it a
ByteBuffer and it delivers every byte or fails. Implemented by the
Stdio stdout/stderr sinks, by File, and by Socket — so a rendered
String (from a Formatter) can reach any of them through one
uniform call.
All-or-nothing: a short OS write is retried internally, so the outcome is SUCCESS — every byte written, count returned — or a FAILURE status. There is no partial write.
THE EXTENT IS THE ARRAY'S OWN LENGTH — there is deliberately no
length parameter (gh #195, removed 2026-08-12). The old shape
write(content, length) passed the caller's integer straight to
POSIX write, which reads that many bytes from the base pointer:
write(twoByteBuffer, 65536) wrote 64 KB of live heap — including a
pointer — to disk, with no diagnostic at any phase. CWE-126, and
severity 10 against I.A's memory-safe floor.
It was also redundant. read/readAt already return a char8[]
whose .length IS the valid extent, so the class conveyed extent by
array length in one direction and by a separate integer in the other.
Three of the four call sites in the whole corpus passed a value equal
to the array's length anyway.
To write PART of a buffer, take a view (ByteBufferView is
REFERENCE char8[] source + start + length) or pass a right-sized
array. A lone length could only ever express a prefix — it has no
offset — so it was never the right tool for a slice.
Spec reference: stdio-spec.md §6.
kernel/src/interfaces.ev:735
Methods
MODIFY METHOD write(binary[] content) RETURNS | int64
PRIMITIVE opaque
opaque — type-erased value primitive.
Boxes a typed value behind a runtime type-tag for heterogeneous
storage. Two static operations are called via the receiver-side
template form opaque[T]->...; two instance operations are
called on a opaque value via d->....
opaque d := opaque[Esquire]->wrap(myEsquire)
Esquire e = opaque[Esquire]->unwrap(d) // moves the value out; d now empty
boolean b = d->loaded() // FALSE only after a successful unwrap
opaque c := d->deepCopy() // non-destructive deep copy
opaque is not a parametric type. The [T] only appears at the
call site of the two statics; the type position is always bare
opaque.
Runtime: hand-written EV_opaque.hpp (the floor header carries
_Opaque plus the four thunks). This .ev file is the doc-only
surface: the compiler does not emit a .hpp from it.
Spec reference: ENVZN_CONSTITUTION.md "### opaque — type-erased value".
kernel/src/opaque.ev:32
Methods
METHOD wrap(T value) RETURNS opaque
Boxes value with a runtime tag of T. Static call form
opaque[T]->wrap(value). Captures the cloner used by a later
deepCopy() at the moment of wrap.
METHOD unwrap(opaque d) RETURNS T
Recovers a typed value from d. Static call form
opaque[T]->unwrap(d). Returns the moved-out value when T
matches d's runtime tag; returns a default-constructed T
on tag mismatch or on an already-unloaded d. Destructive on
success — d is empty afterward.
2.0 note: opaque.unwrap is the documented exception to the pipe-XOR fallible-return convention. Publisher and subscriber are expected to agree on T at the contract boundary; the default-T return on mismatch is a degenerate-case fallback, not a status-distinguishable error.
METHOD loaded() RETURNS boolean
TRUE while this opaque still holds a payload; FALSE only after
a successful unwrap consumed it.
METHOD deepCopy() RETURNS opaque
Non-destructive deep copy. Source and copy are both loaded
after; copying an unloaded opaque returns a fresh unloaded one.
Named deepCopy (not clone) because opaque is move-only and
deliberately does not satisfy the Cloneable interface.
STRUCT DateTimeFields
DateTimeFields — boundary marshalling STRUCT for the gmtime_r round-trip backing DateTime.ev (Phase 2). All fields int32 so the POD-by-value FOREIGN BIND lowering applies (V1 Part E, exercised by kernel_probe/structbind). Not part of the public DateTime surface — callers read the public fields on the DateTime instance.
kernel/src/structs.ev:89
Fields
int32 yearint32 monthint32 dayint32 hourint32 minuteint32 secondint32 millisecond
STRUCT DecimalParts
DecimalParts — the (coefficient, exponent) a decimal text scans into, before
the compiler assembles them into a decimal128. Returned by
NumericUtilities.parseDecimalParts so the String INTO decimal128 lowering can
build Decimal128(coeff, exp) — the same construction the literal path emits —
without the kernel ever constructing a value-class primitive itself.
kernel/src/structs.ev:67
Fields
int128 coefficientint32 exponent
STRUCT EnumKind
Identity — the compile-time reflection / RTTI carrier produced by the
IDENTITY(subject) keyword intrinsic. Every field is computed by the
compiler from static type + binding knowledge; the developer cannot
construct an Identity directly (the keyword is the only producer). See
docs/specifications-drafted/identity-reflection-design.md for the field
semantics and the closed class / modifier vocabularies.
EnumKind — one enum case as a (name, value) pair; the element type of
Identity.cases. Referenced by both Identity and EnumReflection, so it lives
here per the ">1 consumer -> structs.ev" rule. Declared before Identity because
Identity holds an EnumKind[].
kernel/src/structs.ev:150
Fields
String name— The case name — "HIGH".int32 value— The case value — the ordinal (Envzn enums are ordinal).
STRUCT FloatingDecimal32
kernel/src/structs.ev:57
Fields
uint32 mantissaint32 exponent
STRUCT FloatingDecimal64
FloatingDecimal64 / FloatingDecimal32 — the shortest-decimal carriers
produced by FloatFormat's d2d / f2d core methods. Each holds the
decimal mantissa as an unsigned integer plus the decimal exponent
(so the represented value is mantissa * 10^exponent). Reserved
exponent sentinels in the 0x7FFFxxxx range encode IEEE-754 special
cases (nan / ±inf / -0) — see FloatFormat.float64toDecimal.
kernel/src/structs.ev:52
Fields
uint64 mantissaint32 exponent
STRUCT FormatSpec
FormatSpec — parsed $N:<spec> directive carried into Formatter's
per-arg renderer. See stdio-spec.md §4.4 for the grammar and per-
type semantics. Default-constructed values mean "absent" — align
of 0x0 means "type default" (right for numbers, left for everything
else); typeLetter of 0x0 means "render via the type's default";
width of 0 means "no minimum width".
kernel/src/structs.ev:106
Fields
boolean present— Has anything been parsed? When FALSE,Formatter.renderArguses its default-rendering path; when TRUE, the spec fields drive the formatted output.char32 fill— Single fill char. ASCII space (0x20) by default.char32 align— Alignment marker: '<' / '>' / '^', or 0x0 for "type default".char32 sign— Sign marker: '+' (force on positives) / '-' (default — only on negatives), or 0x0 (treated as '-').uint32 width— Minimum render width in code points. 0 means "no minimum".char32 typeLetter— Type letter: 'd' / 'x' / 'X' / 'o' / 'b' / 's', or 0x0 for "no type override (use the arg's default render)".char32 groupSep— Digit-group separator: ',' or '_', or 0x0 for "no grouping" (I.F.iii(d), DESIGN_QUEUE #7 Feature 2). Group size is fixed by the TYPE, not the spec: 3 for decimal, 4 for the radix letters x/X/o/b — nibble-aligned, so a grouped hex value reads by byte pairs. The separator is counted WITHIN the field, so it sits inside zero-padding.
STRUCT Identity
Field order is load-bearing: it is the synthesized-constructor argument order the IDENTITY lowering emits.
kernel/src/structs.ev:161
Fields
String name— Bare head of the type name — "Person", "Array", "int32".String home— Declaring module; primitives report "ENVZN".String fullname— Fully-parameterized spelling — "Array[Person]" (== name for non-generics).String modifier— The binding's declared qualifier — constant / mutable / reference / mutable reference / shared mutable reference; blank for a type subject.String type— The type kind — Primitive / Enum / Group / Struct / Class / Value Class / Hidden Class / Shared Class / Abstract Class / Interface / View Class.boolean generic— True when the type has type-parameters (Array[T], Comparable[T]).boolean internal— True when the type is declared INTERNAL (module-private, §17).String currentLens— The subject's static/declared type when viewed through an ancestor lens (differs from the concrete type); blank otherwise.String[] lenses— Ancestors in order: classes -> abstract classes, then implemented interfaces.boolean blittable— True when a flat byte-copy DUPLICATES the value — both copies then live, both destroyed (C++ trivially-copyable). FALSE for anything owning a heap buffer or handle: String, ByteBuffer, every container.boolean relocatable— True when a flat byte-copy MOVES the value — the source is abandoned with NO destructor run, so only one copy is ever live (trivially-relocatable). The WEAKER and far more common property: an owning handle is notblittable(two owners would double-free) but ISrelocatable, which is why a growing container may memcpy its slots yet must clone() when copied. The one shape that is neither is a SELF-REFERENTIAL value — the value-arrayT[N+], whose backing points into its own inline buffer, so byte-moving it would dangle: DynamicString, DynamicByteBuffer.EnumKind[] cases— For an ENUM type subject: one EnumKind (name + value) per case, in declaration order — the enum's "instances". Empty for every non-enum subject. The generic name<->value logic over this table lives in EnumReflection (EnumReflection.ev).
STRUCT Pow5Entry
Pow5Entry — one 128-bit fixed-point pow5 magic constant, split into
its low and high 64-bit halves. Used by FloatFormat's small-table
Ryu path: DOUBLE_POW5_SPLIT2 and DOUBLE_POW5_INV_SPLIT2 are sequences
of these, and double_computePow5 / double_computeInvPow5 build one
on demand for arbitrary indices. Order matches Ryu's uint64_t mul[2]
convention — .lo is the low half, .hi is the high half.
kernel/src/structs.ev:78
Fields
uint64 louint64 hi
STRUCT ProcessResult
Mirrors enums.ev / interfaces.ev: a single file collecting the
small reserved STRUCTs that the constitution recognises as
part of the kernel surface but that don't merit their own per-
class file.
ProcessResult — the output of a Process->run(...) call.
- exitCode — the child process's exit status (0 on clean
exit, the value passed to
exit(N)otherwise, or -1 if the process was killed by a signal). - out — captured stdout, complete (all bytes drained before this struct is returned).
- err — captured stderr, same drain semantics as
out.
kernel/src/structs.ev:40
Fields
int32 exitCodeString outString err