hanki

serializer

stdlib/core/serializer.hk: the Serializer format abstraction and the built-in binary serializer.

Serializer is the write side of the format-generic serialization framework (HANKI.md §16): the set of structural events an Encode walk emits, scalars, the presence of an Option, and the framing of sequences, maps, structs, and sum variants. A concrete serializer translates those events into one wire format; Encode impls name the events and remain format-agnostic, and a single @derive(Encode) serves every format.

BinarySerializer is the built-in format: the framework's own compact, fixed-width encoding (the layout that the pre-abstraction Encode produced). It wraps a BytesBuilder sink. Per-width integer methods keep it fixed-width; a self-describing format (cbor) ignores the declared width and encodes minimally. begin_struct! writes nothing (a struct's field count is static, recovered from the type on decode) and begin_variant! writes only the tag byte; a framed format uses the counts to emit an array/map header instead.

Every method is an action (!) with an empty effect row, like the BytesBuilder it drives (HANKI.md §4): it mutates the sink but commits no OS capability.

Serializer

trait Serializer

put_bool!

def put_bool!(self, b: bool) -> ()

Writes one bool into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_u8!

def put_u8!(self, n: u8) -> ()

Writes one u8 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_u16!

def put_u16!(self, n: u16) -> ()

Writes one u16 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_u32!

def put_u32!(self, n: u32) -> ()

Writes one u32 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_u64!

def put_u64!(self, n: u64) -> ()

Writes one u64 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_i32!

def put_i32!(self, n: i32) -> ()

Writes one i32 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_i64!

def put_i64!(self, n: i64) -> ()

Writes one i64 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_f64!

def put_f64!(self, x: f64) -> ()

Writes one f64 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_string!

def put_string!(self, s: string) -> ()

Writes one string into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_bytes!

def put_bytes!(self, b: bytes) -> ()

Writes one bytes into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_none!

def put_none!(self) -> ()

Option: a None marker, or a Some marker followed by the payload's own encode!. @no-doctest: structural encode op; the round-trip tests below exercise it

put_some!

def put_some!(self) -> ()

Marks a present optional; the wrapped value follows.

@no-doctest: structural encode op; the round-trip tests below exercise it

begin_seq!

def begin_seq!(self, len: int) -> ()

Sequences and maps carry their element/pair count up front, then that many element encode!s (a map alternates key, value). @no-doctest: structural encode op; the round-trip tests below exercise it

begin_map!

def begin_map!(self, len: int) -> ()

Opens a map of the given length; entries follow pairwise.

@no-doctest: structural encode op; the round-trip tests below exercise it

begin_struct!

def begin_struct!(self, fields: int) -> ()

@derive composites: a struct's fields in declaration order, and a sum's variant tag with that variant's payload count, then the payload encode!s. @no-doctest: structural encode op; the round-trip tests below exercise it

begin_variant!

def begin_variant!(self, tag: u8, payloads: int) -> ()

Opens a sum variant by tag; its payload follows.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_int!

def put_int!(self, n: int) -> ()

-- the exact tier ------------------------------------------------------

int, decimal, rational and f32 are the four types @derive(Encode) used to refuse, and both language defaults are among them: an unsuffixed integer literal is int, an unsuffixed float is decimal. Each of these four events has a default body written over the twelve above, and a third-party Serializer written before they existed goes on compiling and gains a correct, if not compact, encoding at no cost. A self-describing format overrides them with its own numeric forms.

put_int! is the only ground case. The other two reduce to it through the exact decomposition (HANKI.md §3), which is also where their in-band sentinels are handled: a sentinel rides out in the first component, so put_int! handling inf/-inf/undefined covers all three types. Writes one arbitrary-precision int: a class byte (0 finite, 1 inf, 2 -inf, 3 undefined) and, when finite, the decimal digits as a string.

Digits and not magnitude bytes, int being unbounded and pure Hanki having no byte view of a bignum; extracting one would be a division loop per byte. The class byte is not optional: the sentinels have no digit spelling at all, and int.parse refuses "inf", and a digits-only encoding would lose the values division produces.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_decimal!

def put_decimal!(self, d: decimal) -> ()

Writes one decimal as its exact unscaled/scale pair (HANKI.md §3), each through put_int!. Lossless where put_f64! would round, and the sentinel travels in unscaled.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_rational!

def put_rational!(self, r: rational) -> ()

Writes one rational as its reduced numerator/denominator pair (HANKI.md §3), each through put_int!. The sentinel travels in numerator.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_f32!

def put_f32!(self, x: f32) -> ()

Writes one f32 as its IEEE bit pattern. Exact, and the only way in or out: no other name in core converts into f32, and the pattern is the value.

@no-doctest: structural encode op; the round-trip tests below exercise it

intclass

def _int_class(n: int) -> u8

The class byte put_int! writes ahead of an int, and _int_sentinel its inverse. 0 is finite (digits follow); the rest are the in-band sentinels, which have no digit spelling. Written as nested ifs in place of one elif chain: three ==-against-a-constant tests in one chain is H0618, and match has no pattern for a computed constant.

BinarySerializer

struct BinarySerializer
  out: BytesBuilder
end

The built-in fixed-width binary format over a BytesBuilder sink.

BytesBuilder

BytesBuilder, or bytes.BytesBuilder, is a runtime-managed native resource handle (HANKI.md §4): live native state the per-actor memory manager owns, released when the last handle drops. It is single-owner - never copied, moved across a send - and has no fields of its own, so its methods are its whole surface. A handle is minted by an API that opens one; it is never constructed.

seru16_be!

def _ser_u16_be!(out: BytesBuilder, n: u16) -> ()

seru32_be!

def _ser_u32_be!(out: BytesBuilder, n: u32) -> ()

seru64_be!

def _ser_u64_be!(out: BytesBuilder, n: u64) -> ()

impl Serializer<BinarySerializer>

put_bool!

def put_bool!(self, b: bool) -> ()

Writes one bool into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_u8!

def put_u8!(self, n: u8) -> ()

Writes one u8 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_u16!

def put_u16!(self, n: u16) -> ()

Writes one u16 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_u32!

def put_u32!(self, n: u32) -> ()

Writes one u32 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_u64!

def put_u64!(self, n: u64) -> ()

Writes one u64 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_i32!

def put_i32!(self, n: i32) -> ()

Writes one i32 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_i64!

def put_i64!(self, n: i64) -> ()

Writes one i64 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_f64!

def put_f64!(self, x: f64) -> ()

Writes one f64 into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_string!

def put_string!(self, s: string) -> ()

Writes one string into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_bytes!

def put_bytes!(self, b: bytes) -> ()

Writes one bytes into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_none!

def put_none!(self) -> ()

Writes one none into the output.

@no-doctest: structural encode op; the round-trip tests below exercise it

put_some!

def put_some!(self) -> ()

Marks a present optional; the wrapped value follows.

@no-doctest: structural encode op; the round-trip tests below exercise it

begin_seq!

def begin_seq!(self, len: int) -> ()

Opens a sequence of the given length; elements follow.

@no-doctest: structural encode op; the round-trip tests below exercise it

begin_map!

def begin_map!(self, len: int) -> ()

Opens a map of the given length; entries follow pairwise.

@no-doctest: structural encode op; the round-trip tests below exercise it

begin_struct!

def begin_struct!(self, fields: int) -> ()

A struct's field count is static, and the binary format writes no framing. @no-doctest: structural encode op; the round-trip tests below exercise it

begin_variant!

def begin_variant!(self, tag: u8, payloads: int) -> ()

Only the variant tag is written; the payload count is static per variant. @no-doctest: structural encode op; the round-trip tests below exercise it