Lyric
A statically typed application language with a bytecode VM — standalone, and embeddable in a host with a capability-based sandbox.
What changed in v3.5.0 — 2026-08-24
The standard library learns structured data: JSON in and out, and the binary-to-text encodings
beside it. Both modules are written in Lyric; no language change, no format change — with one
exception underneath, where std.string.parseFloat had to become native to make the numbers
honest.
Added
std.json— reading and writing JSON (RFC 8259). One value type carries a whole document:import std.json { JsonValue, parse, serialize };JsonValueis an enum over the six JSON shapes, with arrays asList<JsonValue>and objects asMap<string, JsonValue>— the existing collections, not a second container API. Accessors (asString,asInt,asFloat,asBool,asArray,asObject,at,field,isNull) answernulloff-shape,serializewrites compact JSON,serializePretty(value, indent)the readable form.What the contracts pin down, each with a test:
parseanswers?JsonValue— the library's absence convention, asparseIntandutf8Decodeanswer. Strict RFC 8259: no comments, no trailing commas, noNaN, no lone surrogate escapes, no raw control characters in strings. Trailing content refuses the document.- Numbers keep the distinction the text had. No fraction, no exponent →
Int, exact over the wholeintrange, so 64-bit ids survive a round trip; everything else →Float. An integer pastintfalls back toFloat— magnitude over exactness — rather than wrapping into a silently wrong value. - An integral
Floatserializes as3.0, so it comes back as the float it was; NaN and the infinities have no JSON spelling and serialize asnull, the answerJSON.stringifysettled on long ago. - Hostile nesting answers
null. Containers deeper than 128 levels refuse rather than running into the runtime's call-depth panic — input is data, and data that is not JSON comes back asnull, not as a crash. - Duplicate object keys take the last value, as in JavaScript. Object member order follows
the map: unspecified, as
Mapdocuments.
std.encoding— hex and base64 over raw bytes (RFC 4648), on the sameuint8[]thatutf8Encodeandfile.bytesspeak.hexEncode/base64Encodeare total;hexDecode/base64Decodeanswer?uint8[]and are strict — no whitespace, no missing padding, no characters outside the alphabet, and base64 refuses padding whose unused bits are not zero, so accepted input is exactly what the encoder produces.
Changed
std.string.parseFloatis native now — correctly rounded, exponent-capable. The Lyric predecessor summed fractional digits one by one, which drifts by an ulp on long fractions, and it knew no exponent notation at all (its own doc said so):parseFloat("1e-6")wasnull, andparseFloat(fromFloat(x))did not reliably answerx. Both halves matter the moment JSON numbers flow through, and correct rounding is runtime-class work — the worst cases need arbitrary-precision arithmetic, the same reasonfromFloathas always been native. The contract is unchanged where it existed ("3.5",".5","42", junk →null; no whitespace, noNaN/Infinitywords) and wider where it was missing: exponents parse, and a magnitude beyondfloatrounds to the infinities as IEEE 754 prescribes.Like every new compiler-bound native, a module compiled against this library binds
std.string.parseFloatby name: an older runtime refuses such a module at binding with that name — the designed forward path, as withco.next()in 2.2.0 — and a module that never callsparseFloatloads everywhere it always did.
Documentation
- Guide 13 gains the two module sections and their rows in the module table, and its
capabilities line stops naming
std.io.net— a module that does not exist.