zena:core
import {…} from 'zena:core';
Foundational language types and utilities: strings, errors, options, results, boxes, ranges, arrays, iterators, and byte buffers.
Many declarations here are available automatically in the language prelude.
Evolving String Internals
String representation and low-level byte access methods are undergoing active redesign. See the Strings guide for current details.
Classes
String
final class String implements Hashable, Stringable, Iterable<String>
An immutable sequence of characters with value equality.
Strings are created from string literals, template literals, concatenation,
slicing, and StringBuilder.
Indexing and slicing operate on StringIndex values (str[i],
str[start..end], str[start..], str[..end], str[..]), which are
guaranteed to lie on Unicode scalar boundaries. Iteration (for (let ch in str))
yields grapheme clusters as String slices by default. Use str.codePoints
to iterate Unicode scalar values (i32), str.utf8 for a UTF-8 byte view
(u8), or str.utf16 for a UTF-16 code unit view (u16).
encoding: Encoding { get; }
The encoding of the backing bytes. Encoding is deliberately an internal detail almost everywhere; this exists for boundary code — canonical-ABI marshaling, host interop — that copies the bytes verbatim and must assert what it is copying rather than assume.
isEmpty: boolean { get; }
True when this string contains zero bytes.
length: i32 { get; }
The length of the string in bytes of its own encoding.
Deprecated.
The byte length depends on the encoding. Use isEmpty,
utf8.length, utf16.length, or graphemes.count() instead.
startIndex: StringIndex { get; }
The index of the first character in this string (always byte offset 0).
endIndex: StringIndex { get; }
The "past the end" index of this string.
graphemes: StringGraphemes { get; }
View of this string's extended grapheme clusters as String slices.
Zero heap allocation.
codePoints: StringCodePoints { get; }
View of this string's decoded Unicode scalar values as i32.
Zero heap allocation.
utf8: StringUtf8View { get; }
Contiguous UTF-8 / WTF-8 byte view (u8) of this string.
Zero-copy when this.encoding == Encoding.WTF8; transcodes to a fresh
UTF-8 ByteArray when this.encoding == Encoding.WTF16.
utf16: StringUtf16View { get; }
Contiguous UTF-16 / WTF-16 code unit view (u16) of this string.
Zero-copy when this.encoding == Encoding.WTF16; transcodes to a fresh
little-endian UTF-16 ByteArray when this.encoding == Encoding.WTF8.
static fromByteArray(data: ByteArray, start: i32, end: i32, encoding: Encoding): String
Creates a String from a ByteArray with given bounds and encoding. This is used by StringBuilder and other internal operations.
static fromBytes(bytes: FixedArray<u8>): String
A String over a copy of bytes, taken as WTF-8 — the bytes a
response body or a file arrives as. Zena strings are views over
byte arrays, so nothing is re-encoded; the copy is what turns a
fixed array into the backing store a view needs.
static fromCodePoint(code: i32): String
Creates a String containing a single Unicode code point code.
Throws IndexOutOfBoundsError if code is outside 0..0x10FFFF.
static fromCodePoints<A extends Array<i32>>(codes: A): String
Creates a String from a sequence of Unicode code points.
Throws IndexOutOfBoundsError if any code point is outside 0..0x10FFFF.
toBytes(): FixedArray<u8>
A copy of this string's UTF-8 / WTF-8 bytes. The inverse of fromBytes:
what a list<u8> position wants, a header value or a body.
static fromParts<A extends Array<String>>(parts: A): String
Creates a new string by concatenating all parts. This is more efficient than chained + operators for multiple strings, as it allocates the result array only once.
Generic over the array representation: element access resolves through the bound, so each specialization reads its receiver directly with no interface dispatch.
static fromRawParts(parts: array<String>): String
The raw-array entry the compiler's synthesized string joins call —
template-literal lowering resolves it by name against a
one-parameter raw-array signature. Its body is also the call site
that keeps the FixedArray
indexAfter(i: StringIndex): StringIndex
Returns the StringIndex immediately following the extended grapheme
cluster at i. Throws IndexOutOfBoundsError if i is at endIndex
or is not a valid boundary of this string.
indexBefore(i: StringIndex): StringIndex
Returns the StringIndex at the start of the extended grapheme cluster
immediately preceding i. Throws IndexOutOfBoundsError if i is at
startIndex or is not a valid boundary of this string.
scalarIndexAfter(i: StringIndex): StringIndex
Returns the StringIndex immediately following the Unicode scalar value
at i. Throws IndexOutOfBoundsError if i is at endIndex or is not
a valid boundary of this string.
scalarIndexBefore(i: StringIndex): StringIndex
Returns the StringIndex of the Unicode scalar value immediately
preceding i. Throws IndexOutOfBoundsError if i is at startIndex
or is not a valid boundary of this string.
indexOffsetBy(from: StringIndex, count: i32): inline (true, StringIndex) | inline (false, _)
Advances from by count grapheme clusters (count >= 0 moves forward,
count < 0 moves backward). Returns (true, index) if within bounds, or
(false, _) if stepping past startIndex or endIndex.
slice(start: StringIndex, end: StringIndex): String
O(1) zero-copy slice from start to end (exclusive).
Throws IndexOutOfBoundsError if start or end is not a valid boundary
of this string or if start > end.
slice(start: StringIndex): String
O(1) zero-copy slice from start to endIndex.
reverse(): String
Returns a new String with its extended grapheme clusters in reverse
order. Preserves combining mark sequences and multi-code-point emojis.
getByteAt(index: i32): i32
Returns the byte at index of the string's own encoding.
Deprecated.
Which bytes a string has depends on its encoding. Use
utf8[index] or utf16[index] instead.
codePointAt(index: StringIndex): i32
Returns the Unicode scalar value at index.
Throws IndexOutOfBoundsError if index is at endIndex or is not a
valid boundary of this string.
codePointAt(index: i32): i32
Returns the Unicode code point at byte offset index.
For multi-byte UTF-8 sequences, decodes the full code point.
Returns -1 if index is out of bounds or points to an invalid sequence.
Deprecated.
Use codePointAt(StringIndex) or codePoints instead.
getI32At(index: i32): i32
Returns 4 bytes starting at index packed as a little-endian 32-bit integer.
sliceBytes(start: i32, end: i32): String
O(1) zero-copy slice by byte offsets of the string's own encoding, clamped to the string. Returns a new String sharing the backing array.
SAFETY: Offsets are byte indices, not Unicode boundaries. Slicing inside a multi-byte UTF-8 sequence produces an ill-formed string.
Deprecated.
Use str[start..end] with indices from utf8.indexAt or
utf16.indexAt, or slice(start, end).
copy(): String
Force a copy - creates a new String with its own backing array. Use this when you need to release the parent string's memory.
copyBytesTo(target: ByteArray, targetOffset: i32, start: i32 = 0, length: i32 = -1): void
Copies bytes from this string to a target ByteArray.
regionEqualsBytes(other: String, start: i32, end: i32): boolean
True if this string's bytes are exactly the bytes of other in
[start, end).
For matching against a range of a larger string without slicing it out
first — the inner loop of a string table, which asks "is the text I just
scanned the string already in this slot?" once per probe. Going through
sliceBytes and == instead would allocate a view per probe and re-do
the bounds and hash checks that the caller has already settled.
A range falling outside other does not match, rather than reading out
of bounds — unlike sliceBytes, which clamps. Clamping would be wrong
here: it would silently compare against a shorter range and could report
a match the caller never asked about.
SAFETY: as with sliceBytes, the offsets are bytes and are not required
to fall on code point boundaries — this compares encoded bytes, so a
range that splits a multi-byte sequence simply will not match any whole
string.
startsWith(prefix: String): boolean
Returns true if this string starts with the given prefix.
endsWith(suffix: String): boolean
Returns true if this string ends with the given suffix.
split(separator: String): FixedArray<String>
Splits the string into an array of substrings separated by the given separator.
indexOf(needle: String): inline (true, StringIndex) | inline (false, _)
Finds the first occurrence of needle in this string.
Returns (true, index) if found, or (false, _) if not found.
indexOf(needle: String, from: StringIndex): inline (true, StringIndex) | inline (false, _)
Finds the first occurrence of needle at or after from.
Throws IndexOutOfBoundsError if from is not a valid boundary of this
string. Returns (true, index) if found, or (false, _) if not found.
contains(needle: String): boolean
Returns true if needle occurs anywhere in this string.
Overrides IterableUtils<String>.contains to perform a substring search.
getBackingArrayIfFull(): ByteArray | null
Returns the backing ByteArray if the String spans the entire array (i.e. not a slice). Used for optimization by StringBuilder.
compareTo(other: String): i32
Compares this string with other in lexicographical code point order.
Returns:
< 0ifthis < other0ifthis == other> 0ifthis > other
In UTF-8, lexicographical byte comparison matches Unicode code point order.
asciiLowerCase(): String
Returns this string with every ASCII letter lowercased; every other byte
is left alone. See asciiLowerByte for why this is deliberately not
Unicode-aware.
Byte-wise, which is exactly right for WTF-8: an ASCII byte is always a
whole code point, and no continuation byte falls in 0x41..0x5A, so no
multi-byte sequence can be corrupted. A WTF-16 string would instead need
code-unit stepping — and that branch belongs here, on #encoding, rather
than in every caller, which is the reason this lives on String at all. No
String method supports WTF-16 yet and nothing constructs one today.
Returns this when there is nothing to change, so the common
already-lowercase case does not allocate.
asciiUpperCase(): String
Returns this string with every ASCII letter uppercased.
toLowerCase(): String
Returns this string lowercased by Unicode's default case mapping, as
JavaScript's toLowerCase does: 'ÀB' becomes 'àb', 'İ' becomes
'i̇' (two code points), and a Σ that ends a word becomes ς.
Language-specific mappings, such as the Turkish dotless ı, are not
applied. Returns this when nothing changes. For ASCII letters only,
use asciiLowerCase.
Whether a Σ ends a word is decided more cheaply than JavaScript
decides it, so the two can differ beside an uncommon character
(#isFinalSigma).
toUpperCase(): String
Returns this string uppercased by Unicode's default case mapping, as
JavaScript's toUpperCase does: 'àb' becomes 'ÀB' and 'ß' becomes
'SS'. Returns this when nothing changes. For ASCII letters only, use
asciiUpperCase.
trim(): String
Returns this string without the ASCII whitespace at either end: spaces, tabs, line feeds, form feeds and carriage returns. Other whitespace, like a no-break space, is kept.
' Content-Length: 12\r\n'.trim(); // 'Content-Length: 12'
filter(predicate: (item: String) => boolean): Iterable<String>
all(predicate: (item: String) => boolean): boolean
some(predicate: (item: String) => boolean): boolean
fold<R>(initial: R, combine: (acc: R, item: String) => R): R
find(predicate: (item: String) => boolean): inline (true, String) | inline (false, _)
hashCode(): i32
operator [](index: StringIndex): String
Returns the single extended grapheme cluster starting at index as a
zero-copy String slice. Throws IndexOutOfBoundsError if index is at
endIndex or is not a valid boundary of this string.
operator [](r: Range<StringIndex>): String
Returns the substring covered by r (start..end, start.., or ..end)
as a zero-copy String slice. Throws IndexOutOfBoundsError if the
bounds are invalid or out of order.
operator [](_r: FullRange): String
Returns the full string slice for str[..].
operator +(other: String): String
Concatenates two strings and returns a new string.
operator ==(other: String): boolean
Equality operator - compares the bytes in each view.
StringGraphemes
final extension class StringGraphemes implements Iterable<String>
Zero-allocation view of a String's extended grapheme clusters.
filter(predicate: (item: String) => boolean): Iterable<String>
contains(value: String): boolean
all(predicate: (item: String) => boolean): boolean
some(predicate: (item: String) => boolean): boolean
fold<R>(initial: R, combine: (acc: R, item: String) => R): R
find(predicate: (item: String) => boolean): inline (true, String) | inline (false, _)
count(): i32
Counts the number of extended grapheme clusters in this string in $O(n)$ time.
toArray(): FixedArray<String>
Collects all extended grapheme clusters into a FixedArray<String>.
StringCodePoints
final extension class StringCodePoints implements Iterable<i32>
Zero-allocation view of a String's decoded Unicode scalar values (i32).
filter(predicate: (item: i32) => boolean): Iterable<i32>
contains(value: i32): boolean
all(predicate: (item: i32) => boolean): boolean
some(predicate: (item: i32) => boolean): boolean
fold<R>(initial: R, combine: (acc: R, item: i32) => R): R
find(predicate: (item: i32) => boolean): inline (true, i32) | inline (false, _)
count(): i32
Counts the number of Unicode scalar values in this string in $O(n)$ time.
toArray(): FixedArray<i32>
Collects all decoded Unicode scalar values into a FixedArray<i32>.
StringUtf8View
final class StringUtf8View implements Iterable<u8>
Contiguous view of a String's UTF-8 / WTF-8 encoded bytes (u8).
When created from a WTF8 string, this view shares the string's backing
ByteArray without copying. When created from a WTF16 string, the
string's contents are transcoded to a fresh UTF-8 ByteArray.
new(data: ByteArray, start: i32, end: i32, origin: i32, native: boolean)
indexAt(offset: i32): inline (true, StringIndex) | inline (false, _)
The string's index for the UTF-8 offset offset into this view:
(true, index) when the offset is within [0, length] and on a
Unicode scalar boundary, else (false, _). O(1) for a WTF-8 string,
whose storage this view shares; for a WTF-16 string it counts the
code units of the scalars before the offset.
slice(start: i32, end: i32): StringUtf8View
Returns a zero-copy sub-view of [start, end), clamped to [0, length].
filter(predicate: (item: u8) => boolean): Iterable<u8>
contains(value: u8): boolean
all(predicate: (item: u8) => boolean): boolean
some(predicate: (item: u8) => boolean): boolean
fold<R>(initial: R, combine: (acc: R, item: u8) => R): R
find(predicate: (item: u8) => boolean): inline (true, u8) | inline (false, _)
copyTo(target: ByteArray, targetOffset: i32 = 0, start: i32 = 0, length: i32 = -1): void
Copies UTF-8 bytes from this view into target starting at targetOffset.
toArray(): FixedArray<u8>
Returns a copy of this view's UTF-8 bytes as a FixedArray<u8>.
operator [](index: i32): u8
Returns the UTF-8 byte at index as a u8. Throws
IndexOutOfBoundsError if index is out of bounds.
StringUtf16View
final class StringUtf16View implements Iterable<u16>
Contiguous view of a String's UTF-16 / WTF-16 code units (u16).
When created from a WTF16 string, this view shares the string's backing
ByteArray without copying. When created from a WTF8 string, the
string's contents are transcoded to a fresh little-endian UTF-16 ByteArray.
new(data: ByteArray, start: i32, end: i32, origin: i32, native: boolean)
indexAt(offset: i32): inline (true, StringIndex) | inline (false, _)
The string's index for the code unit offset offset into this view:
(true, index) when the offset is within [0, length] and does not
split a surrogate pair, else (false, _). O(1) for a WTF-16 string,
whose storage this view shares; for a WTF-8 string it counts the
bytes of the scalars before the offset.
slice(start: i32, end: i32): StringUtf16View
Returns a zero-copy sub-view of code units [start, end), clamped to
[0, length].
filter(predicate: (item: u16) => boolean): Iterable<u16>
contains(value: u16): boolean
all(predicate: (item: u16) => boolean): boolean
some(predicate: (item: u16) => boolean): boolean
fold<R>(initial: R, combine: (acc: R, item: u16) => R): R
find(predicate: (item: u16) => boolean): inline (true, u16) | inline (false, _)
toArray(): FixedArray<u16>
Returns a copy of this view's UTF-16 code units as a FixedArray<u16>.
operator [](index: i32): u16
Returns the 16-bit code unit at index as a u16. Throws
IndexOutOfBoundsError if index is out of bounds.
Error
class Error
LookupError
class LookupError extends Error
Base class for errors when a lookup operation fails.
new(message: String)
IndexOutOfBoundsError
class IndexOutOfBoundsError extends LookupError
Thrown when an array/sequence index is out of bounds.
KeyNotFoundError
class KeyNotFoundError extends LookupError
Thrown when a key is not found in a map or similar collection.
new()
AsyncInSyncIteration
class AsyncInSyncIteration extends Error
Thrown by requireSync/requireSyncIterator (zena:async) when the
asynchronous iterator they wrap reports a Pending step — a value that
is not available synchronously. Consume such an iterator with
for await instead (docs/design/async-iteration.md).
new()
ContractError
class ContractError extends Error
Thrown when a contract clause does not hold: a requires or ensures
clause of a function, or an invariant clause of a loop. The message
is the clause as it was written.
new(message: String)
Some
class Some<T>
Represents a present value.
T is scoped, so a Some may hold an owner: a Some<Own<R>> moves
like the owner, a pattern that takes it apart moves the owner out
(if (let Some {value} = o)), and one nobody takes apart releases it
where it is dropped.
None
class None
Represents an absent value.
Box
Range
sealed class Range<T = i32>
Any range: a..b, a.., ..b or ...
Each form is its own class, so a range literal has the type of its
form, and a parameter typed Range accepts all four.
abstract bounds(length: T): inline (T, T)
The start and end (exclusive) of this range in a sequence of
length elements: a range with no start starts at 0, and one with
no end ends at length. The bounds are not clamped to the sequence.
abstract contains(value: T): boolean
Returns true if value falls within this range.
BoundedRange
final class BoundedRange<T = i32> extends Range<T> implements Iterable<T>
A bounded range with both start and end (exclusive). Represents [start, end) - includes start, excludes end. A range whose end is at or before its start is empty.
new(start: T, end: T)
bounds(_length: T): inline (T, T)
contains(value: T): boolean
Returns true if value is in [start, end).
filter(predicate: (item: T) => boolean): Iterable<T>
all(predicate: (item: T) => boolean): boolean
some(predicate: (item: T) => boolean): boolean
fold<R>(initial: R, combine: (acc: R, item: T) => R): R
find(predicate: (item: T) => boolean): inline (true, T) | inline (false, _)
FromRange
final class FromRange<T = i32> extends Range<T> implements Iterable<T>
A range from a start point to infinity/length. Represents [start, ∞) or [start, length).
Iterating a FromRange counts up from start and stops after the
largest i32, 2147483647, instead of wrapping around to negative
numbers.
new(start: T)
start: T
bounds(length: T): inline (T, T)
contains(value: T): boolean
Returns true if value is at least start.
filter(predicate: (item: T) => boolean): Iterable<T>
all(predicate: (item: T) => boolean): boolean
some(predicate: (item: T) => boolean): boolean
fold<R>(initial: R, combine: (acc: R, item: T) => R): R
find(predicate: (item: T) => boolean): inline (true, T) | inline (false, _)
ToRange
final class ToRange<T = i32> extends Range<T>
A range from zero to an end point (exclusive). Represents [0, end) for indexing or (-∞, end) for membership.
FullRange
final class FullRange<T = i32> extends Range<T>
A full range representing all elements. Represents [0, length) for indexing or (-∞, ∞) for membership.
Resource
abstract resource class Resource implements OwnState
The classes declared with the resource modifier — those holding
something the garbage collector cannot reclaim, and therefore carrying a
release obligation.
resource class R satisfies this without an implements clause, the way
a case class satisfies Hashable. It exists to be bounded against:
disown and adopt take a T extends Resource rather than any T,
because moving a value between the affine and manually-managed regimes is
only meaningful for something that has a disposal obligation to move.
There is deliberately no way to hold one. Nothing is assignable to
Resource, because the only reference to a resource a program can name is
a handle — Own<R>, Borrow<R>, Unmanaged<R> — and those are nominally
distinct types that assign to no supertype at all. That is what keeps a
widening conversion from dropping the permissions the handle carries, and
it is a property of the handles rather than of this interface: the same
applies to every supertype, Resource or otherwise.
Resource-ness is what this names; affineness is what Own<T> declares.
They are separate properties — see ownership.md §"Resource-ness and
affineness are different properties" — so this bound restricts neither
Own<T> itself nor the ordinary classes it will eventually wrap.
The root of every resource hierarchy. A resource class with no
explicit superclass extends this one, so the lifecycle flag has an
ordinary in-language home rather than a field codegen injects, and
disown/adopt read it with an ordinary method call rather than an
intrinsic. Being the root is also what makes ownership.md
§"Resource-ness is inherited" cheap to satisfy: single inheritance means
a resource cannot also extend an ordinary class, so the rule that its
chain is all resources is structural rather than a check.
Declaring no method that returns this is load-bearing, not incidental.
That is the whole reason an arbitrary superclass is unsafe for a resource
and this one is not.
ResourceStateError
class ResourceStateError extends Error
Raised when a regime change is handed a resource in the wrong state: adopting one that was never disowned, or disowning one twice.
Both are programming errors rather than expected conditions — the
resource was already claimed by someone else, or already released — so
they throw rather than returning a Result. A tryAdopt handing back
the branch can be added if a caller ever wants it.
new(operation: String, actual: String, expected: String)
TemplateStringsArray
final class TemplateStringsArray
Represents the string literals in a tagged template expression.
When you write tag\Hello ${name}!`` the tag function receives:
- strings: TemplateStringsArray containing ["Hello ", "!"]
- values: array containing [name]
Key properties:
- Immutable: The strings array cannot be modified.
- Cached: The same TemplateStringsArray instance is reused across invocations of the same tagged template expression.
- Interleaved: Strings and values alternate, with strings on both ends.
For
\a${x}b${y}c`: strings = ["a", "b", "c"], values = [x, y]. This means there is alwaysstrings.length == values.length + 1`.
The raw property provides access to the raw (unescaped) string literals,
where escape sequences like \\n appear as two characters (\ and n)
rather than being interpreted as a newline.
Example:
let tag = (strings: TemplateStringsArray, values: FixedArray<anyref>): String => {
// strings[0] = "Line 1\nLine 2" (newline interpreted)
// strings.raw[0] = "Line 1\\nLine 2" (backslash-n literal)
return strings[0];
};
tag\`Line 1\\nLine 2\`;
FilterIterator
final class FilterIterator<T> implements Iterator<T>
An Iterator that yields elements matching a predicate.
FilteredIterable
final class FilteredIterable<T> implements Iterable<T>
An Iterable that lazily filters elements using a predicate.
new(source: Iterable<T>, predicate: (item: T) => boolean)
filter(predicate: (item: T) => boolean): Iterable<T>
contains(value: T): boolean
all(predicate: (item: T) => boolean): boolean
some(predicate: (item: T) => boolean): boolean
fold<R>(initial: R, combine: (acc: R, item: T) => R): R
find(predicate: (item: T) => boolean): inline (true, T) | inline (false, _)
ArrayIterator
final class ArrayIterator<T> implements Iterator<T>
Iterator over a FixedArray
GrowableArrayIterator
final class GrowableArrayIterator<T> implements Iterator<T>
FixedArray
final extension class FixedArray<T> with IterableUtils<T> implements MutableArray<T>, Iterable<T>
new(length: i32, value: T)
length: i32
static from<A>(seq: Array<A>): FixedArray<A>
map<U>(f: (item: T, index: i32, seq: FixedArray<T>) => U): FixedArray<U>
fold<R>(initial: R, combine: (acc: R, item: T) => R): R
reverse(): FixedArray<T>
slice(start: i32, end: i32): FixedArray<T>
Returns a shallow copy of a portion of the array. The start index is inclusive, the end index is exclusive. Negative indices are not supported.
contentEquals(other: Array<T>): boolean
Whether other has the same length and == elements in the same
order, whatever array class it is. == on two arrays compares
identity; this compares contents, as two byte strings are compared.
7 inherited members
Array
Iterable
contains(value: T): boolean
Returns true if the collection contains the specified value.
all(predicate: (item: T) => boolean): boolean
Returns true if all elements match the given predicate. Returns true if the collection is empty.
some(predicate: (item: T) => boolean): boolean
Returns true if at least one element matches the given predicate. Returns false if the collection is empty.
find(predicate: (item: T) => boolean): inline (true, T) | inline (false, _)
Finds the first element that matches the predicate. Returns an inline tuple (true, item) if found, or (false, _) if not.
filter(predicate: (item: T) => boolean): Iterable<T>
Returns a new iterable containing only the elements that match the predicate.
GrowableArray
final class GrowableArray<T> implements MutableArray<T>, Iterable<T>
new(capacity: i32 = 8, adopting: FixedArray<T> | null = null)
adopting, when given, becomes the backing storage, full: length
starts at its length, no copy is made, and capacity is ignored.
The caller must hand the buffer over — writes through either alias
would be visible through the other. from copies; this adopts.
(A constructor parameter rather than a static so it works from
generic bodies — see the issue linked from growable below.)
length: i32 { get; }
static from<A>(seq: Array<A>): GrowableArray<A>
push(value: T): void
pop(): T
clear(): void
Empties the array in place, keeping its buffer for reuse. The cleared slots are not overwritten, so references they held stay reachable until a later push replaces them.
slice(start: i32, end: i32): GrowableArray<T>
A copy of the elements from start up to but not including end.
Bounds outside the array are clamped to it.
map<U>(f: (item: T, index: i32, seq: GrowableArray<T>) => U): GrowableArray<U>
contains(value: T): boolean
all(predicate: (item: T) => boolean): boolean
some(predicate: (item: T) => boolean): boolean
fold<R>(initial: R, combine: (acc: R, item: T) => R): R
find(predicate: (item: T) => boolean): inline (true, T) | inline (false, _)
filter(predicate: (item: T) => boolean): Iterable<T>
contentEquals(other: Array<T>): boolean
Whether other has the same length and == elements in the same
order, whatever array class it is. == on two arrays compares
identity; this compares contents.
ImmutableArray
final extension class ImmutableArray<T> with IterableUtils<T> implements Array<T>
length: i32
slice(start: i32, end: i32): FixedArray<T>
A copy of the elements from start up to but not including end.
Bounds outside the array are clamped to it.
map<U>(f: (item: T, index: i32, seq: ImmutableArray<T>) => U): FixedArray<U>
contentEquals(other: Array<T>): boolean
Whether other has the same length and == elements in the same
order, whatever array class it is. == on two arrays compares
identity; this compares contents.
9 inherited members
Array
new(capacity: i32 = 8): GrowableArray<T>
An empty growable array, with room for capacity items before it grows.
new growable(capacity: i32 = 8): GrowableArray<T>
The same as new Array(), named for symmetry with fixed.
new fixed(length: i32, value: T): FixedArray<T>
A fixed-length array of length copies of value.
Iterable
contains(value: T): boolean
Returns true if the collection contains the specified value.
all(predicate: (item: T) => boolean): boolean
Returns true if all elements match the given predicate. Returns true if the collection is empty.
some(predicate: (item: T) => boolean): boolean
Returns true if at least one element matches the given predicate. Returns false if the collection is empty.
fold<R>(initial: R, combine: (acc: R, item: T) => R): R
Reduces the collection to a single value by accumulating state.
find(predicate: (item: T) => boolean): inline (true, T) | inline (false, _)
Finds the first element that matches the predicate. Returns an inline tuple (true, item) if found, or (false, _) if not.
filter(predicate: (item: T) => boolean): Iterable<T>
Returns a new iterable containing only the elements that match the predicate.
ByteBuffer
final class ByteBuffer
A growable buffer for efficiently constructing binary data.
new(capacity: i32 = 256)
Creates a new ByteBuffer with the specified initial chunk capacity.
writeByte(b: i32): ByteBuffer
Write a single byte.
writeBytes(src: ByteArray): ByteBuffer
Write all bytes from a ByteArray.
writeBytesSlice(src: ByteArray, offset: i32, len: i32): ByteBuffer
Write a slice of bytes from a ByteArray.
writeBufferRange(other: ByteBuffer, start: i32, end: i32): ByteBuffer
Write the byte range [start, end) of another ByteBuffer's contents.
writeBuffer(other: ByteBuffer): ByteBuffer
Write another ByteBuffer's contents.
writeU16(value: i32): ByteBuffer
Write a 16-bit unsigned integer (little-endian).
writeU32(value: i32): ByteBuffer
Write a 32-bit unsigned integer (little-endian).
writeU64(value: i64): ByteBuffer
Write a 64-bit integer (little-endian).
writeF32(value: f32): ByteBuffer
Write a 32-bit float (little-endian).
writeF64(value: f64): ByteBuffer
Write a 64-bit float (little-endian).
writeULEB128(value: i32): ByteBuffer
Write an unsigned LEB128 encoded integer.
writeSLEB128(value: i32): ByteBuffer
Write a signed LEB128 encoded integer.
writeULEB128_64(value: i64): ByteBuffer
Write a 64-bit unsigned LEB128.
writeSLEB128_64(value: i64): ByteBuffer
Write a 64-bit signed LEB128.
getByte(index: i32): i32
Read a byte at an absolute position.
setByte(index: i32, value: i32): void
Write a byte at an absolute position (for patching).
patchU32(index: i32, value: i32): void
Patch a U32 at an absolute position (little-endian).
toByteArray(): ByteArray
Convert to a single contiguous ByteArray.
clear(): void
Clear the buffer, reusing the first chunk.
DataView
final class DataView
Typed reads from a range of a ByteArray. Offsets are relative to the
start of the range, and a read that would leave it throws
IndexOutOfBoundsError. The array is shared, not copied.
new(bytes: ByteArray, start: i32 = 0, length: i32 = -1)
A view of bytes[start..start + length]; with no length, to the end
of the array.
getU8(offset: i32): i32
getU16(offset: i32): i32
getU32(offset: i32): i32
getU64(offset: i32): i64
getF32(offset: i32): f32
getF64(offset: i32): f64
getULEB128(offset: i32): inline (i32, i32)
The unsigned LEB128 value at offset, and the offset after it.
getULEB128_64(offset: i32): inline (i64, i32)
The 64-bit unsigned LEB128 value at offset, and the offset after it.
getSLEB128(offset: i32): inline (i32, i32)
The signed LEB128 value at offset, and the offset after it.
getSLEB128_64(offset: i32): inline (i64, i32)
The 64-bit signed LEB128 value at offset, and the offset after it.
StringBuilder
final class StringBuilder
An append-only sequence of characters for efficient string construction.
new(capacity: i32 = 16)
Creates a new StringBuilder with the specified initial capacity.
append(s: String): StringBuilder
Appends a string to the builder.
appendSlice(s: String, start: i32, end: i32): StringBuilder
Appends a byte slice [start, end) of s directly without allocating an
intermediate String view.
appendByte(b: i32): StringBuilder
Appends a single byte.
appendByte2(b0: i32, b1: i32): StringBuilder
Appends two bytes (such as a two-byte escape sequence).
appendI32(n: i32): StringBuilder
Appends a signed 32-bit integer as decimal. Uses shared conversion from zena:string-convert.
appendU32(n: u32): StringBuilder
Appends an unsigned 32-bit integer as decimal. Uses shared conversion from zena:string-convert.
appendI64(n: i64): StringBuilder
Appends a signed 64-bit integer as decimal. Uses shared conversion from zena:string-convert.
appendU64(n: u64): StringBuilder
Appends an unsigned 64-bit integer as decimal. Uses shared conversion from zena:string-convert.
appendF32(n: f32): StringBuilder
Appends a 32-bit float as decimal. Uses shared conversion from zena:string-convert.
appendF64(n: f64): StringBuilder
Appends a 64-bit float as decimal. Uses shared conversion from zena:string-convert.
toString(): String
Returns the string content.
clear(): void
Clears the builder.
static fromString(s: String): StringBuilder
Creates a new StringBuilder initialized with the content of the given String. If the string uses its entire backing ByteArray, the builder will adopt the array directly (zero-copy).
IterableJoin
extension class IterableJoin<T>
join(stringify: (item: T) => String, separator: String = ', '): String
StringReader
final class StringReader
StringReader - A cursor-based string parser that tracks byte positions.
Use StringReader for parsing to ensure slices are always at valid UTF-8 code point boundaries. Positions from mark() are safe to use with sliceBytes().
Example usage:
let r = new StringReader(input);
r.skipWhitespace();
let start = r.mark();
while (!r.isAtEnd && r.peekByte() != 34) { // 34 = '"'
r.advance(); // Move by code point (safe for Unicode)
}
let token = r.sliceFrom(start); // Safe: positions from mark()
A reader may be given a StringTable, in which case internFrom and
internRange return canonical instances for the text they cover — see
StringTable, exported from this module. Several readers can share one
table, so a parser run over many files reuses the identifiers it has
already seen. Passing no table is not a special case to handle: the intern
methods then behave exactly like sliceFrom/sliceRange, because strings
have value semantics either way.
new(source: String, table: StringTable | null = null)
table: StringTable | null { get; }
The string table this reader interns into, if any.
position: i32 { get; }
Current byte position in the string.
isAtEnd: boolean { get; }
True if at end of string.
source: String { get; }
The source string being read.
remaining: i32 { get; }
Remaining length in bytes.
peekByte(): i32
Peek at the current byte without consuming it. Returns -1 if at end of string. Use for ASCII characters (< 128) like delimiters: { } [ ] , : " etc.
peekByteAt(offset: i32): i32
Peek at a byte at offset from current position. Returns -1 if out of bounds.
advanceByte(): i32
Consume and return the current byte. Returns -1 if at end. WARNING: Only use for ASCII (< 128). For Unicode text, use advance().
peek(): i32
Peek at the current Unicode code point without consuming it. Returns -1 if at end of string. Decodes UTF-8 sequences properly.
TODO: Use shift operators (<< 6, << 12, << 18) when available in Zena. Currently using multiplication as a workaround.
advance(): i32
Consume and return the current Unicode code point. Returns -1 if at end. Advances by the correct number of bytes (1-4 for UTF-8).
TODO: Use shift operators (<< 6, << 12, << 18) when available in Zena. Currently using multiplication as a workaround.
skip(count: i32): void
Skip N code points (not bytes). Properly handles multi-byte UTF-8 sequences.
mark(): i32
Mark the current position for later slicing. Returns an opaque position value safe to use with sliceFrom/sliceRange.
reset(pos: i32): void
Reset to a previously marked position.
sliceFrom(start: i32): String
Extract a slice from a marked position to current position. This is SAFE because both positions are at code point boundaries.
sliceRange(start: i32, end: i32): String
Extract a slice between two marked positions. This is SAFE because both positions are at code point boundaries.
internFrom(start: i32): String
Like sliceFrom, but returns the canonical instance from this reader's
string table — or the plain slice if the reader has no table.
Prefer this over sliceFrom for text a parser will compare or use as a
map key later: identifiers, member names, object keys. It is worth
nothing for text used once, such as a numeric literal, and worse than
nothing for text that is nearly always distinct, such as string literal
contents, which would fill the table with single-use entries.
internRange(start: i32, end: i32): String
Like sliceRange, but interned. See internFrom.
matchByte(expected: i32): boolean
Check if current byte matches, and advance if it does. Returns true if matched.
skipWhitespace(): void
Skip ASCII whitespace (space, tab, newline, carriage return).
skipBytes(n: i32): void
Skip N bytes (not code points). Use with caution - only when you know the bytes are at valid boundaries.
skipBytesWhile(predicate: (byte: i32) => boolean): void
Skip bytes while predicate returns true. Predicate receives byte value (not code point). Useful for ASCII character classes: digits, letters, etc.
readUntil(needle: String): String | null
Reads a substring up to the next occurrence of needle.
If needle is found, returns the substring before it and advances the cursor past needle.
If needle is not found, returns null and does not advance the cursor.
readToEnd(): String
Reads the remaining substring from current position to the end of the string, advancing the cursor to the end.
StringTable
final class StringTable
A set of unique strings: interning one returns the single canonical instance for its bytes, so equal strings become the same object.
Strings have value semantics whether or not they are interned, so interning never changes what a program computes. What it changes is cost, and the costs it removes are mostly downstream of the parser rather than in it:
Stringcaches its FNV hash in a field on first use. An interned identifier is therefore hashed once for the life of the table, not once perHashMap<String, V>probe — and a compiler probes scope and symbol tables with the same identifier over and over.operator ==returns early on reference equality, so comparing two interned strings is a pointer compare instead of a byte loop.- One live
Stringper distinct identifier instead of one per token.
Interning is deliberately not perfect: intern is the only way in, and
nothing stops an equal string from being produced by other means. Code may
treat === as a fast path for equality, never as the definition of it.
Sharing a table ​
A table is an ordinary object, so several StringReaders — several whole
parses — can share one and get cross-file reuse: the second file's let
costs a lookup and no allocation. That is also why intern copies (see
below): a table outliving the text it was filled from must not pin it.
new(capacity: i32 = 512, maxLength: i32 = 128)
maxLength: i32 { get; }
The longest string, in bytes, this table will intern.
Interning is a bet that a string will be seen again, and it is paid for up front with a copy that then lives as long as the table. The bet is a good one for identifiers and keys, which are short and repeat constantly. It is a bad one for the long strings in the same input — a JSON document's text values, a Zena string literal, a WIT doc comment — which are mostly distinct, so each one interned is a permanent copy of bytes that will never be looked up again, and a byte compare on every collision with it.
So the table simply declines. Over-long strings are returned equal but
uninterned, which callers must already tolerate: interning is best-effort
and === is only ever a fast path for equality, never the definition of
it. prime declines too, so no key in the table is longer than this and
intern can never miss one that is.
size: i32 { get; }
The number of distinct strings held.
intern(s: String): String
Returns the canonical instance equal to s, adding a copy of it if this
is the first sighting.
The copy is what makes a long-lived table safe. Zena strings are views into a shared backing array, so interning a three-byte identifier sliced out of a source file would otherwise keep that whole file alive for as long as the table — and a table shared across a compilation would accumulate every file it ever saw. The copy costs one allocation per distinct string, never per occurrence, and leaves the canonical instance on tight storage that its own byte loops then walk.
Use prime for strings that already own their storage permanently.
internRange(source: String, start: i32, end: i32): String
Returns the canonical instance for source's bytes in [start, end),
without slicing that range out first.
This is the form a scanner wants, and the reason it exists is that intern
turns out to be dominated by neither hashing nor probing. Its cost is the
per-probe candidate == s: a virtual call to String.operator == that
re-derives the length and consults the hash cache before reaching the byte
compare, plus the throwaway view allocated to have something to pass it.
Matching against the source range in place removes all of that from the
hit path, which is every occurrence after the first.
prime(s: String): String
Interns s without copying it, making s itself canonical if it is new.
For strings that already own their storage for the life of the program —
literals, above all — where intern's defensive copy would allocate a
second permanent copy of bytes that were never going to be freed.
Priming a table with a language's keywords before parsing is the
motivating case, and it buys more than the avoided copies: a keyword
lexed out of source then interns to the very object the keyword table is
keyed by, so the keyword lookup that follows hits =='s reference-equal
fast path against an already-hashed key.
Declines over-long strings exactly as intern does; see maxLength.
primeAll(words: Array<String>): void
Primes every string in words. See prime.
has(s: String): boolean
True if a string equal to s has been interned.
Interfaces
Equatable
interface Equatable
A type that declares its own equality: an operator == taking another value
of the same type.
Every class has an equality, so == does not need this bound to compile.
What it says is that the class wrote one down, rather than relying on
reference equality. Every hash key is one, since Hashable extends it.
Primitives, enums, and case classes that derive their equality satisfy it
without an implements clause.
The parameter is typed this, which makes == a binary method: Equatable
works as a bound on a type parameter (<T extends Equatable>), where both
operands are the same T. Two values that are each some Equatable may be
different classes, and cannot be compared through it.
operator ==(other: this): boolean
Hashable
interface Hashable extends Equatable
A type that can be used as a key in hash-based collections like HashMap and HashSet.
A hash key declares its equality, so Hashable extends Equatable, and the
two must agree: if two values are equal (per operator ==), they must
return the same hashCode. The reverse is not required — unequal values may
share a hash code, though fewer collisions means better performance.
A class whose equality is identity says so, and returns an identity hash: a value that is unique per instance and stable for its lifetime, such as a counter assigned in the constructor.
class Session implements Hashable {
#id: i32 = nextSessionId();
operator ==(other: Session): boolean { return this === other; }
hashCode(): i32 { return this.#id; }
}
Case classes automatically satisfy Hashable: the compiler derives a
structural hashCode() and operator == from their parameters, unless the
class inherits either one from its base, whose pair is then the hierarchy's
equality. Primitives (i32, boolean, enums, and distinct types over them)
also satisfy the Hashable constraint; they hash to their own value. Strings
hash with FNV-1a, cached after the first computation.
hashCode(): i32
Stringable
interface Stringable
A type that provides a custom string representation for template literals
and toString().
Stringable is not required to pass a value to toString() or interpolate
it into a template literal — non-Stringable classes format as <ClassName>,
records as <record>, and tuples as <tuple>. Implementing Stringable
opts a class into custom formatting via [Stringable.toString]().
class Point implements Stringable {
x: i32;
y: i32;
new(this.x, this.y);
[Stringable.toString](): String {
return `(${this.x}, ${this.y})`;
}
}
Disposable
interface Disposable
A value holding something the garbage collector cannot reclaim.
dispose is symbol-keyed rather than name-keyed. dispose is a common
enough method name that a class may already have one meaning something
unrelated, and this is a protocol the language itself invokes implicitly —
a silent collision would be a resource released at the wrong time. Call it
as value.[Disposable.dispose]().
import { Disposable } from './ownership.zena';
class Descriptor implements Disposable {
#handle: i32;
new(this.#handle);
[Disposable.dispose](): void { descriptorDrop(this.#handle); }
}
Disposal must be idempotent: dispose() may be called on an already
disposed value and must not release the underlying resource twice. Until
affine checking lands in O2, nothing statically prevents a double call, and
a resource wrapper's state flag is what makes the second one a no-op.
Iterator
interface Iterator<T> extends AsyncIterator<T>
next(): Step<T>
Advances the iterator and returns the next element as a Step<T>:
Ready with a value, or Done when exhausted. Never Pending:
that is what makes this iterator synchronous, and what lets a
plain for consume it (async-iteration.md).
Iterable
interface Iterable<T>
A collection that can produce an Iterator.
contains(value: T): boolean
Returns true if the collection contains the specified value.
all(predicate: (item: T) => boolean): boolean
Returns true if all elements match the given predicate. Returns true if the collection is empty.
some(predicate: (item: T) => boolean): boolean
Returns true if at least one element matches the given predicate. Returns false if the collection is empty.
fold<R>(initial: R, combine: (acc: R, item: T) => R): R
Reduces the collection to a single value by accumulating state.
find(predicate: (item: T) => boolean): inline (true, T) | inline (false, _)
Finds the first element that matches the predicate. Returns an inline tuple (true, item) if found, or (false, _) if not.
filter(predicate: (item: T) => boolean): Iterable<T>
Returns a new iterable containing only the elements that match the predicate.
Array
interface Array<T> extends Iterable<T>
A sequence with a length and indexed reads.
This is the read-only view; MutableArray adds indexed writes. The
constructors build the standard implementations — new Array() is a
GrowableArray and new Array.fixed(n, value) a FixedArray — and are
typed as those classes, so the result has the class's full API.
(The modules declaring those classes import this one back; classes and
interfaces may cross an import cycle.)
new(capacity: i32 = 8): GrowableArray<T>
An empty growable array, with room for capacity items before it grows.
new growable(capacity: i32 = 8): GrowableArray<T>
The same as new Array(), named for symmetry with fixed.
new fixed(length: i32, value: T): FixedArray<T>
A fixed-length array of length copies of value.
length: i32 { get; }
map<U>(f: (item: T, index: i32, seq: this) => U): Array<U>
contentEquals(other: Array<T>): boolean
Whether other has the same length and == elements in the same
order, whatever array class it is. == on two arrays compares
identity; this compares contents, as two byte strings are compared.
operator [](index: i32): T
operator [](r: Range): Array<T>
A copy of the elements whose indices are in the range: a..b (from
a up to but not including b), a.. (from a to the end), ..b
(from the start up to b) or .. (all of them). Bounds outside the
array are clamped to it, and a range with no elements gives an empty
array.
6 inherited members
Iterable
contains(value: T): boolean
Returns true if the collection contains the specified value.
all(predicate: (item: T) => boolean): boolean
Returns true if all elements match the given predicate. Returns true if the collection is empty.
some(predicate: (item: T) => boolean): boolean
Returns true if at least one element matches the given predicate. Returns false if the collection is empty.
fold<R>(initial: R, combine: (acc: R, item: T) => R): R
Reduces the collection to a single value by accumulating state.
find(predicate: (item: T) => boolean): inline (true, T) | inline (false, _)
Finds the first element that matches the predicate. Returns an inline tuple (true, item) if found, or (false, _) if not.
filter(predicate: (item: T) => boolean): Iterable<T>
Returns a new iterable containing only the elements that match the predicate.
MutableArray
interface MutableArray<T> extends Array<T>
operator []=(index: i32, value: T): void
13 inherited members
Array
new(capacity: i32 = 8): GrowableArray<T>
An empty growable array, with room for capacity items before it grows.
new growable(capacity: i32 = 8): GrowableArray<T>
The same as new Array(), named for symmetry with fixed.
new fixed(length: i32, value: T): FixedArray<T>
A fixed-length array of length copies of value.
length: i32 { get; }
map<U>(f: (item: T, index: i32, seq: this) => U): Array<U>
contentEquals(other: Array<T>): boolean
Whether other has the same length and == elements in the same
order, whatever array class it is. == on two arrays compares
identity; this compares contents, as two byte strings are compared.
operator [](index: i32): T
Iterable
contains(value: T): boolean
Returns true if the collection contains the specified value.
all(predicate: (item: T) => boolean): boolean
Returns true if all elements match the given predicate. Returns true if the collection is empty.
some(predicate: (item: T) => boolean): boolean
Returns true if at least one element matches the given predicate. Returns false if the collection is empty.
fold<R>(initial: R, combine: (acc: R, item: T) => R): R
Reduces the collection to a single value by accumulating state.
find(predicate: (item: T) => boolean): inline (true, T) | inline (false, _)
Finds the first element that matches the predicate. Returns an inline tuple (true, item) if found, or (false, _) if not.
filter(predicate: (item: T) => boolean): Iterable<T>
Returns a new iterable containing only the elements that match the predicate.
Mixins
IterableUtils
mixin IterableUtils<T> on Iterable<T>
Common iteration utilities for any collection that implements Iterable
filter(predicate: (item: T) => boolean): Iterable<T>
Returns a new iterable containing only the elements that match the predicate.
contains(value: T): boolean
Checks if the collection contains the specified value using equality.
all(predicate: (item: T) => boolean): boolean
Returns true if all elements match the given predicate. Returns true if the collection is empty.
some(predicate: (item: T) => boolean): boolean
Returns true if at least one element matches the given predicate. Returns false if the collection is empty.
fold<R>(initial: R, combine: (acc: R, item: T) => R): R
Reduces the collection to a single value by accumulating state.
find(predicate: (item: T) => boolean): inline (true, T) | inline (false, _)
Finds the first element that matches the predicate. Returns an inline tuple (true, item) if found, or (false, _) if not.
Enums
Encoding
enum Encoding
String encodings supported by Zena strings and byte conversion.
Type aliases
StringIndex
type StringIndex = i32
A position within a String that has been validated to lie on a
Unicode boundary: an offset in the string's own encoding, which an
i32 read of it exposes.
StringIndex values are produced by String methods (startIndex,
endIndex, indexAfter, indexBefore, scalarIndexAfter,
scalarIndexBefore, indexOffsetBy, indexOf) and by the views'
indexAt (utf8.indexAt, utf16.indexAt), and cannot be forged by
casting an arbitrary i32 outside zena:core. Reading the underlying
offset with idx as i32 is always legal.
StackRef
type StackRef = anyref
Opaque host-side stack-trace handle.
Option
type Option<T> = Some<T> | None
A value that is either present (Some) or absent (None).
Result
type Result<T, E> = (true, T) | (false, E)
Result<T, E> — a value that is either an ok payload or an error payload.
A value union of two tuples, told apart by the literal in front: (true, value) or (false, error). Returned as inline Result<T, E>, it is a
WebAssembly multi-value return and costs no allocation; stored in a
variable, field or collection, it is a boxed tuple. The two forms convert
implicitly. See docs/design/result-option.md.
import { Result } from 'zena:core';
let checkedSub = (a: i32, b: i32): inline Result<i32, String> => {
if (b > a) {
return (false, "underflow");
}
return (true, a - b);
};
if (let (true, v) = checkedSub(10, 4)) {
// v is 6
}
let kept: Array<Result<i32, String>> = [checkedSub(1, 2)];
WithDefault
type WithDefault<T>
A slot for a T that may be empty: T itself at a primitive, and
T | null at a reference. A generic class keeps a value it may not
have yet in one, without a Box. defaultValue<T>() is its empty
value, a T fills it, and slot as T reads it back; at a non-null
reference T, reading an empty slot fails the cast.
class Cell<T> {
var #value: WithDefault<T> = defaultValue<T>();
set(value: T): void { this.#value = value; }
get(): T { return this.#value as T; }
}
Own
type Own<T> = T
The owning reference to a resource. Affine: it may be moved, returned or stored, but not duplicated, and it is released when it leaves scope unmoved.
Erased at codegen: a distinct type is nominally distinct for checking but
shares its target's representation, so a handle costs no allocation and no
indirection. The three differ in permissions, not data.
Own<T> is what declares affineness; resource class declares
resource-ness. The two are separate on purpose — see ownership.md
§"Resource-ness and affineness are different properties" — which is why
Own<T> will also apply to ordinary classes (from a provably exclusive
source) once layer 4 needs it.
Borrow
type Borrow<T> = T
A borrowed reference. Freely copied, but second-class: it may not be returned (except derived from a single borrow parameter), stored in a field or array, or captured by a closure. Never releases what it points at.
At unrestricted instantiations Borrow<T> is the identity — Borrow<i32>
is i32 — which is what keeps container APIs from forking.
Unmanaged
type Unmanaged<T> = T
A disowned resource: aliasable like an ordinary reference, and never
implicitly dropped. You must dispose() it yourself or adopt() it back
into the affine world.
This is the population using was designed for. Entering it costs
leak-freedom and compile-time use-after-dispose detection; it costs neither
type soundness nor memory safety, because the state flag turns a bad
adopt into a clean error rather than a double free.
Scoped
type Scoped<T> = T
A value that may not be duplicated and may not outlive the extent it
derives from — the corner of the universe table where both
restrictions bind (ownership.md §"ScopedBorrow across await returns
Scoped<Future<T>>, so the frame holding the borrow cannot escape
the borrow's extent.
A scoped value must be consumed exactly once on every path: a scoped
future by await or a move, a scoped iterator by for/in. It may
not be stored in a field, container, record, or tuple, and may not
be captured by a closure. A first-class Future<T> coerces to
Scoped<Future<T>>; there is no way back.
ScopedFrom
type ScopedFrom<T, R>
Scoped<R> when T carries a second-class type — a scoped value, a
restricted borrow, or a scoped type parameter — and R otherwise
(ownership.md §"Combinator audit", docs/design/type-operators.md).
For a generic whose result must be scoped exactly when an input is:
Future.allSettled<scoped T> returns
ScopedFrom<T, Future<Array<Result<Awaited<T>, Error>>>>, so a
call over scoped futures yields a scoped result the caller must
consume, and a call over ordinary futures yields the ordinary future
it always did. Inside the generic body the operator reads as
Scoped<R>; substitution collapses it once T is known.
TemplateTag
type TemplateTag<T> = (strings: TemplateStringsArray, values: FixedArray<anyref>) => T
Type alias for template tag functions.
Example usage:
const html: TemplateTag<String> = (strings, values) => {
// strings[0], strings[1], etc. are the literal parts
// values[0], values[1], etc. are the interpolated expressions
// strings.raw gives access to raw (unescaped) strings
return '...';
};
const result = html`<div>${name}</div>`;
Step
type Step<T> = inline (0, _, _) | inline (1, inline? T, _)
Stepnext()
(docs/design/async-iteration.md). It is an inline multi-value, so it
travels in stack slots and allocates nothing.
Done(discriminant 0): no more items.Ready(discriminant 1): a value, available now.
A tuple value is unboxed too (inline? T): Step<(K, V)> returns the
key and the value as results of their own, so zip and similar
iterators allocate nothing per step.
The third position is always empty. It is there so that every Step
is also an AsyncStep (zena:async), whose third arm carries a future:
a synchronous iterator can stand wherever an asynchronous one is
expected, and a for await drives both.
Functions
asciiLowerByte
function asciiLowerByte(b: i32): i32
Returns the ASCII lowercase of b, leaving every other byte or code point
unchanged.
asciiUpperByte
function asciiUpperByte(b: i32): i32
Returns the ASCII uppercase of b.
$contractFailed
function $contractFailed(message: String): never
What compiled code calls when a contract clause does not hold. The compiler emits the call, with a message naming the clause, so what a violated contract does is decided here.
unreachable
declare function unreachable(): never
installStackTraceHooks
function installStackTraceHooks(capture: () => StackRef | null, format: (stack: StackRef | null) => String | null): void
Install the host's capture and format.
captureStackTrace
function captureStackTrace(): StackRef | null
formatStackTrace
function formatStackTrace(stack: StackRef | null): String | null
some
function some<T>(value: T): Option<T>
Creates a Some
unit
function unit(): void
The void value, for a tuple's void member: (true, unit()) is a
Result<void, E> that succeeded.
defaultValue
declare function defaultValue<T>(): WithDefault<T>
The value of an empty WithDefault<T>: zero at a primitive, null at a reference.
hash
declare function hash<T>(value: T): i32
The hash code of a value, dispatched on its static type: primitives hash by
value, Strings by content, and classes through hashCode().
This is what hash-based collections call. Prefer it over hashCode() — it
works for primitives, which have no methods.
equals
declare function equals<T>(a: T, b: T): boolean
Whether two values of the same type are equal, dispatched on their static
type: primitives compare by value, Strings by content, and classes through
operator == when the class defines one and by reference otherwise.
toString
declare function toString<T>(value: T): String
Converts any value to a String, dispatched on its static type: primitives
format by value, null as 'null', Stringable types via
[Stringable.toString](), and other classes, records, and tuples with a
type-tag fallback (<ClassName>, <record>, <tuple>).
isFirstClass
declare function isFirstClass<T>(): boolean
Whether T is first-class in this specialization: false where it may
be an owner, a borrow or a value holding one, true elsewhere. A
constant, so a scoped T body branches on what only one kind of
instantiation needs and the other branch folds away. It answers for
the body a specialization shares, so it is false at Unmanaged<R>,
which shares Own<R>'s, and true at Scoped<F>, which shares F's
(ownership.md §"Scoped type arguments").
disown
function disown<T extends Resource>(value: Own<T>): Unmanaged<T>
Leaves the affine regime: takes the owning handle and hands back an aliasable one that is never implicitly dropped.
let raw: Unmanaged<Descriptor> = disown(f); // f is consumed
// … alias it, store it in a field, put it in an ordinary Array …
let f2: Own<Descriptor> = adopt(raw); // back under implicit drop
The handles are erased, so this re-types the object rather than allocating
anything: Own<T> and Unmanaged<T> lower to the same wasm type, and the
body compiles to an identity function returning its argument. The call
itself is not yet inlined away.
Requires the resource to be owned and leaves it disowned, so
disowning the same resource twice throws rather than handing out two
unmanaged aliases with one disposal obligation between them.
disown consumes its argument. Otherwise an Own that will implicitly
drop would coexist with an Unmanaged alias to the same resource, and the
drop would run under an alias that outlives it. Move checking enforces the
consumption for local bindings — a caller that keeps using the original
Own after disowning it gets a compile error, through the ordinary rule
that an argument to an Own parameter is a move. The flag still guards
the routes the checker does not track, such as an Own read out of a
field.
The cast is legal here and nowhere else: Own<T> and Unmanaged<T> are
opaque types declared in this file, and both erase to T, so re-labelling
one as the other mints nothing. See ownership.md §"Handles are not
forgeable" — outside this file the same cast is a diagnostic, which is what
makes disown and adopt the only doors between the two regimes.
adopt
function adopt<T extends Resource>(value: Unmanaged<T>): Own<T>
Re-enters the affine regime: takes a disowned handle back under implicit drop, so it is released at scope exit again.
The inverse of disown, and the only way back — Unmanaged<R>
has no user-callable dispose, since it is not an owner and cannot call the
consuming form. A disowned resource is either scoped with using or adopted
back.
Throws when the resource is not in the disowned state: owned
means something else adopted it first, so two racing adopters do not both
succeed — the loser gets a clear error rather than a second owner and,
once implicit drop lands, a double free.
dropped throws too: every consuming dispose sets it, so adopting an
already-released resource is reported as exactly that.
adopt cannot retract aliases. Aliasability is the point of the disowned
state, so the flag is the only defence here, by design.
map
function map<T, U>(it: Scoped<Iterator<T>>, f: (v: T) => U): Scoped<Iterator<U>>
The elements of a scoped iterator mapped through f. Driving the
result drives it, inside the same extent — the input is consumed
(a scoped iterator is driven exactly once), and the result derives
from the same borrow, so it is scoped too.
filter
function filter<T>(it: Scoped<Iterator<T>>, keep: (v: T) => boolean): Scoped<Iterator<T>>
The elements of a scoped iterator for which keep answers true.
take
function take<T>(it: Scoped<Iterator<T>>, count: i32): Scoped<Iterator<T>>
At most the first count elements of a scoped iterator. The loop is
always entered — every path out of this body must consume it — so
with count <= 0 one element is still pulled from the source before
the frame is disposed.
dedent
function dedent(strings: TemplateStringsArray, values: FixedArray<String>): String
A template tag that strips the source indentation from a multi-line template literal, so a block of text can be written at the indentation of the code around it and still come out flush left:
let usage = dedent`
zena build <entry>
-o <path> where to write the module
`;
// "zena build <entry>\n -o <path> where to write the module"
The rules:
- The opening line is dropped, along with its line break, when it is blank — which it is in the form above, where the content starts on the line after the backtick. The closing line is dropped the same way. Neither is dropped when it has content on it: a line with content is a line, and it takes part in the rule below like any other.
- The indentation removed from every remaining line is the longest common prefix of the indentation of the lines that have content on them. It is compared byte for byte, so a file that mixes tabs and spaces dedents by what its lines actually share rather than by a count that assumes a tab width.
- Blank lines are ignored when computing that prefix and come out empty, so a line of leftover trailing spaces is not what decides the result.
- Interpolated values are inserted verbatim. A value containing newlines is not re-indented, and its newlines do not start lines that participate in the computation: only indentation written in the literal counts. A line that starts with a value is a content line, and the whitespace before the value is its indentation.
Interpolations must be Strings — a tag receives its values without the
conversion ${} performs in an untagged template literal, so write
dedent`count: ${`${n}`}` (or i32ToString(n)) for a number.
The text dedented is the cooked strings, so an escape is processed before
the indentation is measured: a \n written as an escape breaks a line here
exactly as a real newline does, and the line it opens takes part in the
common-indent computation like any other. Use dedentRaw to dedent text
whose escapes are meant to stay as written.
dedentRaw
function dedentRaw(strings: TemplateStringsArray, values: FixedArray<String>): String
dedent over the raw literals, keeping the trailing line break: the tag for
embedding source code in a test.
It differs from dedent in two ways, both of which matter when the text
being embedded is itself code:
- The text dedented is
strings.raw, so an escape stays the characters that were written.dedentRaw`let s = 'a\\nb';`yields a backslash followed by ann, which is the source line someone typed. Underdedentthe same literal yields a real line break, which splits that Zena string literal across two lines, and because the second of them has no indentation it also drops the common indent to nothing. - The line break that ends the last content line survives the closing line being dropped, so the result ends in a newline when the closing backtick sits on its own line. Source files end in a newline, and so does the output of a formatter, so a round-trip comparison needs one here.
let source = dedentRaw`
let f = () => {
x += 2;
};
`;
// "let f = () => {\n x += 2;\n};\n"
Nothing is added that was not written: a template whose closing backtick
follows the last line's content has no trailing break to keep, and the
result has none. Every other rule — which lines the common indent is taken
from, how blank lines are treated, how interpolations are spliced in — is
dedent's, described above.
fixed
function fixed<T>(items: FixedArray<T>): FixedArray<T>
Its argument, unchanged: fixed([1, 2, 3]).
The parameter supplies the mutable context, so a literal argument builds directly as a FixedArray — a mutable literal in expression position, where no annotation can say so. The identity body inlines away, leaving the bare allocation.
growable
function growable<T>(items: FixedArray<T>): GrowableArray<T>
A growable array from an array literal: growable([1, 2, 3]).
The parameter supplies the mutable context, so the literal builds
directly as a FixedArray and is adopted as the backing storage —
no copy, unlike GrowableArray.from over an interface-typed source.
Until literal decorators land, this is the concise spelling for a
growable array with initial contents.
newByteArray
function newByteArray(size: i32): ByteArray
Create a new ByteArray of the specified size, filled with zeros.
copyBytes
function copyBytes(dest: ByteArray, destOffset: i32, src: ByteArray, srcOffset: i32, length: i32): void
Copy bytes from source to destination.
readU8
function readU8(bytes: ByteArray, offset: i32): i32
The byte at offset, from 0 to 255.
readU16
function readU16(bytes: ByteArray, offset: i32): i32
The two bytes at offset, little-endian, from 0 to 65535.
readU32
function readU32(bytes: ByteArray, offset: i32): i32
The four bytes at offset, little-endian. Read as u32 for the unsigned value.
readU64
function readU64(bytes: ByteArray, offset: i32): i64
The eight bytes at offset, little-endian. Read as u64 for the unsigned value.
readF32
function readF32(bytes: ByteArray, offset: i32): f32
The f32 whose bits are the four bytes at offset, little-endian.
readF64
function readF64(bytes: ByteArray, offset: i32): f64
The f64 whose bits are the eight bytes at offset, little-endian.
readULEB128
function readULEB128(bytes: ByteArray, offset: i32, end: i32): inline (i32, i32)
The unsigned LEB128 value at offset, of at most 5 bytes, and the offset
after it; that offset is -1 when the value does not end before end.
Bits past the 32nd are dropped.
readULEB128_64
function readULEB128_64(bytes: ByteArray, offset: i32, end: i32): inline (i64, i32)
The unsigned LEB128 value at offset, of at most 10 bytes, and the
offset after it; that offset is -1 when the value does not end before
end. Read as u64 for values of 2^63 and above.
readSLEB128
function readSLEB128(bytes: ByteArray, offset: i32, end: i32): inline (i32, i32)
The signed LEB128 value at offset, of at most 5 bytes, and the offset
after it, or -1 as for readULEB128.
readSLEB128_64
function readSLEB128_64(bytes: ByteArray, offset: i32, end: i32): inline (i64, i32)
The signed LEB128 value at offset, of at most 10 bytes, and the offset
after it, or -1 as for readULEB128_64.
join
function join(items: Iterable<String>, separator: String = ''): String
Concatenates the strings in items with separator between adjacent elements.
writeI32
function writeI32(value: i32, buffer: ByteArray, bufferEnd: i32): inline (i32, i32)
Writes an i32 to a buffer. Buffer must have at least 11 bytes. Returns (startPos, endPos) where the digits are in buffer[startPos..endPos).
writeU32
function writeU32(value: u32, buffer: ByteArray, bufferEnd: i32): inline (i32, i32)
Writes a u32 to a buffer. Buffer must have at least 10 bytes. Returns (startPos, endPos).
writeI64
function writeI64(value: i64, buffer: ByteArray, bufferEnd: i32): inline (i32, i32)
Writes an i64 to a buffer. Buffer must have at least 20 bytes. Returns (startPos, endPos).
writeU64
function writeU64(value: u64, buffer: ByteArray, bufferEnd: i32): inline (i32, i32)
Writes a u64 to a buffer. Buffer must have at least 20 bytes. Returns (startPos, endPos).
boolToString
function boolToString(value: boolean): String
Converts a bool to "true" or "false".
parseI32
function parseI32(s: String): inline (true, i32) | inline (false, _)
Parse a decimal integer from a string, with optional leading sign.
Returns inline (true, value) on success, inline (false, _) on failure.
Fails if the string is empty or contains non-digit characters (after optional sign).
parseI64
function parseI64(s: String): inline (true, i64) | inline (false, _)
Parse an i64 integer from a string, supporting optional leading sign,
optional hex prefix (0x or 0X), and optional underscores (_).
Returns inline (true, value) on success, inline (false, _) on failure.
decimalToF64
function decimalToF64(d: u64, p: i32, negative: boolean, inexact: boolean, tail: u64): f64
The f64 nearest to d * 10^p, correctly rounded. d is the decimal
significand as an integer below 2^64 and p its decimal exponent.
inexact says the true value lies strictly between d and d + 1
— the caller had more digits than it could keep — and tail is that
leftover fraction scaled by 2^11.
With 19 or fewer significant digits inexact is false and the
result is the correctly rounded one, always. Beyond that the
significand no longer fits: normalizing d to 64 bits leaves only
64 - bits(d) low bits for the remainder, so the value is placed to
within half of the last of them and can land on the wrong side of a
rounding boundary that falls inside that gap. Measured against a
correctly-rounded implementation this is about one input in five
thousand of more than 19 significant digits, always by one ulp
(dev/fp-difftest.js measures it).
Closing it needs an exact-arithmetic fallback, which the algorithm
this comes from also leaves to its caller.
i32ToString
function i32ToString(value: i32): String
Converts an i32 to its decimal string representation.
u32ToString
function u32ToString(value: u32): String
Converts a u32 to its decimal string representation.
i64ToString
function i64ToString(value: i64): String
Converts an i64 to its decimal string representation.
u64ToString
function u64ToString(value: u64): String
Converts a u64 to its decimal string representation.
f32ToString
function f32ToString(value: f32): String
The shortest decimal string that reads back as this f32. The f32
interval is used, not the f64 one the promoted value would have, so
0.1 as f32 prints "0.1" rather than "0.10000000149011612".
f64ToString
function f64ToString(value: f64): String
The shortest decimal string that reads back as this f64.
f64ToPrecision
function f64ToPrecision(value: f64, sigDigits: i32): String
value rounded to sigDigits significant decimal digits, in the
same format f64ToString uses. sigDigits is clamped to [1, 17].
parseF64
function parseF64(s: String): f64
Parses the number at the start of s to the NEAREST f64, correctly
rounded: an optional sign, then Infinity, 0x and hex digits, or a
decimal with an optional fraction and exponent. Anything after the number
is ignored, and a string that does not start with one is NaN.
Apart from hex, which the compiler uses for a hex literal typed as a
float, this is JavaScript's parseFloat on a string that does not start
with whitespace. parseFloat skips leading whitespace, where this reads
no number; JavaScript's whitespace includes a dozen non-ASCII spaces,
and recognizing them would cost every module that parses a float.
The significand is accumulated as an integer and the exponent
tracked alongside, so no rounding happens until the single
decimalToF64 at the end. Digits beyond the nineteenth cannot
change the result except through the tie they might break, so they
are folded into a sticky low digit rather than dropped.
Variables
none
let none: None
The singleton None instance.