Scarlet Industries

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.