A streaming Zstandard (RFC 8878) decoder in pure MoonBit: arbitrary input chunks, bounded output slices, memory bounded by the window.
moon add gaato/zstd///|
test "decompress a whole stream" {
let compressed = b"\x28\xb5\x2f\xfd\x20\x05\x29\x00\x00\x68\x65\x6c\x6c\x6f" // zstd.compress(b"hello")
let text = @zstd.decompress(compressed, max_output=16 * 1024 * 1024)
assert_eq(text, b"hello")
}
///|
test "compress and decompress" {
let text = b"hello, hello, hello, zstd"
let compressed = @zstd.compress(text) // level 3, as zstd's default
assert_eq(@zstd.decompress(compressed), text)
let smaller = @zstd.compress(text, level=12)
assert_eq(@zstd.decompress(smaller), text)
}///|
fn decode_all(chunks : Array[Bytes], sink : (BytesView) -> Unit) -> Unit raise {
let decoder = @zstd.Decoder(max_window_bits=23) // 8 MiB, as RFC 9659 asks of HTTP
let out = FixedArray::make(64 * 1024, b'\x00')
for i, chunk in chunks {
let mut input = chunk[:]
for ;; {
let status = decoder.step_into(input, out, end=i == chunks.length() - 1)
input = input[decoder.last_consumed():]
sink(Bytes::from_array(out[:decoder.last_produced()]))
match status {
Done => return
NeedMoreInput => break
NeedMoreOutput => ()
}
}
}
}
///|
test "decode a stream chunk by chunk" {
let decoded = Buffer()
decode_all(
[b"\x28\xb5\x2f", b"\xfd\x20\x05\x29\x00\x00\x68", b"\x65\x6c\x6c\x6f"],
bytes => decoded.write_bytes(bytes.to_owned()),
)
assert_eq(decoded.to_bytes(), b"hello")
}///|
test "stop after the first frame" {
let frame = b"\x28\xb5\x2f\xfd\x20\x05\x29\x00\x00\x68\x65\x6c\x6c\x6f"
let input = Bytes::from_array([..frame[:], ..b"rest"])
let decoder = @zstd.Decoder(until=EndOfMember)
let out = FixedArray::make(64, b'\x00')
let status = decoder.step_into(input, out, end=true)
assert_true(status is Done)
assert_eq(Bytes::from_array(out[:decoder.last_produced()]), b"hello")
assert_eq(input[decoder.last_consumed():], b"rest")
}///|
fn encode_parts(parts : Array[Bytes], sink : (BytesView) -> Unit) -> Unit raise {
let encoder = @zstd.Encoder()
let out = FixedArray::make(64 * 1024, b'\x00')
for i, part in parts {
let action : @zstd.EncodeAction = if i == parts.length() - 1 {
Finish
} else {
Flush
}
let mut input = part[:]
for ;; {
let status = encoder.step_into(input, out, action~)
input = input[encoder.last_consumed():]
sink(Bytes::from_array(out[:encoder.last_produced()]))
if status is (Done | NeedMoreInput) {
break
}
}
}
}
///|
test "encode a stream part by part" {
let encoded = Buffer()
encode_parts([b"<html>", b"<body>hello</body>", b"</html>"], bytes => {
encoded.write_bytes(bytes.to_owned())
})
let decoded = @zstd.decompress(encoded.to_bytes())
assert_eq(decoded, b"<html><body>hello</body></html>")
}pub struct Decoder {
// private fields
}fn step(self : Decoder, input : BytesView, output : MutArrayView[Byte], end~ : Bool) -> Status raise CodecErrorfn Decoder::step(self : Decoder, input : BytesView, output : MutArrayView[Byte], end~ : Bool) -> Status raise CodecErrorfn Decoder::step_into(self : Decoder, input : BytesView, output : FixedArray[Byte], end~ : Bool) -> Status raise CodecErrorpub struct Encoder {
// private fields
}fn step(self : Encoder, input : BytesView, output : MutArrayView[Byte], action~ : EncodeAction) -> Status raise CodecErrorfn Encoder::step(self : Encoder, input : BytesView, output : MutArrayView[Byte], action~ : EncodeAction) -> Status raise CodecErrorfn Encoder::step_into(self : Encoder, input : BytesView, output : FixedArray[Byte], action~ : EncodeAction) -> Status raise CodecErrorfn compress(input : BytesView, level? : Int, checksum? : Bool) -> Bytesfn decompress(input : BytesView, max_output? : Int, max_window_bits? : Int) -> Bytes raise CodecErrorInstall
Download zipA streaming Zstandard (RFC 8878) decoder in pure MoonBit: arbitrary input chunks, bounded output slices, memory bounded by the window.