class val _DiscriminatedCase[
D: (Equatable[D] val & Stringable val),
Msg: Any val]
let disc: D
let codec: Codec[Msg]
let test_fn: {(Msg): Bool} val
new val create(
d: D,
c: Codec[Msg],
t: {(Msg): Bool} val)
=>
disc = d
codec = c
test_fn = t
class val Discriminated[
D: (Equatable[D] val & Stringable val),
Msg: Any val]
is Codec[Msg]
"""
Tagged union dispatch: a discriminator codec selects which variant codec
to use.
On decode, reads the discriminator and dispatches to the matching variant
codec. On encode, tests each variant's narrowing function to find a match,
then writes the discriminator followed by the variant body.
Built via `DiscriminatedBuilder` — not constructed directly.
"""
let _disc_codec: Codec[D]
let _cases: Array[_DiscriminatedCase[D, Msg]] val
new val _create(
dc: Codec[D],
cs: Array[_DiscriminatedCase[D, Msg]] val)
=>
_disc_codec = dc
_cases = cs
fun decode(data: Array[U8] val, offset: USize)
: ((Msg, USize) | DecodeError)
=>
match \exhaustive\ _disc_codec.decode(data, offset)
| (let d: D, let cd: USize) =>
var i: USize = 0
while i < _cases.size() do
try
let c = _cases(i)?
if c.disc == d then
match c.codec.decode(data, offset + cd)
| (let v: Msg, let cv: USize) => return (v, cd + cv)
| let e: DecodeError => return e
end
end
end
i = i + 1
end
MalformedData("Unknown discriminator: " + d.string(), offset)
| let e: DecodeError => e
end
fun _encode_bits(value: Msg, writer: _BitWriter ref)
: (_BitWriter ref | EncodeError)
=>
var i: USize = 0
while i < _cases.size() do
try
let c = _cases(i)?
if c.test_fn(value) then
match _disc_codec._encode_bits(c.disc, writer)
| let w: _BitWriter ref => return c.codec._encode_bits(value, w)
| let e: EncodeError => return e
end
end
end
i = i + 1
end
EncodeViolation("No matching variant for encode")
class ref DiscriminatedBuilder[
D: (Equatable[D] val & Stringable val),
Msg: Any val]
"""
Builder for constructing a `Discriminated` codec.
Register each variant with `case_of`, providing the discriminator value,
a codec for the variant type, and a narrowing function that casts from
`Msg` to the variant. Duplicate discriminators are ignored (first
registration wins). Call `build()` to produce the `Discriminated` codec.
"""
let _disc_codec: Codec[D]
let _cases: Array[_DiscriminatedCase[D, Msg]] ref
new ref create(disc_codec: Codec[D]) =>
_disc_codec = disc_codec
_cases = Array[_DiscriminatedCase[D, Msg]]
fun ref case_of[V: Msg](
disc: D,
codec: Codec[V],
narrow: {(Msg): V ?} val)
: DiscriminatedBuilder[D, Msg] ref
=>
"""
Register a variant with its discriminator, codec, and narrowing function.
"""
for c in _cases.values() do
if c.disc == disc then return this end
end
let widened = Widen[V, Msg](codec, narrow)
let test_fn: {(Msg): Bool} val =
{(m: Msg): Bool => try narrow(m)?; true else false end }
_cases.push(_DiscriminatedCase[D, Msg](disc, widened, test_fn))
this
fun ref build(): Discriminated[D, Msg] =>
let cs: Array[_DiscriminatedCase[D, Msg]] val =
_cases.clone()
Discriminated[D, Msg]._create(_disc_codec, cs)