Standard Library
JSON, Time & Bytes
Four small, in-language libraries that round out day-to-day work: a total JSON parser and value model, a UTC-only date/time pair, byte-true text codecs and digests, and the transcendental-math and byte-buffer primitives everything else is built on.
JSON
The JSON value model is a plain class, JsonValue — Leviathan
has no type aliases, so a recursive named union can't be declared. A
kind int tag distinguishes the six JSON shapes
(0 null, 1 bool, 2 number,
3 string, 4 array, 5 object).
json::parse is total: malformed input returns
None, it never throws. Once you have a value, navigation is
deliberately two-speed — at(int)/at(string) throw
loudly on a kind mismatch, an out-of-bounds index, or a missing key, while
the asXxx() accessors and atOrNone return
None instead of raising.
JsonValue? doc = json::parse(text); // None on malformed — parse never throws
if (doc != None) {
string name = doc.at("user").at("name").asStr() ?? "?"; // loud navigation + typed access
string out = doc.render(); // compact
string pp = doc.renderPretty(2); // indented, 2-space
}Values are built with labeled "static" constructors rather than a public constructor per kind:
JsonValue v = JsonValue::ofObject(m);
// siblings: ofNull() / ofBool(b) / ofNum(n) / ofStr(s) / ofArray(a) / ofObject(m)| member | behavior |
|---|---|
isNull() … isObject() | kind test, one per tag |
asBool() -> bool? | None on kind mismatch |
asNum() -> float? | None on kind mismatch |
asStr() -> string? | None on kind mismatch |
asArray() -> Array<JsonValue>? | None on kind mismatch |
asObject() -> Map<string, JsonValue>? | None on kind mismatch |
at(int) -> JsonValue / at(string) -> JsonValue | throws on kind mismatch, out-of-bounds, or missing key — loud navigation |
atOrNone(key) -> JsonValue? | the non-throwing sibling of at |
size() -> int | element/member count |
render() -> string | compact JSON text |
renderPretty(int indent) -> string | indented JSON text (renderPretty(2) is typical) |
The parser is recursive-descent with the full JSON escape set, including
\uXXXX surrogate-pair combining (a lone/unpaired surrogate
decodes to U+FFFD rather than failing); numbers are IEEE doubles; nesting
is capped at depth 128; and strict trailing garbage after the top-level
value makes the whole parse return None.
Number rendering uses float.toString()'s form
(42.000000) for v1 — pinned by the test corpus, and
revisited only when float formatting is addressed globally.
engines JSON has full coverage on
the oracle, IR, emit-C++, and LLVM engines. It does not run on the frozen
ELF backend, which never gained the post-freeze byteAt primitive
the value model leans on.
DateTime & Duration
DateTime and Duration are UTC-only value
structs built on the public-domain Howard Hinnant
civil↔days integer algorithm — no lookup tables, leap seconds ignored,
Unix epoch convention throughout.
DateTime t = DateTime::now(); // labeled ctors: now(), ofEpochMs(int)
DateTime u = DateTime::ofEpochMs(784111777000);
int y = u.year(); u.month(); u.day(); u.hour(); u.minute(); u.second(); u.milli(); u.weekday();
string h = u.httpDate(); // "Sun, 06 Nov 1994 08:49:37 GMT" (RFC 7231)
string i = u.iso8601(); // "1994-11-06T08:49:37Z" (adds ".mmm" if nonzero)
DateTime? p = datetime::parseHttpDate(h); // None on malformed
DateTime? q = datetime::parseIso8601(i); // both are datetime:: free functions, no `static`
Duration is built from labeled unit constructors and composes
with plus/minus; its toString()
renders a compact 1h02m00s form:
Duration d = Duration::ofHours(1).plus(Duration::ofMinutes(2)); // ofMillis/Seconds/Minutes/Hours/Days
string ds = d.toString(); // "1h02m00s"
Duration span = t.minus(u); // DateTime.minus -> Duration
DateTime later = u.plus(span); // DateTime.plus(Duration) -> DateTime
DateTime::now() reads std::sysNow(), the
wall-clock epoch-millisecond native every timing facility ultimately
depends on. Coverage: all four active engines (oracle, IR, emit-C++,
LLVM) — not the frozen ELF backend
engines.
Encoding & digests
Byte-true text codecs and cryptographic digests, implemented entirely
in-language. Every decoder is total: malformed input
returns None rather than throwing, because a data error is a
value here, not an exception (Errors &
Resources).
namespace encoding {
string base64Encode(string bytes); string? base64Decode(string b64);
string base64UrlEncode(string bytes); string? base64UrlDecode(string s); // JWT: no '=' pad
string percentEncode(string s); string? percentDecode(string s); // RFC 3986
string hexEncode(string bytes); string? hexDecode(string s);
}
namespace digest returns raw bytes, not hex
or base64 text — compose the result with encoding::hexEncode
or encoding::base64Encode to get a printable digest:
namespace digest {
string md5(string); string sha1(string); string sha256(string);
string hmacSha256(string key, string msg);
}
string etag = encoding::hexEncode(digest::sha256(body));Forgetting the encoding::hexEncode/base64Encode
wrap is the most common mistake here — printing a digest::sha256(...)
result directly prints raw byte content, not the familiar hex string.
Digest implementations use the int64 mask idiom (words masked to 32 bits
after each operation) and are verified against the RFC 1321/3174 + FIPS
180-4 + RFC 4231 test vectors, including the padding-boundary trap set.
They're slow-ish on the interpreters — fine for cookies and etags, not
meant for bulk hashing. Coverage: oracle, IR, emit-C++, LLVM — not frozen
ELF, which never gained the post-freeze byteAt/byteToString
primitives these lean on engines.
namespace math
A small, mostly-native namespace for constants and transcendentals:
namespace math {
const float pi = 3.141592653589793;
const float e = 2.718281828459045;
float log(float x); float log2(float x); float exp(float x);
float sin(float x); float cos(float x); float tan(float x);
float atan2(float y, float x);
int min(int a, int b); int max(int a, int b);
float min(float a, float b); float max(float a, float b);
}
Accessed as math::pi, math::log(x), and so on, or
via uses math; for bare pi/log(x) in
scope (see Namespaces & Injection).
min/max are ordinary in-language functions that
overload by argument type within the one namespace — an int
pair and a float pair coexist. Every other member is a
native free function backed by libm.
std::math
math is a top-level namespace, not
nested under std — a qualified std::math::pi
path is a real gap, not a style choice. math is the
design's own pre-registered fallback for exactly that failure mode.
engines pi/e/min/max
run everywhere. The transcendentals (log, log2,
exp, sin, cos, tan,
atan2) are native libm/LLVM-intrinsic calls, covered on the
oracle, IR, emit-C++, and LLVM backends; on the frozen ELF backend they
fail with a clean coverage diagnostic instead of linking libm into that
zero-dependency backend or silently misbehaving.
Block: the byte buffer
Block is the language's gated mutable byte
buffer — the gate is the type itself, nothing implicit converts
to or from it. Unlike the pure/immutable Array/Map,
Block is a reference type: honestly mutable,
shared by reference like any class.
Block b = Block(4096); // zeroed, fixed length; b.length() == 4096
Block c = Block::fromString(str); // copy a string's bytes into a new Block
int v = b.byteAt(i); // 0..255
b.setByte(i, v); // v must be 0..255 (else throws — not masked)
Block s = b.slice(off, len); // ALIASING view (shares bytes with b)
string t = b.toString(off, len); // copy len bytes -> string
int u = b.int32At(i); b.setInt32(i, u); // little-endian; read sign-extends
int w = b.int64At(i); b.setInt64(i, w); // little-endian
b.fill(off, len, v); // native bulk byte fill
b.blit(dstOff, src, srcOff, len); // native copy; memmove overlap semantics
bool same = b.equals(c); // CONTENT equality (unlike `==`)
int first = b.mismatch(c, from); // first differing index, or -1| method | behavior |
|---|---|
Block(int size) | construct a zeroed buffer of size bytes (fixed length) |
Block::fromString(string s) | new Block copying s's bytes (labeled constructor) |
length() -> int | byte length |
byteAt(int i) -> int | byte 0..255 at i |
setByte(int i, int value) | store a byte; value outside 0..255 throws (loud, not masked) |
slice(int off, int len) -> Block | aliasing view sharing storage — writes through the slice are visible in the parent (zero-copy, by design) |
toString(int off, int len) -> string | copy len bytes from off into a new string |
int32At(int i) -> int / setInt32(int i, int value) | little-endian 4-byte read (sign-extended) / write |
int64At(int i) -> int / setInt64(int i, int value) | little-endian 8-byte read / write |
fill(int off, int len, int value) | fill [off, off+len) with one byte; value must be 0..255 |
blit(int dstOff, Block src, int srcOff, int len) | copy bytes with memmove semantics, including overlapping views of one root |
equals(Block other) -> bool | compare full-view content; unequal lengths return false |
mismatch(Block other, int from) -> int | first differing index at or after from, else -1; unequal lengths throw |
Every access is bounds-checked and throws
RuntimeException on an out-of-range offset or length —
including setByte's 0..255 value range. Empty
bulk ranges are legal at the inclusive end offset; mismatch
accepts from == length() and returns -1.
b.equals(c) compares byte content, while b == c
remains ordinary Block reference identity. Neither operation
changes the other. console.write(b) renders
Block(len=N), not the bytes themselves.
engines Block ships on
the oracle, IR, emit-C++, and LLVM engines as a heap value (tag 11, body
{parentPtr, off, len, dataPtr}) whose slice retains the
root so shared bytes outlive any single view. There is no
ELF lane — the frozen X64Gen backend never gained the ABI addendum
Block needs, so Block-typed I/O and Block
itself are absent there.