Binaries
A <<>> literal builds bytes out of fields whose widths are
measured in bits, and the same syntax used as a pattern takes them apart again.
Building
A string segment contributes its UTF-8 bytes. An integer segment encodes unsigned,
big-endian, into the width it is given; only the low bits survive, so
<<-1:8>> is the byte 255.
fn encode(s Sample) Binary {
payload = binary.from_string('${s.name}=${s.value} ${s.unit}')
flags = match s.gauge {
True -> GAUGE
False -> 0
}
<<'ALM', VERSION:4, flags:4, binary.byte_size(payload):16, payload:binary>>
}
Matching
In a pattern, a field bound earlier sizes a later one: len:16 then
payload:bytes(len). A pattern without a ..rest segment matches
the exact length and nothing else, and a guard can reject a bad version before the body
is touched.
fn decode(frame Binary) Result(Sample, String) {
match frame {
<<'ALM', v:4, _:4, _:16, _:binary>> if v != VERSION -> { Err('unsupported version ${v}') }
<<'ALM', _:4, flags:4, len:16, payload:bytes(len)>> -> { parse_payload(payload, flags) }
_ -> Err('truncated or foreign frame')
}
}
..rest binds everything after the consumed prefix, which is how a stream
buffer peels complete frames and waits for the rest.
fn drain(buffer Binary, seen Int) Int {
match buffer {
<<>> -> seen
<<'ALM', _:4, _:4, len:16, payload:bytes(len), ..rest>> -> {
text = binary.to_string(payload) or '<not text>'
println(' frame ${seen + 1}: ${text}')
drain(rest, seen + 1)
}
_ -> {
println(' ${binary.byte_size(buffer)} bytes left over: wait for more')
seen
}
}
}
Bytes, not text
A binary is bytes, not text. Bytes that are not valid UTF-8 have no
String, so binary.to_string is fallible.
string works in Unicode scalars; binary works in bytes, with
byte_size and bit_size both defined.
binary.parse_int(b, Dec) reads a number straight from bytes.