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:

For class-typed T, COPY routes through Cloneable.clone(); T must implement Cloneable for any *Copy method to be reachable.

Removal:

Inspection without removal:

Iteration:

Sorting and search:

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

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 char16uint16 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 char32uint32 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 char8uint8 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, Comparable

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
}

────────────────────────────────────────────────────────────────── 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, Comparable, Searchable, Viewable

Immutable opaque binary data container.

ByteBuffer is one of four sibling classes in the Envzn text/binary type system:

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 ibuf[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)
        }
    }
}

────────────────────────────────────────────────────────────────── BACKPRESSURE ──────────────────────────────────────────────────────────────────

When the buffer is at capacity, send() consults the policy fixed at construction:

────────────────────────────────────────────────────────────────── 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>, Equatable>

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

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, Equatable

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):

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

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, Equatable, Hashable, Cloneable

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

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.

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

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, Cloneable

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:

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, Comparable, Searchable

Stack-resident mutable binary builder.

DynamicByteBuffer is one of four sibling classes in the Envzn text/binary type system:

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, Comparable, Searchable

Stack-resident mutable text builder.

DynamicString is one of four sibling classes in the Envzn text/binary type system:

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, Cloneable

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

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

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

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

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

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().

────────────────────────────────────────────────────────────────── 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>, Equatable>

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 keyvalue (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

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 level16 << 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

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

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:

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 ──────────────────────────────────────────────────────────────────

────────────────────────────────────────────────────────────────── SKIP / SKIP-BACK BOUNDS ──────────────────────────────────────────────────────────────────

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:

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:

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

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_ intrinsics — not a method): - saturatingAdd: clamps at the type's min/max - checkedAdd: returns (value, STATUS); FAILURE on overflow

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

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

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

CLASS Number

kernel/src/Number.ev:41

Fields

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

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 · IMPLEMENTS Comparable>, Equatable>

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:

For class-typed T, COPY routes through Cloneable.clone(); T must implement Cloneable for any *Copy method to be reachable.

Removal:

Inspection without removal:

Iteration:

Sorting and search:

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) ──────────────────────────────────────────────────────────────────

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

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, Equatable

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 ──────────────────────────────────────────────────────────────────

kernel/src/Path.ev:64

Fields

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

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

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

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, Cloneable

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

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:

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

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, Comparable, Viewable, Searchable

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

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

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:

  1. Extend System (CLASS Main EXTENDS System { ... }).
  2. Provide a no-arg INIT.
  3. 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(...)).

────────────────────────────────────────────────────────────────── 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

CLASS TimeDuration

IMPLEMENTS Cloneable, Hashable, Equatable, Comparable

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

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)

────────────────────────────────────────────────────────────────── 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) ──────────────────────────────────────────────────────────────────

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

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.

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.

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
}

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).

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).

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.

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).

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.

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.

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: - Irreflexivea.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: - Reflexivea.isEqualTo(a) is always TRUE. - Symmetrica.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+).

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:

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

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

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

STRUCT FloatingDecimal32

kernel/src/structs.ev:57

Fields

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

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

STRUCT Identity

Field order is load-bearing: it is the synthesized-constructor argument order the IDENTITY lowering emits.

kernel/src/structs.ev:161

Fields

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

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.

kernel/src/structs.ev:40

Fields