compress

Compression library for MoonBit: flate, gzip, zlib, lzw, bzip2, snappy, lz4, zstd, brotli

compression
deflate
gzip
zlib
lzw
bzip2
snappy
lz4
zstd
zstandard
brotli
moon add bikallem/compress@0.3.4
Download zip
Author
Version
0.3.4
License
Apache-2.0
Last updated
4 months ago
Downloads
2K

Dependencies

README

#bikallem/compress

A pure MoonBit compression library supporting DEFLATE, gzip, zlib, LZW, bzip2, Brotli, Zstandard, and LZ4. Targets native (Linux, Windows, macOS), JavaScript, and WebAssembly.

#Features

  • Pure MoonBit — no FFI required (optional native acceleration for blit/checksum)
  • Multi-target: native, js, and wasm-gc backends
  • Dynamic Huffman coding with optimal fixed/dynamic block selection
  • Level-differentiated compression: fast greedy (1-3), lazy matching (4-9)
  • SA-IS suffix array construction for O(n) bzip2 BWT
  • Hardware-accelerated CRC-32 (PCLMULQDQ) and Adler-32 (SSSE3) on native, software fallback elsewhere
  • Two-level Huffman table decompression with zero-copy direct output
  • BytesView-based streaming API — zero-copy input slicing
  • Signal protocol streaming — no callbacks, no trait objects, explicit control flow
  • Async streaming for DEFLATE via MoonBit's async/io
  • Cross-validated against Go's compress/* stdlib where applicable, plus external golden vectors for additional formats

#Table of Contents

#Packages

PackageDescription
bikallem/compress/flateDEFLATE compression/decompression (RFC 1951)
bikallem/compress/flate/asyncAsync DEFLATE streaming via @io.Reader/@io.Writer
bikallem/compress/gzipgzip format (RFC 1952)
bikallem/compress/zlibzlib format (RFC 1950)
bikallem/compress/lzwLempel-Ziv-Welch (GIF/TIFF/PDF)
bikallem/compress/brotliBrotli compression/decompression (RFC 7932)
bikallem/compress/bzip2bzip2 compression/decompression
bikallem/compress/zstdExperimental Zstandard frame compression/decompression with dictionary support
bikallem/compress/lz4LZ4 frame compression/decompression for independent-block frames
bikallem/compress/checksumCRC-32 and Adler-32 checksums

#Installation

moon add bikallem/compress

#Quick Start

Every package provides one-shot compress/decompress functions for simple use cases:

// DEFLATE (level defaults to DefaultCompression)
let compressed = @flate.compress(data)
let compressed = @flate.compress(data, level=BestSpeed)
let decompressed = @flate.decompress(compressed)

// gzip
let compressed = @gzip.compress(data)
let compressed = @gzip.compress(data, level=BestCompression, header={ name: "data.txt", ..Header::default() })
let (decompressed, header) = @gzip.decompress(compressed)

// zlib (supports preset dictionaries)
let compressed = @zlib.compress(data)
let compressed = @zlib.compress(data, dict=my_dict, level=BestSpeed)
let decompressed = @zlib.decompress(compressed)

// LZW
let compressed = @lzw.compress(data, LSB, 8)
let decompressed = @lzw.decompress(compressed, LSB, 8)

// Brotli (level 0-11)
let compressed = @brotli.compress(data)
let compressed = @brotli.compress(data, level=Level(1))
let decompressed = @brotli.decompress(compressed)

// bzip2 (level 1-9, controls block size)
let compressed = @bzip2.compress(data)
let compressed = @bzip2.compress(data, level=9)
let decompressed = @bzip2.decompress(compressed)

// Zstandard
let compressed = @zstd.compress(data)
let compressed = @zstd.compress(data, level=Fast)
let compressed = @zstd.compress(data, dict=my_zstd_dict)
let decompressed = @zstd.decompress(compressed)
let decompressed = @zstd.decompress(compressed, dict=my_zstd_dict)

// LZ4 (independent-block frames)
let compressed = @lz4.compress(data)
let decompressed = @lz4.decompress(compressed)

// Checksums
let crc = @checksum.crc32(data[:])
let adler = @checksum.adler32(data[:])

#Streaming API

All packages provide Deflater (compressor) and Inflater (decompressor) types with a signal-protocol interface. flate, gzip, zlib, lzw, bzip2, brotli, and zstd stream incrementally. snappy and lz4 currently use buffered wrappers: they accept chunked input, but decompression waits for a complete stream/frame and compression emits output on finalization.

#Compression

Feed data with encode(Some(chunk[:])), finalize with encode(None):

let d = @flate.Deflater::new(level=BestSpeed)
match d.encode(Some(data[:])) {
Ok => () // input buffered, no output yet
Data(out) => ... // compressed output ready
End => ... // shouldn't happen mid-stream
Error(e) => ... // compression error
}
loop d.encode(None) {
Data(out) => { write(out); continue d.encode(None) }
End => break
Ok | Error(_) => break
}

#Decompression

Feed compressed data with src(chunk[:]), pull output with decode():

let d = @flate.Inflater::new()
d.src(compressed_chunk[:])
loop d.decode() {
Await => { d.src(next_chunk[:]); continue d.decode() }
Data(out) => { write(out); continue d.decode() }
End => break
Error(e) => ...
}

#Format Wrappers

gzip and zlib deflaters/inflaters handle headers, checksums, and trailers automatically:

// gzip with custom header
let d = @gzip.Deflater::new(header={ name: "data.txt", ..Header::default() })
// Access the header after decompression
let header = inflater.header()

