A streaming Brotli (RFC 7932) decoder and encoder in pure MoonBit: arbitrary input chunks, bounded output slices, memory bounded by the window.
moon add gaato/brotli///|
test "decompress a whole stream" {
let compressed = b"\x0b\x02\x80hello\x03" // brotli.compress(b"hello")
let text = @brotli.decompress(compressed, max_output=16 * 1024 * 1024)
assert_eq(text, b"hello")
}
///|
test "compress and decompress" {
let text = b"hello, hello, hello, brotli"
let compressed = @brotli.compress(text)
assert_eq(@brotli.decompress(compressed), text)
}///|
fn decode_all(chunks : Array[Bytes], sink : (BytesView) -> Unit) -> Unit raise {
let decoder = @brotli.Decoder()
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"\x0b\x02", b"\x80hel", b"lo\x03"], bytes => {
decoded.write_bytes(bytes.to_owned())
})
assert_eq(decoded.to_bytes(), b"hello")
}///|
fn encode_parts(parts : Array[Bytes], sink : (BytesView) -> Unit) -> Unit {
let encoder = @brotli.Encoder()
let out = FixedArray::make(64 * 1024, b'\x00')
for i, part in parts {
let action : @brotli.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 = @brotli.decompress(encoded.to_bytes())
assert_eq(decoded, b"<html><body>hello</body></html>")
}moon add gaato/brotli///|
test "decompress a whole stream" {
let compressed = b"\x0b\x02\x80hello\x03" // brotli.compress(b"hello")
let text = @brotli.decompress(compressed, max_output=16 * 1024 * 1024)
assert_eq(text, b"hello")
}
///|
test "compress and decompress" {
let text = b"hello, hello, hello, brotli"
let compressed = @brotli.compress(text)
assert_eq(@brotli.decompress(compressed), text)
}///|
fn decode_all(chunks : Array[Bytes], sink : (BytesView) -> Unit) -> Unit raise {
let decoder = @brotli.Decoder()
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"\x0b\x02", b"\x80hel", b"lo\x03"], bytes => {
decoded.write_bytes(bytes.to_owned())
})
assert_eq(decoded.to_bytes(), b"hello")
}///|
fn encode_parts(parts : Array[Bytes], sink : (BytesView) -> Unit) -> Unit {
let encoder = @brotli.Encoder()
let out = FixedArray::make(64 * 1024, b'\x00')
for i, part in parts {
let action : @brotli.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 = @brotli.decompress(encoded.to_bytes())
assert_eq(decoded, b"<html><body>hello</body></html>")
}pub struct Decoder {
// private fields
}fn Decoder::step(self : Decoder, input : BytesView, output : MutArrayView[Byte], end? : Bool) -> Status raise BrotliErrorfn Decoder::step_into(self : Decoder, input : BytesView, output : FixedArray[Byte], end? : Bool) -> Status raise BrotliErrorpub struct Encoder {
// private fields
}fn Encoder::step(self : Encoder, input : BytesView, output : MutArrayView[Byte], action? : EncodeAction) -> Statusfn Encoder::step_into(self : Encoder, input : BytesView, output : FixedArray[Byte], action? : EncodeAction) -> Statusfn compress(input : BytesView, window_bits? : Int) -> Bytesfn decompress(input : BytesView, max_output? : Int, max_window_bits? : Int) -> Bytes raise BrotliErrorInstall
Download zipA streaming Brotli (RFC 7932) decoder and encoder in pure MoonBit: arbitrary input chunks, bounded output slices, memory bounded by the window.