class val ThenConst[A: Any val] is Codec[A]
"""
Decode a value followed by a literal, keeping only the value. On encode,
write the value followed by the literal.
"""
let _codec: Codec[A]
let _literal: Literal
new val create(codec: Codec[A], literal: Literal) =>
_codec = codec
_literal = literal
fun decode(data: Array[U8] val, offset: USize)
: ((A, USize) | DecodeError)
=>
match \exhaustive\ _codec.decode(data, offset)
| (let va: A, let ca: USize) =>
match \exhaustive\ _literal.decode(data, offset + ca)
| (_, let cl: USize) => (va, ca + cl)
| let e: DecodeError => e
end
| let e: DecodeError => e
end
fun _encode_bits(value: A, writer: _BitWriter ref)
: (_BitWriter ref | EncodeError)
=>
match \exhaustive\ _codec._encode_bits(value, writer)
| let w: _BitWriter ref => _literal._encode_bits(None, w)
| let e: EncodeError => e
end
class val ConstThen[A: Any val] is Codec[A]
"""
Decode a literal followed by a value, keeping only the value. On encode,
write the literal followed by the value.
"""
let _literal: Literal
let _codec: Codec[A]
new val create(literal: Literal, codec: Codec[A]) =>
_literal = literal
_codec = codec
fun decode(data: Array[U8] val, offset: USize)
: ((A, USize) | DecodeError)
=>
match \exhaustive\ _literal.decode(data, offset)
| (_, let cl: USize) =>
match \exhaustive\ _codec.decode(data, offset + cl)
| (let va: A, let ca: USize) => (va, cl + ca)
| let e: DecodeError => e
end
| let e: DecodeError => e
end
fun _encode_bits(value: A, writer: _BitWriter ref)
: (_BitWriter ref | EncodeError)
=>
match \exhaustive\ _literal._encode_bits(None, writer)
| let w: _BitWriter ref => _codec._encode_bits(value, w)
| let e: EncodeError => e
end
class val FlatZip[A: Any val, B: Any val] is Codec[(A, B)]
"""
Dependent sequencing: the second codec is determined by the first decoded
value.
On decode, `A` is decoded first, then `f(A)` produces the codec for `B`.
On encode, `A` is available from the tuple, so `f(A)` can produce the
encoder for `B`.
"""
let _codec_a: Codec[A]
let _f: {(A): Codec[B]} val
new val create(ca: Codec[A], f: {(A): Codec[B]} val) =>
_codec_a = ca
_f = f
fun decode(data: Array[U8] val, offset: USize)
: (((A, B), USize) | DecodeError)
=>
match \exhaustive\ _codec_a.decode(data, offset)
| (let va: A, let ca: USize) =>
let codec_b = _f(va)
match \exhaustive\ codec_b.decode(data, offset + ca)
| (let vb: B, let cb: USize) => ((va, vb), ca + cb)
| let e: DecodeError => e
end
| let e: DecodeError => e
end
fun _encode_bits(value: (A, B), writer: _BitWriter ref)
: (_BitWriter ref | EncodeError)
=>
let codec_b = _f(value._1)
match \exhaustive\ _codec_a._encode_bits(value._1, writer)
| let w: _BitWriter ref => codec_b._encode_bits(value._2, w)
| let e: EncodeError => e
end
class val VariableSize[A: Any val] is Codec[A]
"""
Length-prefixed data: a size codec determines how many bytes the body
codec reads from. The size field is structural — it does not appear in
the decoded type.
On decode: read the size, then decode the body from exactly that many
bytes. On encode: encode the body, measure it, write the size, then the
body.
"""
let _size_codec: Codec[U16]
let _body_codec: Codec[A]
new val create(size_codec: Codec[U16], body_codec: Codec[A]) =>
_size_codec = size_codec
_body_codec = body_codec
fun decode(data: Array[U8] val, offset: USize)
: ((A, USize) | DecodeError)
=>
match \exhaustive\ _size_codec.decode(data, offset)
| (let size: U16, let cs: USize) =>
match \exhaustive\ _body_codec.decode(data, offset + cs)
| (let v: A, let cb: USize) =>
let expected_bits = size.usize() * 8
if cb != expected_bits then
MalformedData(
"VariableSize body consumed " + cb.string() +
" bits, expected " + expected_bits.string(),
offset + cs)
else
(v, cs + cb)
end
| let e: DecodeError => e
end
| let e: DecodeError => e
end
fun _encode_bits(value: A, writer: _BitWriter ref)
: (_BitWriter ref | EncodeError)
=>
let body_writer = _BitWriter
match \exhaustive\ _body_codec._encode_bits(value, body_writer)
| let bw: _BitWriter ref =>
try
let body_bytes = bw.done()?
let size = body_bytes.size().u16()
match \exhaustive\ _size_codec._encode_bits(size, writer)
| let w: _BitWriter ref =>
w.write_bytes(body_bytes)
w
| let e: EncodeError => e
end
else
EncodeViolation("VariableSize body not byte-aligned")
end
| let e: EncodeError => e
end
class val Conditional[A: Any val] is Codec[(A | None)]
"""
Conditionally present field: if the flag is true, decode/encode using
the inner codec; if false, the value is `None`.
"""
let _flag: Bool
let _codec: Codec[A]
new val create(flag: Bool, codec: Codec[A]) =>
_flag = flag
_codec = codec
fun decode(data: Array[U8] val, offset: USize)
: (((A | None), USize) | DecodeError)
=>
if _flag then
match \exhaustive\ _codec.decode(data, offset)
| (let v: A, let consumed: USize) => (v, consumed)
| let e: DecodeError => e
end
else
(None, 0)
end
fun _encode_bits(value: (A | None), writer: _BitWriter ref)
: (_BitWriter ref | EncodeError)
=>
match \exhaustive\ value
| let a: A =>
if not _flag then
return EncodeViolation("Value present but flag is false")
end
_codec._encode_bits(a, writer)
| None => writer
end
class val SkipBits is Codec[None]
"""
Skip a fixed number of bits on decode (reading and discarding them),
write zero bits on encode.
"""
let _count: USize
new val create(count: USize) =>
_count = count
fun decode(data: Array[U8] val, offset: USize)
: ((None, USize) | DecodeError)
=>
let cursor = _BitCursor(data, offset / 8, (offset % 8).u8())
if cursor.available_bits() < _count then return InsufficientData end
(None, _count)
fun _encode_bits(value: None, writer: _BitWriter ref)
: (_BitWriter ref | EncodeError)
=>
writer.write_bits(0, _count.u8())
writer