authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-28 11:42:43-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-31 22:10:11-07:00
log6509fa1cf3694ae16333739fa0f3fae1b63f1eaf
treed51cf407b897c3e7ccdb6f2af4d8ed892574cdef
parent88ca75020909f922a5d3edfd1ca0be3ce148040f

std.compress.flate.Decompress: passing basic test case


1 files changed, 78 insertions(+), 48 deletions(-)

lib/std/compress/flate/Decompress.zig+78-48
......@@ -10,7 +10,11 @@ const Decompress = @This();
1010const Token = @import("Token.zig");
1111
1212input: *Reader,
13next_bits: usize,
14remaining_bits: std.math.Log2Int(usize),
15
1316reader: Reader,
17
1418/// Hashes, produces checksum, of uncompressed data for gzip/zlib footer.
1519hasher: Container.Hasher,
1620
......@@ -65,6 +69,8 @@ pub fn init(input: *Reader, container: Container, buffer: []u8) Decompress {
6569 .end = 0,
6670 },
6771 .input = input,
72 .next_bits = 0,
73 .remaining_bits = 0,
6874 .hasher = .init(container),
6975 .lit_dec = .{},
7076 .dst_dec = .{},
......@@ -228,15 +234,15 @@ fn readInner(d: *Decompress, w: *Writer, limit: std.Io.Limit) (Error || Reader.S
228234 return error.InvalidDynamicBlockHeader;
229235
230236 // lengths for code lengths
231 var cl_lens = [_]u4{0} ** 19;
232 for (0..hclen) |i| {
233 cl_lens[flate.HuffmanEncoder.codegen_order[i]] = try d.takeBits(u3);
237 var cl_lens: [19]u4 = @splat(0);
238 for (flate.HuffmanEncoder.codegen_order[0..hclen]) |i| {
239 cl_lens[i] = try d.takeBits(u3);
234240 }
235241 var cl_dec: CodegenDecoder = .{};
236242 try cl_dec.generate(&cl_lens);
237243
238244 // decoded code lengths
239 var dec_lens = [_]u4{0} ** (286 + 30);
245 var dec_lens: [286 + 30]u4 = @splat(0);
240246 var pos: usize = 0;
241247 while (pos < hlit + hdist) {
242248 const sym = try cl_dec.find(try d.peekBitsReverse(u7));
......@@ -352,8 +358,30 @@ fn writeMatch(w: *Writer, length: u16, distance: u16, remaining: usize) !usize {
352358}
353359
354360fn takeBits(d: *Decompress, comptime T: type) !T {
355 _ = d;
356 @panic("TODO");
361 const U = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(T) } });
362 const remaining_bits = d.remaining_bits;
363 const next_bits = d.next_bits;
364 if (remaining_bits >= @bitSizeOf(T)) {
365 const u: U = @truncate(next_bits);
366 d.next_bits = next_bits >> @bitSizeOf(T);
367 d.remaining_bits = remaining_bits - @bitSizeOf(T);
368 return switch (@typeInfo(T)) {
369 .int => u,
370 .@"enum" => @enumFromInt(u),
371 else => @bitCast(u),
372 };
373 }
374 const in = d.input;
375 const next_int = try in.takeInt(usize, .little);
376 const needed_bits = @bitSizeOf(T) - remaining_bits;
377 const u: U = @intCast((next_bits << needed_bits) | (next_int & ((@as(usize, 1) << needed_bits) - 1)));
378 d.next_bits = next_int >> needed_bits;
379 d.remaining_bits = @intCast(@bitSizeOf(usize) - @as(usize, needed_bits));
380 return switch (@typeInfo(T)) {
381 .int => u,
382 .@"enum" => @enumFromInt(u),
383 else => @bitCast(u),
384 };
357385}
358386
359387fn takeBitsReverseBuffered(d: *Decompress, comptime T: type) !T {
......@@ -378,8 +406,20 @@ fn peekBitsReverseBuffered(d: *Decompress, comptime T: type) !T {
378406}
379407
380408fn alignBitsToByte(d: *Decompress) void {
381 _ = d;
382 @panic("TODO");
409 const remaining_bits = d.remaining_bits;
410 const next_bits = d.next_bits;
411 if (remaining_bits == 0) return;
412 const discard_bits = remaining_bits % 8;
413 const n_bytes = remaining_bits / 8;
414 var put_back_bits = next_bits >> discard_bits;
415 const in = d.input;
416 in.seek -= n_bytes;
417 for (in.buffer[in.seek..][0..n_bytes]) |*b| {
418 b.* = @truncate(put_back_bits);
419 put_back_bits >>= 8;
420 }
421 d.remaining_bits = 0;
422 d.next_bits = 0;
383423}
384424
385425fn shiftBits(d: *Decompress, n: u6) !void {
......@@ -691,47 +731,37 @@ test "encode/decode literals" {
691731 }
692732}
693733
694test "decompress" {
695 const cases = [_]struct {
696 in: []const u8,
697 out: []const u8,
698 }{
699 // non compressed block (type 0)
700 .{
701 .in = &[_]u8{
702 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen
703 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data
704 },
705 .out = "Hello world\n",
706 },
707 // fixed code block (type 1)
708 .{
709 .in = &[_]u8{
710 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1
711 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00,
712 },
713 .out = "Hello world\n",
714 },
715 // dynamic block (type 2)
716 .{
717 .in = &[_]u8{
718 0x3d, 0xc6, 0x39, 0x11, 0x00, 0x00, 0x0c, 0x02, // deflate data block type 2
719 0x30, 0x2b, 0xb5, 0x52, 0x1e, 0xff, 0x96, 0x38,
720 0x16, 0x96, 0x5c, 0x1e, 0x94, 0xcb, 0x6d, 0x01,
721 },
722 .out = "ABCDEABCD ABCDEABCD",
723 },
724 };
725 for (cases) |c| {
726 var fb: Reader = .fixed(c.in);
727 var aw: Writer.Allocating = .init(testing.allocator);
728 defer aw.deinit();
734test "basic" {
735 // non compressed block (type 0)
736 try testBasicCase(&[_]u8{
737 0b0000_0001, 0b0000_1100, 0x00, 0b1111_0011, 0xff, // deflate fixed buffer header len, nlen
738 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x0a, // non compressed data
739 }, "Hello world\n");
740
741 // fixed code block (type 1)
742 try testBasicCase(&[_]u8{
743 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1
744 0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00,
745 }, "Hello world\n");
746
747 // dynamic block (type 2)
748 try testBasicCase(&[_]u8{
749 0x3d, 0xc6, 0x39, 0x11, 0x00, 0x00, 0x0c, 0x02, // deflate data block type 2
750 0x30, 0x2b, 0xb5, 0x52, 0x1e, 0xff, 0x96, 0x38,
751 0x16, 0x96, 0x5c, 0x1e, 0x94, 0xcb, 0x6d, 0x01,
752 }, "ABCDEABCD ABCDEABCD");
753}
729754
730 var decompress: Decompress = .init(&fb, .raw, &.{});
731 const r = &decompress.reader;
732 _ = try r.streamRemaining(&aw.writer);
733 try testing.expectEqualStrings(c.out, aw.getWritten());
734 }
755fn testBasicCase(in: []const u8, out: []const u8) !void {
756 var reader: Reader = .fixed(in);
757 var aw: Writer.Allocating = .init(testing.allocator);
758 try aw.ensureUnusedCapacity(flate.history_len + 1);
759 defer aw.deinit();
760
761 var decompress: Decompress = .init(&reader, .raw, &.{});
762 const r = &decompress.reader;
763 _ = try r.streamRemaining(&aw.writer);
764 try testing.expectEqualStrings(out, aw.getWritten());
735765}
736766
737767test "gzip decompress" {