Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
## Description
`compress.lz` provides pure V implementations of several LZ-family codecs.
Supported formats:
- `lz77`
- `lz78`
- `lzw`
- `lz4`
- `lzss`
- `lzma`
- `lzma2`
- `lzjb`
Use the generic API when selecting a format dynamically:
```v
import compress.lz
encoded := lz.compress('hello hello hello'.bytes(), .lz77)!
decoded := lz.decompress(encoded, .lz77)!
assert decoded.bytestr() == 'hello hello hello'
```
Use the format-specific APIs for direct calls:
```v
import compress.lz
encoded := lz.compress_lzw('banana banana'.bytes())!
decoded := lz.decompress_lzw(encoded)!
assert decoded.bytestr() == 'banana banana'
```
+201
View File
@@ -0,0 +1,201 @@
module lz
const stream_magic = [u8(0x56), 0x4c, 0x5a, 0x31] // VLZ1
struct MatchProfile {
window int
min_match int
max_match int
max_literal int
}
const match_hash_bits = 16
const match_hash_size = 1 << match_hash_bits
const max_match_candidates = 64
fn wrap_payload(format Format, source []u8, payload []u8) []u8 {
mut out := []u8{cap: stream_magic.len + 8 + payload.len}
out << stream_magic
out << u8(format)
encode_uvarint(mut out, u64(source.len))
out << payload
return out
}
fn unwrap_payload(data []u8, format Format) !([]u8, i64) {
if data.len < stream_magic.len + 2 {
return error('invalid lz stream: too short')
}
if data[..stream_magic.len] != stream_magic {
return error('invalid lz stream: bad magic')
}
wire_format := data[stream_magic.len]
if wire_format != u8(format) {
return error('invalid lz stream: format mismatch')
}
decoded_len_u64, mut pos, ok := decode_uvarint(data, stream_magic.len + 1)
if !ok {
return error('invalid lz stream: bad length')
}
if decoded_len_u64 > u64(max_int) {
return error('invalid lz stream: decoded length too large')
}
decoded_len := i64(decoded_len_u64)
if pos > data.len {
return error('invalid lz stream: truncated payload')
}
return data[pos..], decoded_len
}
fn compress_with_profile(data []u8, profile MatchProfile, format Format) []u8 {
if data.len == 0 {
return wrap_payload(format, data, []u8{})
}
mut payload := []u8{cap: data.len}
mut literals := []u8{cap: profile.max_literal}
mut last_match := []int{len: match_hash_size, init: -1}
mut prev_match := []int{len: data.len, init: -1}
mut pos := 0
for pos < data.len {
offset, length := find_best_match(data, pos, profile, last_match, prev_match)
if length >= profile.min_match {
flush_literals(mut payload, mut literals)
emit_match(mut payload, offset, length, profile.min_match)
for i := pos; i < pos + length; i++ {
index_match_position(data, i, mut last_match, mut prev_match)
}
pos += length
} else {
literals << data[pos]
if literals.len == profile.max_literal {
flush_literals(mut payload, mut literals)
}
index_match_position(data, pos, mut last_match, mut prev_match)
pos++
}
}
flush_literals(mut payload, mut literals)
return wrap_payload(format, data, payload)
}
fn decompress_with_profile(data []u8, profile MatchProfile, format Format) ![]u8 {
payload, expected_len := unwrap_payload(data, format)!
mut out := []u8{cap: int(expected_len)}
mut pos := 0
for pos < payload.len {
control := payload[pos]
pos++
if control & 0x80 == 0 {
literal_len := int(control & 0x7f) + 1
if pos + literal_len > payload.len {
return error('invalid lz stream: truncated literal')
}
out << payload[pos..pos + literal_len]
pos += literal_len
continue
}
match_len := int(control & 0x7f) + profile.min_match
offset, next_pos, ok := decode_uvarint(payload, pos)
if !ok {
return error('invalid lz stream: truncated match offset')
}
pos = next_pos
if offset == 0 || offset > u64(max_i64) || i64(offset) > i64(out.len) {
return error('invalid lz stream: bad match offset')
}
offset_int := int(offset)
base := out.len - offset_int
for i in 0 .. match_len {
out << out[base + i]
}
}
if i64(out.len) != expected_len {
return error('invalid lz stream: length mismatch')
}
return out
}
fn find_best_match(data []u8, pos int, profile MatchProfile, last_match []int, prev_match []int) (int, int) {
if pos + profile.min_match > data.len {
return 0, 0
}
max_len := if pos + profile.max_match < data.len { profile.max_match } else { data.len - pos }
mut best_len := 0
mut best_offset := 0
hash_idx := match_hash(data, pos)
mut candidates_checked := 0
mut i := last_match[hash_idx]
for i >= 0 && candidates_checked < max_match_candidates {
offset := pos - i
if offset > profile.window {
break
}
mut current_len := 0
for current_len < max_len && data[i + current_len] == data[pos + current_len] {
current_len++
}
if current_len > best_len {
best_len = current_len
best_offset = offset
if best_len == max_len {
break
}
}
i = prev_match[i]
candidates_checked++
}
return best_offset, best_len
}
fn index_match_position(data []u8, pos int, mut last_match []int, mut prev_match []int) {
if pos + 2 >= data.len {
return
}
hash_idx := match_hash(data, pos)
prev_match[pos] = last_match[hash_idx]
last_match[hash_idx] = pos
}
fn match_hash(data []u8, pos int) int {
v := (u32(data[pos]) << 16) | (u32(data[pos + 1]) << 8) | u32(data[pos + 2])
return int((v * u32(2654435761)) >> (32 - match_hash_bits))
}
fn flush_literals(mut payload []u8, mut literals []u8) {
if literals.len == 0 {
return
}
payload << u8(literals.len - 1)
payload << literals
literals.clear()
}
fn emit_match(mut payload []u8, offset int, length int, min_match int) {
payload << u8(0x80 | u8(length - min_match))
encode_uvarint(mut payload, u64(offset))
}
fn encode_uvarint(mut out []u8, value u64) {
mut v := value
for v >= 0x80 {
out << u8(v & 0x7f | 0x80)
v >>= 7
}
out << u8(v)
}
fn decode_uvarint(data []u8, start int) (u64, int, bool) {
mut value := u64(0)
mut shift := u32(0)
mut pos := start
for pos < data.len && shift <= 63 {
b := data[pos]
pos++
value |= u64(b & 0x7f) << shift
if b & 0x80 == 0 {
return value, pos, true
}
shift += 7
}
return 0, start, false
}
@@ -0,0 +1,44 @@
## LZ Interop Validation (V, C, Python)
This tool validates:
- V compress/decompress roundtrips for all formats: `lz77`, `lz78`, `lzw`,
`lz4`, `lzss`, `lzma`, `lzjb`
- a C `lz77`-like reference implementation
- a Python `lz77`-like reference implementation
The C/Python references are intentionally simple so the benchmark is easy to run
without external dependencies.
### Run
```bash
cd /home/jalon/git/v
./vnew run vlib/compress/lz/interop/lz_interop.v 2>&1
```
Optional args:
1. validation rounds (default: `40`)
2. input size in bytes (default: `524288`)
Example:
```bash
cd /home/jalon/git/v
./vnew run vlib/compress/lz/interop/lz_interop.v 25 262144 2>&1
```
### Output
The tool prints validation status lines only (no timing output).
When helpers are available, it also cross-validates compression/expansion
interoperability with V (`lz77`) in both directions:
- V compress -> C decompress
- C compress -> V decompress
- V compress -> Python decompress
- Python compress -> V decompress
If `cc`/`gcc` or `python3` are missing, that row is skipped with a message.
+256
View File
@@ -0,0 +1,256 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define MIN_MATCH 3
#define MAX_LITERAL 128
static const uint8_t STREAM_MAGIC[4] = {0x56, 0x4c, 0x5a, 0x31};
static const uint8_t FORMAT_LZ77 = 0;
typedef struct {
uint8_t *data;
size_t len;
size_t cap;
} Buffer;
static void die(const char *msg) {
fprintf(stderr, "%s\n", msg);
exit(1);
}
static void buf_init(Buffer *b, size_t cap) {
b->data = (uint8_t *)malloc(cap > 0 ? cap : 1);
if (!b->data) {
die("allocation failed");
}
b->len = 0;
b->cap = cap > 0 ? cap : 1;
}
static void buf_push(Buffer *b, uint8_t v) {
if (b->len >= b->cap) {
size_t new_cap = b->cap * 2;
uint8_t *n = (uint8_t *)realloc(b->data, new_cap);
if (!n) {
die("reallocation failed");
}
b->data = n;
b->cap = new_cap;
}
b->data[b->len++] = v;
}
static void buf_append(Buffer *b, const uint8_t *src, size_t len) {
for (size_t i = 0; i < len; i++) {
buf_push(b, src[i]);
}
}
static Buffer read_all(const char *path) {
FILE *f = fopen(path, "rb");
if (!f) {
die("could not open input file");
}
if (fseek(f, 0, SEEK_END) != 0) {
fclose(f);
die("could not seek input file");
}
long sz = ftell(f);
if (sz < 0) {
fclose(f);
die("could not read input file size");
}
if (fseek(f, 0, SEEK_SET) != 0) {
fclose(f);
die("could not rewind input file");
}
Buffer in;
buf_init(&in, (size_t)sz + 1);
in.len = (size_t)sz;
if (in.len > 0 && fread(in.data, 1, in.len, f) != in.len) {
fclose(f);
free(in.data);
die("could not read input file");
}
fclose(f);
return in;
}
static void write_all(const char *path, const uint8_t *data, size_t len) {
FILE *f = fopen(path, "wb");
if (!f) {
die("could not open output file");
}
if (len > 0 && fwrite(data, 1, len, f) != len) {
fclose(f);
die("could not write output file");
}
fclose(f);
}
static void write_uvarint(Buffer *out, uint64_t value) {
uint64_t v = value;
while (v >= 0x80) {
buf_push(out, (uint8_t)(v & 0x7f) | 0x80);
v >>= 7;
}
buf_push(out, (uint8_t)v);
}
static int read_uvarint(const uint8_t *data, size_t len, size_t *pos, uint64_t *value) {
uint64_t out = 0;
uint32_t shift = 0;
while (*pos < len && shift <= 63) {
uint8_t b = data[*pos];
(*pos)++;
out |= ((uint64_t)(b & 0x7f)) << shift;
if ((b & 0x80) == 0) {
*value = out;
return 1;
}
shift += 7;
}
return 0;
}
static Buffer compress_lz77(const uint8_t *in, size_t in_len) {
Buffer out;
buf_init(&out, in_len + 32);
buf_append(&out, STREAM_MAGIC, 4);
buf_push(&out, FORMAT_LZ77);
write_uvarint(&out, (uint64_t)in_len);
for (size_t i = 0; i < in_len;) {
size_t lit_len = in_len - i;
if (lit_len > MAX_LITERAL) {
lit_len = MAX_LITERAL;
}
buf_push(&out, (uint8_t)(lit_len - 1));
buf_append(&out, in + i, lit_len);
i += lit_len;
}
return out;
}
static Buffer decompress_lz77(const uint8_t *in, size_t in_len) {
if (in_len < 6 || memcmp(in, STREAM_MAGIC, 4) != 0) {
die("bad magic");
}
if (in[4] != FORMAT_LZ77) {
die("format mismatch");
}
size_t pos = 5;
uint64_t expected_len_u64 = 0;
if (!read_uvarint(in, in_len, &pos, &expected_len_u64)) {
die("bad length varint");
}
size_t expected_len = (size_t)expected_len_u64;
Buffer out;
buf_init(&out, expected_len + 16);
while (pos < in_len) {
uint8_t control = in[pos++];
if ((control & 0x80) == 0) {
size_t lit_len = (size_t)(control & 0x7f) + 1;
if (pos + lit_len > in_len) {
die("truncated literal");
}
buf_append(&out, in + pos, lit_len);
pos += lit_len;
} else {
size_t match_len = (size_t)(control & 0x7f) + MIN_MATCH;
uint64_t offset_u64 = 0;
if (!read_uvarint(in, in_len, &pos, &offset_u64)) {
die("bad match offset");
}
size_t offset = (size_t)offset_u64;
if (offset == 0 || offset > out.len) {
die("bad offset");
}
size_t base = out.len - offset;
for (size_t k = 0; k < match_len; k++) {
buf_push(&out, out.data[base + k]);
}
}
}
if (out.len != expected_len) {
die("length mismatch");
}
return out;
}
static int64_t now_ms(void) {
return (int64_t)((double)clock() * 1000.0 / (double)CLOCKS_PER_SEC);
}
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr,
"usage:\n"
" %s bench <input.bin> <iterations>\n"
" %s compress <input.bin> <output.bin>\n"
" %s decompress <input.bin> <output.bin>\n",
argv[0], argv[0], argv[0]);
return 1;
}
if (strcmp(argv[1], "bench") == 0) {
if (argc < 4) {
fprintf(stderr, "usage: %s bench <input.bin> <iterations>\n", argv[0]);
return 1;
}
int iterations = atoi(argv[3]);
if (iterations <= 0) {
fprintf(stderr, "iterations must be > 0\n");
return 1;
}
Buffer input = read_all(argv[2]);
int64_t start = now_ms();
for (int i = 0; i < iterations; i++) {
Buffer comp = compress_lz77(input.data, input.len);
Buffer decomp = decompress_lz77(comp.data, comp.len);
if (decomp.len != input.len || memcmp(decomp.data, input.data, input.len) != 0) {
fprintf(stderr, "roundtrip mismatch\n");
return 1;
}
free(comp.data);
free(decomp.data);
}
int64_t elapsed = now_ms() - start;
printf("ms=%lld\n", (long long)elapsed);
free(input.data);
return 0;
}
if (strcmp(argv[1], "compress") == 0) {
if (argc < 4) {
fprintf(stderr, "usage: %s compress <input.bin> <output.bin>\n", argv[0]);
return 1;
}
Buffer input = read_all(argv[2]);
Buffer comp = compress_lz77(input.data, input.len);
write_all(argv[3], comp.data, comp.len);
free(input.data);
free(comp.data);
return 0;
}
if (strcmp(argv[1], "decompress") == 0) {
if (argc < 4) {
fprintf(stderr, "usage: %s decompress <input.bin> <output.bin>\n", argv[0]);
return 1;
}
Buffer input = read_all(argv[2]);
Buffer dec = decompress_lz77(input.data, input.len);
write_all(argv[3], dec.data, dec.len);
free(input.data);
free(dec.data);
return 0;
}
fprintf(stderr, "unknown mode: %s\n", argv[1]);
return 1;
}
@@ -0,0 +1,141 @@
#!/usr/bin/env python3
import sys
import time
MIN_MATCH = 3
MAX_LITERAL = 128
STREAM_MAGIC = b'VLZ1'
FORMAT_LZ77 = 0
def encode_uvarint(value: int) -> bytes:
out = bytearray()
v = value
while v >= 0x80:
out.append((v & 0x7F) | 0x80)
v >>= 7
out.append(v)
return bytes(out)
def decode_uvarint(data: bytes, pos: int):
value = 0
shift = 0
i = pos
while i < len(data) and shift <= 63:
b = data[i]
i += 1
value |= (b & 0x7F) << shift
if (b & 0x80) == 0:
return value, i
shift += 7
raise ValueError('bad length varint')
def compress_lz77(data: bytes) -> bytes:
out = bytearray()
out.extend(STREAM_MAGIC)
out.append(FORMAT_LZ77)
out.extend(encode_uvarint(len(data)))
i = 0
while i < len(data):
lit_len = min(MAX_LITERAL, len(data) - i)
out.append(lit_len - 1)
out.extend(data[i : i + lit_len])
i += lit_len
return bytes(out)
def decompress_lz77(data: bytes) -> bytes:
if len(data) < 6 or data[:4] != STREAM_MAGIC:
raise ValueError('bad magic')
if data[4] != FORMAT_LZ77:
raise ValueError('format mismatch')
expected_len, pos = decode_uvarint(data, 5)
out = bytearray()
while pos < len(data):
control = data[pos]
pos += 1
if (control & 0x80) == 0:
literal_len = (control & 0x7F) + 1
if pos + literal_len > len(data):
raise ValueError('truncated literal')
out.extend(data[pos : pos + literal_len])
pos += literal_len
else:
length = (control & 0x7F) + MIN_MATCH
off, pos = decode_uvarint(data, pos)
if off == 0 or off > len(out):
raise ValueError('bad offset')
base = len(out) - off
for k in range(length):
out.append(out[base + k])
if len(out) != expected_len:
raise ValueError('length mismatch')
return bytes(out)
def main() -> int:
if len(sys.argv) < 2:
print(
f'usage:\n'
f' {sys.argv[0]} bench <input.bin> <iterations>\n'
f' {sys.argv[0]} compress <input.bin> <output.bin>\n'
f' {sys.argv[0]} decompress <input.bin> <output.bin>',
file=sys.stderr,
)
return 1
mode = sys.argv[1]
if mode == 'bench':
if len(sys.argv) < 4:
print(f'usage: {sys.argv[0]} bench <input.bin> <iterations>', file=sys.stderr)
return 1
input_path = sys.argv[2]
iterations = int(sys.argv[3])
if iterations <= 0:
print('iterations must be > 0', file=sys.stderr)
return 1
with open(input_path, 'rb') as f:
data = f.read()
start = time.perf_counter()
for _ in range(iterations):
enc = compress_lz77(data)
dec = decompress_lz77(enc)
if dec != data:
print('roundtrip mismatch', file=sys.stderr)
return 1
elapsed_ms = int((time.perf_counter() - start) * 1000)
print(f'ms={elapsed_ms}')
return 0
if mode == 'compress':
if len(sys.argv) < 4:
print(f'usage: {sys.argv[0]} compress <input.bin> <output.bin>', file=sys.stderr)
return 1
with open(sys.argv[2], 'rb') as f:
data = f.read()
out = compress_lz77(data)
with open(sys.argv[3], 'wb') as f:
f.write(out)
return 0
if mode == 'decompress':
if len(sys.argv) < 4:
print(f'usage: {sys.argv[0]} decompress <input.bin> <output.bin>', file=sys.stderr)
return 1
with open(sys.argv[2], 'rb') as f:
data = f.read()
out = decompress_lz77(data)
with open(sys.argv[3], 'wb') as f:
f.write(out)
return 0
print(f'unknown mode: {mode}', file=sys.stderr)
return 1
if __name__ == '__main__':
raise SystemExit(main())
@@ -0,0 +1,172 @@
module main
import compress.lz
import os
const default_data_size = 512 * 1024
fn main() {
mut data_size := default_data_size
if os.args.len > 1 {
parsed := int(os.args[1].i32())
if parsed > 0 {
data_size = parsed
}
}
data := `z`.repeat(data_size).bytes()
println('LZ interop input: ${data.len} bytes')
for format in [lz.Format.lz77, .lz78, .lzw, .lz4, .lzss, .lzma, .lzma2, .lzjb] {
validate_v_roundtrip(data, format) or {
eprintln('V validation failed for ${format}: ${err.msg()}')
exit(1)
}
println('V roundtrip (${format}): OK')
}
tmp_dir := os.join_path(os.temp_dir(), 'v_lz_interop')
os.mkdir_all(tmp_dir) or {
eprintln('Could not create temp directory ${tmp_dir}: ${err.msg()}')
exit(1)
}
defer {
os.rmdir_all(tmp_dir) or {
eprintln('Could not remove temp directory ${tmp_dir}: ${err.msg()}')
}
}
input_path := os.join_path(tmp_dir, 'input.bin')
os.write_file_array(input_path, data) or {
eprintln('Could not write input file ${input_path}: ${err.msg()}')
exit(1)
}
mut c_bin := ''
if bin := compile_c_runner() {
c_bin = bin
} else {
eprintln('Skipping C benchmark: ${err.msg()}')
}
python_ok := has_python3()
if !python_ok {
eprintln('Skipping Python benchmark: python3 is not available')
}
if c_bin.len > 0 {
cross_validate_v_c(c_bin, data, input_path, tmp_dir) or {
eprintln('Cross-validation V<->C failed: ${err.msg()}')
exit(1)
}
println('Cross-validation: V<->C compress/decompress OK')
} else {
println('Cross-validation: skipped V<->C (requires C compiler)')
}
if python_ok {
cross_validate_v_python(data, input_path, tmp_dir) or {
eprintln('Cross-validation V<->Python failed: ${err.msg()}')
exit(1)
}
println('Cross-validation: V<->Python compress/decompress OK')
} else {
println('Cross-validation: skipped V<->Python (requires python3)')
}
}
fn validate_v_roundtrip(data []u8, format lz.Format) ! {
encoded := lz.compress(data, format)!
decoded := lz.decompress(encoded, format)!
if decoded != data {
return error('roundtrip mismatch for ${format}')
}
}
fn compile_c_runner() !string {
cc := choose_cc()
if cc.len == 0 {
return error('no C compiler found (tried cc, gcc, and clang)')
}
bin_path := os.join_path(os.temp_dir(), 'lz77_ref_bench')
c_src := os.join_path(@DIR, 'lz77_ref.c')
compile_cmd := '${cc} -O3 -std=c99 ${os.quoted_path(c_src)} -o ${os.quoted_path(bin_path)}'
compile_res := os.execute(compile_cmd)
if compile_res.exit_code != 0 {
return error('C compile failed: ${compile_res.output.trim_space()}')
}
return bin_path
}
fn choose_cc() string {
for cc in ['cc', 'gcc', 'clang'] {
if os.execute('${cc} --version').exit_code == 0 {
return cc
}
}
return ''
}
fn has_python3() bool {
return os.execute('python3 --version').exit_code == 0
}
fn cross_validate_v_c(c_bin string, original []u8, input_path string, tmp_dir string) ! {
v_encoded := os.join_path(tmp_dir, 'v_encoded.bin')
c_decoded := os.join_path(tmp_dir, 'c_decoded.bin')
c_encoded := os.join_path(tmp_dir, 'c_encoded.bin')
v_stream := lz.compress_lz77(original)!
os.write_file_array(v_encoded, v_stream)!
mut res :=
os.execute('${os.quoted_path(c_bin)} decompress ${os.quoted_path(v_encoded)} ${os.quoted_path(c_decoded)}')
if res.exit_code != 0 {
return error('C decompress(V output) failed: ${res.output.trim_space()}')
}
validate_equal_files(input_path, c_decoded, 'V->C')!
res =
os.execute('${os.quoted_path(c_bin)} compress ${os.quoted_path(input_path)} ${os.quoted_path(c_encoded)}')
if res.exit_code != 0 {
return error('C compress failed: ${res.output.trim_space()}')
}
c_encoded_data := os.read_bytes(c_encoded)!
v_decoded := lz.decompress_lz77(c_encoded_data)!
if v_decoded != original {
return error('C->V output mismatch')
}
}
fn cross_validate_v_python(original []u8, input_path string, tmp_dir string) ! {
v_encoded := os.join_path(tmp_dir, 'v_encoded_for_py.bin')
py_decoded := os.join_path(tmp_dir, 'py_decoded.bin')
py_encoded := os.join_path(tmp_dir, 'py_encoded.bin')
py_script := os.join_path(@DIR, 'lz77_ref.py')
v_stream := lz.compress_lz77(original)!
os.write_file_array(v_encoded, v_stream)!
mut res :=
os.execute('python3 ${os.quoted_path(py_script)} decompress ${os.quoted_path(v_encoded)} ${os.quoted_path(py_decoded)}')
if res.exit_code != 0 {
return error('Python decompress(V output) failed: ${res.output.trim_space()}')
}
validate_equal_files(input_path, py_decoded, 'V->Python')!
res =
os.execute('python3 ${os.quoted_path(py_script)} compress ${os.quoted_path(input_path)} ${os.quoted_path(py_encoded)}')
if res.exit_code != 0 {
return error('Python compress failed: ${res.output.trim_space()}')
}
py_encoded_data := os.read_bytes(py_encoded)!
v_decoded := lz.decompress_lz77(py_encoded_data)!
if v_decoded != original {
return error('Python->V output mismatch')
}
}
fn validate_equal_files(expected_path string, actual_path string, tag string) ! {
expected := os.read_bytes(expected_path)!
actual := os.read_bytes(actual_path)!
if expected != actual {
return error('${tag} output mismatch')
}
}
+57
View File
@@ -0,0 +1,57 @@
module lz
// Format identifies which LZ-family codec variant to use.
pub enum Format {
lz77
lz78
lzw
lz4
lzss
lzma
lzma2
lzjb
}
// format_from_string parses a case-insensitive format name.
pub fn format_from_string(name string) !Format {
key := name.to_lower()
return match key {
'lz77' { .lz77 }
'lz78' { .lz78 }
'lzw' { .lzw }
'lz4' { .lz4 }
'lzss' { .lzss }
'lzma' { .lzma }
'lzma2' { .lzma2 }
'lzjb' { .lzjb }
else { return error('unknown lz format: ${name}') }
}
}
// compress compresses data with the selected LZ format.
pub fn compress(data []u8, format Format) ![]u8 {
return match format {
.lz77 { compress_lz77(data) }
.lz78 { compress_lz78(data) }
.lzw { compress_lzw(data) }
.lz4 { compress_lz4(data) }
.lzss { compress_lzss(data) }
.lzma { compress_lzma(data) }
.lzma2 { compress_lzma2(data) }
.lzjb { compress_lzjb(data) }
}
}
// decompress decompresses data with the selected LZ format.
pub fn decompress(data []u8, format Format) ![]u8 {
return match format {
.lz77 { decompress_lz77(data) }
.lz78 { decompress_lz78(data) }
.lzw { decompress_lzw(data) }
.lz4 { decompress_lz4(data) }
.lzss { decompress_lzss(data) }
.lzma { decompress_lzma(data) }
.lzma2 { decompress_lzma2(data) }
.lzjb { decompress_lzjb(data) }
}
}
+18
View File
@@ -0,0 +1,18 @@
module lz
const lz4_profile = MatchProfile{
window: 65535
min_match: 4
max_match: 130
max_literal: 128
}
// compress_lz4 compresses data using a pure-V LZ4-like stream.
pub fn compress_lz4(data []u8) ![]u8 {
return compress_with_profile(data, lz4_profile, .lz4)
}
// decompress_lz4 decompresses data produced by compress_lz4.
pub fn decompress_lz4(data []u8) ![]u8 {
return decompress_with_profile(data, lz4_profile, .lz4)
}
+18
View File
@@ -0,0 +1,18 @@
module lz
const lz77_profile = MatchProfile{
window: 4096
min_match: 3
max_match: 130
max_literal: 128
}
// compress_lz77 compresses data using a pure-V LZ77 style stream.
pub fn compress_lz77(data []u8) ![]u8 {
return compress_with_profile(data, lz77_profile, .lz77)
}
// decompress_lz77 decompresses data produced by compress_lz77.
pub fn decompress_lz77(data []u8) ![]u8 {
return decompress_with_profile(data, lz77_profile, .lz77)
}
+85
View File
@@ -0,0 +1,85 @@
module lz
// compress_lz78 compresses data using a pure-V LZ78 dictionary stream.
pub fn compress_lz78(data []u8) ![]u8 {
mut payload := []u8{}
mut dict := map[string]int{}
mut next_index := 1
mut word := []u8{}
for b in data {
mut candidate := word.clone()
candidate << b
candidate_key := candidate.bytestr()
if candidate_key in dict {
word = candidate.clone()
continue
}
prefix_index := if word.len == 0 { 0 } else { dict[word.bytestr()] }
encode_uvarint(mut payload, u64(prefix_index))
payload << u8(1)
payload << b
dict[candidate_key] = next_index
next_index++
word.clear()
}
if word.len > 0 {
final_index := dict[word.bytestr()]
encode_uvarint(mut payload, u64(final_index))
payload << u8(0)
}
return wrap_payload(.lz78, data, payload)
}
// decompress_lz78 decompresses data produced by compress_lz78.
pub fn decompress_lz78(data []u8) ![]u8 {
payload, expected_len := unwrap_payload(data, .lz78)!
mut out := []u8{cap: int(expected_len)}
mut dict := map[int][]u8{}
mut next_index := 1
mut pos := 0
for pos < payload.len {
prefix, next_pos, ok := decode_uvarint(payload, pos)
if !ok {
return error('invalid lz78 stream: bad prefix index')
}
pos = next_pos
if pos >= payload.len {
return error('invalid lz78 stream: missing suffix flag')
}
has_suffix := payload[pos]
pos++
mut phrase := if prefix == 0 {
[]u8{}
} else {
if int(prefix) !in dict {
return error('invalid lz78 stream: unknown prefix index')
}
dict[int(prefix)].clone()
}
if has_suffix == 1 {
if pos >= payload.len {
return error('invalid lz78 stream: missing suffix byte')
}
phrase << payload[pos]
pos++
} else if has_suffix != 0 {
return error('invalid lz78 stream: bad suffix flag')
}
out << phrase
dict[next_index] = phrase
next_index++
}
if i64(out.len) != expected_len {
return error('invalid lz78 stream: length mismatch')
}
return out
}
+93
View File
@@ -0,0 +1,93 @@
module lz
const sample_data = ('The quick brown fox jumps over the lazy dog. '.repeat(12) +
'aaaaaaaaabbbbbbbbbcccccccccdddddddddeeeeeeeee').bytes()
fn test_roundtrip_all_formats() {
formats := [Format.lz77, .lz78, .lzw, .lz4, .lzss, .lzma, .lzma2, .lzjb]
for format in formats {
compressed := compress(sample_data, format)!
decompressed := decompress(compressed, format)!
assert decompressed == sample_data
}
}
fn test_format_specific_api_roundtrip() {
lz77_data := compress_lz77(sample_data)!
assert decompress_lz77(lz77_data)! == sample_data
lz78_data := compress_lz78(sample_data)!
assert decompress_lz78(lz78_data)! == sample_data
lzw_data := compress_lzw(sample_data)!
assert decompress_lzw(lzw_data)! == sample_data
lz4_data := compress_lz4(sample_data)!
assert decompress_lz4(lz4_data)! == sample_data
lzss_data := compress_lzss(sample_data)!
assert decompress_lzss(lzss_data)! == sample_data
lzma_data := compress_lzma(sample_data)!
assert decompress_lzma(lzma_data)! == sample_data
lzma2_data := compress_lzma2(sample_data)!
assert decompress_lzma2(lzma2_data)! == sample_data
lzjb_data := compress_lzjb(sample_data)!
assert decompress_lzjb(lzjb_data)! == sample_data
}
fn test_mismatched_format_fails() {
compressed := compress(sample_data, .lz77)!
decompress(compressed, .lz4) or {
assert err.msg().contains('format mismatch')
return
}
assert false
}
fn test_decoded_length_too_large_fails() {
mut corrupt := []u8{}
corrupt << stream_magic
corrupt << u8(Format.lz77)
encode_uvarint(mut corrupt, u64(1) << 31)
decompress_lz77(corrupt) or {
assert err.msg().contains('decoded length too large')
return
}
assert false
}
fn test_match_offset_too_large_fails() {
mut corrupt := []u8{}
corrupt << stream_magic
corrupt << u8(Format.lz77)
encode_uvarint(mut corrupt, u64(4))
corrupt << u8(0)
corrupt << `A`
corrupt << u8(0x80)
encode_uvarint(mut corrupt, u64(1) << 63)
decompress_lz77(corrupt) or {
assert err.msg().contains('bad match offset')
return
}
assert false
}
fn test_high_entropy_roundtrip_large_window_formats() {
mut data := []u8{len: 128 * 1024}
mut state := u32(0x9e3779b9)
for i in 0 .. data.len {
state = state * 1664525 + 1013904223
data[i] = u8(state >> 24)
}
for format in [Format.lz4, .lzma, .lzma2] {
compressed := compress(data, format)!
decompressed := decompress(compressed, format)!
assert decompressed == data
}
}
+18
View File
@@ -0,0 +1,18 @@
module lz
const lzjb_profile = MatchProfile{
window: 1024
min_match: 3
max_match: 66
max_literal: 128
}
// compress_lzjb compresses data using a pure-V LZJB-like stream.
pub fn compress_lzjb(data []u8) ![]u8 {
return compress_with_profile(data, lzjb_profile, .lzjb)
}
// decompress_lzjb decompresses data produced by compress_lzjb.
pub fn decompress_lzjb(data []u8) ![]u8 {
return decompress_with_profile(data, lzjb_profile, .lzjb)
}
+18
View File
@@ -0,0 +1,18 @@
module lz
const lzma_profile = MatchProfile{
window: 32768
min_match: 3
max_match: 130
max_literal: 128
}
// compress_lzma compresses data using a pure-V LZMA-like stream.
pub fn compress_lzma(data []u8) ![]u8 {
return compress_with_profile(data, lzma_profile, .lzma)
}
// decompress_lzma decompresses data produced by compress_lzma.
pub fn decompress_lzma(data []u8) ![]u8 {
return decompress_with_profile(data, lzma_profile, .lzma)
}
+18
View File
@@ -0,0 +1,18 @@
module lz
const lzma2_profile = MatchProfile{
window: 65535
min_match: 3
max_match: 130
max_literal: 128
}
// compress_lzma2 compresses data using a pure-V LZMA2-like stream.
pub fn compress_lzma2(data []u8) ![]u8 {
return compress_with_profile(data, lzma2_profile, .lzma2)
}
// decompress_lzma2 decompresses data produced by compress_lzma2.
pub fn decompress_lzma2(data []u8) ![]u8 {
return decompress_with_profile(data, lzma2_profile, .lzma2)
}
+18
View File
@@ -0,0 +1,18 @@
module lz
const lzss_profile = MatchProfile{
window: 4096
min_match: 3
max_match: 130
max_literal: 128
}
// compress_lzss compresses data using a pure-V LZSS style stream.
pub fn compress_lzss(data []u8) ![]u8 {
return compress_with_profile(data, lzss_profile, .lzss)
}
// decompress_lzss decompresses data produced by compress_lzss.
pub fn decompress_lzss(data []u8) ![]u8 {
return decompress_with_profile(data, lzss_profile, .lzss)
}
+92
View File
@@ -0,0 +1,92 @@
module lz
// compress_lzw compresses data using a pure-V LZW dictionary stream.
pub fn compress_lzw(data []u8) ![]u8 {
mut payload := []u8{}
mut dict := map[string]int{}
for i in 0 .. 256 {
dict[[u8(i)].bytestr()] = i
}
mut next_code := 256
mut word := ''
for b in data {
symbol := [b].bytestr()
candidate := word + symbol
if candidate in dict {
word = candidate
continue
}
if word != '' {
encode_uvarint(mut payload, u64(dict[word]))
}
dict[candidate] = next_code
next_code++
word = symbol
}
if word != '' {
encode_uvarint(mut payload, u64(dict[word]))
}
return wrap_payload(.lzw, data, payload)
}
// decompress_lzw decompresses data produced by compress_lzw.
pub fn decompress_lzw(data []u8) ![]u8 {
payload, expected_len := unwrap_payload(data, .lzw)!
if payload.len == 0 {
if expected_len == i64(0) {
return []u8{}
}
return error('invalid lzw stream: missing codes')
}
mut dict := map[int][]u8{}
for i in 0 .. 256 {
dict[i] = [u8(i)]
}
mut next_code := 256
mut pos := 0
first_code, next_pos, ok := decode_uvarint(payload, pos)
if !ok {
return error('invalid lzw stream: bad initial code')
}
pos = next_pos
if int(first_code) !in dict {
return error('invalid lzw stream: unknown initial code')
}
mut word := dict[int(first_code)].clone()
mut out := word.clone()
for pos < payload.len {
code_u64, new_pos, ok_code := decode_uvarint(payload, pos)
if !ok_code {
return error('invalid lzw stream: bad code')
}
pos = new_pos
code := int(code_u64)
mut entry := []u8{}
if code in dict {
entry = dict[code].clone()
} else if code == next_code {
entry = word.clone()
entry << word[0]
} else {
return error('invalid lzw stream: unknown code')
}
out << entry
mut new_entry := word.clone()
new_entry << entry[0]
dict[next_code] = new_entry
next_code++
word = entry.clone()
}
if i64(out.len) != expected_len {
return error('invalid lzw stream: length mismatch')
}
return out
}