hanki

deserializer

stdlib/core/deserializer.hk: the Deserializer format abstraction and the built-in binary deserializer.

Deserializer is the read side of the format-generic serialization framework (HANKI.md §16), the inverse of Serializer: each method reads one structural event from a source and advances it, returning a DecodeError (with the byte offset) on truncation or a bad tag. Decode impls name the events and stay format-agnostic.

BinaryDeserializer reads the built-in fixed-width binary format over a BytesReader cursor, the inverse of BinarySerializer. The cursor tracks position, and no offset is threaded by hand. take_struct! reads nothing (a struct's field count is static); a framed format would read and check an array header instead.

Every read is total: a short read becomes Err(Truncated), never a crash. Each method is an action (!) with an empty effect row, like the BytesReader it drives (HANKI.md §4).

DecodeError

type DecodeError
  Truncated(int)
  BadTag(u8, int)
  BadUtf8(int)
  TooDeep(int)
end

Why a decode failed, with the byte offset at which it was detected.

Deserializer

trait Deserializer

take_bool!

def take_bool!(self) -> Result<bool, DecodeError>

Reads one bool from the input.

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

take_u8!

def take_u8!(self) -> Result<u8, DecodeError>

Reads one u8 from the input.

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

take_u16!

def take_u16!(self) -> Result<u16, DecodeError>

Reads one u16 from the input.

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

take_u32!

def take_u32!(self) -> Result<u32, DecodeError>

Reads one u32 from the input.

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

take_u64!

def take_u64!(self) -> Result<u64, DecodeError>

Reads one u64 from the input.

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

take_i32!

def take_i32!(self) -> Result<i32, DecodeError>

Reads one i32 from the input.

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

take_i64!

def take_i64!(self) -> Result<i64, DecodeError>

Reads one i64 from the input.

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

take_f64!

def take_f64!(self) -> Result<f64, DecodeError>

Reads one f64 from the input.

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

take_string!

def take_string!(self) -> Result<string, DecodeError>

Reads one string from the input.

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

take_bytes!

def take_bytes!(self) -> Result<bytes, DecodeError>

Reads one bytes from the input.

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

takeissome!

def take_is_some!(self) -> Result<bool, DecodeError>

true if a Some payload follows, false for None. @no-doctest: structural decode op; the round-trip tests below exercise it

take_seq!

def take_seq!(self) -> Result<int, DecodeError>

The element / pair count of a sequence or map. @no-doctest: structural decode op; the round-trip tests below exercise it

take_map!

def take_map!(self) -> Result<int, DecodeError>

Reads a map header, returning its length.

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

take_struct!

def take_struct!(self, fields: int) -> Result<(), DecodeError>

Read (and, for a framed format, check) a struct header of fields fields. @no-doctest: structural decode op; the round-trip tests below exercise it

take_variant!

def take_variant!(self) -> Result<u8, DecodeError>

A sum's variant tag. @no-doctest: structural decode op; the round-trip tests below exercise it

position!

def position!(self) -> int

The current read offset, for locating a DecodeError (e.g. the byte a bad variant tag was read from). @no-doctest: structural decode op; the round-trip tests below exercise it

remaining!

def remaining!(self) -> int

Unread bytes left in the source. A decoded collection bounds its element count against this (a sequence of N elements needs at least N wire bytes), so a crafted huge count can't drive an unbounded decode loop. @no-doctest: structural decode op; the round-trip tests below exercise it

take_int!

def take_int!(self) -> Result<int, DecodeError>

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

The read side of Serializer's exact-tier events, in the same shape and for the same reason: each has a default body over the events above, and a Deserializer written before they existed goes on compiling. take_int! is the ground case; the other two reduce to it through the exact decomposition, sentinels included. Reads one arbitrary-precision int written by put_int!: the class byte, then the digits when it says finite.

A class byte outside 0 ..= 3 is BadTag, and digits int.parse rejects are BadTag at the same offset. The payload is structurally a string, and Truncated/BadUtf8 are already spoken for by take_string!.

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

take_decimal!

def take_decimal!(self) -> Result<decimal, DecodeError>

Reads one decimal from the unscaled/scale pair put_decimal! wrote.

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

take_rational!

def take_rational!(self) -> Result<rational, DecodeError>

Reads one rational from the numerator/denominator pair put_rational! wrote.

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

take_f32!

def take_f32!(self) -> Result<f32, DecodeError>

Reads one f32 from the IEEE bit pattern put_f32! wrote. Every 32-bit pattern is some f32, and this cannot fail beyond the underlying read.

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

intsentinel

def _int_sentinel(class: u8) -> Option<int>

The inverse of serializer._int_class for the three sentinel classes; None for a class byte that is neither a sentinel nor the finite 0.

BinaryDeserializer

struct BinaryDeserializer
  src: BytesReader
end

The built-in fixed-width binary format over a BytesReader cursor.

BytesReader

BytesReader, or bytes.BytesReader, 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.

detake!

def _de_take!(src: BytesReader, n: int) -> Result<bytes, DecodeError>

delenprefixed!

def _de_lenprefixed!(src: BytesReader) -> Result<bytes, DecodeError>

A u32 big-endian length prefix, then that many bytes.

beu16

def _be_u16(b: bytes) -> u16

beu32

def _be_u32(b: bytes) -> u32

beu64

def _be_u64(b: bytes) -> u64

deu32!

def _de_u32!(src: BytesReader) -> Result<u32, DecodeError>

u32 read shared by length prefixes, counts, and the public take_u32!.

deu64!

def _de_u64!(src: BytesReader) -> Result<u64, DecodeError>

u64 read shared by the public take_u64! / take_i64! / take_f64!.

impl Deserializer<BinaryDeserializer>

take_bool!

def take_bool!(self) -> Result<bool, DecodeError>

Reads one bool from the input.

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

take_u8!

def take_u8!(self) -> Result<u8, DecodeError>

Reads one u8 from the input.

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

take_u16!

def take_u16!(self) -> Result<u16, DecodeError>

Reads one u16 from the input.

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

take_u32!

def take_u32!(self) -> Result<u32, DecodeError>

Reads one u32 from the input.

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

take_u64!

def take_u64!(self) -> Result<u64, DecodeError>

Reads one u64 from the input.

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

take_i32!

def take_i32!(self) -> Result<i32, DecodeError>

Reads one i32 from the input.

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

take_i64!

def take_i64!(self) -> Result<i64, DecodeError>

Reads one i64 from the input.

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

take_f64!

def take_f64!(self) -> Result<f64, DecodeError>

Reads one f64 from the input.

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

take_string!

def take_string!(self) -> Result<string, DecodeError>

Reads one string from the input.

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

take_bytes!

def take_bytes!(self) -> Result<bytes, DecodeError>

Reads one bytes from the input.

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

takeissome!

def take_is_some!(self) -> Result<bool, DecodeError>

Reads an optional's presence flag; the value follows when true.

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

take_seq!

def take_seq!(self) -> Result<int, DecodeError>

Reads a seq header, returning its length.

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

take_map!

def take_map!(self) -> Result<int, DecodeError>

Reads a map header, returning its length.

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

take_struct!

def take_struct!(self, fields: int) -> Result<(), DecodeError>

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

take_variant!

def take_variant!(self) -> Result<u8, DecodeError>

Reads a sum variant's tag; its payload follows.

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

position!

def position!(self) -> int

The current read offset, in bytes.

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

remaining!

def remaining!(self) -> int

How many bytes remain unread.

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