// zlib with preset dictionary
let d = @zlib.Deflater::new(dict=my_dict)
let i = @zlib.Inflater::new(dict=my_dict)

// LZW with bit order and literal width
let d = @lzw.Deflater::new(MSB, 8)
let i = @lzw.Inflater::new(MSB, 8)

// Brotli
let d = @brotli.Deflater::new(level=Level(6))
let i = @brotli.Inflater::new()

// bzip2
let d = @bzip2.Deflater::new(level=9)
let i = @bzip2.Inflater::new()

// Zstandard
let d = @zstd.Deflater::new(level=Fast, dict=my_zstd_dict)
let i = @zstd.Inflater::new(dict=my_zstd_dict)

// LZ4 (buffered wrapper, independent-block frames only)
let d = @lz4.Deflater::new()
let i = @lz4.Inflater::new()

// Get remaining unprocessed input after decompression
let leftover = inflater.remaining()

#Async Streaming

The flate/async package provides async wrappers that work with MoonBit's @io.Reader and @io.Writer interfaces:

// Async DEFLATE compression
async fn compress_stream(reader : &@io.Reader, writer : &@io.Writer) -> Unit {
@flate.async.compress(reader, writer, level=BestSpeed)
}

// Async DEFLATE decompression
async fn decompress_stream(reader : &@io.Reader, writer : &@io.Writer) -> Unit {
@flate.async.decompress(reader, writer)
}

#Compression Levels

DEFLATE, gzip, and zlib support compression levels via @flate.CompressionLevel:

LevelDescription
NoCompressionStore blocks only (level 0)
BestSpeedFastest compression (level 1)
Level(2..8)Trade-off between speed and ratio
BestCompressionSmallest output (level 9)
DefaultCompressionBalanced default (level 6)
HuffmanOnlyHuffman encoding, no LZ77 matching

bzip2 uses its own level parameter (1-9), controlling block size (N x 100KB).

Brotli uses @brotli.CompressionLevel: Level(0) through Level(11), Default (level 6), or Best (level 11). Higher levels use longer hash chains for better compression ratios.

Zstandard uses @zstd.CompressionLevel: Fast, Default, Best, or Level(Int).

Zstandard status: The current codec supports raw, RLE, Huffman-compressed, and treeless literals plus predefined, RLE, repeat, and custom FSE sequence tables. Raw-content and formatted dictionaries are supported for decoding, one-shot compression can emit dictionary IDs for formatted dictionaries, and the Deflater/Inflater wrappers now process frames incrementally. Compression is still an experimental subset encoder rather than a full parity implementation of upstream zstd.

Brotli features: The decoder is fully RFC 7932 compliant, including the 122KB static dictionary with 121 word transforms. The encoder supports context modeling (level 5+), which uses the previous byte to select among multiple literal Huffman trees for better compression of structured text. Block splitting and static dictionary compression by the encoder are not yet implemented. Quality levels 10-11 use the same hash-chain algorithm as level 9. Output is verified against Go's andybalholm/brotli reference decoder.

#Checksums

Stateful hashers implement the Hasher trait for incremental updates:

let h = @checksum.CRC32::new()
h.update(chunk1[:])
h.update(chunk2[:])
let result = h.checksum()

#Performance

Benchmarked on the native backend against Go's standard library (v0.1.2). Ratio < 1 means MoonBit is faster.

Run benchmarks: ./tools/bench.sh --go

#DEFLATE

BenchmarkMoonBitGoRatio
compress 1 KB16 µs67 µs0.24x
compress 10 KB63 µs124 µs0.51x
compress 100 KB570 µs298 µs1.91x
compress 1 MB6.2 ms2.0 ms3.06x
compress speed 1 KB12 µs134 µs0.09x
compress speed 10 KB15 µs125 µs0.12x
decompress 1 KB0.82 µs4.0 µs0.21x
decompress 10 KB5.1 µs10.7 µs0.47x
decompress 100 KB29 µs58 µs0.50x
decompress 1 MB305 µs915 µs0.33x
decompress 10 MB4.8 ms10.5 ms0.46x

Decompression is 2-5x faster than Go at all sizes. BestSpeed compression is 8-11x faster. Default compression is faster up to 10 KB; at larger sizes Go's more aggressive match-finding gives it an edge.

#bzip2

BenchmarkMoonBitGoRatio
compress 1 KB54 µs754 µs0.07x
compress 10 KB500 µs2,047 µs0.24x
compress 100 KB5.4 ms10.2 ms0.53x
compress 1 MB107 ms112 ms0.95x
decompress 1 KB123 µs420 µs0.29x
decompress 10 KB167 µs541 µs0.31x
decompress 100 KB680 µs1,225 µs0.55x
decompress 1 MB6.0 ms7.2 ms0.83x

bzip2 uses SA-IS (O(n) suffix array construction) for the Burrows-Wheeler Transform. Go's benchmark uses the system bzip2 binary (C) for compression and Go's compress/bzip2 for decompression.

#LZW

BenchmarkMoonBitGoRatio
compress 1 KB7.4 µs8.6 µs0.86x
compress 10 KB41 µs42 µs0.98x
compress 100 KB424 µs427 µs0.99x
compress 1 MB4.6 ms4.3 ms1.05x
decompress 1 KB3.7 µs4.7 µs0.78x
decompress 10 KB16 µs26 µs0.63x
decompress 100 KB140 µs244 µs0.57x
decompress 1 MB1.6 ms2.7 ms0.57x

LZW compression is at parity with Go. Decompression is 1.3-1.8x faster.

#License

Apache-2.0