mirror of
https://github.com/ziglang/zig.git
synced 2025-12-22 06:03:16 +00:00
std: hacking around with buffered reader / writer semantics
I think I'm going to back out these vtable changes in the next commit
This commit is contained in:
parent
890a02c345
commit
6ac7931bec
@ -1,3 +1,5 @@
|
|||||||
|
const std = @import("../std.zig");
|
||||||
|
|
||||||
/// Deflate is a lossless data compression file format that uses a combination
|
/// Deflate is a lossless data compression file format that uses a combination
|
||||||
/// of LZ77 and Huffman coding.
|
/// of LZ77 and Huffman coding.
|
||||||
pub const deflate = @import("flate/deflate.zig");
|
pub const deflate = @import("flate/deflate.zig");
|
||||||
@ -7,77 +9,48 @@ pub const deflate = @import("flate/deflate.zig");
|
|||||||
pub const inflate = @import("flate/inflate.zig");
|
pub const inflate = @import("flate/inflate.zig");
|
||||||
|
|
||||||
/// Decompress compressed data from reader and write plain data to the writer.
|
/// Decompress compressed data from reader and write plain data to the writer.
|
||||||
pub fn decompress(reader: anytype, writer: anytype) !void {
|
pub fn decompress(reader: *std.io.BufferedReader, writer: *std.io.BufferedWriter) anyerror!void {
|
||||||
try inflate.decompress(.raw, reader, writer);
|
try inflate.decompress(.raw, reader, writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decompressor type
|
pub const Decompressor = inflate.Decompressor(.raw);
|
||||||
pub fn Decompressor(comptime ReaderType: type) type {
|
|
||||||
return inflate.Decompressor(.raw, ReaderType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create Decompressor which will read compressed data from reader.
|
|
||||||
pub fn decompressor(reader: anytype) Decompressor(@TypeOf(reader)) {
|
|
||||||
return inflate.decompressor(.raw, reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compression level, trades between speed and compression size.
|
/// Compression level, trades between speed and compression size.
|
||||||
pub const Options = deflate.Options;
|
pub const Options = deflate.Options;
|
||||||
|
|
||||||
/// Compress plain data from reader and write compressed data to the writer.
|
/// Compress plain data from reader and write compressed data to the writer.
|
||||||
pub fn compress(reader: anytype, writer: anytype, options: Options) !void {
|
pub fn compress(reader: *std.io.BufferedReader, writer: *std.io.BufferedWriter, options: Options) anyerror!void {
|
||||||
try deflate.compress(.raw, reader, writer, options);
|
try deflate.compress(.raw, reader, writer, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compressor type
|
pub const Compressor = deflate.Compressor(.raw);
|
||||||
pub fn Compressor(comptime WriterType: type) type {
|
|
||||||
return deflate.Compressor(.raw, WriterType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create Compressor which outputs compressed data to the writer.
|
|
||||||
pub fn compressor(writer: anytype, options: Options) !Compressor(@TypeOf(writer)) {
|
|
||||||
return try deflate.compressor(.raw, writer, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Huffman only compression. Without Lempel-Ziv match searching. Faster
|
/// Huffman only compression. Without Lempel-Ziv match searching. Faster
|
||||||
/// compression, less memory requirements but bigger compressed sizes.
|
/// compression, less memory requirements but bigger compressed sizes.
|
||||||
pub const huffman = struct {
|
pub const huffman = struct {
|
||||||
pub fn compress(reader: anytype, writer: anytype) !void {
|
pub fn compress(reader: *std.io.BufferedReader, writer: *std.io.BufferedWriter) anyerror!void {
|
||||||
try deflate.huffman.compress(.raw, reader, writer);
|
try deflate.huffman.compress(.raw, reader, writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Compressor(comptime WriterType: type) type {
|
pub const Compressor = deflate.huffman.Compressor(.raw);
|
||||||
return deflate.huffman.Compressor(.raw, WriterType);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compressor(writer: anytype) !huffman.Compressor(@TypeOf(writer)) {
|
|
||||||
return deflate.huffman.compressor(.raw, writer);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// No compression store only. Compressed size is slightly bigger than plain.
|
// No compression store only. Compressed size is slightly bigger than plain.
|
||||||
pub const store = struct {
|
pub const store = struct {
|
||||||
pub fn compress(reader: anytype, writer: anytype) !void {
|
pub fn compress(reader: *std.io.BufferedReader, writer: *std.io.BufferedWriter) anyerror!void {
|
||||||
try deflate.store.compress(.raw, reader, writer);
|
try deflate.store.compress(.raw, reader, writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Compressor(comptime WriterType: type) type {
|
pub const Compressor = deflate.store.Compressor(.raw);
|
||||||
return deflate.store.Compressor(.raw, WriterType);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compressor(writer: anytype) !store.Compressor(@TypeOf(writer)) {
|
|
||||||
return deflate.store.compressor(.raw, writer);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Container defines header/footer around deflate bit stream. Gzip and zlib
|
const builtin = @import("builtin");
|
||||||
/// compression algorithms are containers around deflate bit stream body.
|
|
||||||
const Container = @import("flate/container.zig").Container;
|
|
||||||
const std = @import("std");
|
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const fixedBufferStream = std.io.fixedBufferStream;
|
const fixedBufferStream = std.io.fixedBufferStream;
|
||||||
const print = std.debug.print;
|
const print = std.debug.print;
|
||||||
const builtin = @import("builtin");
|
/// Container defines header/footer around deflate bit stream. Gzip and zlib
|
||||||
|
/// compression algorithms are containers around deflate bit stream body.
|
||||||
|
const Container = @import("flate/container.zig").Container;
|
||||||
|
|
||||||
test {
|
test {
|
||||||
_ = deflate;
|
_ = deflate;
|
||||||
|
|||||||
@ -1,421 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const testing = std.testing;
|
|
||||||
|
|
||||||
pub const Flags = packed struct(u3) {
|
|
||||||
/// dont advance internal buffer, just get bits, leave them in buffer
|
|
||||||
peek: bool = false,
|
|
||||||
/// assume that there is no need to fill, fill should be called before
|
|
||||||
buffered: bool = false,
|
|
||||||
/// bit reverse read bits
|
|
||||||
reverse: bool = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Bit reader used during inflate (decompression). Has internal buffer of 64
|
|
||||||
/// bits which shifts right after bits are consumed. Uses forward_reader to fill
|
|
||||||
/// that internal buffer when needed.
|
|
||||||
///
|
|
||||||
/// readF is the core function. Supports few different ways of getting bits
|
|
||||||
/// controlled by flags. In hot path we try to avoid checking whether we need to
|
|
||||||
/// fill buffer from forward_reader by calling fill in advance and readF with
|
|
||||||
/// buffered flag set.
|
|
||||||
///
|
|
||||||
pub fn BitReader(comptime T: type) type {
|
|
||||||
assert(T == u32 or T == u64);
|
|
||||||
const t_bytes: usize = @sizeOf(T);
|
|
||||||
const Tshift = if (T == u64) u6 else u5;
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
// Underlying reader used for filling internal bits buffer
|
|
||||||
forward_reader: *std.io.BufferedReader,
|
|
||||||
// Internal buffer of 64 bits
|
|
||||||
bits: T = 0,
|
|
||||||
// Number of bits in the buffer
|
|
||||||
nbits: u32 = 0,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn init(forward_reader: *std.io.BufferedReader) Self {
|
|
||||||
var self = Self{ .forward_reader = forward_reader };
|
|
||||||
self.fill(1) catch {};
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to have `nice` bits are available in buffer. Reads from
|
|
||||||
/// forward reader if there is no `nice` bits in buffer. Returns error
|
|
||||||
/// if end of forward stream is reached and internal buffer is empty.
|
|
||||||
/// It will not error if less than `nice` bits are in buffer, only when
|
|
||||||
/// all bits are exhausted. During inflate we usually know what is the
|
|
||||||
/// maximum bits for the next step but usually that step will need less
|
|
||||||
/// bits to decode. So `nice` is not hard limit, it will just try to have
|
|
||||||
/// that number of bits available. If end of forward stream is reached
|
|
||||||
/// it may be some extra zero bits in buffer.
|
|
||||||
pub fn fill(self: *Self, nice: u6) !void {
|
|
||||||
if (self.nbits >= nice and nice != 0) {
|
|
||||||
return; // We have enough bits
|
|
||||||
}
|
|
||||||
// Read more bits from forward reader
|
|
||||||
|
|
||||||
// Number of empty bytes in bits, round nbits to whole bytes.
|
|
||||||
const empty_bytes =
|
|
||||||
@as(u8, if (self.nbits & 0x7 == 0) t_bytes else t_bytes - 1) - // 8 for 8, 16, 24..., 7 otherwise
|
|
||||||
(self.nbits >> 3); // 0 for 0-7, 1 for 8-16, ... same as / 8
|
|
||||||
|
|
||||||
var buf: [t_bytes]u8 = [_]u8{0} ** t_bytes;
|
|
||||||
const bytes_read = self.forward_reader.readAll(buf[0..empty_bytes]) catch 0;
|
|
||||||
if (bytes_read > 0) {
|
|
||||||
const u: T = std.mem.readInt(T, buf[0..t_bytes], .little);
|
|
||||||
self.bits |= u << @as(Tshift, @intCast(self.nbits));
|
|
||||||
self.nbits += 8 * @as(u8, @intCast(bytes_read));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.nbits == 0)
|
|
||||||
return error.EndOfStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read exactly buf.len bytes into buf.
|
|
||||||
pub fn readAll(self: *Self, buf: []u8) !void {
|
|
||||||
assert(self.alignBits() == 0); // internal bits must be at byte boundary
|
|
||||||
|
|
||||||
// First read from internal bits buffer.
|
|
||||||
var n: usize = 0;
|
|
||||||
while (self.nbits > 0 and n < buf.len) {
|
|
||||||
buf[n] = try self.readF(u8, .{ .buffered = true });
|
|
||||||
n += 1;
|
|
||||||
}
|
|
||||||
// Then use forward reader for all other bytes.
|
|
||||||
try self.forward_reader.readNoEof(buf[n..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Alias for readF(U, 0).
|
|
||||||
pub fn read(self: *Self, comptime U: type) !U {
|
|
||||||
return self.readF(U, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Alias for readF with flag.peak set.
|
|
||||||
pub inline fn peekF(self: *Self, comptime U: type, comptime how: Flags) !U {
|
|
||||||
return self.readF(U, .{
|
|
||||||
.peek = true,
|
|
||||||
.buffered = how.buffered,
|
|
||||||
.reverse = how.reverse,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read with flags provided.
|
|
||||||
pub fn readF(self: *Self, comptime U: type, comptime how: Flags) !U {
|
|
||||||
if (U == T) {
|
|
||||||
assert(how == 0);
|
|
||||||
assert(self.alignBits() == 0);
|
|
||||||
try self.fill(@bitSizeOf(T));
|
|
||||||
if (self.nbits != @bitSizeOf(T)) return error.EndOfStream;
|
|
||||||
const v = self.bits;
|
|
||||||
self.nbits = 0;
|
|
||||||
self.bits = 0;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
const n: Tshift = @bitSizeOf(U);
|
|
||||||
switch (how) {
|
|
||||||
0 => { // `normal` read
|
|
||||||
try self.fill(n); // ensure that there are n bits in the buffer
|
|
||||||
const u: U = @truncate(self.bits); // get n bits
|
|
||||||
try self.shift(n); // advance buffer for n
|
|
||||||
return u;
|
|
||||||
},
|
|
||||||
.{ .peek = true } => { // no shift, leave bits in the buffer
|
|
||||||
try self.fill(n);
|
|
||||||
return @truncate(self.bits);
|
|
||||||
},
|
|
||||||
.{ .buffered = true } => { // no fill, assume that buffer has enough bits
|
|
||||||
const u: U = @truncate(self.bits);
|
|
||||||
try self.shift(n);
|
|
||||||
return u;
|
|
||||||
},
|
|
||||||
.{ .reverse = true } => { // same as 0 with bit reverse
|
|
||||||
try self.fill(n);
|
|
||||||
const u: U = @truncate(self.bits);
|
|
||||||
try self.shift(n);
|
|
||||||
return @bitReverse(u);
|
|
||||||
},
|
|
||||||
.{ .peek = true, .reverse = true } => {
|
|
||||||
try self.fill(n);
|
|
||||||
return @bitReverse(@as(U, @truncate(self.bits)));
|
|
||||||
},
|
|
||||||
.{ .buffered = true, .reverse = true } => {
|
|
||||||
const u: U = @truncate(self.bits);
|
|
||||||
try self.shift(n);
|
|
||||||
return @bitReverse(u);
|
|
||||||
},
|
|
||||||
.{ .peek = true, .buffered = true },
|
|
||||||
=> {
|
|
||||||
return @truncate(self.bits);
|
|
||||||
},
|
|
||||||
.{ .peek = true, .buffered = true, .reverse = true } => {
|
|
||||||
return @bitReverse(@as(U, @truncate(self.bits)));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read n number of bits.
|
|
||||||
/// Only buffered flag can be used in how.
|
|
||||||
pub fn readN(self: *Self, n: u4, comptime how: u3) !u16 {
|
|
||||||
switch (how) {
|
|
||||||
0 => {
|
|
||||||
try self.fill(n);
|
|
||||||
},
|
|
||||||
.{ .buffered = true } => {},
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
const mask: u16 = (@as(u16, 1) << n) - 1;
|
|
||||||
const u: u16 = @as(u16, @truncate(self.bits)) & mask;
|
|
||||||
try self.shift(n);
|
|
||||||
return u;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Advance buffer for n bits.
|
|
||||||
pub fn shift(self: *Self, n: Tshift) !void {
|
|
||||||
if (n > self.nbits) return error.EndOfStream;
|
|
||||||
self.bits >>= n;
|
|
||||||
self.nbits -= n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skip n bytes.
|
|
||||||
pub fn skipBytes(self: *Self, n: u16) !void {
|
|
||||||
for (0..n) |_| {
|
|
||||||
try self.fill(8);
|
|
||||||
try self.shift(8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of bits to align stream to the byte boundary.
|
|
||||||
fn alignBits(self: *Self) u3 {
|
|
||||||
return @intCast(self.nbits & 0x7);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Align stream to the byte boundary.
|
|
||||||
pub fn alignToByte(self: *Self) void {
|
|
||||||
const ab = self.alignBits();
|
|
||||||
if (ab > 0) self.shift(ab) catch unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Skip zero terminated string.
|
|
||||||
pub fn skipStringZ(self: *Self) !void {
|
|
||||||
while (true) {
|
|
||||||
if (try self.readF(u8, 0) == 0) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read deflate fixed fixed code.
|
|
||||||
/// Reads first 7 bits, and then maybe 1 or 2 more to get full 7,8 or 9 bit code.
|
|
||||||
/// ref: https://datatracker.ietf.org/doc/html/rfc1951#page-12
|
|
||||||
/// Lit Value Bits Codes
|
|
||||||
/// --------- ---- -----
|
|
||||||
/// 0 - 143 8 00110000 through
|
|
||||||
/// 10111111
|
|
||||||
/// 144 - 255 9 110010000 through
|
|
||||||
/// 111111111
|
|
||||||
/// 256 - 279 7 0000000 through
|
|
||||||
/// 0010111
|
|
||||||
/// 280 - 287 8 11000000 through
|
|
||||||
/// 11000111
|
|
||||||
pub fn readFixedCode(self: *Self) !u16 {
|
|
||||||
try self.fill(7 + 2);
|
|
||||||
const code7 = try self.readF(u7, .{ .buffered = true, .reverse = true });
|
|
||||||
if (code7 <= 0b0010_111) { // 7 bits, 256-279, codes 0000_000 - 0010_111
|
|
||||||
return @as(u16, code7) + 256;
|
|
||||||
} else if (code7 <= 0b1011_111) { // 8 bits, 0-143, codes 0011_0000 through 1011_1111
|
|
||||||
return (@as(u16, code7) << 1) + @as(u16, try self.readF(u1, .{ .buffered = true })) - 0b0011_0000;
|
|
||||||
} else if (code7 <= 0b1100_011) { // 8 bit, 280-287, codes 1100_0000 - 1100_0111
|
|
||||||
return (@as(u16, code7 - 0b1100000) << 1) + try self.readF(u1, .{ .buffered = true }) + 280;
|
|
||||||
} else { // 9 bit, 144-255, codes 1_1001_0000 - 1_1111_1111
|
|
||||||
return (@as(u16, code7 - 0b1100_100) << 2) + @as(u16, try self.readF(u2, .{ .buffered = true, .reverse = true })) + 144;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test "readF" {
|
|
||||||
var input: std.io.BufferedReader = undefined;
|
|
||||||
input.initFixed(&[_]u8{ 0xf3, 0x48, 0xcd, 0xc9, 0x00, 0x00 });
|
|
||||||
var br: BitReader(u64) = .init(&input);
|
|
||||||
|
|
||||||
try testing.expectEqual(@as(u8, 48), br.nbits);
|
|
||||||
try testing.expectEqual(@as(u64, 0xc9cd48f3), br.bits);
|
|
||||||
|
|
||||||
try testing.expect(try br.readF(u1, 0) == 0b0000_0001);
|
|
||||||
try testing.expect(try br.readF(u2, 0) == 0b0000_0001);
|
|
||||||
try testing.expectEqual(@as(u8, 48 - 3), br.nbits);
|
|
||||||
try testing.expectEqual(@as(u3, 5), br.alignBits());
|
|
||||||
|
|
||||||
try testing.expect(try br.readF(u8, .{ .peek = true }) == 0b0001_1110);
|
|
||||||
try testing.expect(try br.readF(u9, .{ .peek = true }) == 0b1_0001_1110);
|
|
||||||
try br.shift(9);
|
|
||||||
try testing.expectEqual(@as(u8, 36), br.nbits);
|
|
||||||
try testing.expectEqual(@as(u3, 4), br.alignBits());
|
|
||||||
|
|
||||||
try testing.expect(try br.readF(u4, 0) == 0b0100);
|
|
||||||
try testing.expectEqual(@as(u8, 32), br.nbits);
|
|
||||||
try testing.expectEqual(@as(u3, 0), br.alignBits());
|
|
||||||
|
|
||||||
try br.shift(1);
|
|
||||||
try testing.expectEqual(@as(u3, 7), br.alignBits());
|
|
||||||
try br.shift(1);
|
|
||||||
try testing.expectEqual(@as(u3, 6), br.alignBits());
|
|
||||||
br.alignToByte();
|
|
||||||
try testing.expectEqual(@as(u3, 0), br.alignBits());
|
|
||||||
|
|
||||||
try testing.expectEqual(@as(u64, 0xc9), br.bits);
|
|
||||||
try testing.expectEqual(@as(u16, 0x9), try br.readN(4, 0));
|
|
||||||
try testing.expectEqual(@as(u16, 0xc), try br.readN(4, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "read block type 1 data" {
|
|
||||||
inline for ([_]type{ u64, u32 }) |T| {
|
|
||||||
const data = [_]u8{
|
|
||||||
0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1
|
|
||||||
0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00,
|
|
||||||
0x0c, 0x01, 0x02, 0x03, //
|
|
||||||
0xaa, 0xbb, 0xcc, 0xdd,
|
|
||||||
};
|
|
||||||
var fbs: std.io.BufferedReader = undefined;
|
|
||||||
fbs.initFixed(&data);
|
|
||||||
var br: BitReader(T) = .init(&fbs);
|
|
||||||
|
|
||||||
try testing.expectEqual(@as(u1, 1), try br.readF(u1, 0)); // bfinal
|
|
||||||
try testing.expectEqual(@as(u2, 1), try br.readF(u2, 0)); // block_type
|
|
||||||
|
|
||||||
for ("Hello world\n") |c| {
|
|
||||||
try testing.expectEqual(@as(u8, c), try br.readF(u8, .{ .reverse = true }) - 0x30);
|
|
||||||
}
|
|
||||||
try testing.expectEqual(@as(u7, 0), try br.readF(u7, 0)); // end of block
|
|
||||||
br.alignToByte();
|
|
||||||
try testing.expectEqual(@as(u32, 0x0302010c), try br.readF(u32, 0));
|
|
||||||
try testing.expectEqual(@as(u16, 0xbbaa), try br.readF(u16, 0));
|
|
||||||
try testing.expectEqual(@as(u16, 0xddcc), try br.readF(u16, 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "shift/fill" {
|
|
||||||
const data = [_]u8{
|
|
||||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
|
||||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
|
||||||
};
|
|
||||||
var fbs: std.io.BufferedReader = undefined;
|
|
||||||
fbs.initFixed(&data);
|
|
||||||
var br: BitReader(u64) = .init(&fbs);
|
|
||||||
|
|
||||||
try testing.expectEqual(@as(u64, 0x08_07_06_05_04_03_02_01), br.bits);
|
|
||||||
try br.shift(8);
|
|
||||||
try testing.expectEqual(@as(u64, 0x00_08_07_06_05_04_03_02), br.bits);
|
|
||||||
try br.fill(60); // fill with 1 byte
|
|
||||||
try testing.expectEqual(@as(u64, 0x01_08_07_06_05_04_03_02), br.bits);
|
|
||||||
try br.shift(8 * 4 + 4);
|
|
||||||
try testing.expectEqual(@as(u64, 0x00_00_00_00_00_10_80_70), br.bits);
|
|
||||||
|
|
||||||
try br.fill(60); // fill with 4 bytes (shift by 4)
|
|
||||||
try testing.expectEqual(@as(u64, 0x00_50_40_30_20_10_80_70), br.bits);
|
|
||||||
try testing.expectEqual(@as(u8, 8 * 7 + 4), br.nbits);
|
|
||||||
|
|
||||||
try br.shift(@intCast(br.nbits)); // clear buffer
|
|
||||||
try br.fill(8); // refill with the rest of the bytes
|
|
||||||
try testing.expectEqual(@as(u64, 0x00_00_00_00_00_08_07_06), br.bits);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "readAll" {
|
|
||||||
inline for ([_]type{ u64, u32 }) |T| {
|
|
||||||
const data = [_]u8{
|
|
||||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
|
||||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
|
||||||
};
|
|
||||||
var fbs: std.io.BufferedReader = undefined;
|
|
||||||
fbs.initFixed(&data);
|
|
||||||
var br: BitReader(T) = .init(&fbs);
|
|
||||||
|
|
||||||
switch (T) {
|
|
||||||
u64 => try testing.expectEqual(@as(u64, 0x08_07_06_05_04_03_02_01), br.bits),
|
|
||||||
u32 => try testing.expectEqual(@as(u32, 0x04_03_02_01), br.bits),
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
|
|
||||||
var out: [16]u8 = undefined;
|
|
||||||
try br.readAll(out[0..]);
|
|
||||||
try testing.expect(br.nbits == 0);
|
|
||||||
try testing.expect(br.bits == 0);
|
|
||||||
|
|
||||||
try testing.expectEqualSlices(u8, data[0..16], &out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "readFixedCode" {
|
|
||||||
inline for ([_]type{ u64, u32 }) |T| {
|
|
||||||
const fixed_codes = @import("huffman_encoder.zig").fixed_codes;
|
|
||||||
|
|
||||||
var fbs: std.io.BufferedReader = undefined;
|
|
||||||
fbs.initFixed(&fixed_codes);
|
|
||||||
var rdr: BitReader(T) = .init(&fbs);
|
|
||||||
|
|
||||||
for (0..286) |c| {
|
|
||||||
try testing.expectEqual(c, try rdr.readFixedCode());
|
|
||||||
}
|
|
||||||
try testing.expect(rdr.nbits == 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "u32 leaves no bits on u32 reads" {
|
|
||||||
const data = [_]u8{
|
|
||||||
0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
||||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
|
||||||
};
|
|
||||||
var fbs: std.io.BufferedReader = undefined;
|
|
||||||
fbs.initFixed(&data);
|
|
||||||
var br: BitReader(u32) = .init(&fbs);
|
|
||||||
|
|
||||||
_ = try br.read(u3);
|
|
||||||
try testing.expectEqual(29, br.nbits);
|
|
||||||
br.alignToByte();
|
|
||||||
try testing.expectEqual(24, br.nbits);
|
|
||||||
try testing.expectEqual(0x04_03_02_01, try br.read(u32));
|
|
||||||
try testing.expectEqual(0, br.nbits);
|
|
||||||
try testing.expectEqual(0x08_07_06_05, try br.read(u32));
|
|
||||||
try testing.expectEqual(0, br.nbits);
|
|
||||||
|
|
||||||
_ = try br.read(u9);
|
|
||||||
try testing.expectEqual(23, br.nbits);
|
|
||||||
br.alignToByte();
|
|
||||||
try testing.expectEqual(16, br.nbits);
|
|
||||||
try testing.expectEqual(0x0e_0d_0c_0b, try br.read(u32));
|
|
||||||
try testing.expectEqual(0, br.nbits);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "u64 need fill after alignToByte" {
|
|
||||||
const data = [_]u8{
|
|
||||||
0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
||||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
|
||||||
};
|
|
||||||
|
|
||||||
// without fill
|
|
||||||
var fbs: std.io.BufferedReader = undefined;
|
|
||||||
fbs.initFixed(&data);
|
|
||||||
var br: BitReader(u64) = .init(&fbs);
|
|
||||||
_ = try br.read(u23);
|
|
||||||
try testing.expectEqual(41, br.nbits);
|
|
||||||
br.alignToByte();
|
|
||||||
try testing.expectEqual(40, br.nbits);
|
|
||||||
try testing.expectEqual(0x06_05_04_03, try br.read(u32));
|
|
||||||
try testing.expectEqual(8, br.nbits);
|
|
||||||
try testing.expectEqual(0x0a_09_08_07, try br.read(u32));
|
|
||||||
try testing.expectEqual(32, br.nbits);
|
|
||||||
|
|
||||||
// fill after align ensures all bits filled
|
|
||||||
fbs.reset();
|
|
||||||
br = .init(&fbs);
|
|
||||||
_ = try br.read(u23);
|
|
||||||
try testing.expectEqual(41, br.nbits);
|
|
||||||
br.alignToByte();
|
|
||||||
try br.fill(0);
|
|
||||||
try testing.expectEqual(64, br.nbits);
|
|
||||||
try testing.expectEqual(0x06_05_04_03, try br.read(u32));
|
|
||||||
try testing.expectEqual(32, br.nbits);
|
|
||||||
try testing.expectEqual(0x0a_09_08_07, try br.read(u32));
|
|
||||||
try testing.expectEqual(0, br.nbits);
|
|
||||||
}
|
|
||||||
@ -3,7 +3,6 @@ const assert = std.debug.assert;
|
|||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
const hfd = @import("huffman_decoder.zig");
|
const hfd = @import("huffman_decoder.zig");
|
||||||
const BitReader = @import("bit_reader.zig").BitReader;
|
|
||||||
const CircularBuffer = @import("CircularBuffer.zig");
|
const CircularBuffer = @import("CircularBuffer.zig");
|
||||||
const Container = @import("container.zig").Container;
|
const Container = @import("container.zig").Container;
|
||||||
const Token = @import("Token.zig");
|
const Token = @import("Token.zig");
|
||||||
@ -48,16 +47,14 @@ pub fn Decompressor(comptime container: Container) type {
|
|||||||
/// * 64K for history (CircularBuffer)
|
/// * 64K for history (CircularBuffer)
|
||||||
/// * ~10K huffman decoders (Literal and DistanceDecoder)
|
/// * ~10K huffman decoders (Literal and DistanceDecoder)
|
||||||
///
|
///
|
||||||
pub fn Inflate(comptime container: Container, comptime LookaheadType: type) type {
|
pub fn Inflate(comptime container: Container, comptime Lookahead: type) type {
|
||||||
assert(LookaheadType == u32 or LookaheadType == u64);
|
assert(Lookahead == u32 or Lookahead == u64);
|
||||||
const BitReaderType = BitReader(LookaheadType);
|
const LookaheadBitReader = BitReader(Lookahead);
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
const F = BitReaderType.flag;
|
bits: LookaheadBitReader,
|
||||||
|
|
||||||
bits: BitReaderType,
|
|
||||||
hist: CircularBuffer = .{},
|
hist: CircularBuffer = .{},
|
||||||
// Hashes, produces checkusm, of uncompressed data for gzip/zlib footer.
|
// Hashes, produces checksum, of uncompressed data for gzip/zlib footer.
|
||||||
hasher: container.Hasher() = .{},
|
hasher: container.Hasher() = .{},
|
||||||
|
|
||||||
// dynamic block huffman code decoders
|
// dynamic block huffman code decoders
|
||||||
@ -79,7 +76,7 @@ pub fn Inflate(comptime container: Container, comptime LookaheadType: type) type
|
|||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub const Error = BitReaderType.Error || Container.Error || hfd.Error || error{
|
pub const Error = anyerror || Container.Error || hfd.Error || error{
|
||||||
InvalidCode,
|
InvalidCode,
|
||||||
InvalidMatch,
|
InvalidMatch,
|
||||||
InvalidBlockType,
|
InvalidBlockType,
|
||||||
@ -88,10 +85,10 @@ pub fn Inflate(comptime container: Container, comptime LookaheadType: type) type
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(bw: *std.io.BufferedReader) Self {
|
pub fn init(bw: *std.io.BufferedReader) Self {
|
||||||
return .{ .bits = BitReaderType.init(bw) };
|
return .{ .bits = LookaheadBitReader.init(bw) };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn blockHeader(self: *Self) !void {
|
fn blockHeader(self: *Self) anyerror!void {
|
||||||
self.bfinal = try self.bits.read(u1);
|
self.bfinal = try self.bits.read(u1);
|
||||||
self.block_type = try self.bits.read(u2);
|
self.block_type = try self.bits.read(u2);
|
||||||
}
|
}
|
||||||
@ -129,7 +126,10 @@ pub fn Inflate(comptime container: Container, comptime LookaheadType: type) type
|
|||||||
fn fixedDistanceCode(self: *Self, code: u8) !void {
|
fn fixedDistanceCode(self: *Self, code: u8) !void {
|
||||||
try self.bits.fill(5 + 5 + 13);
|
try self.bits.fill(5 + 5 + 13);
|
||||||
const length = try self.decodeLength(code);
|
const length = try self.decodeLength(code);
|
||||||
const distance = try self.decodeDistance(try self.bits.readF(u5, F.buffered | F.reverse));
|
const distance = try self.decodeDistance(try self.bits.readF(u5, .{
|
||||||
|
.buffered = true,
|
||||||
|
.reverse = true,
|
||||||
|
}));
|
||||||
try self.hist.writeMatch(length, distance);
|
try self.hist.writeMatch(length, distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ pub fn Inflate(comptime container: Container, comptime LookaheadType: type) type
|
|||||||
return if (ml.extra_bits == 0) // 0 - 5 extra bits
|
return if (ml.extra_bits == 0) // 0 - 5 extra bits
|
||||||
ml.base
|
ml.base
|
||||||
else
|
else
|
||||||
ml.base + try self.bits.readN(ml.extra_bits, F.buffered);
|
ml.base + try self.bits.readN(ml.extra_bits, .{ .buffered = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decodeDistance(self: *Self, code: u8) !u16 {
|
fn decodeDistance(self: *Self, code: u8) !u16 {
|
||||||
@ -148,7 +148,7 @@ pub fn Inflate(comptime container: Container, comptime LookaheadType: type) type
|
|||||||
return if (md.extra_bits == 0) // 0 - 13 extra bits
|
return if (md.extra_bits == 0) // 0 - 13 extra bits
|
||||||
md.base
|
md.base
|
||||||
else
|
else
|
||||||
md.base + try self.bits.readN(md.extra_bits, F.buffered);
|
md.base + try self.bits.readN(md.extra_bits, .{ .buffered = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dynamicBlockHeader(self: *Self) !void {
|
fn dynamicBlockHeader(self: *Self) !void {
|
||||||
@ -171,7 +171,7 @@ pub fn Inflate(comptime container: Container, comptime LookaheadType: type) type
|
|||||||
var dec_lens = [_]u4{0} ** (286 + 30);
|
var dec_lens = [_]u4{0} ** (286 + 30);
|
||||||
var pos: usize = 0;
|
var pos: usize = 0;
|
||||||
while (pos < hlit + hdist) {
|
while (pos < hlit + hdist) {
|
||||||
const sym = try cl_dec.find(try self.bits.peekF(u7, F.reverse));
|
const sym = try cl_dec.find(try self.bits.peekF(u7, .{ .reverse = true }));
|
||||||
try self.bits.shift(sym.code_bits);
|
try self.bits.shift(sym.code_bits);
|
||||||
pos += try self.dynamicCodeLength(sym.symbol, &dec_lens, pos);
|
pos += try self.dynamicCodeLength(sym.symbol, &dec_lens, pos);
|
||||||
}
|
}
|
||||||
@ -230,13 +230,13 @@ pub fn Inflate(comptime container: Container, comptime LookaheadType: type) type
|
|||||||
.literal => self.hist.write(sym.symbol),
|
.literal => self.hist.write(sym.symbol),
|
||||||
.match => { // Decode match backreference <length, distance>
|
.match => { // Decode match backreference <length, distance>
|
||||||
// fill so we can use buffered reads
|
// fill so we can use buffered reads
|
||||||
if (LookaheadType == u32)
|
if (Lookahead == u32)
|
||||||
try self.bits.fill(5 + 15)
|
try self.bits.fill(5 + 15)
|
||||||
else
|
else
|
||||||
try self.bits.fill(5 + 15 + 13);
|
try self.bits.fill(5 + 15 + 13);
|
||||||
const length = try self.decodeLength(sym.symbol);
|
const length = try self.decodeLength(sym.symbol);
|
||||||
const dsm = try self.decodeSymbol(&self.dst_dec);
|
const dsm = try self.decodeSymbol(&self.dst_dec);
|
||||||
if (LookaheadType == u32) try self.bits.fill(13);
|
if (Lookahead == u32) try self.bits.fill(13);
|
||||||
const distance = try self.decodeDistance(dsm.symbol);
|
const distance = try self.decodeDistance(dsm.symbol);
|
||||||
try self.hist.writeMatch(length, distance);
|
try self.hist.writeMatch(length, distance);
|
||||||
},
|
},
|
||||||
@ -251,7 +251,7 @@ pub fn Inflate(comptime container: Container, comptime LookaheadType: type) type
|
|||||||
// used. Shift bit reader for that much bits, those bits are used. And
|
// used. Shift bit reader for that much bits, those bits are used. And
|
||||||
// return symbol.
|
// return symbol.
|
||||||
fn decodeSymbol(self: *Self, decoder: anytype) !hfd.Symbol {
|
fn decodeSymbol(self: *Self, decoder: anytype) !hfd.Symbol {
|
||||||
const sym = try decoder.find(try self.bits.peekF(u15, F.buffered | F.reverse));
|
const sym = try decoder.find(try self.bits.peekF(u15, .{ .buffered = true, .reverse = true }));
|
||||||
try self.bits.shift(sym.code_bits);
|
try self.bits.shift(sym.code_bits);
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
@ -338,22 +338,48 @@ pub fn Inflate(comptime container: Container, comptime LookaheadType: type) type
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reader interface
|
fn reader_streamRead(
|
||||||
|
ctx: ?*anyopaque,
|
||||||
pub const Reader = std.io.Reader(*Self, Error, read);
|
bw: *std.io.BufferedWriter,
|
||||||
|
limit: std.io.Reader.Limit,
|
||||||
/// Returns the number of bytes read. It may be less than buffer.len.
|
) std.io.Reader.RwResult {
|
||||||
/// If the number of bytes read is 0, it means end of stream.
|
const self: *Self = @alignCast(@ptrCast(ctx));
|
||||||
/// End of stream is not an error condition.
|
const out = bw.writableSlice(1) catch |err| return .{ .write_err = err };
|
||||||
pub fn read(self: *Self, buffer: []u8) Error!usize {
|
const in = self.get(limit.min(out.len)) catch |err| return .{ .read_err = err };
|
||||||
if (buffer.len == 0) return 0;
|
if (in.len == 0) return .{ .read_end = true };
|
||||||
const out = try self.get(buffer.len);
|
@memcpy(out[0..in.len], in);
|
||||||
@memcpy(buffer[0..out.len], out);
|
return .{ .len = in.len };
|
||||||
return out.len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reader(self: *Self) Reader {
|
fn reader_streamReadVec(ctx: ?*anyopaque, data: []const []u8) std.io.Reader.Result {
|
||||||
return .{ .context = self };
|
const self: *Self = @alignCast(@ptrCast(ctx));
|
||||||
|
var total: usize = 0;
|
||||||
|
for (data) |buffer| {
|
||||||
|
if (buffer.len == 0) break;
|
||||||
|
const out = self.get(buffer.len) catch |err| {
|
||||||
|
return .{ .len = total, .err = err };
|
||||||
|
};
|
||||||
|
if (out.len == 0) break;
|
||||||
|
@memcpy(buffer[0..out.len], out);
|
||||||
|
total += out.len;
|
||||||
|
}
|
||||||
|
return .{ .len = total, .end = total == 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn streamReadVec(self: *Self, data: []const []u8) std.io.Reader.Result {
|
||||||
|
return reader_streamReadVec(self, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reader(self: *Self) std.io.Reader {
|
||||||
|
return .{
|
||||||
|
.context = self,
|
||||||
|
.vtable = &.{
|
||||||
|
.posRead = null,
|
||||||
|
.posReadVec = null,
|
||||||
|
.streamRead = reader_streamRead,
|
||||||
|
.streamReadVec = reader_streamReadVec,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -567,3 +593,427 @@ test "bug 19895" {
|
|||||||
var buf: [0]u8 = undefined;
|
var buf: [0]u8 = undefined;
|
||||||
try testing.expectEqual(0, try decomp.read(&buf));
|
try testing.expectEqual(0, try decomp.read(&buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bit reader used during inflate (decompression). Has internal buffer of 64
|
||||||
|
/// bits which shifts right after bits are consumed. Uses forward_reader to fill
|
||||||
|
/// that internal buffer when needed.
|
||||||
|
///
|
||||||
|
/// readF is the core function. Supports few different ways of getting bits
|
||||||
|
/// controlled by flags. In hot path we try to avoid checking whether we need to
|
||||||
|
/// fill buffer from forward_reader by calling fill in advance and readF with
|
||||||
|
/// buffered flag set.
|
||||||
|
///
|
||||||
|
pub fn BitReader(comptime T: type) type {
|
||||||
|
assert(T == u32 or T == u64);
|
||||||
|
const t_bytes: usize = @sizeOf(T);
|
||||||
|
const Tshift = if (T == u64) u6 else u5;
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
// Underlying reader used for filling internal bits buffer
|
||||||
|
forward_reader: *std.io.BufferedReader,
|
||||||
|
// Internal buffer of 64 bits
|
||||||
|
bits: T = 0,
|
||||||
|
// Number of bits in the buffer
|
||||||
|
nbits: u32 = 0,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const Flags = packed struct(u3) {
|
||||||
|
/// dont advance internal buffer, just get bits, leave them in buffer
|
||||||
|
peek: bool = false,
|
||||||
|
/// assume that there is no need to fill, fill should be called before
|
||||||
|
buffered: bool = false,
|
||||||
|
/// bit reverse read bits
|
||||||
|
reverse: bool = false,
|
||||||
|
|
||||||
|
/// work around https://github.com/ziglang/zig/issues/18882
|
||||||
|
pub inline fn toInt(f: Flags) u3 {
|
||||||
|
return @bitCast(f);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(forward_reader: *std.io.BufferedReader) Self {
|
||||||
|
var self = Self{ .forward_reader = forward_reader };
|
||||||
|
self.fill(1) catch {};
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to have `nice` bits are available in buffer. Reads from
|
||||||
|
/// forward reader if there is no `nice` bits in buffer. Returns error
|
||||||
|
/// if end of forward stream is reached and internal buffer is empty.
|
||||||
|
/// It will not error if less than `nice` bits are in buffer, only when
|
||||||
|
/// all bits are exhausted. During inflate we usually know what is the
|
||||||
|
/// maximum bits for the next step but usually that step will need less
|
||||||
|
/// bits to decode. So `nice` is not hard limit, it will just try to have
|
||||||
|
/// that number of bits available. If end of forward stream is reached
|
||||||
|
/// it may be some extra zero bits in buffer.
|
||||||
|
pub fn fill(self: *Self, nice: u6) !void {
|
||||||
|
if (self.nbits >= nice and nice != 0) {
|
||||||
|
return; // We have enough bits
|
||||||
|
}
|
||||||
|
// Read more bits from forward reader
|
||||||
|
|
||||||
|
// Number of empty bytes in bits, round nbits to whole bytes.
|
||||||
|
const empty_bytes =
|
||||||
|
@as(u8, if (self.nbits & 0x7 == 0) t_bytes else t_bytes - 1) - // 8 for 8, 16, 24..., 7 otherwise
|
||||||
|
(self.nbits >> 3); // 0 for 0-7, 1 for 8-16, ... same as / 8
|
||||||
|
|
||||||
|
var buf: [t_bytes]u8 = [_]u8{0} ** t_bytes;
|
||||||
|
const bytes_read = self.forward_reader.partialRead(buf[0..empty_bytes]) catch 0;
|
||||||
|
if (bytes_read > 0) {
|
||||||
|
const u: T = std.mem.readInt(T, buf[0..t_bytes], .little);
|
||||||
|
self.bits |= u << @as(Tshift, @intCast(self.nbits));
|
||||||
|
self.nbits += 8 * @as(u8, @intCast(bytes_read));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.nbits == 0)
|
||||||
|
return error.EndOfStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read exactly buf.len bytes into buf.
|
||||||
|
pub fn readAll(self: *Self, buf: []u8) anyerror!void {
|
||||||
|
assert(self.alignBits() == 0); // internal bits must be at byte boundary
|
||||||
|
|
||||||
|
// First read from internal bits buffer.
|
||||||
|
var n: usize = 0;
|
||||||
|
while (self.nbits > 0 and n < buf.len) {
|
||||||
|
buf[n] = try self.readF(u8, .{ .buffered = true });
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
// Then use forward reader for all other bytes.
|
||||||
|
try self.forward_reader.read(buf[n..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Alias for readF(U, 0).
|
||||||
|
pub fn read(self: *Self, comptime U: type) !U {
|
||||||
|
return self.readF(U, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Alias for readF with flag.peak set.
|
||||||
|
pub inline fn peekF(self: *Self, comptime U: type, comptime how: Flags) !U {
|
||||||
|
return self.readF(U, .{
|
||||||
|
.peek = true,
|
||||||
|
.buffered = how.buffered,
|
||||||
|
.reverse = how.reverse,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read with flags provided.
|
||||||
|
pub fn readF(self: *Self, comptime U: type, comptime how: Flags) !U {
|
||||||
|
if (U == T) {
|
||||||
|
assert(how.toInt() == 0);
|
||||||
|
assert(self.alignBits() == 0);
|
||||||
|
try self.fill(@bitSizeOf(T));
|
||||||
|
if (self.nbits != @bitSizeOf(T)) return error.EndOfStream;
|
||||||
|
const v = self.bits;
|
||||||
|
self.nbits = 0;
|
||||||
|
self.bits = 0;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
const n: Tshift = @bitSizeOf(U);
|
||||||
|
// work around https://github.com/ziglang/zig/issues/18882
|
||||||
|
switch (how.toInt()) {
|
||||||
|
@as(Flags, .{}).toInt() => { // `normal` read
|
||||||
|
try self.fill(n); // ensure that there are n bits in the buffer
|
||||||
|
const u: U = @truncate(self.bits); // get n bits
|
||||||
|
try self.shift(n); // advance buffer for n
|
||||||
|
return u;
|
||||||
|
},
|
||||||
|
@as(Flags, .{ .peek = true }).toInt() => { // no shift, leave bits in the buffer
|
||||||
|
try self.fill(n);
|
||||||
|
return @truncate(self.bits);
|
||||||
|
},
|
||||||
|
@as(Flags, .{ .buffered = true }).toInt() => { // no fill, assume that buffer has enough bits
|
||||||
|
const u: U = @truncate(self.bits);
|
||||||
|
try self.shift(n);
|
||||||
|
return u;
|
||||||
|
},
|
||||||
|
@as(Flags, .{ .reverse = true }).toInt() => { // same as 0 with bit reverse
|
||||||
|
try self.fill(n);
|
||||||
|
const u: U = @truncate(self.bits);
|
||||||
|
try self.shift(n);
|
||||||
|
return @bitReverse(u);
|
||||||
|
},
|
||||||
|
@as(Flags, .{ .peek = true, .reverse = true }).toInt() => {
|
||||||
|
try self.fill(n);
|
||||||
|
return @bitReverse(@as(U, @truncate(self.bits)));
|
||||||
|
},
|
||||||
|
@as(Flags, .{ .buffered = true, .reverse = true }).toInt() => {
|
||||||
|
const u: U = @truncate(self.bits);
|
||||||
|
try self.shift(n);
|
||||||
|
return @bitReverse(u);
|
||||||
|
},
|
||||||
|
@as(Flags, .{ .peek = true, .buffered = true }).toInt() => {
|
||||||
|
return @truncate(self.bits);
|
||||||
|
},
|
||||||
|
@as(Flags, .{ .peek = true, .buffered = true, .reverse = true }).toInt() => {
|
||||||
|
return @bitReverse(@as(U, @truncate(self.bits)));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read n number of bits.
|
||||||
|
/// Only buffered flag can be used in how.
|
||||||
|
pub fn readN(self: *Self, n: u4, comptime how: Flags) !u16 {
|
||||||
|
// work around https://github.com/ziglang/zig/issues/18882
|
||||||
|
switch (how.toInt()) {
|
||||||
|
@as(Flags, .{}).toInt() => {
|
||||||
|
try self.fill(n);
|
||||||
|
},
|
||||||
|
@as(Flags, .{ .buffered = true }).toInt() => {},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
const mask: u16 = (@as(u16, 1) << n) - 1;
|
||||||
|
const u: u16 = @as(u16, @truncate(self.bits)) & mask;
|
||||||
|
try self.shift(n);
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance buffer for n bits.
|
||||||
|
pub fn shift(self: *Self, n: Tshift) !void {
|
||||||
|
if (n > self.nbits) return error.EndOfStream;
|
||||||
|
self.bits >>= n;
|
||||||
|
self.nbits -= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skip n bytes.
|
||||||
|
pub fn skipBytes(self: *Self, n: u16) !void {
|
||||||
|
for (0..n) |_| {
|
||||||
|
try self.fill(8);
|
||||||
|
try self.shift(8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of bits to align stream to the byte boundary.
|
||||||
|
fn alignBits(self: *Self) u3 {
|
||||||
|
return @intCast(self.nbits & 0x7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Align stream to the byte boundary.
|
||||||
|
pub fn alignToByte(self: *Self) void {
|
||||||
|
const ab = self.alignBits();
|
||||||
|
if (ab > 0) self.shift(ab) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skip zero terminated string.
|
||||||
|
pub fn skipStringZ(self: *Self) !void {
|
||||||
|
while (true) {
|
||||||
|
if (try self.readF(u8, 0) == 0) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read deflate fixed fixed code.
|
||||||
|
/// Reads first 7 bits, and then maybe 1 or 2 more to get full 7,8 or 9 bit code.
|
||||||
|
/// ref: https://datatracker.ietf.org/doc/html/rfc1951#page-12
|
||||||
|
/// Lit Value Bits Codes
|
||||||
|
/// --------- ---- -----
|
||||||
|
/// 0 - 143 8 00110000 through
|
||||||
|
/// 10111111
|
||||||
|
/// 144 - 255 9 110010000 through
|
||||||
|
/// 111111111
|
||||||
|
/// 256 - 279 7 0000000 through
|
||||||
|
/// 0010111
|
||||||
|
/// 280 - 287 8 11000000 through
|
||||||
|
/// 11000111
|
||||||
|
pub fn readFixedCode(self: *Self) !u16 {
|
||||||
|
try self.fill(7 + 2);
|
||||||
|
const code7 = try self.readF(u7, .{ .buffered = true, .reverse = true });
|
||||||
|
if (code7 <= 0b0010_111) { // 7 bits, 256-279, codes 0000_000 - 0010_111
|
||||||
|
return @as(u16, code7) + 256;
|
||||||
|
} else if (code7 <= 0b1011_111) { // 8 bits, 0-143, codes 0011_0000 through 1011_1111
|
||||||
|
return (@as(u16, code7) << 1) + @as(u16, try self.readF(u1, .{ .buffered = true })) - 0b0011_0000;
|
||||||
|
} else if (code7 <= 0b1100_011) { // 8 bit, 280-287, codes 1100_0000 - 1100_0111
|
||||||
|
return (@as(u16, code7 - 0b1100000) << 1) + try self.readF(u1, .{ .buffered = true }) + 280;
|
||||||
|
} else { // 9 bit, 144-255, codes 1_1001_0000 - 1_1111_1111
|
||||||
|
return (@as(u16, code7 - 0b1100_100) << 2) + @as(u16, try self.readF(u2, .{ .buffered = true, .reverse = true })) + 144;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "readF" {
|
||||||
|
var input: std.io.BufferedReader = undefined;
|
||||||
|
input.initFixed(&[_]u8{ 0xf3, 0x48, 0xcd, 0xc9, 0x00, 0x00 });
|
||||||
|
var br: BitReader(u64) = .init(&input);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(u8, 48), br.nbits);
|
||||||
|
try testing.expectEqual(@as(u64, 0xc9cd48f3), br.bits);
|
||||||
|
|
||||||
|
try testing.expect(try br.readF(u1, 0) == 0b0000_0001);
|
||||||
|
try testing.expect(try br.readF(u2, 0) == 0b0000_0001);
|
||||||
|
try testing.expectEqual(@as(u8, 48 - 3), br.nbits);
|
||||||
|
try testing.expectEqual(@as(u3, 5), br.alignBits());
|
||||||
|
|
||||||
|
try testing.expect(try br.readF(u8, .{ .peek = true }) == 0b0001_1110);
|
||||||
|
try testing.expect(try br.readF(u9, .{ .peek = true }) == 0b1_0001_1110);
|
||||||
|
try br.shift(9);
|
||||||
|
try testing.expectEqual(@as(u8, 36), br.nbits);
|
||||||
|
try testing.expectEqual(@as(u3, 4), br.alignBits());
|
||||||
|
|
||||||
|
try testing.expect(try br.readF(u4, 0) == 0b0100);
|
||||||
|
try testing.expectEqual(@as(u8, 32), br.nbits);
|
||||||
|
try testing.expectEqual(@as(u3, 0), br.alignBits());
|
||||||
|
|
||||||
|
try br.shift(1);
|
||||||
|
try testing.expectEqual(@as(u3, 7), br.alignBits());
|
||||||
|
try br.shift(1);
|
||||||
|
try testing.expectEqual(@as(u3, 6), br.alignBits());
|
||||||
|
br.alignToByte();
|
||||||
|
try testing.expectEqual(@as(u3, 0), br.alignBits());
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(u64, 0xc9), br.bits);
|
||||||
|
try testing.expectEqual(@as(u16, 0x9), try br.readN(4, 0));
|
||||||
|
try testing.expectEqual(@as(u16, 0xc), try br.readN(4, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "read block type 1 data" {
|
||||||
|
inline for ([_]type{ u64, u32 }) |T| {
|
||||||
|
const data = [_]u8{
|
||||||
|
0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0x28, 0xcf, // deflate data block type 1
|
||||||
|
0x2f, 0xca, 0x49, 0xe1, 0x02, 0x00,
|
||||||
|
0x0c, 0x01, 0x02, 0x03, //
|
||||||
|
0xaa, 0xbb, 0xcc, 0xdd,
|
||||||
|
};
|
||||||
|
var fbs: std.io.BufferedReader = undefined;
|
||||||
|
fbs.initFixed(&data);
|
||||||
|
var br: BitReader(T) = .init(&fbs);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(u1, 1), try br.readF(u1, 0)); // bfinal
|
||||||
|
try testing.expectEqual(@as(u2, 1), try br.readF(u2, 0)); // block_type
|
||||||
|
|
||||||
|
for ("Hello world\n") |c| {
|
||||||
|
try testing.expectEqual(@as(u8, c), try br.readF(u8, .{ .reverse = true }) - 0x30);
|
||||||
|
}
|
||||||
|
try testing.expectEqual(@as(u7, 0), try br.readF(u7, 0)); // end of block
|
||||||
|
br.alignToByte();
|
||||||
|
try testing.expectEqual(@as(u32, 0x0302010c), try br.readF(u32, 0));
|
||||||
|
try testing.expectEqual(@as(u16, 0xbbaa), try br.readF(u16, 0));
|
||||||
|
try testing.expectEqual(@as(u16, 0xddcc), try br.readF(u16, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "shift/fill" {
|
||||||
|
const data = [_]u8{
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
};
|
||||||
|
var fbs: std.io.BufferedReader = undefined;
|
||||||
|
fbs.initFixed(&data);
|
||||||
|
var br: BitReader(u64) = .init(&fbs);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(u64, 0x08_07_06_05_04_03_02_01), br.bits);
|
||||||
|
try br.shift(8);
|
||||||
|
try testing.expectEqual(@as(u64, 0x00_08_07_06_05_04_03_02), br.bits);
|
||||||
|
try br.fill(60); // fill with 1 byte
|
||||||
|
try testing.expectEqual(@as(u64, 0x01_08_07_06_05_04_03_02), br.bits);
|
||||||
|
try br.shift(8 * 4 + 4);
|
||||||
|
try testing.expectEqual(@as(u64, 0x00_00_00_00_00_10_80_70), br.bits);
|
||||||
|
|
||||||
|
try br.fill(60); // fill with 4 bytes (shift by 4)
|
||||||
|
try testing.expectEqual(@as(u64, 0x00_50_40_30_20_10_80_70), br.bits);
|
||||||
|
try testing.expectEqual(@as(u8, 8 * 7 + 4), br.nbits);
|
||||||
|
|
||||||
|
try br.shift(@intCast(br.nbits)); // clear buffer
|
||||||
|
try br.fill(8); // refill with the rest of the bytes
|
||||||
|
try testing.expectEqual(@as(u64, 0x00_00_00_00_00_08_07_06), br.bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "readAll" {
|
||||||
|
inline for ([_]type{ u64, u32 }) |T| {
|
||||||
|
const data = [_]u8{
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
};
|
||||||
|
var fbs: std.io.BufferedReader = undefined;
|
||||||
|
fbs.initFixed(&data);
|
||||||
|
var br: BitReader(T) = .init(&fbs);
|
||||||
|
|
||||||
|
switch (T) {
|
||||||
|
u64 => try testing.expectEqual(@as(u64, 0x08_07_06_05_04_03_02_01), br.bits),
|
||||||
|
u32 => try testing.expectEqual(@as(u32, 0x04_03_02_01), br.bits),
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
|
||||||
|
var out: [16]u8 = undefined;
|
||||||
|
try br.readAll(out[0..]);
|
||||||
|
try testing.expect(br.nbits == 0);
|
||||||
|
try testing.expect(br.bits == 0);
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, data[0..16], &out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "readFixedCode" {
|
||||||
|
inline for ([_]type{ u64, u32 }) |T| {
|
||||||
|
const fixed_codes = @import("huffman_encoder.zig").fixed_codes;
|
||||||
|
|
||||||
|
var fbs: std.io.BufferedReader = undefined;
|
||||||
|
fbs.initFixed(&fixed_codes);
|
||||||
|
var rdr: BitReader(T) = .init(&fbs);
|
||||||
|
|
||||||
|
for (0..286) |c| {
|
||||||
|
try testing.expectEqual(c, try rdr.readFixedCode());
|
||||||
|
}
|
||||||
|
try testing.expect(rdr.nbits == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "u32 leaves no bits on u32 reads" {
|
||||||
|
const data = [_]u8{
|
||||||
|
0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||||
|
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||||
|
};
|
||||||
|
var fbs: std.io.BufferedReader = undefined;
|
||||||
|
fbs.initFixed(&data);
|
||||||
|
var br: BitReader(u32) = .init(&fbs);
|
||||||
|
|
||||||
|
_ = try br.read(u3);
|
||||||
|
try testing.expectEqual(29, br.nbits);
|
||||||
|
br.alignToByte();
|
||||||
|
try testing.expectEqual(24, br.nbits);
|
||||||
|
try testing.expectEqual(0x04_03_02_01, try br.read(u32));
|
||||||
|
try testing.expectEqual(0, br.nbits);
|
||||||
|
try testing.expectEqual(0x08_07_06_05, try br.read(u32));
|
||||||
|
try testing.expectEqual(0, br.nbits);
|
||||||
|
|
||||||
|
_ = try br.read(u9);
|
||||||
|
try testing.expectEqual(23, br.nbits);
|
||||||
|
br.alignToByte();
|
||||||
|
try testing.expectEqual(16, br.nbits);
|
||||||
|
try testing.expectEqual(0x0e_0d_0c_0b, try br.read(u32));
|
||||||
|
try testing.expectEqual(0, br.nbits);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "u64 need fill after alignToByte" {
|
||||||
|
const data = [_]u8{
|
||||||
|
0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||||
|
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
// without fill
|
||||||
|
var fbs: std.io.BufferedReader = undefined;
|
||||||
|
fbs.initFixed(&data);
|
||||||
|
var br: BitReader(u64) = .init(&fbs);
|
||||||
|
_ = try br.read(u23);
|
||||||
|
try testing.expectEqual(41, br.nbits);
|
||||||
|
br.alignToByte();
|
||||||
|
try testing.expectEqual(40, br.nbits);
|
||||||
|
try testing.expectEqual(0x06_05_04_03, try br.read(u32));
|
||||||
|
try testing.expectEqual(8, br.nbits);
|
||||||
|
try testing.expectEqual(0x0a_09_08_07, try br.read(u32));
|
||||||
|
try testing.expectEqual(32, br.nbits);
|
||||||
|
|
||||||
|
// fill after align ensures all bits filled
|
||||||
|
fbs.reset();
|
||||||
|
br = .init(&fbs);
|
||||||
|
_ = try br.read(u23);
|
||||||
|
try testing.expectEqual(41, br.nbits);
|
||||||
|
br.alignToByte();
|
||||||
|
try br.fill(0);
|
||||||
|
try testing.expectEqual(64, br.nbits);
|
||||||
|
try testing.expectEqual(0x06_05_04_03, try br.read(u32));
|
||||||
|
try testing.expectEqual(32, br.nbits);
|
||||||
|
try testing.expectEqual(0x0a_09_08_07, try br.read(u32));
|
||||||
|
try testing.expectEqual(0, br.nbits);
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,539 +0,0 @@
|
|||||||
const std = @import("../../std.zig");
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const math = std.math;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
pub const lzbuffer = @import("decode/lzbuffer.zig");
|
|
||||||
|
|
||||||
const LzCircularBuffer = lzbuffer.LzCircularBuffer;
|
|
||||||
const Vec2D = @import("vec2d.zig").Vec2D;
|
|
||||||
|
|
||||||
pub const RangeDecoder = struct {
|
|
||||||
range: u32,
|
|
||||||
code: u32,
|
|
||||||
|
|
||||||
pub fn init(br: *std.io.BufferedReader) !RangeDecoder {
|
|
||||||
const reserved = try br.takeByte();
|
|
||||||
if (reserved != 0) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
return .{
|
|
||||||
.range = 0xFFFF_FFFF,
|
|
||||||
.code = try br.readInt(u32, .big),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub inline fn isFinished(self: RangeDecoder) bool {
|
|
||||||
return self.code == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fn normalize(self: *RangeDecoder, br: *std.io.BufferedReader) !void {
|
|
||||||
if (self.range < 0x0100_0000) {
|
|
||||||
self.range <<= 8;
|
|
||||||
self.code = (self.code << 8) ^ @as(u32, try br.takeByte());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fn getBit(self: *RangeDecoder, br: *std.io.BufferedReader) !bool {
|
|
||||||
self.range >>= 1;
|
|
||||||
|
|
||||||
const bit = self.code >= self.range;
|
|
||||||
if (bit)
|
|
||||||
self.code -= self.range;
|
|
||||||
|
|
||||||
try self.normalize(br);
|
|
||||||
return bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: *RangeDecoder, br: *std.io.BufferedReader, count: usize) !u32 {
|
|
||||||
var result: u32 = 0;
|
|
||||||
var i: usize = 0;
|
|
||||||
while (i < count) : (i += 1)
|
|
||||||
result = (result << 1) ^ @intFromBool(try self.getBit(br));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub inline fn decodeBit(self: *RangeDecoder, br: *std.io.BufferedReader, prob: *u16, update: bool) !bool {
|
|
||||||
const bound = (self.range >> 11) * prob.*;
|
|
||||||
|
|
||||||
if (self.code < bound) {
|
|
||||||
if (update)
|
|
||||||
prob.* += (0x800 - prob.*) >> 5;
|
|
||||||
self.range = bound;
|
|
||||||
|
|
||||||
try self.normalize(br);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
if (update)
|
|
||||||
prob.* -= prob.* >> 5;
|
|
||||||
self.code -= bound;
|
|
||||||
self.range -= bound;
|
|
||||||
|
|
||||||
try self.normalize(br);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parseBitTree(
|
|
||||||
self: *RangeDecoder,
|
|
||||||
br: *std.io.BufferedReader,
|
|
||||||
num_bits: u5,
|
|
||||||
probs: []u16,
|
|
||||||
update: bool,
|
|
||||||
) !u32 {
|
|
||||||
var tmp: u32 = 1;
|
|
||||||
var i: @TypeOf(num_bits) = 0;
|
|
||||||
while (i < num_bits) : (i += 1) {
|
|
||||||
const bit = try self.decodeBit(br, &probs[tmp], update);
|
|
||||||
tmp = (tmp << 1) ^ @intFromBool(bit);
|
|
||||||
}
|
|
||||||
return tmp - (@as(u32, 1) << num_bits);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parseReverseBitTree(
|
|
||||||
self: *RangeDecoder,
|
|
||||||
br: *std.io.BufferedReader,
|
|
||||||
num_bits: u5,
|
|
||||||
probs: []u16,
|
|
||||||
offset: usize,
|
|
||||||
update: bool,
|
|
||||||
) !u32 {
|
|
||||||
var result: u32 = 0;
|
|
||||||
var tmp: usize = 1;
|
|
||||||
var i: @TypeOf(num_bits) = 0;
|
|
||||||
while (i < num_bits) : (i += 1) {
|
|
||||||
const bit = @intFromBool(try self.decodeBit(br, &probs[offset + tmp], update));
|
|
||||||
tmp = (tmp << 1) ^ bit;
|
|
||||||
result ^= @as(u32, bit) << i;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn BitTree(comptime num_bits: usize) type {
|
|
||||||
return struct {
|
|
||||||
probs: [1 << num_bits]u16 = @splat(0x400),
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn parse(
|
|
||||||
self: *Self,
|
|
||||||
br: *std.io.BufferedReader,
|
|
||||||
decoder: *RangeDecoder,
|
|
||||||
update: bool,
|
|
||||||
) !u32 {
|
|
||||||
return decoder.parseBitTree(br, num_bits, &self.probs, update);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parseReverse(
|
|
||||||
self: *Self,
|
|
||||||
br: *std.io.BufferedReader,
|
|
||||||
decoder: *RangeDecoder,
|
|
||||||
update: bool,
|
|
||||||
) !u32 {
|
|
||||||
return decoder.parseReverseBitTree(br, num_bits, &self.probs, 0, update);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(self: *Self) void {
|
|
||||||
@memset(&self.probs, 0x400);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const LenDecoder = struct {
|
|
||||||
choice: u16 = 0x400,
|
|
||||||
choice2: u16 = 0x400,
|
|
||||||
low_coder: [16]BitTree(3) = @splat(.{}),
|
|
||||||
mid_coder: [16]BitTree(3) = @splat(.{}),
|
|
||||||
high_coder: BitTree(8) = .{},
|
|
||||||
|
|
||||||
pub fn decode(
|
|
||||||
self: *LenDecoder,
|
|
||||||
br: *std.io.BufferedReader,
|
|
||||||
decoder: *RangeDecoder,
|
|
||||||
pos_state: usize,
|
|
||||||
update: bool,
|
|
||||||
) !usize {
|
|
||||||
if (!try decoder.decodeBit(br, &self.choice, update)) {
|
|
||||||
return @as(usize, try self.low_coder[pos_state].parse(br, decoder, update));
|
|
||||||
} else if (!try decoder.decodeBit(br, &self.choice2, update)) {
|
|
||||||
return @as(usize, try self.mid_coder[pos_state].parse(br, decoder, update)) + 8;
|
|
||||||
} else {
|
|
||||||
return @as(usize, try self.high_coder.parse(br, decoder, update)) + 16;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(self: *LenDecoder) void {
|
|
||||||
self.choice = 0x400;
|
|
||||||
self.choice2 = 0x400;
|
|
||||||
for (&self.low_coder) |*t| t.reset();
|
|
||||||
for (&self.mid_coder) |*t| t.reset();
|
|
||||||
self.high_coder.reset();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Options = struct {
|
|
||||||
unpacked_size: UnpackedSize = .read_from_header,
|
|
||||||
memlimit: ?usize = null,
|
|
||||||
allow_incomplete: bool = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const UnpackedSize = union(enum) {
|
|
||||||
read_from_header,
|
|
||||||
read_header_but_use_provided: ?u64,
|
|
||||||
use_provided: ?u64,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ProcessingStatus = enum {
|
|
||||||
continue_,
|
|
||||||
finished,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Properties = struct {
|
|
||||||
lc: u4,
|
|
||||||
lp: u3,
|
|
||||||
pb: u3,
|
|
||||||
|
|
||||||
fn validate(self: Properties) void {
|
|
||||||
assert(self.lc <= 8);
|
|
||||||
assert(self.lp <= 4);
|
|
||||||
assert(self.pb <= 4);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Params = struct {
|
|
||||||
properties: Properties,
|
|
||||||
dict_size: u32,
|
|
||||||
unpacked_size: ?u64,
|
|
||||||
|
|
||||||
pub fn readHeader(reader: anytype, options: Options) !Params {
|
|
||||||
var props = try reader.readByte();
|
|
||||||
if (props >= 225) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lc = @as(u4, @intCast(props % 9));
|
|
||||||
props /= 9;
|
|
||||||
const lp = @as(u3, @intCast(props % 5));
|
|
||||||
props /= 5;
|
|
||||||
const pb = @as(u3, @intCast(props));
|
|
||||||
|
|
||||||
const dict_size_provided = try reader.readInt(u32, .little);
|
|
||||||
const dict_size = @max(0x1000, dict_size_provided);
|
|
||||||
|
|
||||||
const unpacked_size = switch (options.unpacked_size) {
|
|
||||||
.read_from_header => blk: {
|
|
||||||
const unpacked_size_provided = try reader.readInt(u64, .little);
|
|
||||||
const marker_mandatory = unpacked_size_provided == 0xFFFF_FFFF_FFFF_FFFF;
|
|
||||||
break :blk if (marker_mandatory)
|
|
||||||
null
|
|
||||||
else
|
|
||||||
unpacked_size_provided;
|
|
||||||
},
|
|
||||||
.read_header_but_use_provided => |x| blk: {
|
|
||||||
_ = try reader.readInt(u64, .little);
|
|
||||||
break :blk x;
|
|
||||||
},
|
|
||||||
.use_provided => |x| x,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Params{
|
|
||||||
.properties = Properties{ .lc = lc, .lp = lp, .pb = pb },
|
|
||||||
.dict_size = dict_size,
|
|
||||||
.unpacked_size = unpacked_size,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const DecoderState = struct {
|
|
||||||
lzma_props: Properties,
|
|
||||||
unpacked_size: ?u64,
|
|
||||||
literal_probs: Vec2D(u16),
|
|
||||||
pos_slot_decoder: [4]BitTree(6),
|
|
||||||
align_decoder: BitTree(4),
|
|
||||||
pos_decoders: [115]u16,
|
|
||||||
is_match: [192]u16,
|
|
||||||
is_rep: [12]u16,
|
|
||||||
is_rep_g0: [12]u16,
|
|
||||||
is_rep_g1: [12]u16,
|
|
||||||
is_rep_g2: [12]u16,
|
|
||||||
is_rep_0long: [192]u16,
|
|
||||||
state: usize,
|
|
||||||
rep: [4]usize,
|
|
||||||
len_decoder: LenDecoder,
|
|
||||||
rep_len_decoder: LenDecoder,
|
|
||||||
|
|
||||||
pub fn init(
|
|
||||||
allocator: Allocator,
|
|
||||||
lzma_props: Properties,
|
|
||||||
unpacked_size: ?u64,
|
|
||||||
) !DecoderState {
|
|
||||||
return .{
|
|
||||||
.lzma_props = lzma_props,
|
|
||||||
.unpacked_size = unpacked_size,
|
|
||||||
.literal_probs = try Vec2D(u16).init(allocator, 0x400, .{ @as(usize, 1) << (lzma_props.lc + lzma_props.lp), 0x300 }),
|
|
||||||
.pos_slot_decoder = @splat(.{}),
|
|
||||||
.align_decoder = .{},
|
|
||||||
.pos_decoders = @splat(0x400),
|
|
||||||
.is_match = @splat(0x400),
|
|
||||||
.is_rep = @splat(0x400),
|
|
||||||
.is_rep_g0 = @splat(0x400),
|
|
||||||
.is_rep_g1 = @splat(0x400),
|
|
||||||
.is_rep_g2 = @splat(0x400),
|
|
||||||
.is_rep_0long = @splat(0x400),
|
|
||||||
.state = 0,
|
|
||||||
.rep = @splat(0),
|
|
||||||
.len_decoder = .{},
|
|
||||||
.rep_len_decoder = .{},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *DecoderState, allocator: Allocator) void {
|
|
||||||
self.literal_probs.deinit(allocator);
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resetState(self: *DecoderState, allocator: Allocator, new_props: Properties) !void {
|
|
||||||
new_props.validate();
|
|
||||||
if (self.lzma_props.lc + self.lzma_props.lp == new_props.lc + new_props.lp) {
|
|
||||||
self.literal_probs.fill(0x400);
|
|
||||||
} else {
|
|
||||||
self.literal_probs.deinit(allocator);
|
|
||||||
self.literal_probs = try Vec2D(u16).init(allocator, 0x400, .{ @as(usize, 1) << (new_props.lc + new_props.lp), 0x300 });
|
|
||||||
}
|
|
||||||
|
|
||||||
self.lzma_props = new_props;
|
|
||||||
for (&self.pos_slot_decoder) |*t| t.reset();
|
|
||||||
self.align_decoder.reset();
|
|
||||||
self.pos_decoders = @splat(0x400);
|
|
||||||
self.is_match = @splat(0x400);
|
|
||||||
self.is_rep = @splat(0x400);
|
|
||||||
self.is_rep_g0 = @splat(0x400);
|
|
||||||
self.is_rep_g1 = @splat(0x400);
|
|
||||||
self.is_rep_g2 = @splat(0x400);
|
|
||||||
self.is_rep_0long = @splat(0x400);
|
|
||||||
self.state = 0;
|
|
||||||
self.rep = @splat(0);
|
|
||||||
self.len_decoder.reset();
|
|
||||||
self.rep_len_decoder.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn processNextInner(
|
|
||||||
self: *DecoderState,
|
|
||||||
allocator: Allocator,
|
|
||||||
reader: anytype,
|
|
||||||
writer: anytype,
|
|
||||||
buffer: anytype,
|
|
||||||
decoder: *RangeDecoder,
|
|
||||||
update: bool,
|
|
||||||
) !ProcessingStatus {
|
|
||||||
const pos_state = buffer.len & ((@as(usize, 1) << self.lzma_props.pb) - 1);
|
|
||||||
|
|
||||||
if (!try decoder.decodeBit(
|
|
||||||
reader,
|
|
||||||
&self.is_match[(self.state << 4) + pos_state],
|
|
||||||
update,
|
|
||||||
)) {
|
|
||||||
const byte: u8 = try self.decodeLiteral(reader, buffer, decoder, update);
|
|
||||||
|
|
||||||
if (update) {
|
|
||||||
try buffer.appendLiteral(allocator, byte, writer);
|
|
||||||
|
|
||||||
self.state = if (self.state < 4)
|
|
||||||
0
|
|
||||||
else if (self.state < 10)
|
|
||||||
self.state - 3
|
|
||||||
else
|
|
||||||
self.state - 6;
|
|
||||||
}
|
|
||||||
return .continue_;
|
|
||||||
}
|
|
||||||
|
|
||||||
var len: usize = undefined;
|
|
||||||
if (try decoder.decodeBit(reader, &self.is_rep[self.state], update)) {
|
|
||||||
if (!try decoder.decodeBit(reader, &self.is_rep_g0[self.state], update)) {
|
|
||||||
if (!try decoder.decodeBit(
|
|
||||||
reader,
|
|
||||||
&self.is_rep_0long[(self.state << 4) + pos_state],
|
|
||||||
update,
|
|
||||||
)) {
|
|
||||||
if (update) {
|
|
||||||
self.state = if (self.state < 7) 9 else 11;
|
|
||||||
const dist = self.rep[0] + 1;
|
|
||||||
try buffer.appendLz(allocator, 1, dist, writer);
|
|
||||||
}
|
|
||||||
return .continue_;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const idx: usize = if (!try decoder.decodeBit(reader, &self.is_rep_g1[self.state], update))
|
|
||||||
1
|
|
||||||
else if (!try decoder.decodeBit(reader, &self.is_rep_g2[self.state], update))
|
|
||||||
2
|
|
||||||
else
|
|
||||||
3;
|
|
||||||
if (update) {
|
|
||||||
const dist = self.rep[idx];
|
|
||||||
var i = idx;
|
|
||||||
while (i > 0) : (i -= 1) {
|
|
||||||
self.rep[i] = self.rep[i - 1];
|
|
||||||
}
|
|
||||||
self.rep[0] = dist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
len = try self.rep_len_decoder.decode(reader, decoder, pos_state, update);
|
|
||||||
|
|
||||||
if (update) {
|
|
||||||
self.state = if (self.state < 7) 8 else 11;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (update) {
|
|
||||||
self.rep[3] = self.rep[2];
|
|
||||||
self.rep[2] = self.rep[1];
|
|
||||||
self.rep[1] = self.rep[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
len = try self.len_decoder.decode(reader, decoder, pos_state, update);
|
|
||||||
|
|
||||||
if (update) {
|
|
||||||
self.state = if (self.state < 7) 7 else 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rep_0 = try self.decodeDistance(reader, decoder, len, update);
|
|
||||||
|
|
||||||
if (update) {
|
|
||||||
self.rep[0] = rep_0;
|
|
||||||
if (self.rep[0] == 0xFFFF_FFFF) {
|
|
||||||
if (decoder.isFinished()) {
|
|
||||||
return .finished;
|
|
||||||
}
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update) {
|
|
||||||
len += 2;
|
|
||||||
|
|
||||||
const dist = self.rep[0] + 1;
|
|
||||||
try buffer.appendLz(allocator, len, dist, writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return .continue_;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn processNext(
|
|
||||||
self: *DecoderState,
|
|
||||||
allocator: Allocator,
|
|
||||||
reader: anytype,
|
|
||||||
writer: anytype,
|
|
||||||
buffer: anytype,
|
|
||||||
decoder: *RangeDecoder,
|
|
||||||
) !ProcessingStatus {
|
|
||||||
return self.processNextInner(allocator, reader, writer, buffer, decoder, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process(
|
|
||||||
self: *DecoderState,
|
|
||||||
allocator: Allocator,
|
|
||||||
reader: anytype,
|
|
||||||
writer: anytype,
|
|
||||||
buffer: anytype,
|
|
||||||
decoder: *RangeDecoder,
|
|
||||||
) !ProcessingStatus {
|
|
||||||
process_next: {
|
|
||||||
if (self.unpacked_size) |unpacked_size| {
|
|
||||||
if (buffer.len >= unpacked_size) {
|
|
||||||
break :process_next;
|
|
||||||
}
|
|
||||||
} else if (decoder.isFinished()) {
|
|
||||||
break :process_next;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (try self.processNext(allocator, reader, writer, buffer, decoder)) {
|
|
||||||
.continue_ => return .continue_,
|
|
||||||
.finished => break :process_next,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.unpacked_size) |unpacked_size| {
|
|
||||||
if (buffer.len != unpacked_size) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return .finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decodeLiteral(
|
|
||||||
self: *DecoderState,
|
|
||||||
reader: anytype,
|
|
||||||
buffer: anytype,
|
|
||||||
decoder: *RangeDecoder,
|
|
||||||
update: bool,
|
|
||||||
) !u8 {
|
|
||||||
const def_prev_byte = 0;
|
|
||||||
const prev_byte = @as(usize, buffer.lastOr(def_prev_byte));
|
|
||||||
|
|
||||||
var result: usize = 1;
|
|
||||||
const lit_state = ((buffer.len & ((@as(usize, 1) << self.lzma_props.lp) - 1)) << self.lzma_props.lc) +
|
|
||||||
(prev_byte >> (8 - self.lzma_props.lc));
|
|
||||||
const probs = try self.literal_probs.getMut(lit_state);
|
|
||||||
|
|
||||||
if (self.state >= 7) {
|
|
||||||
var match_byte = @as(usize, try buffer.lastN(self.rep[0] + 1));
|
|
||||||
|
|
||||||
while (result < 0x100) {
|
|
||||||
const match_bit = (match_byte >> 7) & 1;
|
|
||||||
match_byte <<= 1;
|
|
||||||
const bit = @intFromBool(try decoder.decodeBit(
|
|
||||||
reader,
|
|
||||||
&probs[((@as(usize, 1) + match_bit) << 8) + result],
|
|
||||||
update,
|
|
||||||
));
|
|
||||||
result = (result << 1) ^ bit;
|
|
||||||
if (match_bit != bit) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (result < 0x100) {
|
|
||||||
result = (result << 1) ^ @intFromBool(try decoder.decodeBit(reader, &probs[result], update));
|
|
||||||
}
|
|
||||||
|
|
||||||
return @as(u8, @truncate(result - 0x100));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decodeDistance(
|
|
||||||
self: *DecoderState,
|
|
||||||
reader: anytype,
|
|
||||||
decoder: *RangeDecoder,
|
|
||||||
length: usize,
|
|
||||||
update: bool,
|
|
||||||
) !usize {
|
|
||||||
const len_state = if (length > 3) 3 else length;
|
|
||||||
|
|
||||||
const pos_slot = @as(usize, try self.pos_slot_decoder[len_state].parse(reader, decoder, update));
|
|
||||||
if (pos_slot < 4)
|
|
||||||
return pos_slot;
|
|
||||||
|
|
||||||
const num_direct_bits = @as(u5, @intCast((pos_slot >> 1) - 1));
|
|
||||||
var result = (2 ^ (pos_slot & 1)) << num_direct_bits;
|
|
||||||
|
|
||||||
if (pos_slot < 14) {
|
|
||||||
result += try decoder.parseReverseBitTree(
|
|
||||||
reader,
|
|
||||||
num_direct_bits,
|
|
||||||
&self.pos_decoders,
|
|
||||||
result - pos_slot,
|
|
||||||
update,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
result += @as(usize, try decoder.get(reader, num_direct_bits - 4)) << 4;
|
|
||||||
result += try self.align_decoder.parseReverse(reader, decoder, update);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,228 +0,0 @@
|
|||||||
const std = @import("../../../std.zig");
|
|
||||||
const math = std.math;
|
|
||||||
const mem = std.mem;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const ArrayListUnmanaged = std.ArrayListUnmanaged;
|
|
||||||
|
|
||||||
/// An accumulating buffer for LZ sequences
|
|
||||||
pub const LzAccumBuffer = struct {
|
|
||||||
/// Buffer
|
|
||||||
buf: ArrayListUnmanaged(u8),
|
|
||||||
|
|
||||||
/// Buffer memory limit
|
|
||||||
memlimit: usize,
|
|
||||||
|
|
||||||
/// Total number of bytes sent through the buffer
|
|
||||||
len: usize,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn init(memlimit: usize) Self {
|
|
||||||
return Self{
|
|
||||||
.buf = .{},
|
|
||||||
.memlimit = memlimit,
|
|
||||||
.len = 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn appendByte(self: *Self, allocator: Allocator, byte: u8) !void {
|
|
||||||
try self.buf.append(allocator, byte);
|
|
||||||
self.len += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset the internal dictionary
|
|
||||||
pub fn reset(self: *Self, writer: anytype) !void {
|
|
||||||
try writer.writeAll(self.buf.items);
|
|
||||||
self.buf.clearRetainingCapacity();
|
|
||||||
self.len = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve the last byte or return a default
|
|
||||||
pub fn lastOr(self: Self, lit: u8) u8 {
|
|
||||||
const buf_len = self.buf.items.len;
|
|
||||||
return if (buf_len == 0)
|
|
||||||
lit
|
|
||||||
else
|
|
||||||
self.buf.items[buf_len - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve the n-th last byte
|
|
||||||
pub fn lastN(self: Self, dist: usize) !u8 {
|
|
||||||
const buf_len = self.buf.items.len;
|
|
||||||
if (dist > buf_len) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.buf.items[buf_len - dist];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append a literal
|
|
||||||
pub fn appendLiteral(
|
|
||||||
self: *Self,
|
|
||||||
allocator: Allocator,
|
|
||||||
lit: u8,
|
|
||||||
writer: anytype,
|
|
||||||
) !void {
|
|
||||||
_ = writer;
|
|
||||||
if (self.len >= self.memlimit) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
try self.buf.append(allocator, lit);
|
|
||||||
self.len += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch an LZ sequence (length, distance) from inside the buffer
|
|
||||||
pub fn appendLz(
|
|
||||||
self: *Self,
|
|
||||||
allocator: Allocator,
|
|
||||||
len: usize,
|
|
||||||
dist: usize,
|
|
||||||
writer: anytype,
|
|
||||||
) !void {
|
|
||||||
_ = writer;
|
|
||||||
|
|
||||||
const buf_len = self.buf.items.len;
|
|
||||||
if (dist > buf_len) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset = buf_len - dist;
|
|
||||||
var i: usize = 0;
|
|
||||||
while (i < len) : (i += 1) {
|
|
||||||
const x = self.buf.items[offset];
|
|
||||||
try self.buf.append(allocator, x);
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
self.len += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(self: *Self, writer: anytype) !void {
|
|
||||||
try writer.writeAll(self.buf.items);
|
|
||||||
self.buf.clearRetainingCapacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self, allocator: Allocator) void {
|
|
||||||
self.buf.deinit(allocator);
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A circular buffer for LZ sequences
|
|
||||||
pub const LzCircularBuffer = struct {
|
|
||||||
/// Circular buffer
|
|
||||||
buf: ArrayListUnmanaged(u8),
|
|
||||||
|
|
||||||
/// Length of the buffer
|
|
||||||
dict_size: usize,
|
|
||||||
|
|
||||||
/// Buffer memory limit
|
|
||||||
memlimit: usize,
|
|
||||||
|
|
||||||
/// Current position
|
|
||||||
cursor: usize,
|
|
||||||
|
|
||||||
/// Total number of bytes sent through the buffer
|
|
||||||
len: usize,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn init(dict_size: usize, memlimit: usize) Self {
|
|
||||||
return Self{
|
|
||||||
.buf = .{},
|
|
||||||
.dict_size = dict_size,
|
|
||||||
.memlimit = memlimit,
|
|
||||||
.cursor = 0,
|
|
||||||
.len = 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: Self, index: usize) u8 {
|
|
||||||
return if (0 <= index and index < self.buf.items.len)
|
|
||||||
self.buf.items[index]
|
|
||||||
else
|
|
||||||
0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(self: *Self, allocator: Allocator, index: usize, value: u8) !void {
|
|
||||||
if (index >= self.memlimit) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
try self.buf.ensureTotalCapacity(allocator, index + 1);
|
|
||||||
while (self.buf.items.len < index) {
|
|
||||||
self.buf.appendAssumeCapacity(0);
|
|
||||||
}
|
|
||||||
self.buf.appendAssumeCapacity(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve the last byte or return a default
|
|
||||||
pub fn lastOr(self: Self, lit: u8) u8 {
|
|
||||||
return if (self.len == 0)
|
|
||||||
lit
|
|
||||||
else
|
|
||||||
self.get((self.dict_size + self.cursor - 1) % self.dict_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve the n-th last byte
|
|
||||||
pub fn lastN(self: Self, dist: usize) !u8 {
|
|
||||||
if (dist > self.dict_size or dist > self.len) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
const offset = (self.dict_size + self.cursor - dist) % self.dict_size;
|
|
||||||
return self.get(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append a literal
|
|
||||||
pub fn appendLiteral(
|
|
||||||
self: *Self,
|
|
||||||
allocator: Allocator,
|
|
||||||
lit: u8,
|
|
||||||
writer: anytype,
|
|
||||||
) !void {
|
|
||||||
try self.set(allocator, self.cursor, lit);
|
|
||||||
self.cursor += 1;
|
|
||||||
self.len += 1;
|
|
||||||
|
|
||||||
// Flush the circular buffer to the output
|
|
||||||
if (self.cursor == self.dict_size) {
|
|
||||||
try writer.writeAll(self.buf.items);
|
|
||||||
self.cursor = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch an LZ sequence (length, distance) from inside the buffer
|
|
||||||
pub fn appendLz(
|
|
||||||
self: *Self,
|
|
||||||
allocator: Allocator,
|
|
||||||
len: usize,
|
|
||||||
dist: usize,
|
|
||||||
writer: anytype,
|
|
||||||
) !void {
|
|
||||||
if (dist > self.dict_size or dist > self.len) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset = (self.dict_size + self.cursor - dist) % self.dict_size;
|
|
||||||
var i: usize = 0;
|
|
||||||
while (i < len) : (i += 1) {
|
|
||||||
const x = self.get(offset);
|
|
||||||
try self.appendLiteral(allocator, x, writer);
|
|
||||||
offset += 1;
|
|
||||||
if (offset == self.dict_size) {
|
|
||||||
offset = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(self: *Self, writer: anytype) !void {
|
|
||||||
if (self.cursor > 0) {
|
|
||||||
try writer.writeAll(self.buf.items[0..self.cursor]);
|
|
||||||
self.cursor = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self, allocator: Allocator) void {
|
|
||||||
self.buf.deinit(allocator);
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
const std = @import("../../std.zig");
|
|
||||||
const lzma = @import("../lzma.zig");
|
|
||||||
|
|
||||||
fn testDecompress(compressed: []const u8) ![]u8 {
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
var stream = std.io.fixedBufferStream(compressed);
|
|
||||||
var decompressor = try lzma.decompress(allocator, stream.reader());
|
|
||||||
defer decompressor.deinit();
|
|
||||||
const reader = decompressor.reader();
|
|
||||||
return reader.readAllAlloc(allocator, std.math.maxInt(usize));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn testDecompressEqual(expected: []const u8, compressed: []const u8) !void {
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
const decomp = try testDecompress(compressed);
|
|
||||||
defer allocator.free(decomp);
|
|
||||||
try std.testing.expectEqualSlices(u8, expected, decomp);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn testDecompressError(expected: anyerror, compressed: []const u8) !void {
|
|
||||||
return std.testing.expectError(expected, testDecompress(compressed));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "decompress empty world" {
|
|
||||||
try testDecompressEqual(
|
|
||||||
"",
|
|
||||||
&[_]u8{
|
|
||||||
0x5d, 0x00, 0x00, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x83, 0xff,
|
|
||||||
0xfb, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "decompress hello world" {
|
|
||||||
try testDecompressEqual(
|
|
||||||
"Hello world\n",
|
|
||||||
&[_]u8{
|
|
||||||
0x5d, 0x00, 0x00, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x24, 0x19,
|
|
||||||
0x49, 0x98, 0x6f, 0x10, 0x19, 0xc6, 0xd7, 0x31, 0xeb, 0x36, 0x50, 0xb2, 0x98, 0x48, 0xff, 0xfe,
|
|
||||||
0xa5, 0xb0, 0x00,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "decompress huge dict" {
|
|
||||||
try testDecompressEqual(
|
|
||||||
"Hello world\n",
|
|
||||||
&[_]u8{
|
|
||||||
0x5d, 0x7f, 0x7f, 0x7f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x24, 0x19,
|
|
||||||
0x49, 0x98, 0x6f, 0x10, 0x19, 0xc6, 0xd7, 0x31, 0xeb, 0x36, 0x50, 0xb2, 0x98, 0x48, 0xff, 0xfe,
|
|
||||||
0xa5, 0xb0, 0x00,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "unknown size with end of payload marker" {
|
|
||||||
try testDecompressEqual(
|
|
||||||
"Hello\nWorld!\n",
|
|
||||||
@embedFile("testdata/good-unknown_size-with_eopm.lzma"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "known size without end of payload marker" {
|
|
||||||
try testDecompressEqual(
|
|
||||||
"Hello\nWorld!\n",
|
|
||||||
@embedFile("testdata/good-known_size-without_eopm.lzma"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "known size with end of payload marker" {
|
|
||||||
try testDecompressEqual(
|
|
||||||
"Hello\nWorld!\n",
|
|
||||||
@embedFile("testdata/good-known_size-with_eopm.lzma"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "too big uncompressed size in header" {
|
|
||||||
try testDecompressError(
|
|
||||||
error.CorruptInput,
|
|
||||||
@embedFile("testdata/bad-too_big_size-with_eopm.lzma"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "too small uncompressed size in header" {
|
|
||||||
try testDecompressError(
|
|
||||||
error.CorruptInput,
|
|
||||||
@embedFile("testdata/bad-too_small_size-without_eopm-3.lzma"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "reading one byte" {
|
|
||||||
const compressed = @embedFile("testdata/good-known_size-with_eopm.lzma");
|
|
||||||
var stream = std.io.fixedBufferStream(compressed);
|
|
||||||
var decompressor = try lzma.decompress(std.testing.allocator, stream.reader());
|
|
||||||
defer decompressor.deinit();
|
|
||||||
|
|
||||||
var buffer = [1]u8{0};
|
|
||||||
_ = try decompressor.read(buffer[0..]);
|
|
||||||
}
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
const std = @import("../../std.zig");
|
|
||||||
const math = std.math;
|
|
||||||
const mem = std.mem;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
pub fn Vec2D(comptime T: type) type {
|
|
||||||
return struct {
|
|
||||||
data: []T,
|
|
||||||
cols: usize,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, value: T, size: struct { usize, usize }) !Self {
|
|
||||||
const len = try math.mul(usize, size[0], size[1]);
|
|
||||||
const data = try allocator.alloc(T, len);
|
|
||||||
@memset(data, value);
|
|
||||||
return Self{
|
|
||||||
.data = data,
|
|
||||||
.cols = size[1],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self, allocator: Allocator) void {
|
|
||||||
allocator.free(self.data);
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fill(self: *Self, value: T) void {
|
|
||||||
@memset(self.data, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fn _get(self: Self, row: usize) ![]T {
|
|
||||||
const start_row = try math.mul(usize, row, self.cols);
|
|
||||||
const end_row = try math.add(usize, start_row, self.cols);
|
|
||||||
return self.data[start_row..end_row];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: Self, row: usize) ![]const T {
|
|
||||||
return self._get(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getMut(self: *Self, row: usize) ![]T {
|
|
||||||
return self._get(row);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const testing = std.testing;
|
|
||||||
const expectEqualSlices = std.testing.expectEqualSlices;
|
|
||||||
const expectError = std.testing.expectError;
|
|
||||||
|
|
||||||
test "init" {
|
|
||||||
const allocator = testing.allocator;
|
|
||||||
var vec2d = try Vec2D(i32).init(allocator, 1, .{ 2, 3 });
|
|
||||||
defer vec2d.deinit(allocator);
|
|
||||||
|
|
||||||
try expectEqualSlices(i32, &.{ 1, 1, 1 }, try vec2d.get(0));
|
|
||||||
try expectEqualSlices(i32, &.{ 1, 1, 1 }, try vec2d.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "init overflow" {
|
|
||||||
const allocator = testing.allocator;
|
|
||||||
try expectError(
|
|
||||||
error.Overflow,
|
|
||||||
Vec2D(i32).init(allocator, 1, .{ math.maxInt(usize), math.maxInt(usize) }),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "fill" {
|
|
||||||
const allocator = testing.allocator;
|
|
||||||
var vec2d = try Vec2D(i32).init(allocator, 0, .{ 2, 3 });
|
|
||||||
defer vec2d.deinit(allocator);
|
|
||||||
|
|
||||||
vec2d.fill(7);
|
|
||||||
|
|
||||||
try expectEqualSlices(i32, &.{ 7, 7, 7 }, try vec2d.get(0));
|
|
||||||
try expectEqualSlices(i32, &.{ 7, 7, 7 }, try vec2d.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "get" {
|
|
||||||
var data = [_]i32{ 0, 1, 2, 3, 4, 5, 6, 7 };
|
|
||||||
const vec2d = Vec2D(i32){
|
|
||||||
.data = &data,
|
|
||||||
.cols = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
try expectEqualSlices(i32, &.{ 0, 1 }, try vec2d.get(0));
|
|
||||||
try expectEqualSlices(i32, &.{ 2, 3 }, try vec2d.get(1));
|
|
||||||
try expectEqualSlices(i32, &.{ 4, 5 }, try vec2d.get(2));
|
|
||||||
try expectEqualSlices(i32, &.{ 6, 7 }, try vec2d.get(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "getMut" {
|
|
||||||
var data = [_]i32{ 0, 1, 2, 3, 4, 5, 6, 7 };
|
|
||||||
var vec2d = Vec2D(i32){
|
|
||||||
.data = &data,
|
|
||||||
.cols = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
const row = try vec2d.getMut(1);
|
|
||||||
row[1] = 9;
|
|
||||||
|
|
||||||
try expectEqualSlices(i32, &.{ 0, 1 }, try vec2d.get(0));
|
|
||||||
// (1, 1) should be 9.
|
|
||||||
try expectEqualSlices(i32, &.{ 2, 9 }, try vec2d.get(1));
|
|
||||||
try expectEqualSlices(i32, &.{ 4, 5 }, try vec2d.get(2));
|
|
||||||
try expectEqualSlices(i32, &.{ 6, 7 }, try vec2d.get(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "get multiplication overflow" {
|
|
||||||
const allocator = testing.allocator;
|
|
||||||
var matrix = try Vec2D(i32).init(allocator, 0, .{ 3, 4 });
|
|
||||||
defer matrix.deinit(allocator);
|
|
||||||
|
|
||||||
const row = (math.maxInt(usize) / 4) + 1;
|
|
||||||
try expectError(error.Overflow, matrix.get(row));
|
|
||||||
try expectError(error.Overflow, matrix.getMut(row));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "get addition overflow" {
|
|
||||||
const allocator = testing.allocator;
|
|
||||||
var matrix = try Vec2D(i32).init(allocator, 0, .{ 3, 5 });
|
|
||||||
defer matrix.deinit(allocator);
|
|
||||||
|
|
||||||
const row = math.maxInt(usize) / 5;
|
|
||||||
try expectError(error.Overflow, matrix.get(row));
|
|
||||||
try expectError(error.Overflow, matrix.getMut(row));
|
|
||||||
}
|
|
||||||
@ -1,15 +1,276 @@
|
|||||||
const std = @import("../std.zig");
|
const std = @import("../std.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const lzma = std.compress.lzma;
|
||||||
|
|
||||||
pub const decode = @import("lzma2/decode.zig");
|
pub fn decompress(gpa: Allocator, reader: *std.io.BufferedReader, writer: *std.io.BufferedWriter) anyerror!void {
|
||||||
|
var decoder = try Decode.init(gpa);
|
||||||
pub fn decompress(allocator: Allocator, reader: *std.io.BufferedReader, writer: *std.io.BufferedWriter) !void {
|
defer decoder.deinit(gpa);
|
||||||
var decoder = try decode.Decoder.init(allocator);
|
return decoder.decompress(gpa, reader, writer);
|
||||||
defer decoder.deinit(allocator);
|
|
||||||
return decoder.decompress(allocator, reader, writer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
pub const Decode = struct {
|
||||||
|
lzma1: lzma.Decode,
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator) !Decode {
|
||||||
|
return .{
|
||||||
|
.lzma1 = try lzma.Decode.init(
|
||||||
|
allocator,
|
||||||
|
.{
|
||||||
|
.lc = 0,
|
||||||
|
.lp = 0,
|
||||||
|
.pb = 0,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Decode, allocator: Allocator) void {
|
||||||
|
self.lzma1.deinit(allocator);
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decompress(
|
||||||
|
self: *Decode,
|
||||||
|
allocator: Allocator,
|
||||||
|
reader: *std.io.BufferedReader,
|
||||||
|
writer: *std.io.BufferedWriter,
|
||||||
|
) !void {
|
||||||
|
var accum = LzAccumBuffer.init(std.math.maxInt(usize));
|
||||||
|
defer accum.deinit(allocator);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const status = try reader.takeByte();
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
0 => break,
|
||||||
|
1 => try parseUncompressed(allocator, reader, writer, &accum, true),
|
||||||
|
2 => try parseUncompressed(allocator, reader, writer, &accum, false),
|
||||||
|
else => try self.parseLzma(allocator, reader, writer, &accum, status),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try accum.finish(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseLzma(
|
||||||
|
self: *Decode,
|
||||||
|
allocator: Allocator,
|
||||||
|
br: *std.io.BufferedReader,
|
||||||
|
writer: *std.io.BufferedWriter,
|
||||||
|
accum: *LzAccumBuffer,
|
||||||
|
status: u8,
|
||||||
|
) !void {
|
||||||
|
if (status & 0x80 == 0) {
|
||||||
|
return error.CorruptInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Reset = struct {
|
||||||
|
dict: bool,
|
||||||
|
state: bool,
|
||||||
|
props: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
const reset = switch ((status >> 5) & 0x3) {
|
||||||
|
0 => Reset{
|
||||||
|
.dict = false,
|
||||||
|
.state = false,
|
||||||
|
.props = false,
|
||||||
|
},
|
||||||
|
1 => Reset{
|
||||||
|
.dict = false,
|
||||||
|
.state = true,
|
||||||
|
.props = false,
|
||||||
|
},
|
||||||
|
2 => Reset{
|
||||||
|
.dict = false,
|
||||||
|
.state = true,
|
||||||
|
.props = true,
|
||||||
|
},
|
||||||
|
3 => Reset{
|
||||||
|
.dict = true,
|
||||||
|
.state = true,
|
||||||
|
.props = true,
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
const unpacked_size = blk: {
|
||||||
|
var tmp: u64 = status & 0x1F;
|
||||||
|
tmp <<= 16;
|
||||||
|
tmp |= try br.takeInt(u16, .big);
|
||||||
|
break :blk tmp + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const packed_size = blk: {
|
||||||
|
const tmp: u17 = try br.takeInt(u16, .big);
|
||||||
|
break :blk tmp + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (reset.dict) {
|
||||||
|
try accum.reset(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reset.state) {
|
||||||
|
var new_props = self.lzma1.properties;
|
||||||
|
|
||||||
|
if (reset.props) {
|
||||||
|
var props = try br.takeByte();
|
||||||
|
if (props >= 225) {
|
||||||
|
return error.CorruptInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lc = @as(u4, @intCast(props % 9));
|
||||||
|
props /= 9;
|
||||||
|
const lp = @as(u3, @intCast(props % 5));
|
||||||
|
props /= 5;
|
||||||
|
const pb = @as(u3, @intCast(props));
|
||||||
|
|
||||||
|
if (lc + lp > 4) {
|
||||||
|
return error.CorruptInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_props = .{ .lc = lc, .lp = lp, .pb = pb };
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.lzma1.resetState(allocator, new_props);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.lzma1.unpacked_size = unpacked_size + accum.len;
|
||||||
|
|
||||||
|
var range_decoder: lzma.RangeDecoder = undefined;
|
||||||
|
var bytes_read = try lzma.RangeDecoder.init(br);
|
||||||
|
while (try self.lzma1.process(allocator, br, writer, accum, &range_decoder, &bytes_read) == .cont) {}
|
||||||
|
|
||||||
|
if (bytes_read != packed_size) {
|
||||||
|
return error.CorruptInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseUncompressed(
|
||||||
|
allocator: Allocator,
|
||||||
|
reader: *std.io.BufferedReader,
|
||||||
|
writer: *std.io.BufferedWriter,
|
||||||
|
accum: *LzAccumBuffer,
|
||||||
|
reset_dict: bool,
|
||||||
|
) !void {
|
||||||
|
const unpacked_size = @as(u17, try reader.takeInt(u16, .big)) + 1;
|
||||||
|
|
||||||
|
if (reset_dict) {
|
||||||
|
try accum.reset(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
var i: @TypeOf(unpacked_size) = 0;
|
||||||
|
while (i < unpacked_size) : (i += 1) {
|
||||||
|
try accum.appendByte(allocator, try reader.takeByte());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An accumulating buffer for LZ sequences
|
||||||
|
const LzAccumBuffer = struct {
|
||||||
|
/// Buffer
|
||||||
|
buf: std.ArrayListUnmanaged(u8),
|
||||||
|
|
||||||
|
/// Buffer memory limit
|
||||||
|
memlimit: usize,
|
||||||
|
|
||||||
|
/// Total number of bytes sent through the buffer
|
||||||
|
len: usize,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn init(memlimit: usize) Self {
|
||||||
|
return Self{
|
||||||
|
.buf = .{},
|
||||||
|
.memlimit = memlimit,
|
||||||
|
.len = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn appendByte(self: *Self, allocator: Allocator, byte: u8) !void {
|
||||||
|
try self.buf.append(allocator, byte);
|
||||||
|
self.len += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the internal dictionary
|
||||||
|
pub fn reset(self: *Self, writer: anytype) !void {
|
||||||
|
try writer.writeAll(self.buf.items);
|
||||||
|
self.buf.clearRetainingCapacity();
|
||||||
|
self.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the last byte or return a default
|
||||||
|
pub fn lastOr(self: Self, lit: u8) u8 {
|
||||||
|
const buf_len = self.buf.items.len;
|
||||||
|
return if (buf_len == 0)
|
||||||
|
lit
|
||||||
|
else
|
||||||
|
self.buf.items[buf_len - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the n-th last byte
|
||||||
|
pub fn lastN(self: Self, dist: usize) !u8 {
|
||||||
|
const buf_len = self.buf.items.len;
|
||||||
|
if (dist > buf_len) {
|
||||||
|
return error.CorruptInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.buf.items[buf_len - dist];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append a literal
|
||||||
|
pub fn appendLiteral(
|
||||||
|
self: *Self,
|
||||||
|
allocator: Allocator,
|
||||||
|
lit: u8,
|
||||||
|
writer: anytype,
|
||||||
|
) !void {
|
||||||
|
_ = writer;
|
||||||
|
if (self.len >= self.memlimit) {
|
||||||
|
return error.CorruptInput;
|
||||||
|
}
|
||||||
|
try self.buf.append(allocator, lit);
|
||||||
|
self.len += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch an LZ sequence (length, distance) from inside the buffer
|
||||||
|
pub fn appendLz(
|
||||||
|
self: *Self,
|
||||||
|
allocator: Allocator,
|
||||||
|
len: usize,
|
||||||
|
dist: usize,
|
||||||
|
writer: anytype,
|
||||||
|
) !void {
|
||||||
|
_ = writer;
|
||||||
|
|
||||||
|
const buf_len = self.buf.items.len;
|
||||||
|
if (dist > buf_len) {
|
||||||
|
return error.CorruptInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = buf_len - dist;
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < len) : (i += 1) {
|
||||||
|
const x = self.buf.items[offset];
|
||||||
|
try self.buf.append(allocator, x);
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
self.len += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self: *Self, writer: anytype) !void {
|
||||||
|
try writer.writeAll(self.buf.items);
|
||||||
|
self.buf.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self, allocator: Allocator) void {
|
||||||
|
self.buf.deinit(allocator);
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test decompress {
|
||||||
const expected = "Hello\nWorld!\n";
|
const expected = "Hello\nWorld!\n";
|
||||||
const compressed = [_]u8{
|
const compressed = [_]u8{
|
||||||
0x01, 0x00, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x0A, 0x02,
|
0x01, 0x00, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x0A, 0x02,
|
||||||
|
|||||||
@ -1,169 +0,0 @@
|
|||||||
const std = @import("../../std.zig");
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const lzma = @import("../lzma.zig");
|
|
||||||
const DecoderState = lzma.decode.DecoderState;
|
|
||||||
const LzAccumBuffer = lzma.decode.lzbuffer.LzAccumBuffer;
|
|
||||||
const Properties = lzma.decode.Properties;
|
|
||||||
const RangeDecoder = lzma.decode.RangeDecoder;
|
|
||||||
|
|
||||||
pub const Decoder = struct {
|
|
||||||
lzma_state: DecoderState,
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator) !Decoder {
|
|
||||||
return Decoder{
|
|
||||||
.lzma_state = try DecoderState.init(
|
|
||||||
allocator,
|
|
||||||
Properties{
|
|
||||||
.lc = 0,
|
|
||||||
.lp = 0,
|
|
||||||
.pb = 0,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Decoder, allocator: Allocator) void {
|
|
||||||
self.lzma_state.deinit(allocator);
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decompress(
|
|
||||||
self: *Decoder,
|
|
||||||
allocator: Allocator,
|
|
||||||
reader: *std.io.BufferedReader,
|
|
||||||
writer: *std.io.BufferedWriter,
|
|
||||||
) !void {
|
|
||||||
var accum = LzAccumBuffer.init(std.math.maxInt(usize));
|
|
||||||
defer accum.deinit(allocator);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const status = try reader.takeByte();
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
0 => break,
|
|
||||||
1 => try parseUncompressed(allocator, reader, writer, &accum, true),
|
|
||||||
2 => try parseUncompressed(allocator, reader, writer, &accum, false),
|
|
||||||
else => try self.parseLzma(allocator, reader, writer, &accum, status),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try accum.finish(writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parseLzma(
|
|
||||||
self: *Decoder,
|
|
||||||
allocator: Allocator,
|
|
||||||
br: *std.io.BufferedReader,
|
|
||||||
writer: *std.io.BufferedWriter,
|
|
||||||
accum: *LzAccumBuffer,
|
|
||||||
status: u8,
|
|
||||||
) !void {
|
|
||||||
if (status & 0x80 == 0) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Reset = struct {
|
|
||||||
dict: bool,
|
|
||||||
state: bool,
|
|
||||||
props: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
const reset = switch ((status >> 5) & 0x3) {
|
|
||||||
0 => Reset{
|
|
||||||
.dict = false,
|
|
||||||
.state = false,
|
|
||||||
.props = false,
|
|
||||||
},
|
|
||||||
1 => Reset{
|
|
||||||
.dict = false,
|
|
||||||
.state = true,
|
|
||||||
.props = false,
|
|
||||||
},
|
|
||||||
2 => Reset{
|
|
||||||
.dict = false,
|
|
||||||
.state = true,
|
|
||||||
.props = true,
|
|
||||||
},
|
|
||||||
3 => Reset{
|
|
||||||
.dict = true,
|
|
||||||
.state = true,
|
|
||||||
.props = true,
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
|
|
||||||
const unpacked_size = blk: {
|
|
||||||
var tmp: u64 = status & 0x1F;
|
|
||||||
tmp <<= 16;
|
|
||||||
tmp |= try br.takeInt(u16, .big);
|
|
||||||
break :blk tmp + 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
const packed_size = blk: {
|
|
||||||
const tmp: u17 = try br.takeInt(u16, .big);
|
|
||||||
break :blk tmp + 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (reset.dict) {
|
|
||||||
try accum.reset(writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reset.state) {
|
|
||||||
var new_props = self.lzma_state.lzma_props;
|
|
||||||
|
|
||||||
if (reset.props) {
|
|
||||||
var props = try br.takeByte();
|
|
||||||
if (props >= 225) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lc = @as(u4, @intCast(props % 9));
|
|
||||||
props /= 9;
|
|
||||||
const lp = @as(u3, @intCast(props % 5));
|
|
||||||
props /= 5;
|
|
||||||
const pb = @as(u3, @intCast(props));
|
|
||||||
|
|
||||||
if (lc + lp > 4) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
new_props = Properties{ .lc = lc, .lp = lp, .pb = pb };
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.lzma_state.resetState(allocator, new_props);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.lzma_state.unpacked_size = unpacked_size + accum.len;
|
|
||||||
|
|
||||||
var counter: std.io.CountingReader = .{ .child_reader = br.reader() };
|
|
||||||
var counter_reader = counter.reader().unbuffered();
|
|
||||||
|
|
||||||
var rangecoder = try RangeDecoder.init(&counter_reader);
|
|
||||||
while (try self.lzma_state.process(allocator, counter_reader, writer, accum, &rangecoder) == .continue_) {}
|
|
||||||
|
|
||||||
if (counter.bytes_read != packed_size) {
|
|
||||||
return error.CorruptInput;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parseUncompressed(
|
|
||||||
allocator: Allocator,
|
|
||||||
reader: *std.io.BufferedReader,
|
|
||||||
writer: *std.io.BufferedWriter,
|
|
||||||
accum: *LzAccumBuffer,
|
|
||||||
reset_dict: bool,
|
|
||||||
) !void {
|
|
||||||
const unpacked_size = @as(u17, try reader.takeInt(u16, .big)) + 1;
|
|
||||||
|
|
||||||
if (reset_dict) {
|
|
||||||
try accum.reset(writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
var i: @TypeOf(unpacked_size) = 0;
|
|
||||||
while (i < unpacked_size) : (i += 1) {
|
|
||||||
try accum.appendByte(allocator, try reader.takeByte());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -16,191 +16,187 @@ pub const DecompressorOptions = struct {
|
|||||||
pub const default_window_buffer_len = 8 * 1024 * 1024;
|
pub const default_window_buffer_len = 8 * 1024 * 1024;
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn Decompressor(comptime ReaderType: type) type {
|
pub const Decompressor = struct {
|
||||||
return struct {
|
const Self = @This();
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
const table_size_max = types.compressed_block.table_size_max;
|
const table_size_max = types.compressed_block.table_size_max;
|
||||||
|
|
||||||
source: std.io.CountingReader(ReaderType),
|
source: std.io.CountingReader,
|
||||||
state: enum { NewFrame, InFrame, LastBlock },
|
state: enum { NewFrame, InFrame, LastBlock },
|
||||||
decode_state: decompress.block.DecodeState,
|
decode_state: decompress.block.DecodeState,
|
||||||
frame_context: decompress.FrameContext,
|
frame_context: decompress.FrameContext,
|
||||||
buffer: WindowBuffer,
|
buffer: WindowBuffer,
|
||||||
literal_fse_buffer: [table_size_max.literal]types.compressed_block.Table.Fse,
|
literal_fse_buffer: [table_size_max.literal]types.compressed_block.Table.Fse,
|
||||||
match_fse_buffer: [table_size_max.match]types.compressed_block.Table.Fse,
|
match_fse_buffer: [table_size_max.match]types.compressed_block.Table.Fse,
|
||||||
offset_fse_buffer: [table_size_max.offset]types.compressed_block.Table.Fse,
|
offset_fse_buffer: [table_size_max.offset]types.compressed_block.Table.Fse,
|
||||||
literals_buffer: [types.block_size_max]u8,
|
literals_buffer: [types.block_size_max]u8,
|
||||||
sequence_buffer: [types.block_size_max]u8,
|
sequence_buffer: [types.block_size_max]u8,
|
||||||
verify_checksum: bool,
|
verify_checksum: bool,
|
||||||
checksum: ?u32,
|
checksum: ?u32,
|
||||||
current_frame_decompressed_size: usize,
|
current_frame_decompressed_size: usize,
|
||||||
|
|
||||||
const WindowBuffer = struct {
|
const WindowBuffer = struct {
|
||||||
data: []u8 = undefined,
|
data: []u8 = undefined,
|
||||||
read_index: usize = 0,
|
read_index: usize = 0,
|
||||||
write_index: usize = 0,
|
write_index: usize = 0,
|
||||||
};
|
|
||||||
|
|
||||||
pub const Error = ReaderType.Error || error{
|
|
||||||
ChecksumFailure,
|
|
||||||
DictionaryIdFlagUnsupported,
|
|
||||||
MalformedBlock,
|
|
||||||
MalformedFrame,
|
|
||||||
OutOfMemory,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Reader = std.io.Reader(*Self, Error, read);
|
|
||||||
|
|
||||||
pub fn init(source: ReaderType, options: DecompressorOptions) Self {
|
|
||||||
return .{
|
|
||||||
.source = std.io.countingReader(source),
|
|
||||||
.state = .NewFrame,
|
|
||||||
.decode_state = undefined,
|
|
||||||
.frame_context = undefined,
|
|
||||||
.buffer = .{ .data = options.window_buffer },
|
|
||||||
.literal_fse_buffer = undefined,
|
|
||||||
.match_fse_buffer = undefined,
|
|
||||||
.offset_fse_buffer = undefined,
|
|
||||||
.literals_buffer = undefined,
|
|
||||||
.sequence_buffer = undefined,
|
|
||||||
.verify_checksum = options.verify_checksum,
|
|
||||||
.checksum = undefined,
|
|
||||||
.current_frame_decompressed_size = undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn frameInit(self: *Self) !void {
|
|
||||||
const source_reader = self.source.reader();
|
|
||||||
switch (try decompress.decodeFrameHeader(source_reader)) {
|
|
||||||
.skippable => |header| {
|
|
||||||
try source_reader.skipBytes(header.frame_size, .{});
|
|
||||||
self.state = .NewFrame;
|
|
||||||
},
|
|
||||||
.zstandard => |header| {
|
|
||||||
const frame_context = try decompress.FrameContext.init(
|
|
||||||
header,
|
|
||||||
self.buffer.data.len,
|
|
||||||
self.verify_checksum,
|
|
||||||
);
|
|
||||||
|
|
||||||
const decode_state = decompress.block.DecodeState.init(
|
|
||||||
&self.literal_fse_buffer,
|
|
||||||
&self.match_fse_buffer,
|
|
||||||
&self.offset_fse_buffer,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.decode_state = decode_state;
|
|
||||||
self.frame_context = frame_context;
|
|
||||||
|
|
||||||
self.checksum = null;
|
|
||||||
self.current_frame_decompressed_size = 0;
|
|
||||||
|
|
||||||
self.state = .InFrame;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reader(self: *Self) Reader {
|
|
||||||
return .{ .context = self };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(self: *Self, buffer: []u8) Error!usize {
|
|
||||||
if (buffer.len == 0) return 0;
|
|
||||||
|
|
||||||
var size: usize = 0;
|
|
||||||
while (size == 0) {
|
|
||||||
while (self.state == .NewFrame) {
|
|
||||||
const initial_count = self.source.bytes_read;
|
|
||||||
self.frameInit() catch |err| switch (err) {
|
|
||||||
error.DictionaryIdFlagUnsupported => return error.DictionaryIdFlagUnsupported,
|
|
||||||
error.EndOfStream => return if (self.source.bytes_read == initial_count)
|
|
||||||
0
|
|
||||||
else
|
|
||||||
error.MalformedFrame,
|
|
||||||
else => return error.MalformedFrame,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
size = try self.readInner(buffer);
|
|
||||||
}
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readInner(self: *Self, buffer: []u8) Error!usize {
|
|
||||||
std.debug.assert(self.state != .NewFrame);
|
|
||||||
|
|
||||||
var ring_buffer = RingBuffer{
|
|
||||||
.data = self.buffer.data,
|
|
||||||
.read_index = self.buffer.read_index,
|
|
||||||
.write_index = self.buffer.write_index,
|
|
||||||
};
|
|
||||||
defer {
|
|
||||||
self.buffer.read_index = ring_buffer.read_index;
|
|
||||||
self.buffer.write_index = ring_buffer.write_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
const source_reader = self.source.reader();
|
|
||||||
while (ring_buffer.isEmpty() and self.state != .LastBlock) {
|
|
||||||
const header_bytes = source_reader.readBytesNoEof(3) catch
|
|
||||||
return error.MalformedFrame;
|
|
||||||
const block_header = decompress.block.decodeBlockHeader(&header_bytes);
|
|
||||||
|
|
||||||
decompress.block.decodeBlockReader(
|
|
||||||
&ring_buffer,
|
|
||||||
source_reader,
|
|
||||||
block_header,
|
|
||||||
&self.decode_state,
|
|
||||||
self.frame_context.block_size_max,
|
|
||||||
&self.literals_buffer,
|
|
||||||
&self.sequence_buffer,
|
|
||||||
) catch
|
|
||||||
return error.MalformedBlock;
|
|
||||||
|
|
||||||
if (self.frame_context.content_size) |size| {
|
|
||||||
if (self.current_frame_decompressed_size > size) return error.MalformedFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = ring_buffer.len();
|
|
||||||
self.current_frame_decompressed_size += size;
|
|
||||||
|
|
||||||
if (self.frame_context.hasher_opt) |*hasher| {
|
|
||||||
if (size > 0) {
|
|
||||||
const written_slice = ring_buffer.sliceLast(size);
|
|
||||||
hasher.update(written_slice.first);
|
|
||||||
hasher.update(written_slice.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (block_header.last_block) {
|
|
||||||
self.state = .LastBlock;
|
|
||||||
if (self.frame_context.has_checksum) {
|
|
||||||
const checksum = source_reader.readInt(u32, .little) catch
|
|
||||||
return error.MalformedFrame;
|
|
||||||
if (self.verify_checksum) {
|
|
||||||
if (self.frame_context.hasher_opt) |*hasher| {
|
|
||||||
if (checksum != decompress.computeChecksum(hasher))
|
|
||||||
return error.ChecksumFailure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (self.frame_context.content_size) |content_size| {
|
|
||||||
if (content_size != self.current_frame_decompressed_size) {
|
|
||||||
return error.MalformedFrame;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const size = @min(ring_buffer.len(), buffer.len);
|
|
||||||
if (size > 0) {
|
|
||||||
ring_buffer.readFirstAssumeLength(buffer, size);
|
|
||||||
}
|
|
||||||
if (self.state == .LastBlock and ring_buffer.len() == 0) {
|
|
||||||
self.state = .NewFrame;
|
|
||||||
}
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
pub const Error = anyerror || error{
|
||||||
|
ChecksumFailure,
|
||||||
|
DictionaryIdFlagUnsupported,
|
||||||
|
MalformedBlock,
|
||||||
|
MalformedFrame,
|
||||||
|
OutOfMemory,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(source: *std.io.BufferedReader, options: DecompressorOptions) Self {
|
||||||
|
return .{
|
||||||
|
.source = std.io.countingReader(source),
|
||||||
|
.state = .NewFrame,
|
||||||
|
.decode_state = undefined,
|
||||||
|
.frame_context = undefined,
|
||||||
|
.buffer = .{ .data = options.window_buffer },
|
||||||
|
.literal_fse_buffer = undefined,
|
||||||
|
.match_fse_buffer = undefined,
|
||||||
|
.offset_fse_buffer = undefined,
|
||||||
|
.literals_buffer = undefined,
|
||||||
|
.sequence_buffer = undefined,
|
||||||
|
.verify_checksum = options.verify_checksum,
|
||||||
|
.checksum = undefined,
|
||||||
|
.current_frame_decompressed_size = undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frameInit(self: *Self) !void {
|
||||||
|
const source_reader = self.source;
|
||||||
|
switch (try decompress.decodeFrameHeader(source_reader)) {
|
||||||
|
.skippable => |header| {
|
||||||
|
try source_reader.skipBytes(header.frame_size, .{});
|
||||||
|
self.state = .NewFrame;
|
||||||
|
},
|
||||||
|
.zstandard => |header| {
|
||||||
|
const frame_context = try decompress.FrameContext.init(
|
||||||
|
header,
|
||||||
|
self.buffer.data.len,
|
||||||
|
self.verify_checksum,
|
||||||
|
);
|
||||||
|
|
||||||
|
const decode_state = decompress.block.DecodeState.init(
|
||||||
|
&self.literal_fse_buffer,
|
||||||
|
&self.match_fse_buffer,
|
||||||
|
&self.offset_fse_buffer,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.decode_state = decode_state;
|
||||||
|
self.frame_context = frame_context;
|
||||||
|
|
||||||
|
self.checksum = null;
|
||||||
|
self.current_frame_decompressed_size = 0;
|
||||||
|
|
||||||
|
self.state = .InFrame;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reader(self: *Self) std.io.Reader {
|
||||||
|
return .{ .context = self };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(self: *Self, buffer: []u8) Error!usize {
|
||||||
|
if (buffer.len == 0) return 0;
|
||||||
|
|
||||||
|
var size: usize = 0;
|
||||||
|
while (size == 0) {
|
||||||
|
while (self.state == .NewFrame) {
|
||||||
|
const initial_count = self.source.bytes_read;
|
||||||
|
self.frameInit() catch |err| switch (err) {
|
||||||
|
error.DictionaryIdFlagUnsupported => return error.DictionaryIdFlagUnsupported,
|
||||||
|
error.EndOfStream => return if (self.source.bytes_read == initial_count)
|
||||||
|
0
|
||||||
|
else
|
||||||
|
error.MalformedFrame,
|
||||||
|
else => return error.MalformedFrame,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
size = try self.readInner(buffer);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn readInner(self: *Self, buffer: []u8) Error!usize {
|
||||||
|
std.debug.assert(self.state != .NewFrame);
|
||||||
|
|
||||||
|
var ring_buffer = RingBuffer{
|
||||||
|
.data = self.buffer.data,
|
||||||
|
.read_index = self.buffer.read_index,
|
||||||
|
.write_index = self.buffer.write_index,
|
||||||
|
};
|
||||||
|
defer {
|
||||||
|
self.buffer.read_index = ring_buffer.read_index;
|
||||||
|
self.buffer.write_index = ring_buffer.write_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source_reader = self.source;
|
||||||
|
while (ring_buffer.isEmpty() and self.state != .LastBlock) {
|
||||||
|
const header_bytes = source_reader.readBytesNoEof(3) catch
|
||||||
|
return error.MalformedFrame;
|
||||||
|
const block_header = decompress.block.decodeBlockHeader(&header_bytes);
|
||||||
|
|
||||||
|
decompress.block.decodeBlockReader(
|
||||||
|
&ring_buffer,
|
||||||
|
source_reader,
|
||||||
|
block_header,
|
||||||
|
&self.decode_state,
|
||||||
|
self.frame_context.block_size_max,
|
||||||
|
&self.literals_buffer,
|
||||||
|
&self.sequence_buffer,
|
||||||
|
) catch
|
||||||
|
return error.MalformedBlock;
|
||||||
|
|
||||||
|
if (self.frame_context.content_size) |size| {
|
||||||
|
if (self.current_frame_decompressed_size > size) return error.MalformedFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = ring_buffer.len();
|
||||||
|
self.current_frame_decompressed_size += size;
|
||||||
|
|
||||||
|
if (self.frame_context.hasher_opt) |*hasher| {
|
||||||
|
if (size > 0) {
|
||||||
|
const written_slice = ring_buffer.sliceLast(size);
|
||||||
|
hasher.update(written_slice.first);
|
||||||
|
hasher.update(written_slice.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (block_header.last_block) {
|
||||||
|
self.state = .LastBlock;
|
||||||
|
if (self.frame_context.has_checksum) {
|
||||||
|
const checksum = source_reader.readInt(u32, .little) catch
|
||||||
|
return error.MalformedFrame;
|
||||||
|
if (self.verify_checksum) {
|
||||||
|
if (self.frame_context.hasher_opt) |*hasher| {
|
||||||
|
if (checksum != decompress.computeChecksum(hasher))
|
||||||
|
return error.ChecksumFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self.frame_context.content_size) |content_size| {
|
||||||
|
if (content_size != self.current_frame_decompressed_size) {
|
||||||
|
return error.MalformedFrame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = @min(ring_buffer.len(), buffer.len);
|
||||||
|
if (size > 0) {
|
||||||
|
ring_buffer.readFirstAssumeLength(buffer, size);
|
||||||
|
}
|
||||||
|
if (self.state == .LastBlock and ring_buffer.len() == 0) {
|
||||||
|
self.state = .NewFrame;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn decompressor(reader: anytype, options: DecompressorOptions) Decompressor(@TypeOf(reader)) {
|
pub fn decompressor(reader: anytype, options: DecompressorOptions) Decompressor(@TypeOf(reader)) {
|
||||||
return Decompressor(@TypeOf(reader)).init(reader, options);
|
return Decompressor(@TypeOf(reader)).init(reader, options);
|
||||||
|
|||||||
@ -2212,7 +2212,7 @@ pub const ElfModule = struct {
|
|||||||
var separate_debug_filename: ?[]const u8 = null;
|
var separate_debug_filename: ?[]const u8 = null;
|
||||||
var separate_debug_crc: ?u32 = null;
|
var separate_debug_crc: ?u32 = null;
|
||||||
|
|
||||||
for (shdrs) |*shdr| {
|
shdrs: for (shdrs) |*shdr| {
|
||||||
if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
|
if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
|
||||||
const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
|
const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
|
||||||
|
|
||||||
@ -2246,8 +2246,22 @@ pub const ElfModule = struct {
|
|||||||
const decompressed_section = try gpa.alloc(u8, ch_size);
|
const decompressed_section = try gpa.alloc(u8, ch_size);
|
||||||
errdefer gpa.free(decompressed_section);
|
errdefer gpa.free(decompressed_section);
|
||||||
|
|
||||||
const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
|
{
|
||||||
assert(read == decompressed_section.len);
|
var read_index: usize = 0;
|
||||||
|
while (true) {
|
||||||
|
const read_result = zlib_stream.streamReadVec(&.{decompressed_section[read_index..]});
|
||||||
|
read_result.err catch {
|
||||||
|
gpa.free(decompressed_section);
|
||||||
|
continue :shdrs;
|
||||||
|
};
|
||||||
|
read_index += read_result.len;
|
||||||
|
if (read_index == decompressed_section.len) break;
|
||||||
|
if (read_result.end) {
|
||||||
|
gpa.free(decompressed_section);
|
||||||
|
continue :shdrs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break :blk .{
|
break :blk .{
|
||||||
.data = decompressed_section,
|
.data = decompressed_section,
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
//! Optimized for performance in debug builds.
|
//! Optimized for performance in debug builds.
|
||||||
|
|
||||||
|
// TODO I'm pretty sure this can be deleted thanks to the new std.io.BufferedReader semantics
|
||||||
|
|
||||||
const std = @import("../std.zig");
|
const std = @import("../std.zig");
|
||||||
const MemoryAccessor = std.debug.MemoryAccessor;
|
const MemoryAccessor = std.debug.MemoryAccessor;
|
||||||
|
|
||||||
@ -9,20 +11,20 @@ buf: []const u8,
|
|||||||
pos: usize = 0,
|
pos: usize = 0,
|
||||||
endian: std.builtin.Endian,
|
endian: std.builtin.Endian,
|
||||||
|
|
||||||
pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer };
|
pub const Error = error{ EndOfStream, Overflow, InvalidBuffer };
|
||||||
|
|
||||||
pub fn seekTo(fbr: *FixedBufferReader, pos: u64) Error!void {
|
pub fn seekTo(fbr: *FixedBufferReader, pos: u64) Error!void {
|
||||||
if (pos > fbr.buf.len) return error.EndOfBuffer;
|
if (pos > fbr.buf.len) return error.EndOfStream;
|
||||||
fbr.pos = @intCast(pos);
|
fbr.pos = @intCast(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn seekForward(fbr: *FixedBufferReader, amount: u64) Error!void {
|
pub fn seekForward(fbr: *FixedBufferReader, amount: u64) Error!void {
|
||||||
if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer;
|
if (fbr.buf.len - fbr.pos < amount) return error.EndOfStream;
|
||||||
fbr.pos += @intCast(amount);
|
fbr.pos += @intCast(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn readByte(fbr: *FixedBufferReader) Error!u8 {
|
pub inline fn readByte(fbr: *FixedBufferReader) Error!u8 {
|
||||||
if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer;
|
if (fbr.pos >= fbr.buf.len) return error.EndOfStream;
|
||||||
defer fbr.pos += 1;
|
defer fbr.pos += 1;
|
||||||
return fbr.buf[fbr.pos];
|
return fbr.buf[fbr.pos];
|
||||||
}
|
}
|
||||||
@ -33,7 +35,7 @@ pub fn readByteSigned(fbr: *FixedBufferReader) Error!i8 {
|
|||||||
|
|
||||||
pub fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T {
|
pub fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T {
|
||||||
const size = @divExact(@typeInfo(T).int.bits, 8);
|
const size = @divExact(@typeInfo(T).int.bits, 8);
|
||||||
if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer;
|
if (fbr.buf.len - fbr.pos < size) return error.EndOfStream;
|
||||||
defer fbr.pos += size;
|
defer fbr.pos += size;
|
||||||
return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian);
|
return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian);
|
||||||
}
|
}
|
||||||
@ -50,11 +52,21 @@ pub fn readIntChecked(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T {
|
pub fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T {
|
||||||
return std.leb.readUleb128(T, fbr);
|
var br: std.io.BufferedReader = undefined;
|
||||||
|
br.initFixed(fbr.buf);
|
||||||
|
br.seek = fbr.pos;
|
||||||
|
const result = br.takeUleb128(T);
|
||||||
|
fbr.pos = br.seek;
|
||||||
|
return @errorCast(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn readIleb128(fbr: *FixedBufferReader, comptime T: type) Error!T {
|
pub fn readIleb128(fbr: *FixedBufferReader, comptime T: type) Error!T {
|
||||||
return std.leb.readIleb128(T, fbr);
|
var br: std.io.BufferedReader = undefined;
|
||||||
|
br.initFixed(fbr.buf);
|
||||||
|
br.seek = fbr.pos;
|
||||||
|
const result = br.takeIleb128(T);
|
||||||
|
fbr.pos = br.seek;
|
||||||
|
return @errorCast(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn readAddress(fbr: *FixedBufferReader, format: std.dwarf.Format) Error!u64 {
|
pub fn readAddress(fbr: *FixedBufferReader, format: std.dwarf.Format) Error!u64 {
|
||||||
@ -76,7 +88,7 @@ pub fn readAddressChecked(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 {
|
pub fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 {
|
||||||
if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer;
|
if (fbr.buf.len - fbr.pos < len) return error.EndOfStream;
|
||||||
defer fbr.pos += len;
|
defer fbr.pos += len;
|
||||||
return fbr.buf[fbr.pos..][0..len];
|
return fbr.buf[fbr.pos..][0..len];
|
||||||
}
|
}
|
||||||
@ -87,7 +99,7 @@ pub fn readBytesTo(fbr: *FixedBufferReader, comptime sentinel: u8) Error![:senti
|
|||||||
fbr.buf,
|
fbr.buf,
|
||||||
fbr.pos,
|
fbr.pos,
|
||||||
sentinel,
|
sentinel,
|
||||||
}) orelse return error.EndOfBuffer;
|
}) orelse return error.EndOfStream;
|
||||||
defer fbr.pos = end + 1;
|
defer fbr.pos = end + 1;
|
||||||
return fbr.buf[fbr.pos..end :sentinel];
|
return fbr.buf[fbr.pos..end :sentinel];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2028,13 +2028,13 @@ pub const VirtualMachine = struct {
|
|||||||
var prev_row: Row = self.current_row;
|
var prev_row: Row = self.current_row;
|
||||||
|
|
||||||
var cie_stream: std.io.BufferedReader = undefined;
|
var cie_stream: std.io.BufferedReader = undefined;
|
||||||
cie_stream.initFixed(&cie.initial_instructions);
|
cie_stream.initFixed(cie.initial_instructions);
|
||||||
var fde_stream: std.io.BufferedReader = undefined;
|
var fde_stream: std.io.BufferedReader = undefined;
|
||||||
fde_stream.initFixed(&fde.instructions);
|
fde_stream.initFixed(fde.instructions);
|
||||||
const streams: [2]*std.io.FixedBufferStream = .{ &cie_stream, &fde_stream };
|
const streams: [2]*std.io.BufferedReader = .{ &cie_stream, &fde_stream };
|
||||||
|
|
||||||
for (&streams, 0..) |stream, i| {
|
for (&streams, 0..) |stream, i| {
|
||||||
while (stream.pos < stream.buffer.len) {
|
while (stream.seek < stream.buffer.len) {
|
||||||
const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian);
|
const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian);
|
||||||
prev_row = try self.step(allocator, cie, i == 0, instruction);
|
prev_row = try self.step(allocator, cie, i == 0, instruction);
|
||||||
if (pc < fde.pc_begin + self.current_row.offset) return prev_row;
|
if (pc < fde.pc_begin + self.current_row.offset) return prev_row;
|
||||||
|
|||||||
@ -91,7 +91,7 @@ pub const Options = struct {
|
|||||||
/// A user type may be a `struct`, `vector`, `union` or `enum` type.
|
/// A user type may be a `struct`, `vector`, `union` or `enum` type.
|
||||||
///
|
///
|
||||||
/// To print literal curly braces, escape them by writing them twice, e.g. `{{` or `}}`.
|
/// To print literal curly braces, escape them by writing them twice, e.g. `{{` or `}}`.
|
||||||
pub fn format(bw: *std.io.BufferedWriter, comptime fmt: []const u8, args: anytype) anyerror!void {
|
pub fn format(bw: *std.io.BufferedWriter, comptime fmt: []const u8, args: anytype) anyerror!usize {
|
||||||
const ArgsType = @TypeOf(args);
|
const ArgsType = @TypeOf(args);
|
||||||
const args_type_info = @typeInfo(ArgsType);
|
const args_type_info = @typeInfo(ArgsType);
|
||||||
if (args_type_info != .@"struct") {
|
if (args_type_info != .@"struct") {
|
||||||
@ -107,6 +107,7 @@ pub fn format(bw: *std.io.BufferedWriter, comptime fmt: []const u8, args: anytyp
|
|||||||
comptime var arg_state: ArgState = .{ .args_len = fields_info.len };
|
comptime var arg_state: ArgState = .{ .args_len = fields_info.len };
|
||||||
comptime var i = 0;
|
comptime var i = 0;
|
||||||
comptime var literal: []const u8 = "";
|
comptime var literal: []const u8 = "";
|
||||||
|
var bytes_written: usize = 0;
|
||||||
inline while (true) {
|
inline while (true) {
|
||||||
const start_index = i;
|
const start_index = i;
|
||||||
|
|
||||||
@ -136,7 +137,7 @@ pub fn format(bw: *std.io.BufferedWriter, comptime fmt: []const u8, args: anytyp
|
|||||||
|
|
||||||
// Write out the literal
|
// Write out the literal
|
||||||
if (literal.len != 0) {
|
if (literal.len != 0) {
|
||||||
try bw.writeAll(literal);
|
bytes_written += try bw.writeAllCount(literal);
|
||||||
literal = "";
|
literal = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +197,7 @@ pub fn format(bw: *std.io.BufferedWriter, comptime fmt: []const u8, args: anytyp
|
|||||||
const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
|
const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
|
||||||
@compileError("too few arguments");
|
@compileError("too few arguments");
|
||||||
|
|
||||||
try bw.printValue(
|
bytes_written += try bw.printValue(
|
||||||
placeholder.specifier_arg,
|
placeholder.specifier_arg,
|
||||||
.{
|
.{
|
||||||
.fill = placeholder.fill,
|
.fill = placeholder.fill,
|
||||||
@ -217,6 +218,8 @@ pub fn format(bw: *std.io.BufferedWriter, comptime fmt: []const u8, args: anytyp
|
|||||||
else => @compileError(comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"),
|
else => @compileError(comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return bytes_written;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cacheString(str: anytype) []const u8 {
|
fn cacheString(str: anytype) []const u8 {
|
||||||
@ -852,11 +855,10 @@ pub fn bufPrintZ(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintErr
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Count the characters needed for format.
|
/// Count the characters needed for format.
|
||||||
pub fn count(comptime fmt: []const u8, args: anytype) u64 {
|
pub fn count(comptime fmt: []const u8, args: anytype) usize {
|
||||||
var counting_writer: std.io.CountingWriter = .{ .child_writer = std.io.null_writer };
|
var buffer: [std.atomic.cache_line]u8 = undefined;
|
||||||
var bw = counting_writer.writer().unbuffered();
|
var bw = std.io.Writer.null.buffered(&buffer);
|
||||||
bw.print(fmt, args) catch unreachable;
|
return bw.printCount(fmt, args) catch unreachable;
|
||||||
return counting_writer.bytes_written;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const AllocPrintError = error{OutOfMemory};
|
pub const AllocPrintError = error{OutOfMemory};
|
||||||
|
|||||||
@ -1512,23 +1512,48 @@ pub fn writeFileAllUnseekable(self: File, in_file: File, args: WriteFileOptions)
|
|||||||
return @errorCast(writeFileAllUnseekableInner(self, in_file, args));
|
return @errorCast(writeFileAllUnseekableInner(self, in_file, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeFileAllUnseekableInner(self: File, in_file: File, args: WriteFileOptions) anyerror!void {
|
fn writeFileAllUnseekableInner(out_file: File, in_file: File, args: WriteFileOptions) anyerror!void {
|
||||||
const headers = args.headers_and_trailers[0..args.header_count];
|
const headers = args.headers_and_trailers[0..args.header_count];
|
||||||
const trailers = args.headers_and_trailers[args.header_count..];
|
const trailers = args.headers_and_trailers[args.header_count..];
|
||||||
|
|
||||||
try self.writevAll(headers);
|
try out_file.writevAll(headers);
|
||||||
|
|
||||||
try in_file.reader().skipBytes(args.in_offset, .{ .buf_size = 4096 });
|
// Some possible optimizations here:
|
||||||
|
// * Could writev buffer multiple times if the amount to discard is larger than 4096
|
||||||
|
// * Could combine discard and read in one readv if amount to discard is small
|
||||||
|
|
||||||
var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
|
var buffer: [4096]u8 = undefined;
|
||||||
|
var remaining = args.in_offset;
|
||||||
|
while (remaining > 0) {
|
||||||
|
const n = try in_file.read(buffer[0..@min(buffer.len, remaining)]);
|
||||||
|
if (n == 0) return error.EndOfStream;
|
||||||
|
remaining -= n;
|
||||||
|
}
|
||||||
if (args.in_len) |len| {
|
if (args.in_len) |len| {
|
||||||
var stream = std.io.limitedReader(in_file.reader(), len);
|
remaining = len;
|
||||||
try fifo.pump(stream.reader(), self.writer());
|
var buffer_index: usize = 0;
|
||||||
|
while (remaining > 0) {
|
||||||
|
const n = buffer_index + try in_file.read(buffer[buffer_index..@min(buffer.len, remaining)]);
|
||||||
|
if (n == 0) return error.EndOfStream;
|
||||||
|
const written = try out_file.write(buffer[0..n]);
|
||||||
|
if (written == 0) return error.EndOfStream;
|
||||||
|
remaining -= written;
|
||||||
|
std.mem.copyForwards(u8, &buffer, buffer[written..n]);
|
||||||
|
buffer_index = n - written;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
try fifo.pump(in_file.reader(), self.writer());
|
var buffer_index: usize = 0;
|
||||||
|
while (true) {
|
||||||
|
const n = buffer_index + try in_file.read(buffer[buffer_index..]);
|
||||||
|
if (n == 0) break;
|
||||||
|
const written = try out_file.write(buffer[0..n]);
|
||||||
|
if (written == 0) return error.EndOfStream;
|
||||||
|
std.mem.copyForwards(u8, &buffer, buffer[written..n]);
|
||||||
|
buffer_index = n - written;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.writevAll(trailers);
|
try out_file.writevAll(trailers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Low level function which can fail for OS-specific reasons.
|
/// Low level function which can fail for OS-specific reasons.
|
||||||
@ -1645,7 +1670,7 @@ pub fn reader_posReadVec(context: *anyopaque, data: []const []u8, offset: u64) a
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn reader_streamRead(
|
pub fn reader_streamRead(
|
||||||
context: *anyopaque,
|
context: ?*anyopaque,
|
||||||
bw: *std.io.BufferedWriter,
|
bw: *std.io.BufferedWriter,
|
||||||
limit: std.io.Reader.Limit,
|
limit: std.io.Reader.Limit,
|
||||||
) anyerror!std.io.Reader.Status {
|
) anyerror!std.io.Reader.Status {
|
||||||
@ -1658,7 +1683,7 @@ pub fn reader_streamRead(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reader_streamReadVec(context: *anyopaque, data: []const []u8) anyerror!std.io.Reader.Status {
|
pub fn reader_streamReadVec(context: ?*anyopaque, data: []const []u8) anyerror!std.io.Reader.Status {
|
||||||
const file = opaqueToHandle(context);
|
const file = opaqueToHandle(context);
|
||||||
const n = try file.readv(data);
|
const n = try file.readv(data);
|
||||||
return .{
|
return .{
|
||||||
@ -1667,12 +1692,12 @@ pub fn reader_streamReadVec(context: *anyopaque, data: []const []u8) anyerror!st
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writer_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
|
pub fn writer_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) std.io.Writer.Result {
|
||||||
const file = opaqueToHandle(context);
|
const file = opaqueToHandle(context);
|
||||||
var splat_buffer: [256]u8 = undefined;
|
var splat_buffer: [256]u8 = undefined;
|
||||||
if (is_windows) {
|
if (is_windows) {
|
||||||
if (data.len == 1 and splat == 0) return 0;
|
if (data.len == 1 and splat == 0) return 0;
|
||||||
return windows.WriteFile(file, data[0], null);
|
return .{ .len = windows.WriteFile(file, data[0], null) catch |err| return .{ .err = err } };
|
||||||
}
|
}
|
||||||
var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;
|
var iovecs: [max_buffers_len]std.posix.iovec_const = undefined;
|
||||||
var len: usize = @min(iovecs.len, data.len);
|
var len: usize = @min(iovecs.len, data.len);
|
||||||
@ -1681,8 +1706,8 @@ pub fn writer_writeSplat(context: *anyopaque, data: []const []const u8, splat: u
|
|||||||
.len = d.len,
|
.len = d.len,
|
||||||
};
|
};
|
||||||
switch (splat) {
|
switch (splat) {
|
||||||
0 => return std.posix.writev(file, iovecs[0 .. len - 1]),
|
0 => return .{ .len = std.posix.writev(file, iovecs[0 .. len - 1]) catch |err| return .{ .err = err } },
|
||||||
1 => return std.posix.writev(file, iovecs[0..len]),
|
1 => return .{ .len = std.posix.writev(file, iovecs[0..len]) catch |err| return .{ .err = err } },
|
||||||
else => {
|
else => {
|
||||||
const pattern = data[data.len - 1];
|
const pattern = data[data.len - 1];
|
||||||
if (pattern.len == 1) {
|
if (pattern.len == 1) {
|
||||||
@ -1700,21 +1725,21 @@ pub fn writer_writeSplat(context: *anyopaque, data: []const []const u8, splat: u
|
|||||||
iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };
|
iovecs[len] = .{ .base = &splat_buffer, .len = remaining_splat };
|
||||||
len += 1;
|
len += 1;
|
||||||
}
|
}
|
||||||
return std.posix.writev(file, iovecs[0..len]);
|
return .{ .len = std.posix.writev(file, iovecs[0..len]) catch |err| return .{ .err = err } };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return std.posix.writev(file, iovecs[0..len]);
|
return .{ .len = std.posix.writev(file, iovecs[0..len]) catch |err| return .{ .err = err } };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writer_writeFile(
|
pub fn writer_writeFile(
|
||||||
context: *anyopaque,
|
context: ?*anyopaque,
|
||||||
in_file: std.fs.File,
|
in_file: std.fs.File,
|
||||||
in_offset: u64,
|
in_offset: u64,
|
||||||
in_len: std.io.Writer.FileLen,
|
in_len: std.io.Writer.FileLen,
|
||||||
headers_and_trailers: []const []const u8,
|
headers_and_trailers: []const []const u8,
|
||||||
headers_len: usize,
|
headers_len: usize,
|
||||||
) anyerror!usize {
|
) std.io.Writer.Result {
|
||||||
const out_fd = opaqueToHandle(context);
|
const out_fd = opaqueToHandle(context);
|
||||||
const in_fd = in_file.handle;
|
const in_fd = in_file.handle;
|
||||||
const len_int = switch (in_len) {
|
const len_int = switch (in_len) {
|
||||||
|
|||||||
@ -20,8 +20,6 @@ pub const Writer = @import("io/Writer.zig");
|
|||||||
pub const BufferedReader = @import("io/BufferedReader.zig");
|
pub const BufferedReader = @import("io/BufferedReader.zig");
|
||||||
pub const BufferedWriter = @import("io/BufferedWriter.zig");
|
pub const BufferedWriter = @import("io/BufferedWriter.zig");
|
||||||
pub const AllocatingWriter = @import("io/AllocatingWriter.zig");
|
pub const AllocatingWriter = @import("io/AllocatingWriter.zig");
|
||||||
pub const CountingWriter = @import("io/CountingWriter.zig");
|
|
||||||
pub const CountingReader = @import("io/CountingReader.zig");
|
|
||||||
|
|
||||||
pub const CWriter = @import("io/c_writer.zig").CWriter;
|
pub const CWriter = @import("io/c_writer.zig").CWriter;
|
||||||
pub const cWriter = @import("io/c_writer.zig").cWriter;
|
pub const cWriter = @import("io/c_writer.zig").cWriter;
|
||||||
@ -48,46 +46,6 @@ pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAt
|
|||||||
|
|
||||||
pub const tty = @import("io/tty.zig");
|
pub const tty = @import("io/tty.zig");
|
||||||
|
|
||||||
/// A `Writer` that discards all data.
|
|
||||||
pub const null_writer: Writer = .{
|
|
||||||
.context = undefined,
|
|
||||||
.vtable = &.{
|
|
||||||
.writeSplat = null_writeSplat,
|
|
||||||
.writeFile = null_writeFile,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn null_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
|
|
||||||
_ = context;
|
|
||||||
const headers = data[0 .. data.len - 1];
|
|
||||||
const pattern = data[headers.len..];
|
|
||||||
var written: usize = pattern.len * splat;
|
|
||||||
for (headers) |bytes| written += bytes.len;
|
|
||||||
return written;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn null_writeFile(
|
|
||||||
context: *anyopaque,
|
|
||||||
file: std.fs.File,
|
|
||||||
offset: u64,
|
|
||||||
len: Writer.FileLen,
|
|
||||||
headers_and_trailers: []const []const u8,
|
|
||||||
headers_len: usize,
|
|
||||||
) anyerror!usize {
|
|
||||||
_ = context;
|
|
||||||
_ = offset;
|
|
||||||
_ = headers_len;
|
|
||||||
_ = file;
|
|
||||||
if (len == .entire_file) return error.Unimplemented;
|
|
||||||
var n: usize = 0;
|
|
||||||
for (headers_and_trailers) |bytes| n += bytes.len;
|
|
||||||
return len.int() + n;
|
|
||||||
}
|
|
||||||
|
|
||||||
test null_writer {
|
|
||||||
try null_writer.writeAll("yay");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll(
|
pub fn poll(
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
comptime StreamEnum: type,
|
comptime StreamEnum: type,
|
||||||
@ -494,8 +452,6 @@ test {
|
|||||||
_ = BufferedReader;
|
_ = BufferedReader;
|
||||||
_ = Reader;
|
_ = Reader;
|
||||||
_ = Writer;
|
_ = Writer;
|
||||||
_ = CountingWriter;
|
|
||||||
_ = CountingReader;
|
|
||||||
_ = AllocatingWriter;
|
_ = AllocatingWriter;
|
||||||
_ = @import("io/bit_reader.zig");
|
_ = @import("io/bit_reader.zig");
|
||||||
_ = @import("io/bit_writer.zig");
|
_ = @import("io/bit_writer.zig");
|
||||||
|
|||||||
@ -14,26 +14,37 @@ seek: usize,
|
|||||||
storage: BufferedWriter,
|
storage: BufferedWriter,
|
||||||
unbuffered_reader: Reader,
|
unbuffered_reader: Reader,
|
||||||
|
|
||||||
|
pub fn init(br: *BufferedReader, r: Reader, buffer: []u8) void {
|
||||||
|
br.* = .{
|
||||||
|
.seek = 0,
|
||||||
|
.storage = undefined,
|
||||||
|
.unbuffered_reader = r,
|
||||||
|
};
|
||||||
|
br.storage.initFixed(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs `br` such that it will read from `buffer` and then end.
|
||||||
pub fn initFixed(br: *BufferedReader, buffer: []const u8) void {
|
pub fn initFixed(br: *BufferedReader, buffer: []const u8) void {
|
||||||
br.* = .{
|
br.* = .{
|
||||||
.seek = 0,
|
.seek = 0,
|
||||||
.storage = .{
|
.storage = .{
|
||||||
.buffer = buffer,
|
.buffer = .initBuffer(@constCast(buffer)),
|
||||||
.mode = .fixed,
|
.unbuffered_writer = .{
|
||||||
},
|
.context = undefined,
|
||||||
.reader = .{
|
.vtable = &std.io.Writer.VTable.eof,
|
||||||
.context = br,
|
|
||||||
.vtable = &.{
|
|
||||||
.streamRead = null,
|
|
||||||
.posRead = null,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
.unbuffered_reader = &.{
|
||||||
|
.context = undefined,
|
||||||
|
.vtable = &std.io.Reader.VTable.eof,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(br: *BufferedReader) void {
|
pub fn storageBuffer(br: *BufferedReader) []u8 {
|
||||||
br.storage.deinit();
|
assert(br.storage.unbuffered_writer.vtable == &std.io.Writer.VTable.eof);
|
||||||
br.* = undefined;
|
assert(br.unbuffered_reader.vtable == &std.io.Reader.VTable.eof);
|
||||||
|
return br.storage.buffer.allocatedSlice();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Although `BufferedReader` can easily satisfy the `Reader` interface, it's
|
/// Although `BufferedReader` can easily satisfy the `Reader` interface, it's
|
||||||
@ -51,30 +62,31 @@ pub fn reader(br: *BufferedReader) Reader {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn passthru_streamRead(ctx: *anyopaque, bw: *BufferedWriter, limit: Reader.Limit) anyerror!Reader.Status {
|
fn passthru_streamRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) anyerror!Reader.RwResult {
|
||||||
const br: *BufferedReader = @alignCast(@ptrCast(ctx));
|
const br: *BufferedReader = @alignCast(@ptrCast(ctx));
|
||||||
const buffer = br.storage.buffer.items;
|
const buffer = br.storage.buffer.items;
|
||||||
const buffered = buffer[br.seek..];
|
const buffered = buffer[br.seek..];
|
||||||
const limited = buffered[0..limit.min(buffered.len)];
|
const limited = buffered[0..limit.min(buffered.len)];
|
||||||
if (limited.len > 0) {
|
if (limited.len > 0) {
|
||||||
const n = try bw.writeSplat(limited, 1);
|
const result = bw.writeSplat(limited, 1);
|
||||||
br.seek += n;
|
br.seek += result.len;
|
||||||
return .{
|
return .{
|
||||||
.end = false,
|
.len = result.len,
|
||||||
.len = @intCast(n),
|
.write_err = result.err,
|
||||||
|
.write_end = result.end,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return br.unbuffered_reader.streamRead(bw, limit);
|
return br.unbuffered_reader.streamRead(bw, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn passthru_streamReadVec(ctx: *anyopaque, data: []const []u8) anyerror!Reader.Status {
|
fn passthru_streamReadVec(ctx: ?*anyopaque, data: []const []u8) anyerror!Reader.Status {
|
||||||
const br: *BufferedReader = @alignCast(@ptrCast(ctx));
|
const br: *BufferedReader = @alignCast(@ptrCast(ctx));
|
||||||
_ = br;
|
_ = br;
|
||||||
_ = data;
|
_ = data;
|
||||||
@panic("TODO");
|
@panic("TODO");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn passthru_posRead(ctx: *anyopaque, bw: *BufferedWriter, limit: Reader.Limit, off: u64) anyerror!Reader.Status {
|
fn passthru_posRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit, off: u64) anyerror!Reader.Status {
|
||||||
const br: *BufferedReader = @alignCast(@ptrCast(ctx));
|
const br: *BufferedReader = @alignCast(@ptrCast(ctx));
|
||||||
const buffer = br.storage.buffer.items;
|
const buffer = br.storage.buffer.items;
|
||||||
if (off < buffer.len) {
|
if (off < buffer.len) {
|
||||||
@ -84,7 +96,7 @@ fn passthru_posRead(ctx: *anyopaque, bw: *BufferedWriter, limit: Reader.Limit, o
|
|||||||
return br.unbuffered_reader.posRead(bw, limit, off - buffer.len);
|
return br.unbuffered_reader.posRead(bw, limit, off - buffer.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn passthru_posReadVec(ctx: *anyopaque, data: []const []u8, off: u64) anyerror!Reader.Status {
|
fn passthru_posReadVec(ctx: ?*anyopaque, data: []const []u8, off: u64) anyerror!Reader.Status {
|
||||||
const br: *BufferedReader = @alignCast(@ptrCast(ctx));
|
const br: *BufferedReader = @alignCast(@ptrCast(ctx));
|
||||||
_ = br;
|
_ = br;
|
||||||
_ = data;
|
_ = data;
|
||||||
@ -155,8 +167,24 @@ pub fn takeArray(br: *BufferedReader, comptime n: usize) anyerror!*[n]u8 {
|
|||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
/// * `toss`
|
/// * `toss`
|
||||||
/// * `discardAll`
|
/// * `discardUntilEnd`
|
||||||
|
/// * `discardUpTo`
|
||||||
pub fn discard(br: *BufferedReader, n: usize) anyerror!void {
|
pub fn discard(br: *BufferedReader, n: usize) anyerror!void {
|
||||||
|
if ((try discardUpTo(br, n)) != n) return error.EndOfStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skips the next `n` bytes from the stream, advancing the seek position.
|
||||||
|
///
|
||||||
|
/// Unlike `toss` which is infallible, in this function `n` can be any amount.
|
||||||
|
///
|
||||||
|
/// Returns the number of bytes discarded, which is less than `n` if and only
|
||||||
|
/// if the stream reached the end.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * `discard`
|
||||||
|
/// * `toss`
|
||||||
|
/// * `discardUntilEnd`
|
||||||
|
pub fn discardUpTo(br: *BufferedReader, n: usize) anyerror!usize {
|
||||||
const list = &br.storage.buffer;
|
const list = &br.storage.buffer;
|
||||||
var remaining = n;
|
var remaining = n;
|
||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
@ -168,19 +196,22 @@ pub fn discard(br: *BufferedReader, n: usize) anyerror!void {
|
|||||||
remaining -= (list.items.len - br.seek);
|
remaining -= (list.items.len - br.seek);
|
||||||
list.items.len = 0;
|
list.items.len = 0;
|
||||||
br.seek = 0;
|
br.seek = 0;
|
||||||
const status = try br.unbuffered_reader.streamRead(&br.storage, .none);
|
const result = try br.unbuffered_reader.streamRead(&br.storage, .none);
|
||||||
|
result.write_err catch unreachable;
|
||||||
|
try result.read_err;
|
||||||
|
assert(result.len == list.items.len);
|
||||||
if (remaining <= list.items.len) continue;
|
if (remaining <= list.items.len) continue;
|
||||||
if (status.end) return error.EndOfStream;
|
if (result.end) return n - remaining;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the stream until the end, ignoring all the data.
|
/// Reads the stream until the end, ignoring all the data.
|
||||||
/// Returns the number of bytes discarded.
|
/// Returns the number of bytes discarded.
|
||||||
pub fn discardAll(br: *BufferedReader) anyerror!usize {
|
pub fn discardUntilEnd(br: *BufferedReader) anyerror!usize {
|
||||||
const list = &br.storage.buffer;
|
const list = &br.storage.buffer;
|
||||||
var total: usize = list.items.len;
|
var total: usize = list.items.len;
|
||||||
list.items.len = 0;
|
list.items.len = 0;
|
||||||
total += try br.unbuffered_reader.discardAll();
|
total += try br.unbuffered_reader.discardUntilEnd();
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,6 +255,15 @@ pub fn read(br: *BufferedReader, buffer: []u8) anyerror!void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
|
||||||
|
/// means the stream reached the end. Reaching the end of a stream is not an error
|
||||||
|
/// condition.
|
||||||
|
pub fn partialRead(br: *BufferedReader, buffer: []u8) anyerror!usize {
|
||||||
|
_ = br;
|
||||||
|
_ = buffer;
|
||||||
|
@panic("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a slice of the next bytes of buffered data from the stream until
|
/// Returns a slice of the next bytes of buffered data from the stream until
|
||||||
/// `delimiter` is found, advancing the seek position.
|
/// `delimiter` is found, advancing the seek position.
|
||||||
///
|
///
|
||||||
@ -463,6 +503,95 @@ pub fn takeEnum(br: *BufferedReader, comptime Enum: type, endian: std.builtin.En
|
|||||||
return std.meta.intToEnum(Enum, int);
|
return std.meta.intToEnum(Enum, int);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a single unsigned LEB128 value from the given reader as type T,
|
||||||
|
/// or error.Overflow if the value cannot fit.
|
||||||
|
pub fn takeUleb128(br: *std.io.BufferedReader, comptime T: type) anyerror!T {
|
||||||
|
const U = if (@typeInfo(T).int.bits < 8) u8 else T;
|
||||||
|
const ShiftT = std.math.Log2Int(U);
|
||||||
|
|
||||||
|
const max_group = (@typeInfo(U).int.bits + 6) / 7;
|
||||||
|
|
||||||
|
var value: U = 0;
|
||||||
|
var group: ShiftT = 0;
|
||||||
|
|
||||||
|
while (group < max_group) : (group += 1) {
|
||||||
|
const byte = try br.takeByte();
|
||||||
|
|
||||||
|
const ov = @shlWithOverflow(@as(U, byte & 0x7f), group * 7);
|
||||||
|
if (ov[1] != 0) return error.Overflow;
|
||||||
|
|
||||||
|
value |= ov[0];
|
||||||
|
if (byte & 0x80 == 0) break;
|
||||||
|
} else {
|
||||||
|
return error.Overflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only applies in the case that we extended to u8
|
||||||
|
if (U != T) {
|
||||||
|
if (value > std.math.maxInt(T)) return error.Overflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return @truncate(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a single signed LEB128 value from the given reader as type T,
|
||||||
|
/// or `error.Overflow` if the value cannot fit.
|
||||||
|
pub fn takeIleb128(br: *std.io.BufferedReader, comptime T: type) anyerror!T {
|
||||||
|
const S = if (@typeInfo(T).int.bits < 8) i8 else T;
|
||||||
|
const U = std.meta.Int(.unsigned, @typeInfo(S).int.bits);
|
||||||
|
const ShiftU = std.math.Log2Int(U);
|
||||||
|
|
||||||
|
const max_group = (@typeInfo(U).int.bits + 6) / 7;
|
||||||
|
|
||||||
|
var value = @as(U, 0);
|
||||||
|
var group = @as(ShiftU, 0);
|
||||||
|
|
||||||
|
while (group < max_group) : (group += 1) {
|
||||||
|
const byte = try br.takeByte();
|
||||||
|
|
||||||
|
const shift = group * 7;
|
||||||
|
const ov = @shlWithOverflow(@as(U, byte & 0x7f), shift);
|
||||||
|
if (ov[1] != 0) {
|
||||||
|
// Overflow is ok so long as the sign bit is set and this is the last byte
|
||||||
|
if (byte & 0x80 != 0) return error.Overflow;
|
||||||
|
if (@as(S, @bitCast(ov[0])) >= 0) return error.Overflow;
|
||||||
|
|
||||||
|
// and all the overflowed bits are 1
|
||||||
|
const remaining_shift = @as(u3, @intCast(@typeInfo(U).int.bits - @as(u16, shift)));
|
||||||
|
const remaining_bits = @as(i8, @bitCast(byte | 0x80)) >> remaining_shift;
|
||||||
|
if (remaining_bits != -1) return error.Overflow;
|
||||||
|
} else {
|
||||||
|
// If we don't overflow and this is the last byte and the number being decoded
|
||||||
|
// is negative, check that the remaining bits are 1
|
||||||
|
if ((byte & 0x80 == 0) and (@as(S, @bitCast(ov[0])) < 0)) {
|
||||||
|
const remaining_shift = @as(u3, @intCast(@typeInfo(U).int.bits - @as(u16, shift)));
|
||||||
|
const remaining_bits = @as(i8, @bitCast(byte | 0x80)) >> remaining_shift;
|
||||||
|
if (remaining_bits != -1) return error.Overflow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value |= ov[0];
|
||||||
|
if (byte & 0x80 == 0) {
|
||||||
|
const needs_sign_ext = group + 1 < max_group;
|
||||||
|
if (byte & 0x40 != 0 and needs_sign_ext) {
|
||||||
|
const ones = @as(S, -1);
|
||||||
|
value |= @as(U, @bitCast(ones)) << (shift + 7);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return error.Overflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = @as(S, @bitCast(value));
|
||||||
|
// Only applies if we extended to i8
|
||||||
|
if (S != T) {
|
||||||
|
if (result > std.math.maxInt(T) or result < std.math.minInt(T)) return error.Overflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return @truncate(result);
|
||||||
|
}
|
||||||
|
|
||||||
test initFixed {
|
test initFixed {
|
||||||
var br: BufferedReader = undefined;
|
var br: BufferedReader = undefined;
|
||||||
br.initFixed("a\x02");
|
br.initFixed("a\x02");
|
||||||
@ -501,7 +630,7 @@ test discard {
|
|||||||
try testing.expectError(error.EndOfStream, br.discard(1));
|
try testing.expectError(error.EndOfStream, br.discard(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
test discardAll {
|
test discardUntilEnd {
|
||||||
return error.Unimplemented;
|
return error.Unimplemented;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -576,3 +705,11 @@ test takeStructEndian {
|
|||||||
test takeEnum {
|
test takeEnum {
|
||||||
return error.Unimplemented;
|
return error.Unimplemented;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test takeUleb128 {
|
||||||
|
return error.Unimplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
test takeIleb128 {
|
||||||
|
return error.Unimplemented;
|
||||||
|
}
|
||||||
|
|||||||
@ -43,7 +43,7 @@ const fixed_vtable: Writer.VTable = .{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Replaces the `BufferedWriter` with a new one that writes to `buffer` and
|
/// Replaces the `BufferedWriter` with a new one that writes to `buffer` and
|
||||||
/// returns `error.NoSpaceLeft` when it is full.
|
/// then ends when it is full.
|
||||||
pub fn initFixed(bw: *BufferedWriter, buffer: []u8) void {
|
pub fn initFixed(bw: *BufferedWriter, buffer: []u8) void {
|
||||||
bw.* = .{
|
bw.* = .{
|
||||||
.unbuffered_writer = .{
|
.unbuffered_writer = .{
|
||||||
@ -77,6 +77,36 @@ pub fn unusedCapacitySlice(bw: *const BufferedWriter) []u8 {
|
|||||||
return bw.buffer.unusedCapacitySlice();
|
return bw.buffer.unusedCapacitySlice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn writableSlice(bw: *BufferedWriter, minimum_length: usize) anyerror![]u8 {
|
||||||
|
const list = &bw.buffer;
|
||||||
|
assert(list.capacity >= minimum_length);
|
||||||
|
const cap_slice = list.unusedCapacitySlice();
|
||||||
|
if (cap_slice.len >= minimum_length) {
|
||||||
|
@branchHint(.likely);
|
||||||
|
return cap_slice;
|
||||||
|
}
|
||||||
|
const buffer = list.items;
|
||||||
|
const result = bw.unbuffered_writer.write(buffer);
|
||||||
|
if (result.len == buffer.len) {
|
||||||
|
@branchHint(.likely);
|
||||||
|
list.items.len = 0;
|
||||||
|
try result.err;
|
||||||
|
return list.unusedCapacitySlice();
|
||||||
|
}
|
||||||
|
if (result.len > 0) {
|
||||||
|
const remainder = buffer[result.len..];
|
||||||
|
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
||||||
|
list.items.len = remainder.len;
|
||||||
|
}
|
||||||
|
try result.err;
|
||||||
|
return list.unusedCapacitySlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// After calling `writableSlice`, this function tracks how many bytes were written to it.
|
||||||
|
pub fn advance(bw: *BufferedWriter, n: usize) void {
|
||||||
|
bw.items.len += n;
|
||||||
|
}
|
||||||
|
|
||||||
/// The `data` parameter is mutable because this function needs to mutate the
|
/// The `data` parameter is mutable because this function needs to mutate the
|
||||||
/// fields in order to handle partial writes from `Writer.VTable.writev`.
|
/// fields in order to handle partial writes from `Writer.VTable.writev`.
|
||||||
pub fn writevAll(bw: *BufferedWriter, data: [][]const u8) anyerror!void {
|
pub fn writevAll(bw: *BufferedWriter, data: [][]const u8) anyerror!void {
|
||||||
@ -92,15 +122,15 @@ pub fn writevAll(bw: *BufferedWriter, data: [][]const u8) anyerror!void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writeSplat(bw: *BufferedWriter, data: []const []const u8, splat: usize) anyerror!usize {
|
pub fn writeSplat(bw: *BufferedWriter, data: []const []const u8, splat: usize) Writer.Result {
|
||||||
return passthru_writeSplat(bw, data, splat);
|
return passthru_writeSplat(bw, data, splat);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writev(bw: *BufferedWriter, data: []const []const u8) anyerror!usize {
|
pub fn writev(bw: *BufferedWriter, data: []const []const u8) Writer.Result {
|
||||||
return passthru_writeSplat(bw, data, 1);
|
return passthru_writeSplat(bw, data, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
|
fn passthru_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Writer.Result {
|
||||||
const bw: *BufferedWriter = @alignCast(@ptrCast(context));
|
const bw: *BufferedWriter = @alignCast(@ptrCast(context));
|
||||||
const list = &bw.buffer;
|
const list = &bw.buffer;
|
||||||
const buffer = list.allocatedSlice();
|
const buffer = list.allocatedSlice();
|
||||||
@ -126,27 +156,45 @@ fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usi
|
|||||||
if (len >= remaining_data.len) {
|
if (len >= remaining_data.len) {
|
||||||
@branchHint(.likely);
|
@branchHint(.likely);
|
||||||
// Made it past the headers, so we can enable splatting.
|
// Made it past the headers, so we can enable splatting.
|
||||||
const n = try bw.unbuffered_writer.writeSplat(send_buffers, splat);
|
const result = bw.unbuffered_writer.writeSplat(send_buffers, splat);
|
||||||
|
const n = result.len;
|
||||||
if (n < end) {
|
if (n < end) {
|
||||||
@branchHint(.unlikely);
|
@branchHint(.unlikely);
|
||||||
const remainder = buffer[n..end];
|
const remainder = buffer[n..end];
|
||||||
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
||||||
list.items.len = remainder.len;
|
list.items.len = remainder.len;
|
||||||
return end - start_end;
|
return .{
|
||||||
|
.err = result.err,
|
||||||
|
.len = end - start_end,
|
||||||
|
.end = result.end,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
list.items.len = 0;
|
list.items.len = 0;
|
||||||
return n - start_end;
|
return .{
|
||||||
|
.err = result.err,
|
||||||
|
.len = n - start_end,
|
||||||
|
.end = result.end,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const n = try bw.unbuffered_writer.writeSplat(send_buffers, 1);
|
const result = try bw.unbuffered_writer.writeSplat(send_buffers, 1);
|
||||||
|
const n = result.len;
|
||||||
if (n < end) {
|
if (n < end) {
|
||||||
@branchHint(.unlikely);
|
@branchHint(.unlikely);
|
||||||
const remainder = buffer[n..end];
|
const remainder = buffer[n..end];
|
||||||
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
||||||
list.items.len = remainder.len;
|
list.items.len = remainder.len;
|
||||||
return end - start_end;
|
return .{
|
||||||
|
.err = result.err,
|
||||||
|
.len = end - start_end,
|
||||||
|
.end = result.end,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
list.items.len = 0;
|
list.items.len = 0;
|
||||||
return n - start_end;
|
return .{
|
||||||
|
.err = result.err,
|
||||||
|
.len = n - start_end,
|
||||||
|
.end = result.end,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const pattern = data[data.len - 1];
|
const pattern = data[data.len - 1];
|
||||||
@ -156,7 +204,7 @@ fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usi
|
|||||||
// It was added in the loop above; undo it here.
|
// It was added in the loop above; undo it here.
|
||||||
end -= pattern.len;
|
end -= pattern.len;
|
||||||
list.items.len = end;
|
list.items.len = end;
|
||||||
return end - start_end;
|
return .{ .len = end - start_end };
|
||||||
}
|
}
|
||||||
|
|
||||||
const remaining_splat = splat - 1;
|
const remaining_splat = splat - 1;
|
||||||
@ -164,7 +212,7 @@ fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usi
|
|||||||
switch (pattern.len) {
|
switch (pattern.len) {
|
||||||
0 => {
|
0 => {
|
||||||
list.items.len = end;
|
list.items.len = end;
|
||||||
return end - start_end;
|
return .{ .len = end - start_end };
|
||||||
},
|
},
|
||||||
1 => {
|
1 => {
|
||||||
const new_end = end + remaining_splat;
|
const new_end = end + remaining_splat;
|
||||||
@ -172,20 +220,29 @@ fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usi
|
|||||||
@branchHint(.likely);
|
@branchHint(.likely);
|
||||||
@memset(buffer[end..new_end], pattern[0]);
|
@memset(buffer[end..new_end], pattern[0]);
|
||||||
list.items.len = new_end;
|
list.items.len = new_end;
|
||||||
return new_end - start_end;
|
return .{ .len = new_end - start_end };
|
||||||
}
|
}
|
||||||
buffers[0] = buffer[0..end];
|
buffers[0] = buffer[0..end];
|
||||||
buffers[1] = pattern;
|
buffers[1] = pattern;
|
||||||
const n = try bw.unbuffered_writer.writeSplat(buffers[0..2], remaining_splat);
|
const result = bw.unbuffered_writer.writeSplat(buffers[0..2], remaining_splat);
|
||||||
|
const n = result.len;
|
||||||
if (n < end) {
|
if (n < end) {
|
||||||
@branchHint(.unlikely);
|
@branchHint(.unlikely);
|
||||||
const remainder = buffer[n..end];
|
const remainder = buffer[n..end];
|
||||||
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
||||||
list.items.len = remainder.len;
|
list.items.len = remainder.len;
|
||||||
return end - start_end;
|
return .{
|
||||||
|
.err = result.err,
|
||||||
|
.len = end - start_end,
|
||||||
|
.end = result.end,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
list.items.len = 0;
|
list.items.len = 0;
|
||||||
return n - start_end;
|
return .{
|
||||||
|
.err = result.err,
|
||||||
|
.len = n - start_end,
|
||||||
|
.end = result.end,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
const new_end = end + pattern.len * remaining_splat;
|
const new_end = end + pattern.len * remaining_splat;
|
||||||
@ -195,46 +252,43 @@ fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usi
|
|||||||
@memcpy(buffer[end..][0..pattern.len], pattern);
|
@memcpy(buffer[end..][0..pattern.len], pattern);
|
||||||
}
|
}
|
||||||
list.items.len = new_end;
|
list.items.len = new_end;
|
||||||
return new_end - start_end;
|
return .{ .len = new_end - start_end };
|
||||||
}
|
}
|
||||||
buffers[0] = buffer[0..end];
|
buffers[0] = buffer[0..end];
|
||||||
buffers[1] = pattern;
|
buffers[1] = pattern;
|
||||||
const n = try bw.unbuffered_writer.writeSplat(buffers[0..2], remaining_splat);
|
const result = bw.unbuffered_writer.writeSplat(buffers[0..2], remaining_splat);
|
||||||
|
const n = result.len;
|
||||||
if (n < end) {
|
if (n < end) {
|
||||||
@branchHint(.unlikely);
|
@branchHint(.unlikely);
|
||||||
const remainder = buffer[n..end];
|
const remainder = buffer[n..end];
|
||||||
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
||||||
list.items.len = remainder.len;
|
list.items.len = remainder.len;
|
||||||
return end - start_end;
|
return .{
|
||||||
|
.err = result.err,
|
||||||
|
.len = end - start_end,
|
||||||
|
.end = result.end,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
list.items.len = 0;
|
list.items.len = 0;
|
||||||
return n - start_end;
|
return .{
|
||||||
|
.err = result.err,
|
||||||
|
.len = n - start_end,
|
||||||
|
.end = result.end,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fixed_writev(context: *anyopaque, data: []const []const u8) anyerror!usize {
|
|
||||||
const bw: *BufferedWriter = @alignCast(@ptrCast(context));
|
|
||||||
const list = &bw.buffer;
|
|
||||||
// When this function is called it means the buffer got full, so it's time
|
|
||||||
// to return an error. However, we still need to make sure all of the
|
|
||||||
// available buffer has been used.
|
|
||||||
const first = data[0];
|
|
||||||
const dest = list.unusedCapacitySlice();
|
|
||||||
@memcpy(dest, first[0..dest.len]);
|
|
||||||
list.items.len = list.capacity;
|
|
||||||
return error.NoSpaceLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// When this function is called it means the buffer got full, so it's time
|
/// When this function is called it means the buffer got full, so it's time
|
||||||
/// to return an error. However, we still need to make sure all of the
|
/// to return an error. However, we still need to make sure all of the
|
||||||
/// available buffer has been filled.
|
/// available buffer has been filled.
|
||||||
fn fixed_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
|
fn fixed_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Writer.Result {
|
||||||
const bw: *BufferedWriter = @alignCast(@ptrCast(context));
|
const bw: *BufferedWriter = @alignCast(@ptrCast(context));
|
||||||
const list = &bw.buffer;
|
const list = &bw.buffer;
|
||||||
|
const start_len = list.items.len;
|
||||||
for (data) |bytes| {
|
for (data) |bytes| {
|
||||||
const dest = list.unusedCapacitySlice();
|
const dest = list.unusedCapacitySlice();
|
||||||
if (dest.len == 0) return error.NoSpaceLeft;
|
if (dest.len == 0) return .{ .len = list.items.len - start_len, .end = true };
|
||||||
const len = @min(bytes.len, dest.len);
|
const len = @min(bytes.len, dest.len);
|
||||||
@memcpy(dest[0..len], bytes[0..len]);
|
@memcpy(dest[0..len], bytes[0..len]);
|
||||||
list.items.len += len;
|
list.items.len += len;
|
||||||
@ -247,90 +301,153 @@ fn fixed_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize)
|
|||||||
else => for (0..splat - 1) |i| @memcpy(dest[i * pattern.len ..][0..pattern.len], pattern),
|
else => for (0..splat - 1) |i| @memcpy(dest[i * pattern.len ..][0..pattern.len], pattern),
|
||||||
}
|
}
|
||||||
list.items.len = list.capacity;
|
list.items.len = list.capacity;
|
||||||
return error.NoSpaceLeft;
|
return .{ .len = list.items.len - start_len, .end = true };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(bw: *BufferedWriter, bytes: []const u8) anyerror!usize {
|
pub fn write(bw: *BufferedWriter, bytes: []const u8) Writer.Result {
|
||||||
const list = &bw.buffer;
|
const list = &bw.buffer;
|
||||||
const buffer = list.allocatedSlice();
|
const buffer = list.allocatedSlice();
|
||||||
const end = list.items.len;
|
const end = list.items.len;
|
||||||
const new_end = end + bytes.len;
|
const new_end = end + bytes.len;
|
||||||
if (new_end > buffer.len) {
|
if (new_end > buffer.len) {
|
||||||
var data: [2][]const u8 = .{ buffer[0..end], bytes };
|
var data: [2][]const u8 = .{ buffer[0..end], bytes };
|
||||||
const n = try bw.unbuffered_writer.writev(&data);
|
const result = bw.unbuffered_writer.writev(&data);
|
||||||
|
const n = result.len;
|
||||||
if (n < end) {
|
if (n < end) {
|
||||||
@branchHint(.unlikely);
|
@branchHint(.unlikely);
|
||||||
const remainder = buffer[n..end];
|
const remainder = buffer[n..end];
|
||||||
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
||||||
list.items.len = remainder.len;
|
list.items.len = remainder.len;
|
||||||
return 0;
|
return .{
|
||||||
|
.err = result.err,
|
||||||
|
.len = 0,
|
||||||
|
.end = result.end,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
list.items.len = 0;
|
list.items.len = 0;
|
||||||
return n - end;
|
return .{
|
||||||
|
.err = result.err,
|
||||||
|
.len = n - end,
|
||||||
|
.end = result.end,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@memcpy(buffer[end..new_end], bytes);
|
@memcpy(buffer[end..new_end], bytes);
|
||||||
list.items.len = new_end;
|
list.items.len = new_end;
|
||||||
return bytes.len;
|
return bytes.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function is provided by the `Writer`, however it is
|
|
||||||
/// duplicated here so that `bw` can be passed to `std.fmt.format` directly,
|
|
||||||
/// avoiding one indirect function call.
|
|
||||||
pub fn writeAll(bw: *BufferedWriter, bytes: []const u8) anyerror!void {
|
pub fn writeAll(bw: *BufferedWriter, bytes: []const u8) anyerror!void {
|
||||||
|
if ((try writeUntilEnd(bw, bytes)) != bytes.len) return error.WriteStreamEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeAllCount(bw: *BufferedWriter, bytes: []const u8) anyerror!usize {
|
||||||
|
try writeAll(bw, bytes);
|
||||||
|
return bytes.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the number returned is less than `bytes.len` it indicates end of stream.
|
||||||
|
pub fn writeUntilEnd(bw: *BufferedWriter, bytes: []const u8) anyerror!usize {
|
||||||
var index: usize = 0;
|
var index: usize = 0;
|
||||||
while (index < bytes.len) index += try write(bw, bytes[index..]);
|
while (true) {
|
||||||
|
const result = write(bw, bytes[index..]);
|
||||||
|
try result.err;
|
||||||
|
index += result.len;
|
||||||
|
assert(index <= bytes.len);
|
||||||
|
if (index == bytes.len or result.end) return index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print(bw: *BufferedWriter, comptime format: []const u8, args: anytype) anyerror!void {
|
pub fn print(bw: *BufferedWriter, comptime format: []const u8, args: anytype) anyerror!void {
|
||||||
|
_ = try std.fmt.format(bw, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn printCount(bw: *BufferedWriter, comptime format: []const u8, args: anytype) anyerror!usize {
|
||||||
return std.fmt.format(bw, format, args);
|
return std.fmt.format(bw, format, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writeByte(bw: *BufferedWriter, byte: u8) anyerror!void {
|
pub fn writeByte(bw: *BufferedWriter, byte: u8) anyerror!void {
|
||||||
|
if ((try writeByteUntilEnd(bw, byte)) == 0) return error.WriteStreamEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeByteCount(bw: *BufferedWriter, byte: u8) anyerror!usize {
|
||||||
|
try writeByte(bw, byte);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns 0 or 1 indicating how many bytes were written.
|
||||||
|
/// `0` means end of stream encountered.
|
||||||
|
pub fn writeByteUntilEnd(bw: *BufferedWriter, byte: u8) anyerror!usize {
|
||||||
const list = &bw.buffer;
|
const list = &bw.buffer;
|
||||||
const buffer = list.items;
|
const buffer = list.items;
|
||||||
if (buffer.len < list.capacity) {
|
if (buffer.len < list.capacity) {
|
||||||
@branchHint(.likely);
|
@branchHint(.likely);
|
||||||
buffer.ptr[buffer.len] = byte;
|
buffer.ptr[buffer.len] = byte;
|
||||||
list.items.len = buffer.len + 1;
|
list.items.len = buffer.len + 1;
|
||||||
return;
|
return 1;
|
||||||
}
|
}
|
||||||
var buffers: [2][]const u8 = .{ buffer, &.{byte} };
|
var buffers: [2][]const u8 = .{ buffer, &.{byte} };
|
||||||
while (true) {
|
while (true) {
|
||||||
const n = try bw.unbuffered_writer.writev(&buffers);
|
const result = bw.unbuffered_writer.writev(&buffers);
|
||||||
|
try result.err;
|
||||||
|
const n = result.len;
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
@branchHint(.unlikely);
|
@branchHint(.unlikely);
|
||||||
|
if (result.end) return 0;
|
||||||
continue;
|
continue;
|
||||||
} else if (n >= buffer.len) {
|
} else if (n >= buffer.len) {
|
||||||
@branchHint(.likely);
|
@branchHint(.likely);
|
||||||
if (n > buffer.len) {
|
if (n > buffer.len) {
|
||||||
@branchHint(.likely);
|
@branchHint(.likely);
|
||||||
list.items.len = 0;
|
list.items.len = 0;
|
||||||
return;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
buffer[0] = byte;
|
buffer[0] = byte;
|
||||||
list.items.len = 1;
|
list.items.len = 1;
|
||||||
return;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const remainder = buffer[n..];
|
const remainder = buffer[n..];
|
||||||
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
|
||||||
buffer[remainder.len] = byte;
|
buffer[remainder.len] = byte;
|
||||||
list.items.len = remainder.len + 1;
|
list.items.len = remainder.len + 1;
|
||||||
return;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the same byte many times, performing the underlying write call as
|
/// Writes the same byte many times, performing the underlying write call as
|
||||||
/// many times as necessary.
|
/// many times as necessary, returning `error.WriteStreamEnd` if the byte
|
||||||
|
/// could not be repeated `n` times.
|
||||||
pub fn splatByteAll(bw: *BufferedWriter, byte: u8, n: usize) anyerror!void {
|
pub fn splatByteAll(bw: *BufferedWriter, byte: u8, n: usize) anyerror!void {
|
||||||
var remaining: usize = n;
|
if ((try splatByteUntilEnd(bw, byte, n)) != n) return error.WriteStreamEnd;
|
||||||
while (remaining > 0) remaining -= try splatByte(bw, byte, remaining);
|
}
|
||||||
|
|
||||||
|
/// Writes the same byte many times, performing the underlying write call as
|
||||||
|
/// many times as necessary, returning `error.WriteStreamEnd` if the byte
|
||||||
|
/// could not be repeated `n` times, or returning `n` on success.
|
||||||
|
pub fn splatByteAllCount(bw: *BufferedWriter, byte: u8, n: usize) anyerror!usize {
|
||||||
|
try splatByteAll(bw, byte, n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the same byte many times, performing the underlying write call as
|
||||||
|
/// many times as necessary.
|
||||||
|
///
|
||||||
|
/// If the number returned is less than `n` it indicates end of stream.
|
||||||
|
pub fn splatByteUntilEnd(bw: *BufferedWriter, byte: u8, n: usize) anyerror!usize {
|
||||||
|
var index: usize = 0;
|
||||||
|
while (true) {
|
||||||
|
const result = splatByte(bw, byte, n - index);
|
||||||
|
try result.err;
|
||||||
|
index += result.len;
|
||||||
|
assert(index <= n);
|
||||||
|
if (index == n or result.end) return index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the same byte many times, allowing short writes.
|
/// Writes the same byte many times, allowing short writes.
|
||||||
///
|
///
|
||||||
/// Does maximum of one underlying `Writer.VTable.writev`.
|
/// Does maximum of one underlying `Writer.VTable.writeSplat`.
|
||||||
pub fn splatByte(bw: *BufferedWriter, byte: u8, n: usize) anyerror!usize {
|
pub fn splatByte(bw: *BufferedWriter, byte: u8, n: usize) Writer.Result {
|
||||||
return passthru_writeSplat(bw, &.{&.{byte}}, n);
|
return passthru_writeSplat(bw, &.{&.{byte}}, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,7 +506,7 @@ pub fn writeFile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn passthru_writeFile(
|
fn passthru_writeFile(
|
||||||
context: *anyopaque,
|
context: ?*anyopaque,
|
||||||
file: std.fs.File,
|
file: std.fs.File,
|
||||||
offset: u64,
|
offset: u64,
|
||||||
len: Writer.FileLen,
|
len: Writer.FileLen,
|
||||||
@ -544,32 +661,34 @@ pub fn alignBuffer(
|
|||||||
width: usize,
|
width: usize,
|
||||||
alignment: std.fmt.Alignment,
|
alignment: std.fmt.Alignment,
|
||||||
fill: u8,
|
fill: u8,
|
||||||
) anyerror!void {
|
) anyerror!usize {
|
||||||
const padding = if (buffer.len < width) width - buffer.len else 0;
|
const padding = if (buffer.len < width) width - buffer.len else 0;
|
||||||
if (padding == 0) {
|
if (padding == 0) {
|
||||||
@branchHint(.likely);
|
@branchHint(.likely);
|
||||||
return bw.writeAll(buffer);
|
return bw.writeAllCount(buffer);
|
||||||
}
|
}
|
||||||
|
var n: usize = 0;
|
||||||
switch (alignment) {
|
switch (alignment) {
|
||||||
.left => {
|
.left => {
|
||||||
try bw.writeAll(buffer);
|
n += try bw.writeAllCount(buffer);
|
||||||
try bw.splatByteAll(fill, padding);
|
n += try bw.splatByteAllCount(fill, padding);
|
||||||
},
|
},
|
||||||
.center => {
|
.center => {
|
||||||
const left_padding = padding / 2;
|
const left_padding = padding / 2;
|
||||||
const right_padding = (padding + 1) / 2;
|
const right_padding = (padding + 1) / 2;
|
||||||
try bw.splatByteAll(fill, left_padding);
|
n += try bw.splatByteAllCount(fill, left_padding);
|
||||||
try bw.writeAll(buffer);
|
n += try bw.writeAllCount(buffer);
|
||||||
try bw.splatByteAll(fill, right_padding);
|
n += try bw.splatByteAllCount(fill, right_padding);
|
||||||
},
|
},
|
||||||
.right => {
|
.right => {
|
||||||
try bw.splatByteAll(fill, padding);
|
n += try bw.splatByteAllCount(fill, padding);
|
||||||
try bw.writeAll(buffer);
|
n += try bw.writeAllCount(buffer);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn alignBufferOptions(bw: *BufferedWriter, buffer: []const u8, options: std.fmt.Options) anyerror!void {
|
pub fn alignBufferOptions(bw: *BufferedWriter, buffer: []const u8, options: std.fmt.Options) anyerror!usize {
|
||||||
return alignBuffer(bw, buffer, options.width orelse buffer.len, options.alignment, options.fill);
|
return alignBuffer(bw, buffer, options.width orelse buffer.len, options.alignment, options.fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -604,7 +723,7 @@ pub fn printValue(
|
|||||||
options: std.fmt.Options,
|
options: std.fmt.Options,
|
||||||
value: anytype,
|
value: anytype,
|
||||||
max_depth: usize,
|
max_depth: usize,
|
||||||
) anyerror!void {
|
) anyerror!usize {
|
||||||
const T = @TypeOf(value);
|
const T = @TypeOf(value);
|
||||||
const actual_fmt = comptime if (std.mem.eql(u8, fmt, ANY))
|
const actual_fmt = comptime if (std.mem.eql(u8, fmt, ANY))
|
||||||
defaultFormatString(T)
|
defaultFormatString(T)
|
||||||
@ -619,13 +738,10 @@ pub fn printValue(
|
|||||||
|
|
||||||
if (std.meta.hasMethod(T, "format")) {
|
if (std.meta.hasMethod(T, "format")) {
|
||||||
if (fmt.len > 0 and fmt[0] == 'f') {
|
if (fmt.len > 0 and fmt[0] == 'f') {
|
||||||
return value.format(fmt[1..], options, bw);
|
return value.format(bw, fmt[1..]);
|
||||||
} else {
|
} else if (fmt.len == 0) {
|
||||||
//@deprecated();
|
// after 0.15.0 is tagged, delete the hasMethod condition and this compile error
|
||||||
// After 0.14.0 is tagged, uncomment this next line:
|
@compileError("ambiguous format string; specify {f} to call format method, or {any} to skip it");
|
||||||
//@compileError("ambiguous format string; specify {f} to call format method, or {any} to skip it");
|
|
||||||
//and then delete the `hasMethod` condition
|
|
||||||
return value.format(fmt, options, bw);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -662,92 +778,104 @@ pub fn printValue(
|
|||||||
},
|
},
|
||||||
.error_set => {
|
.error_set => {
|
||||||
if (actual_fmt.len > 0 and actual_fmt[0] == 's') {
|
if (actual_fmt.len > 0 and actual_fmt[0] == 's') {
|
||||||
return bw.writeAll(@errorName(value));
|
return bw.writeAllCount(@errorName(value));
|
||||||
} else if (actual_fmt.len != 0) {
|
} else if (actual_fmt.len != 0) {
|
||||||
invalidFmtError(fmt, value);
|
invalidFmtError(fmt, value);
|
||||||
} else {
|
} else {
|
||||||
try bw.writeAll("error.");
|
var n: usize = 0;
|
||||||
return bw.writeAll(@errorName(value));
|
n += try bw.writeAllCount("error.");
|
||||||
|
n += try bw.writeAllCount(@errorName(value));
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.@"enum" => |enumInfo| {
|
.@"enum" => |enum_info| {
|
||||||
try bw.writeAll(@typeName(T));
|
var n: usize = 0;
|
||||||
if (enumInfo.is_exhaustive) {
|
n += try bw.writeAllCount(@typeName(T));
|
||||||
|
if (enum_info.is_exhaustive) {
|
||||||
if (actual_fmt.len != 0) invalidFmtError(fmt, value);
|
if (actual_fmt.len != 0) invalidFmtError(fmt, value);
|
||||||
try bw.writeAll(".");
|
n += try bw.writeAllCount(".");
|
||||||
try bw.writeAll(@tagName(value));
|
n += try bw.writeAllCount(@tagName(value));
|
||||||
return;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use @tagName only if value is one of known fields
|
// Use @tagName only if value is one of known fields
|
||||||
@setEvalBranchQuota(3 * enumInfo.fields.len);
|
@setEvalBranchQuota(3 * enum_info.fields.len);
|
||||||
inline for (enumInfo.fields) |enumField| {
|
inline for (enum_info.fields) |enumField| {
|
||||||
if (@intFromEnum(value) == enumField.value) {
|
if (@intFromEnum(value) == enumField.value) {
|
||||||
try bw.writeAll(".");
|
n += try bw.writeAllCount(".");
|
||||||
try bw.writeAll(@tagName(value));
|
n += try bw.writeAllCount(@tagName(value));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try bw.writeByte('(');
|
n += try bw.writeByteCount('(');
|
||||||
try printValue(bw, actual_fmt, options, @intFromEnum(value), max_depth);
|
n += try printValue(bw, actual_fmt, options, @intFromEnum(value), max_depth);
|
||||||
try bw.writeByte(')');
|
n += try bw.writeByteCount(')');
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
.@"union" => |info| {
|
.@"union" => |info| {
|
||||||
if (actual_fmt.len != 0) invalidFmtError(fmt, value);
|
if (actual_fmt.len != 0) invalidFmtError(fmt, value);
|
||||||
try bw.writeAll(@typeName(T));
|
var n: usize = 0;
|
||||||
|
n += try bw.writeAllCount(@typeName(T));
|
||||||
if (max_depth == 0) {
|
if (max_depth == 0) {
|
||||||
return bw.writeAll("{ ... }");
|
n += bw.writeAllCount("{ ... }");
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
if (info.tag_type) |UnionTagType| {
|
if (info.tag_type) |UnionTagType| {
|
||||||
try bw.writeAll("{ .");
|
n += try bw.writeAllCount("{ .");
|
||||||
try bw.writeAll(@tagName(@as(UnionTagType, value)));
|
n += try bw.writeAllCount(@tagName(@as(UnionTagType, value)));
|
||||||
try bw.writeAll(" = ");
|
n += try bw.writeAllCount(" = ");
|
||||||
inline for (info.fields) |u_field| {
|
inline for (info.fields) |u_field| {
|
||||||
if (value == @field(UnionTagType, u_field.name)) {
|
if (value == @field(UnionTagType, u_field.name)) {
|
||||||
try printValue(bw, ANY, options, @field(value, u_field.name), max_depth - 1);
|
n += try printValue(bw, ANY, options, @field(value, u_field.name), max_depth - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try bw.writeAll(" }");
|
n += try bw.writeAllCount(" }");
|
||||||
} else {
|
} else {
|
||||||
try bw.writeByte('@');
|
n += try bw.writeByte('@');
|
||||||
try bw.printIntOptions(@intFromPtr(&value), 16, .lower);
|
n += try bw.printIntOptions(@intFromPtr(&value), 16, .lower);
|
||||||
}
|
}
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
.@"struct" => |info| {
|
.@"struct" => |info| {
|
||||||
if (actual_fmt.len != 0) invalidFmtError(fmt, value);
|
if (actual_fmt.len != 0) invalidFmtError(fmt, value);
|
||||||
|
var n: usize = 0;
|
||||||
if (info.is_tuple) {
|
if (info.is_tuple) {
|
||||||
// Skip the type and field names when formatting tuples.
|
// Skip the type and field names when formatting tuples.
|
||||||
if (max_depth == 0) {
|
if (max_depth == 0) {
|
||||||
return bw.writeAll("{ ... }");
|
n += try bw.writeAllCount("{ ... }");
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
try bw.writeAll("{");
|
n += try bw.writeAllCount("{");
|
||||||
inline for (info.fields, 0..) |f, i| {
|
inline for (info.fields, 0..) |f, i| {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
try bw.writeAll(" ");
|
n += try bw.writeAllCount(" ");
|
||||||
} else {
|
} else {
|
||||||
try bw.writeAll(", ");
|
n += try bw.writeAllCount(", ");
|
||||||
}
|
}
|
||||||
try printValue(bw, ANY, options, @field(value, f.name), max_depth - 1);
|
n += try printValue(bw, ANY, options, @field(value, f.name), max_depth - 1);
|
||||||
}
|
}
|
||||||
return bw.writeAll(" }");
|
n += try bw.writeAllCount(" }");
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
try bw.writeAll(@typeName(T));
|
n += try bw.writeAllCount(@typeName(T));
|
||||||
if (max_depth == 0) {
|
if (max_depth == 0) {
|
||||||
return bw.writeAll("{ ... }");
|
n += try bw.writeAllCount("{ ... }");
|
||||||
|
return n;
|
||||||
}
|
}
|
||||||
try bw.writeAll("{");
|
n += try bw.writeAllCount("{");
|
||||||
inline for (info.fields, 0..) |f, i| {
|
inline for (info.fields, 0..) |f, i| {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
try bw.writeAll(" .");
|
n += try bw.writeAllCount(" .");
|
||||||
} else {
|
} else {
|
||||||
try bw.writeAll(", .");
|
n += try bw.writeAllCount(", .");
|
||||||
}
|
}
|
||||||
try bw.writeAll(f.name);
|
n += try bw.writeAllCount(f.name);
|
||||||
try bw.writeAll(" = ");
|
n += try bw.writeAllCount(" = ");
|
||||||
try printValue(bw, ANY, options, @field(value, f.name), max_depth - 1);
|
n += try printValue(bw, ANY, options, @field(value, f.name), max_depth - 1);
|
||||||
}
|
}
|
||||||
try bw.writeAll(" }");
|
n += try bw.writeAllCount(" }");
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
.pointer => |ptr_info| switch (ptr_info.size) {
|
.pointer => |ptr_info| switch (ptr_info.size) {
|
||||||
.one => switch (@typeInfo(ptr_info.child)) {
|
.one => switch (@typeInfo(ptr_info.child)) {
|
||||||
@ -756,8 +884,10 @@ pub fn printValue(
|
|||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" };
|
var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" };
|
||||||
try writevAll(bw, &buffers);
|
var n: usize = 0;
|
||||||
try printIntOptions(bw, @intFromPtr(value), 16, .lower, options);
|
n += try writevAll(bw, &buffers);
|
||||||
|
n += try printIntOptions(bw, @intFromPtr(value), 16, .lower, options);
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.many, .c => {
|
.many, .c => {
|
||||||
@ -775,7 +905,7 @@ pub fn printValue(
|
|||||||
if (actual_fmt.len == 0)
|
if (actual_fmt.len == 0)
|
||||||
@compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})");
|
@compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})");
|
||||||
if (max_depth == 0) {
|
if (max_depth == 0) {
|
||||||
return bw.writeAll("{ ... }");
|
return bw.writeAllCount("{ ... }");
|
||||||
}
|
}
|
||||||
if (ptr_info.child == u8) switch (actual_fmt.len) {
|
if (ptr_info.child == u8) switch (actual_fmt.len) {
|
||||||
1 => switch (actual_fmt[0]) {
|
1 => switch (actual_fmt[0]) {
|
||||||
@ -789,21 +919,23 @@ pub fn printValue(
|
|||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
};
|
};
|
||||||
try bw.writeAll("{ ");
|
var n: usize = 0;
|
||||||
|
n += try bw.writeAllCount("{ ");
|
||||||
for (value, 0..) |elem, i| {
|
for (value, 0..) |elem, i| {
|
||||||
try printValue(bw, actual_fmt, options, elem, max_depth - 1);
|
n += try printValue(bw, actual_fmt, options, elem, max_depth - 1);
|
||||||
if (i != value.len - 1) {
|
if (i != value.len - 1) {
|
||||||
try bw.writeAll(", ");
|
n += try bw.writeAllCount(", ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try bw.writeAll(" }");
|
n += try bw.writeAllCount(" }");
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.array => |info| {
|
.array => |info| {
|
||||||
if (actual_fmt.len == 0)
|
if (actual_fmt.len == 0)
|
||||||
@compileError("cannot format array without a specifier (i.e. {s} or {any})");
|
@compileError("cannot format array without a specifier (i.e. {s} or {any})");
|
||||||
if (max_depth == 0) {
|
if (max_depth == 0) {
|
||||||
return bw.writeAll("{ ... }");
|
return bw.writeAllCount("{ ... }");
|
||||||
}
|
}
|
||||||
if (info.child == u8) {
|
if (info.child == u8) {
|
||||||
if (actual_fmt[0] == 's') {
|
if (actual_fmt[0] == 's') {
|
||||||
@ -814,28 +946,32 @@ pub fn printValue(
|
|||||||
return printHex(bw, &value, .upper);
|
return printHex(bw, &value, .upper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try bw.writeAll("{ ");
|
var n: usize = 0;
|
||||||
|
n += try bw.writeAllCount("{ ");
|
||||||
for (value, 0..) |elem, i| {
|
for (value, 0..) |elem, i| {
|
||||||
try printValue(bw, actual_fmt, options, elem, max_depth - 1);
|
n += try printValue(bw, actual_fmt, options, elem, max_depth - 1);
|
||||||
if (i < value.len - 1) {
|
if (i < value.len - 1) {
|
||||||
try bw.writeAll(", ");
|
n += try bw.writeAllCount(", ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try bw.writeAll(" }");
|
n += try bw.writeAllCount(" }");
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
.vector => |info| {
|
.vector => |info| {
|
||||||
if (max_depth == 0) {
|
if (max_depth == 0) {
|
||||||
return bw.writeAll("{ ... }");
|
return bw.writeAllCount("{ ... }");
|
||||||
}
|
}
|
||||||
try bw.writeAll("{ ");
|
var n: usize = 0;
|
||||||
|
n += try bw.writeAllCount("{ ");
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < info.len) : (i += 1) {
|
while (i < info.len) : (i += 1) {
|
||||||
try printValue(bw, actual_fmt, options, value[i], max_depth - 1);
|
n += try printValue(bw, actual_fmt, options, value[i], max_depth - 1);
|
||||||
if (i < info.len - 1) {
|
if (i < info.len - 1) {
|
||||||
try bw.writeAll(", ");
|
n += try bw.writeAllCount(", ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try bw.writeAll(" }");
|
n += try bw.writeAllCount(" }");
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
.@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"),
|
.@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"),
|
||||||
.type => {
|
.type => {
|
||||||
@ -860,7 +996,7 @@ pub fn printInt(
|
|||||||
comptime fmt: []const u8,
|
comptime fmt: []const u8,
|
||||||
options: std.fmt.Options,
|
options: std.fmt.Options,
|
||||||
value: anytype,
|
value: anytype,
|
||||||
) anyerror!void {
|
) anyerror!usize {
|
||||||
const int_value = if (@TypeOf(value) == comptime_int) blk: {
|
const int_value = if (@TypeOf(value) == comptime_int) blk: {
|
||||||
const Int = std.math.IntFittingRange(value, value);
|
const Int = std.math.IntFittingRange(value, value);
|
||||||
break :blk @as(Int, value);
|
break :blk @as(Int, value);
|
||||||
@ -904,15 +1040,15 @@ pub fn printInt(
|
|||||||
comptime unreachable;
|
comptime unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn printAsciiChar(bw: *BufferedWriter, c: u8, options: std.fmt.Options) anyerror!void {
|
pub fn printAsciiChar(bw: *BufferedWriter, c: u8, options: std.fmt.Options) anyerror!usize {
|
||||||
return alignBufferOptions(bw, @as(*const [1]u8, &c), options);
|
return alignBufferOptions(bw, @as(*const [1]u8, &c), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn printAscii(bw: *BufferedWriter, bytes: []const u8, options: std.fmt.Options) anyerror!void {
|
pub fn printAscii(bw: *BufferedWriter, bytes: []const u8, options: std.fmt.Options) anyerror!usize {
|
||||||
return alignBufferOptions(bw, bytes, options);
|
return alignBufferOptions(bw, bytes, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn printUnicodeCodepoint(bw: *BufferedWriter, c: u21, options: std.fmt.Options) anyerror!void {
|
pub fn printUnicodeCodepoint(bw: *BufferedWriter, c: u21, options: std.fmt.Options) anyerror!usize {
|
||||||
var buf: [4]u8 = undefined;
|
var buf: [4]u8 = undefined;
|
||||||
const len = try std.unicode.utf8Encode(c, &buf);
|
const len = try std.unicode.utf8Encode(c, &buf);
|
||||||
return alignBufferOptions(bw, buf[0..len], options);
|
return alignBufferOptions(bw, buf[0..len], options);
|
||||||
@ -924,7 +1060,7 @@ pub fn printIntOptions(
|
|||||||
base: u8,
|
base: u8,
|
||||||
case: std.fmt.Case,
|
case: std.fmt.Case,
|
||||||
options: std.fmt.Options,
|
options: std.fmt.Options,
|
||||||
) anyerror!void {
|
) anyerror!usize {
|
||||||
assert(base >= 2);
|
assert(base >= 2);
|
||||||
|
|
||||||
const int_value = if (@TypeOf(value) == comptime_int) blk: {
|
const int_value = if (@TypeOf(value) == comptime_int) blk: {
|
||||||
@ -991,7 +1127,7 @@ pub fn printFloat(
|
|||||||
comptime fmt: []const u8,
|
comptime fmt: []const u8,
|
||||||
options: std.fmt.Options,
|
options: std.fmt.Options,
|
||||||
value: anytype,
|
value: anytype,
|
||||||
) anyerror!void {
|
) anyerror!usize {
|
||||||
var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined;
|
var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined;
|
||||||
|
|
||||||
if (fmt.len > 1) invalidFmtError(fmt, value);
|
if (fmt.len > 1) invalidFmtError(fmt, value);
|
||||||
@ -1279,7 +1415,7 @@ pub fn printDuration(bw: *BufferedWriter, nanoseconds: anytype, options: std.fmt
|
|||||||
return alignBufferOptions(bw, sub_bw.getWritten(), options);
|
return alignBufferOptions(bw, sub_bw.getWritten(), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn printHex(bw: *BufferedWriter, bytes: []const u8, case: std.fmt.Case) anyerror!void {
|
pub fn printHex(bw: *BufferedWriter, bytes: []const u8, case: std.fmt.Case) anyerror!usize {
|
||||||
const charset = switch (case) {
|
const charset = switch (case) {
|
||||||
.upper => "0123456789ABCDEF",
|
.upper => "0123456789ABCDEF",
|
||||||
.lower => "0123456789abcdef",
|
.lower => "0123456789abcdef",
|
||||||
@ -1288,12 +1424,68 @@ pub fn printHex(bw: *BufferedWriter, bytes: []const u8, case: std.fmt.Case) anye
|
|||||||
try writeByte(bw, charset[c >> 4]);
|
try writeByte(bw, charset[c >> 4]);
|
||||||
try writeByte(bw, charset[c & 15]);
|
try writeByte(bw, charset[c & 15]);
|
||||||
}
|
}
|
||||||
|
return bytes.len * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn printBase64(bw: *BufferedWriter, bytes: []const u8) anyerror!void {
|
pub fn printBase64(bw: *BufferedWriter, bytes: []const u8) anyerror!usize {
|
||||||
var chunker = std.mem.window(u8, bytes, 3, 3);
|
var chunker = std.mem.window(u8, bytes, 3, 3);
|
||||||
var temp: [5]u8 = undefined;
|
var temp: [5]u8 = undefined;
|
||||||
while (chunker.next()) |chunk| try bw.writeAll(std.base64.standard.Encoder.encode(&temp, chunk));
|
var n: usize = 0;
|
||||||
|
while (chunker.next()) |chunk| {
|
||||||
|
n += try bw.writeAllCount(std.base64.standard.Encoder.encode(&temp, chunk));
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a single unsigned integer as unsigned LEB128 to the given writer.
|
||||||
|
pub fn writeUleb128(bw: *std.io.BufferedWriter, arg: anytype) anyerror!usize {
|
||||||
|
const Arg = @TypeOf(arg);
|
||||||
|
const Int = switch (Arg) {
|
||||||
|
comptime_int => std.math.IntFittingRange(arg, arg),
|
||||||
|
else => Arg,
|
||||||
|
};
|
||||||
|
const Value = if (@typeInfo(Int).int.bits < 8) u8 else Int;
|
||||||
|
var value: Value = arg;
|
||||||
|
var n: usize = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const byte: u8 = @truncate(value & 0x7f);
|
||||||
|
value >>= 7;
|
||||||
|
if (value == 0) {
|
||||||
|
try bw.writeByte(byte);
|
||||||
|
return n + 1;
|
||||||
|
} else {
|
||||||
|
try bw.writeByte(byte | 0x80);
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a single signed integer as signed LEB128 to the given writer.
|
||||||
|
pub fn writeIleb128(bw: *std.io.BufferedWriter, arg: anytype) anyerror!usize {
|
||||||
|
const Arg = @TypeOf(arg);
|
||||||
|
const Int = switch (Arg) {
|
||||||
|
comptime_int => std.math.IntFittingRange(-@abs(arg), @abs(arg)),
|
||||||
|
else => Arg,
|
||||||
|
};
|
||||||
|
const Signed = if (@typeInfo(Int).int.bits < 8) i8 else Int;
|
||||||
|
const Unsigned = std.meta.Int(.unsigned, @typeInfo(Signed).int.bits);
|
||||||
|
var value: Signed = arg;
|
||||||
|
var n: usize = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const unsigned: Unsigned = @bitCast(value);
|
||||||
|
const byte: u8 = @truncate(unsigned);
|
||||||
|
value >>= 6;
|
||||||
|
if (value == -1 or value == 0) {
|
||||||
|
try bw.writeByte(byte & 0x7F);
|
||||||
|
return n + 1;
|
||||||
|
} else {
|
||||||
|
value >>= 1;
|
||||||
|
try bw.writeByte(byte | 0x80);
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "formatValue max_depth" {
|
test "formatValue max_depth" {
|
||||||
@ -1590,15 +1782,15 @@ test "fixed output" {
|
|||||||
try bw.writeAll("world");
|
try bw.writeAll("world");
|
||||||
try testing.expect(std.mem.eql(u8, bw.getWritten(), "Helloworld"));
|
try testing.expect(std.mem.eql(u8, bw.getWritten(), "Helloworld"));
|
||||||
|
|
||||||
try testing.expectError(error.NoSpaceLeft, bw.writeAll("!"));
|
try testing.expectError(error.WriteStreamEnd, bw.writeAll("!"));
|
||||||
try testing.expect(std.mem.eql(u8, bw.getWritten(), "Helloworld"));
|
try testing.expect(std.mem.eql(u8, bw.getWritten(), "Helloworld"));
|
||||||
|
|
||||||
bw.reset();
|
bw.reset();
|
||||||
try testing.expect(bw.getWritten().len == 0);
|
try testing.expect(bw.getWritten().len == 0);
|
||||||
|
|
||||||
try testing.expectError(error.NoSpaceLeft, bw.writeAll("Hello world!"));
|
try testing.expectError(error.WriteStreamEnd, bw.writeAll("Hello world!"));
|
||||||
try testing.expect(std.mem.eql(u8, bw.getWritten(), "Hello worl"));
|
try testing.expect(std.mem.eql(u8, bw.getWritten(), "Hello worl"));
|
||||||
|
|
||||||
try bw.seekTo((try bw.getEndPos()) + 1);
|
try bw.seekTo((try bw.getEndPos()) + 1);
|
||||||
try testing.expectError(error.NoSpaceLeft, bw.writeAll("H"));
|
try testing.expectError(error.WriteStreamEnd, bw.writeAll("H"));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
//! A Reader that counts how many bytes has been read from it.
|
|
||||||
|
|
||||||
const std = @import("../std.zig");
|
|
||||||
const CountingReader = @This();
|
|
||||||
|
|
||||||
child_reader: std.io.Reader,
|
|
||||||
bytes_read: u64 = 0,
|
|
||||||
|
|
||||||
pub fn read(self: *@This(), buf: []u8) anyerror!usize {
|
|
||||||
const amt = try self.child_reader.read(buf);
|
|
||||||
self.bytes_read += amt;
|
|
||||||
return amt;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reader(self: *@This()) std.io.Reader {
|
|
||||||
return .{ .context = self };
|
|
||||||
}
|
|
||||||
|
|
||||||
test CountingReader {
|
|
||||||
const bytes = "yay" ** 20;
|
|
||||||
var fbs: std.io.BufferedReader = undefined;
|
|
||||||
fbs.initFixed(bytes);
|
|
||||||
var counting_stream: CountingReader = .{ .child_reader = fbs.reader() };
|
|
||||||
var stream = counting_stream.reader().unbuffered();
|
|
||||||
while (stream.readByte()) |_| {} else |err| {
|
|
||||||
try std.testing.expectError(error.EndOfStream, err);
|
|
||||||
}
|
|
||||||
try std.testing.expect(counting_stream.bytes_read == bytes.len);
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
//! TODO make this more like AllocatingWriter, managing the state of
|
|
||||||
//! BufferedWriter both as the output and the input, but with only
|
|
||||||
//! one buffer.
|
|
||||||
const std = @import("../std.zig");
|
|
||||||
const CountingWriter = @This();
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const native_endian = @import("builtin").target.cpu.arch.endian();
|
|
||||||
const Writer = std.io.Writer;
|
|
||||||
const testing = std.testing;
|
|
||||||
|
|
||||||
/// Underlying stream to passthrough bytes to.
|
|
||||||
child_writer: Writer,
|
|
||||||
bytes_written: u64 = 0,
|
|
||||||
|
|
||||||
pub fn writer(cw: *CountingWriter) Writer {
|
|
||||||
return .{
|
|
||||||
.context = cw,
|
|
||||||
.vtable = &.{
|
|
||||||
.writeSplat = passthru_writeSplat,
|
|
||||||
.writeFile = passthru_writeFile,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
|
|
||||||
const cw: *CountingWriter = @alignCast(@ptrCast(context));
|
|
||||||
const n = try cw.child_writer.writeSplat(data, splat);
|
|
||||||
cw.bytes_written += n;
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn passthru_writeFile(
|
|
||||||
context: *anyopaque,
|
|
||||||
file: std.fs.File,
|
|
||||||
offset: u64,
|
|
||||||
len: Writer.FileLen,
|
|
||||||
headers_and_trailers: []const []const u8,
|
|
||||||
headers_len: usize,
|
|
||||||
) anyerror!usize {
|
|
||||||
const cw: *CountingWriter = @alignCast(@ptrCast(context));
|
|
||||||
const n = try cw.child_writer.writeFile(file, offset, len, headers_and_trailers, headers_len);
|
|
||||||
cw.bytes_written += n;
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
test CountingWriter {
|
|
||||||
var cw: CountingWriter = .{ .child_writer = std.io.null_writer };
|
|
||||||
var bw = cw.writer().unbuffered();
|
|
||||||
const bytes = "yay";
|
|
||||||
try bw.writeAll(bytes);
|
|
||||||
try testing.expect(cw.bytes_written == bytes.len);
|
|
||||||
}
|
|
||||||
@ -19,8 +19,8 @@ pub const VTable = struct {
|
|||||||
///
|
///
|
||||||
/// If this is `null` it is equivalent to always returning
|
/// If this is `null` it is equivalent to always returning
|
||||||
/// `error.Unseekable`.
|
/// `error.Unseekable`.
|
||||||
posRead: ?*const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit, offset: u64) Result,
|
posRead: ?*const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit, offset: u64) RwResult,
|
||||||
posReadVec: ?*const fn (ctx: ?*anyopaque, data: []const []u8, offset: u64) VecResult,
|
posReadVec: ?*const fn (ctx: ?*anyopaque, data: []const []u8, offset: u64) Result,
|
||||||
|
|
||||||
/// Writes bytes from the internally tracked stream position to `bw`, or
|
/// Writes bytes from the internally tracked stream position to `bw`, or
|
||||||
/// returns `error.Unstreamable`, indicating `posRead` should be used
|
/// returns `error.Unstreamable`, indicating `posRead` should be used
|
||||||
@ -37,38 +37,34 @@ pub const VTable = struct {
|
|||||||
///
|
///
|
||||||
/// If this is `null` it is equivalent to always returning
|
/// If this is `null` it is equivalent to always returning
|
||||||
/// `error.Unstreamable`.
|
/// `error.Unstreamable`.
|
||||||
streamRead: ?*const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit) Result,
|
streamRead: ?*const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit) RwResult,
|
||||||
streamReadVec: ?*const fn (ctx: ?*anyopaque, data: []const []u8) VecResult,
|
streamReadVec: ?*const fn (ctx: ?*anyopaque, data: []const []u8) Result,
|
||||||
|
|
||||||
|
pub const eof: VTable = .{
|
||||||
|
.posRead = eof_posRead,
|
||||||
|
.posReadVec = eof_posReadVec,
|
||||||
|
.streamRead = eof_streamRead,
|
||||||
|
.streamReadVec = eof_streamReadVec,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Len = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(usize) - 1 } });
|
pub const Result = std.io.Writer.Result;
|
||||||
|
|
||||||
pub const VecResult = struct {
|
pub const RwResult = struct {
|
||||||
/// Even when a failure occurs, `Effect.written` may be nonzero, and
|
len: usize = 0,
|
||||||
/// `Effect.end` may be true.
|
read_err: anyerror!void = {},
|
||||||
failure: anyerror!void,
|
write_err: anyerror!void = {},
|
||||||
effect: VecEffect,
|
read_end: bool = false,
|
||||||
};
|
write_end: bool = false,
|
||||||
|
|
||||||
pub const Result = struct {
|
|
||||||
/// Even when a failure occurs, `Effect.written` may be nonzero, and
|
|
||||||
/// `Effect.end` may be true.
|
|
||||||
failure: anyerror!void,
|
|
||||||
write_effect: Effect,
|
|
||||||
read_effect: Effect,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Effect = packed struct(usize) {
|
|
||||||
/// Number of bytes that were read from the reader or written to the
|
|
||||||
/// writer.
|
|
||||||
len: Len,
|
|
||||||
/// Indicates end of stream.
|
|
||||||
end: bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Limit = enum(usize) {
|
pub const Limit = enum(usize) {
|
||||||
none = std.math.maxInt(usize),
|
none = std.math.maxInt(usize),
|
||||||
_,
|
_,
|
||||||
|
|
||||||
|
pub fn min(l: Limit, int: usize) usize {
|
||||||
|
return @min(int, @intFromEnum(l));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Returns total number of bytes written to `w`.
|
/// Returns total number of bytes written to `w`.
|
||||||
@ -133,25 +129,11 @@ pub fn streamReadAlloc(r: Reader, gpa: std.mem.Allocator, max_size: usize) anyer
|
|||||||
|
|
||||||
/// Reads the stream until the end, ignoring all the data.
|
/// Reads the stream until the end, ignoring all the data.
|
||||||
/// Returns the number of bytes discarded.
|
/// Returns the number of bytes discarded.
|
||||||
pub fn discardAll(r: Reader) anyerror!usize {
|
pub fn discardUntilEnd(r: Reader) anyerror!usize {
|
||||||
var bw = std.io.null_writer.unbuffered();
|
var bw = std.io.null_writer.unbuffered();
|
||||||
return streamReadAll(r, &bw);
|
return streamReadAll(r, &bw);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn buffered(r: Reader, buffer: []u8) std.io.BufferedReader {
|
|
||||||
return .{
|
|
||||||
.reader = r,
|
|
||||||
.buffered_writer = .{
|
|
||||||
.buffer = buffer,
|
|
||||||
.mode = .fixed,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unbuffered(r: Reader) std.io.BufferedReader {
|
|
||||||
return buffered(r, &.{});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn allocating(r: Reader, gpa: std.mem.Allocator) std.io.BufferedReader {
|
pub fn allocating(r: Reader, gpa: std.mem.Allocator) std.io.BufferedReader {
|
||||||
return .{
|
return .{
|
||||||
.reader = r,
|
.reader = r,
|
||||||
@ -189,3 +171,31 @@ test "when the backing reader provides one byte at a time" {
|
|||||||
defer std.testing.allocator.free(res);
|
defer std.testing.allocator.free(res);
|
||||||
try std.testing.expectEqualStrings(str, res);
|
try std.testing.expectEqualStrings(str, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn eof_posRead(ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit, offset: u64) RwResult {
|
||||||
|
_ = ctx;
|
||||||
|
_ = bw;
|
||||||
|
_ = limit;
|
||||||
|
_ = offset;
|
||||||
|
return .{ .end = true };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof_posReadVec(ctx: ?*anyopaque, data: []const []u8, offset: u64) Result {
|
||||||
|
_ = ctx;
|
||||||
|
_ = data;
|
||||||
|
_ = offset;
|
||||||
|
return .{ .end = true };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof_streamRead(ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit) RwResult {
|
||||||
|
_ = ctx;
|
||||||
|
_ = bw;
|
||||||
|
_ = limit;
|
||||||
|
return .{ .end = true };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof_streamReadVec(ctx: ?*anyopaque, data: []const []u8) Result {
|
||||||
|
_ = ctx;
|
||||||
|
_ = data;
|
||||||
|
return .{ .end = true };
|
||||||
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ const std = @import("../std.zig");
|
|||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Writer = @This();
|
const Writer = @This();
|
||||||
|
|
||||||
context: *anyopaque,
|
context: ?*anyopaque,
|
||||||
vtable: *const VTable,
|
vtable: *const VTable,
|
||||||
|
|
||||||
pub const VTable = struct {
|
pub const VTable = struct {
|
||||||
@ -17,7 +17,7 @@ pub const VTable = struct {
|
|||||||
/// Number of bytes returned may be zero, which does not mean
|
/// Number of bytes returned may be zero, which does not mean
|
||||||
/// end-of-stream. A subsequent call may return nonzero, or may signal end
|
/// end-of-stream. A subsequent call may return nonzero, or may signal end
|
||||||
/// of stream via an error.
|
/// of stream via an error.
|
||||||
writeSplat: *const fn (ctx: *anyopaque, data: []const []const u8, splat: usize) Result,
|
writeSplat: *const fn (ctx: ?*anyopaque, data: []const []const u8, splat: usize) Result,
|
||||||
|
|
||||||
/// Writes contents from an open file. `headers` are written first, then `len`
|
/// Writes contents from an open file. `headers` are written first, then `len`
|
||||||
/// bytes of `file` starting from `offset`, then `trailers`.
|
/// bytes of `file` starting from `offset`, then `trailers`.
|
||||||
@ -29,7 +29,7 @@ pub const VTable = struct {
|
|||||||
/// end-of-stream. A subsequent call may return nonzero, or may signal end
|
/// end-of-stream. A subsequent call may return nonzero, or may signal end
|
||||||
/// of stream via an error.
|
/// of stream via an error.
|
||||||
writeFile: *const fn (
|
writeFile: *const fn (
|
||||||
ctx: *anyopaque,
|
ctx: ?*anyopaque,
|
||||||
file: std.fs.File,
|
file: std.fs.File,
|
||||||
offset: Offset,
|
offset: Offset,
|
||||||
/// When zero, it means copy until the end of the file is reached.
|
/// When zero, it means copy until the end of the file is reached.
|
||||||
@ -39,25 +39,26 @@ pub const VTable = struct {
|
|||||||
headers_and_trailers: []const []const u8,
|
headers_and_trailers: []const []const u8,
|
||||||
headers_len: usize,
|
headers_len: usize,
|
||||||
) Result,
|
) Result,
|
||||||
};
|
|
||||||
|
|
||||||
pub const Len = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(usize) - 1 } });
|
pub const eof: VTable = .{
|
||||||
|
.writeSplat = eof_writeSplat,
|
||||||
|
.writeFile = eof_writeFile,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
pub const Result = struct {
|
pub const Result = struct {
|
||||||
/// Even when a failure occurs, `Effect.written` may be nonzero, and
|
/// Even when a failure occurs, `len` may be nonzero, and `end` may be
|
||||||
/// `Effect.end` may be true.
|
/// true.
|
||||||
failure: anyerror!void,
|
err: anyerror!void = {},
|
||||||
effect: Effect,
|
/// Number of bytes that were transferred. When an error occurs, ideally
|
||||||
};
|
/// this will be zero, but may not always be the case.
|
||||||
|
len: usize = 0,
|
||||||
pub const Effect = packed struct(usize) {
|
|
||||||
/// Number of bytes that were written to `writer`.
|
|
||||||
len: Len,
|
|
||||||
/// Indicates end of stream.
|
/// Indicates end of stream.
|
||||||
end: bool,
|
end: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Offset = enum(u64) {
|
pub const Offset = enum(u64) {
|
||||||
|
/// Indicates to read the file as a stream.
|
||||||
none = std.math.maxInt(u64),
|
none = std.math.maxInt(u64),
|
||||||
_,
|
_,
|
||||||
|
|
||||||
@ -66,6 +67,11 @@ pub const Offset = enum(u64) {
|
|||||||
assert(result != .none);
|
assert(result != .none);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toInt(o: Offset) ?u64 {
|
||||||
|
if (o == .none) return null;
|
||||||
|
return @intFromEnum(o);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FileLen = enum(u64) {
|
pub const FileLen = enum(u64) {
|
||||||
@ -84,11 +90,11 @@ pub const FileLen = enum(u64) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn writev(w: Writer, data: []const []const u8) anyerror!usize {
|
pub fn writev(w: Writer, data: []const []const u8) Result {
|
||||||
return w.vtable.writeSplat(w.context, data, 1);
|
return w.vtable.writeSplat(w.context, data, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writeSplat(w: Writer, data: []const []const u8, splat: usize) anyerror!usize {
|
pub fn writeSplat(w: Writer, data: []const []const u8, splat: usize) Result {
|
||||||
return w.vtable.writeSplat(w.context, data, splat);
|
return w.vtable.writeSplat(w.context, data, splat);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,25 +105,25 @@ pub fn writeFile(
|
|||||||
len: FileLen,
|
len: FileLen,
|
||||||
headers_and_trailers: []const []const u8,
|
headers_and_trailers: []const []const u8,
|
||||||
headers_len: usize,
|
headers_len: usize,
|
||||||
) anyerror!usize {
|
) Result {
|
||||||
return w.vtable.writeFile(w.context, file, offset, len, headers_and_trailers, headers_len);
|
return w.vtable.writeFile(w.context, file, offset, len, headers_and_trailers, headers_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unimplemented_writeFile(
|
pub fn unimplemented_writeFile(
|
||||||
context: *anyopaque,
|
context: ?*anyopaque,
|
||||||
file: std.fs.File,
|
file: std.fs.File,
|
||||||
offset: u64,
|
offset: u64,
|
||||||
len: FileLen,
|
len: FileLen,
|
||||||
headers_and_trailers: []const []const u8,
|
headers_and_trailers: []const []const u8,
|
||||||
headers_len: usize,
|
headers_len: usize,
|
||||||
) anyerror!usize {
|
) Result {
|
||||||
_ = context;
|
_ = context;
|
||||||
_ = file;
|
_ = file;
|
||||||
_ = offset;
|
_ = offset;
|
||||||
_ = len;
|
_ = len;
|
||||||
_ = headers_and_trailers;
|
_ = headers_and_trailers;
|
||||||
_ = headers_len;
|
_ = headers_len;
|
||||||
return error.Unimplemented;
|
return .{ .err = error.Unimplemented };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn buffered(w: Writer, buffer: []u8) std.io.BufferedWriter {
|
pub fn buffered(w: Writer, buffer: []u8) std.io.BufferedWriter {
|
||||||
@ -130,3 +136,74 @@ pub fn buffered(w: Writer, buffer: []u8) std.io.BufferedWriter {
|
|||||||
pub fn unbuffered(w: Writer) std.io.BufferedWriter {
|
pub fn unbuffered(w: Writer) std.io.BufferedWriter {
|
||||||
return buffered(w, &.{});
|
return buffered(w, &.{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A `Writer` that discards all data.
|
||||||
|
pub const @"null": Writer = .{
|
||||||
|
.context = undefined,
|
||||||
|
.vtable = &.{
|
||||||
|
.writeSplat = null_writeSplat,
|
||||||
|
.writeFile = null_writeFile,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn null_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Result {
|
||||||
|
_ = context;
|
||||||
|
const headers = data[0 .. data.len - 1];
|
||||||
|
const pattern = data[headers.len..];
|
||||||
|
var written: usize = pattern.len * splat;
|
||||||
|
for (headers) |bytes| written += bytes.len;
|
||||||
|
return .{ .len = written };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn null_writeFile(
|
||||||
|
context: ?*anyopaque,
|
||||||
|
file: std.fs.File,
|
||||||
|
offset: Offset,
|
||||||
|
len: FileLen,
|
||||||
|
headers_and_trailers: []const []const u8,
|
||||||
|
headers_len: usize,
|
||||||
|
) Result {
|
||||||
|
_ = context;
|
||||||
|
var n: usize = 0;
|
||||||
|
if (len == .entire_file) {
|
||||||
|
const headers = headers_and_trailers[0..headers_len];
|
||||||
|
for (headers) |bytes| n += bytes.len;
|
||||||
|
if (offset.toInt()) |off| {
|
||||||
|
const stat = file.stat() catch |err| return .{ .err = err, .len = n };
|
||||||
|
n += stat.size - off;
|
||||||
|
for (headers_and_trailers[headers_len..]) |bytes| n += bytes.len;
|
||||||
|
return .{ .len = n };
|
||||||
|
}
|
||||||
|
@panic("TODO stream from file until eof, counting");
|
||||||
|
}
|
||||||
|
for (headers_and_trailers) |bytes| n += bytes.len;
|
||||||
|
return .{ .len = len.int() + n };
|
||||||
|
}
|
||||||
|
|
||||||
|
test @"null" {
|
||||||
|
try @"null".writeAll("yay");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Result {
|
||||||
|
_ = context;
|
||||||
|
_ = data;
|
||||||
|
_ = splat;
|
||||||
|
return .{ .end = true };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof_writeFile(
|
||||||
|
context: ?*anyopaque,
|
||||||
|
file: std.fs.File,
|
||||||
|
offset: u64,
|
||||||
|
len: FileLen,
|
||||||
|
headers_and_trailers: []const []const u8,
|
||||||
|
headers_len: usize,
|
||||||
|
) Result {
|
||||||
|
_ = context;
|
||||||
|
_ = file;
|
||||||
|
_ = offset;
|
||||||
|
_ = len;
|
||||||
|
_ = headers_and_trailers;
|
||||||
|
_ = headers_len;
|
||||||
|
return .{ .end = true };
|
||||||
|
}
|
||||||
|
|||||||
@ -2,151 +2,6 @@ const builtin = @import("builtin");
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
/// Read a single unsigned LEB128 value from the given reader as type T,
|
|
||||||
/// or error.Overflow if the value cannot fit.
|
|
||||||
pub fn readUleb128(comptime T: type, reader: anytype) !T {
|
|
||||||
const U = if (@typeInfo(T).int.bits < 8) u8 else T;
|
|
||||||
const ShiftT = std.math.Log2Int(U);
|
|
||||||
|
|
||||||
const max_group = (@typeInfo(U).int.bits + 6) / 7;
|
|
||||||
|
|
||||||
var value: U = 0;
|
|
||||||
var group: ShiftT = 0;
|
|
||||||
|
|
||||||
while (group < max_group) : (group += 1) {
|
|
||||||
const byte = try reader.readByte();
|
|
||||||
|
|
||||||
const ov = @shlWithOverflow(@as(U, byte & 0x7f), group * 7);
|
|
||||||
if (ov[1] != 0) return error.Overflow;
|
|
||||||
|
|
||||||
value |= ov[0];
|
|
||||||
if (byte & 0x80 == 0) break;
|
|
||||||
} else {
|
|
||||||
return error.Overflow;
|
|
||||||
}
|
|
||||||
|
|
||||||
// only applies in the case that we extended to u8
|
|
||||||
if (U != T) {
|
|
||||||
if (value > std.math.maxInt(T)) return error.Overflow;
|
|
||||||
}
|
|
||||||
|
|
||||||
return @as(T, @truncate(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deprecated: use `readUleb128`
|
|
||||||
pub const readULEB128 = readUleb128;
|
|
||||||
|
|
||||||
/// Write a single unsigned integer as unsigned LEB128 to the given writer.
|
|
||||||
pub fn writeUleb128(writer: anytype, arg: anytype) !void {
|
|
||||||
const Arg = @TypeOf(arg);
|
|
||||||
const Int = switch (Arg) {
|
|
||||||
comptime_int => std.math.IntFittingRange(arg, arg),
|
|
||||||
else => Arg,
|
|
||||||
};
|
|
||||||
const Value = if (@typeInfo(Int).int.bits < 8) u8 else Int;
|
|
||||||
var value: Value = arg;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const byte: u8 = @truncate(value & 0x7f);
|
|
||||||
value >>= 7;
|
|
||||||
if (value == 0) {
|
|
||||||
try writer.writeByte(byte);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
try writer.writeByte(byte | 0x80);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deprecated: use `writeUleb128`
|
|
||||||
pub const writeULEB128 = writeUleb128;
|
|
||||||
|
|
||||||
/// Read a single signed LEB128 value from the given reader as type T,
|
|
||||||
/// or error.Overflow if the value cannot fit.
|
|
||||||
pub fn readIleb128(comptime T: type, reader: anytype) !T {
|
|
||||||
const S = if (@typeInfo(T).int.bits < 8) i8 else T;
|
|
||||||
const U = std.meta.Int(.unsigned, @typeInfo(S).int.bits);
|
|
||||||
const ShiftU = std.math.Log2Int(U);
|
|
||||||
|
|
||||||
const max_group = (@typeInfo(U).int.bits + 6) / 7;
|
|
||||||
|
|
||||||
var value = @as(U, 0);
|
|
||||||
var group = @as(ShiftU, 0);
|
|
||||||
|
|
||||||
while (group < max_group) : (group += 1) {
|
|
||||||
const byte = try reader.readByte();
|
|
||||||
|
|
||||||
const shift = group * 7;
|
|
||||||
const ov = @shlWithOverflow(@as(U, byte & 0x7f), shift);
|
|
||||||
if (ov[1] != 0) {
|
|
||||||
// Overflow is ok so long as the sign bit is set and this is the last byte
|
|
||||||
if (byte & 0x80 != 0) return error.Overflow;
|
|
||||||
if (@as(S, @bitCast(ov[0])) >= 0) return error.Overflow;
|
|
||||||
|
|
||||||
// and all the overflowed bits are 1
|
|
||||||
const remaining_shift = @as(u3, @intCast(@typeInfo(U).int.bits - @as(u16, shift)));
|
|
||||||
const remaining_bits = @as(i8, @bitCast(byte | 0x80)) >> remaining_shift;
|
|
||||||
if (remaining_bits != -1) return error.Overflow;
|
|
||||||
} else {
|
|
||||||
// If we don't overflow and this is the last byte and the number being decoded
|
|
||||||
// is negative, check that the remaining bits are 1
|
|
||||||
if ((byte & 0x80 == 0) and (@as(S, @bitCast(ov[0])) < 0)) {
|
|
||||||
const remaining_shift = @as(u3, @intCast(@typeInfo(U).int.bits - @as(u16, shift)));
|
|
||||||
const remaining_bits = @as(i8, @bitCast(byte | 0x80)) >> remaining_shift;
|
|
||||||
if (remaining_bits != -1) return error.Overflow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value |= ov[0];
|
|
||||||
if (byte & 0x80 == 0) {
|
|
||||||
const needs_sign_ext = group + 1 < max_group;
|
|
||||||
if (byte & 0x40 != 0 and needs_sign_ext) {
|
|
||||||
const ones = @as(S, -1);
|
|
||||||
value |= @as(U, @bitCast(ones)) << (shift + 7);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return error.Overflow;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = @as(S, @bitCast(value));
|
|
||||||
// Only applies if we extended to i8
|
|
||||||
if (S != T) {
|
|
||||||
if (result > std.math.maxInt(T) or result < std.math.minInt(T)) return error.Overflow;
|
|
||||||
}
|
|
||||||
|
|
||||||
return @as(T, @truncate(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deprecated: use `readIleb128`
|
|
||||||
pub const readILEB128 = readIleb128;
|
|
||||||
|
|
||||||
/// Write a single signed integer as signed LEB128 to the given writer.
|
|
||||||
pub fn writeIleb128(writer: anytype, arg: anytype) !void {
|
|
||||||
const Arg = @TypeOf(arg);
|
|
||||||
const Int = switch (Arg) {
|
|
||||||
comptime_int => std.math.IntFittingRange(-@abs(arg), @abs(arg)),
|
|
||||||
else => Arg,
|
|
||||||
};
|
|
||||||
const Signed = if (@typeInfo(Int).int.bits < 8) i8 else Int;
|
|
||||||
const Unsigned = std.meta.Int(.unsigned, @typeInfo(Signed).int.bits);
|
|
||||||
var value: Signed = arg;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const unsigned: Unsigned = @bitCast(value);
|
|
||||||
const byte: u8 = @truncate(unsigned);
|
|
||||||
value >>= 6;
|
|
||||||
if (value == -1 or value == 0) {
|
|
||||||
try writer.writeByte(byte & 0x7F);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
value >>= 1;
|
|
||||||
try writer.writeByte(byte | 0x80);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is an "advanced" function. It allows one to use a fixed amount of memory to store a
|
/// This is an "advanced" function. It allows one to use a fixed amount of memory to store a
|
||||||
/// ULEB128. This defeats the entire purpose of using this data encoding; it will no longer use
|
/// ULEB128. This defeats the entire purpose of using this data encoding; it will no longer use
|
||||||
/// fewer bytes to store smaller numbers. The advantage of using a fixed width is that it makes
|
/// fewer bytes to store smaller numbers. The advantage of using a fixed width is that it makes
|
||||||
@ -176,9 +31,6 @@ pub fn writeUnsignedExtended(slice: []u8, arg: anytype) void {
|
|||||||
slice[slice.len - 1] = @as(u7, @intCast(value));
|
slice[slice.len - 1] = @as(u7, @intCast(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deprecated: use `writeIleb128`
|
|
||||||
pub const writeILEB128 = writeIleb128;
|
|
||||||
|
|
||||||
test writeUnsignedFixed {
|
test writeUnsignedFixed {
|
||||||
{
|
{
|
||||||
var buf: [4]u8 = undefined;
|
var buf: [4]u8 = undefined;
|
||||||
@ -261,42 +113,45 @@ test writeSignedFixed {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tests
|
|
||||||
fn test_read_stream_ileb128(comptime T: type, encoded: []const u8) !T {
|
fn test_read_stream_ileb128(comptime T: type, encoded: []const u8) !T {
|
||||||
var reader = std.io.fixedBufferStream(encoded);
|
var br: std.io.BufferedReader = undefined;
|
||||||
return try readIleb128(T, reader.reader());
|
br.initFixed(encoded);
|
||||||
|
return br.takeIleb128(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_read_stream_uleb128(comptime T: type, encoded: []const u8) !T {
|
fn test_read_stream_uleb128(comptime T: type, encoded: []const u8) !T {
|
||||||
var reader = std.io.fixedBufferStream(encoded);
|
var br: std.io.BufferedReader = undefined;
|
||||||
return try readUleb128(T, reader.reader());
|
br.initFixed(encoded);
|
||||||
|
return br.takeUleb128(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_read_ileb128(comptime T: type, encoded: []const u8) !T {
|
fn test_read_ileb128(comptime T: type, encoded: []const u8) !T {
|
||||||
var reader = std.io.fixedBufferStream(encoded);
|
var br: std.io.BufferedReader = undefined;
|
||||||
const v1 = try readIleb128(T, reader.reader());
|
br.initFixed(encoded);
|
||||||
return v1;
|
return br.readIleb128(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_read_uleb128(comptime T: type, encoded: []const u8) !T {
|
fn test_read_uleb128(comptime T: type, encoded: []const u8) !T {
|
||||||
var reader = std.io.fixedBufferStream(encoded);
|
var br: std.io.BufferedReader = undefined;
|
||||||
const v1 = try readUleb128(T, reader.reader());
|
br.initFixed(encoded);
|
||||||
return v1;
|
return br.readUleb128(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void {
|
fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void {
|
||||||
var reader = std.io.fixedBufferStream(encoded);
|
var br: std.io.BufferedReader = undefined;
|
||||||
|
br.initFixed(encoded);
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < N) : (i += 1) {
|
while (i < N) : (i += 1) {
|
||||||
_ = try readIleb128(T, reader.reader());
|
_ = try br.readIleb128(T);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_read_uleb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void {
|
fn test_read_uleb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void {
|
||||||
var reader = std.io.fixedBufferStream(encoded);
|
var br: std.io.BufferedReader = undefined;
|
||||||
|
br.initFixed(encoded);
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < N) : (i += 1) {
|
while (i < N) : (i += 1) {
|
||||||
_ = try readUleb128(T, reader.reader());
|
_ = try br.readUleb128(T);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,8 +247,8 @@ fn test_write_leb128(value: anytype) !void {
|
|||||||
const signedness = @typeInfo(T).int.signedness;
|
const signedness = @typeInfo(T).int.signedness;
|
||||||
const t_signed = signedness == .signed;
|
const t_signed = signedness == .signed;
|
||||||
|
|
||||||
const writeStream = if (t_signed) writeIleb128 else writeUleb128;
|
const writeStream = if (t_signed) std.io.BufferedWriter.writeIleb128 else std.io.BufferedWriter.writeUleb128;
|
||||||
const readStream = if (t_signed) readIleb128 else readUleb128;
|
const readStream = if (t_signed) std.io.BufferedReader.readIleb128 else std.io.BufferedReader.readUleb128;
|
||||||
|
|
||||||
// decode to a larger bit size too, to ensure sign extension
|
// decode to a larger bit size too, to ensure sign extension
|
||||||
// is working as expected
|
// is working as expected
|
||||||
@ -412,23 +267,24 @@ fn test_write_leb128(value: anytype) !void {
|
|||||||
const max_groups = if (@typeInfo(T).int.bits == 0) 1 else (@typeInfo(T).int.bits + 6) / 7;
|
const max_groups = if (@typeInfo(T).int.bits == 0) 1 else (@typeInfo(T).int.bits + 6) / 7;
|
||||||
|
|
||||||
var buf: [max_groups]u8 = undefined;
|
var buf: [max_groups]u8 = undefined;
|
||||||
var fbs = std.io.fixedBufferStream(&buf);
|
var bw: std.io.BufferedWriter = undefined;
|
||||||
|
bw.initFixed(&buf);
|
||||||
|
|
||||||
// stream write
|
// stream write
|
||||||
try writeStream(fbs.writer(), value);
|
try testing.expect((try writeStream(&bw, value)) == bytes_needed);
|
||||||
const w1_pos = fbs.pos;
|
try testing.expect(bw.buffer.items.len == bytes_needed);
|
||||||
try testing.expect(w1_pos == bytes_needed);
|
|
||||||
|
|
||||||
// stream read
|
// stream read
|
||||||
fbs.pos = 0;
|
var br: std.io.BufferedReader = undefined;
|
||||||
const sr = try readStream(T, fbs.reader());
|
br.initFixed(&buf);
|
||||||
try testing.expect(fbs.pos == w1_pos);
|
const sr = try readStream(&br, T);
|
||||||
|
try testing.expect(br.seek == bytes_needed);
|
||||||
try testing.expect(sr == value);
|
try testing.expect(sr == value);
|
||||||
|
|
||||||
// bigger type stream read
|
// bigger type stream read
|
||||||
fbs.pos = 0;
|
bw.buffer.items.len = 0;
|
||||||
const bsr = try readStream(B, fbs.reader());
|
const bsr = try readStream(&bw, B);
|
||||||
try testing.expect(fbs.pos == w1_pos);
|
try testing.expect(bw.buffer.items.len == bytes_needed);
|
||||||
try testing.expect(bsr == value);
|
try testing.expect(bsr == value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -189,24 +189,22 @@ fn renderErrorMessageToWriter(
|
|||||||
indent: usize,
|
indent: usize,
|
||||||
) anyerror!void {
|
) anyerror!void {
|
||||||
const ttyconf = options.ttyconf;
|
const ttyconf = options.ttyconf;
|
||||||
var counting_writer: std.io.CountingWriter = .{ .child_writer = bw.writer() };
|
|
||||||
var counting_bw = counting_writer.writer().unbuffered();
|
|
||||||
const err_msg = eb.getErrorMessage(err_msg_index);
|
const err_msg = eb.getErrorMessage(err_msg_index);
|
||||||
|
// This is the length of the part before the error message:
|
||||||
|
// e.g. "file.zig:4:5: error: "
|
||||||
|
var prefix_len: usize = 0;
|
||||||
if (err_msg.src_loc != .none) {
|
if (err_msg.src_loc != .none) {
|
||||||
const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc));
|
const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc));
|
||||||
try counting_bw.splatByteAll(' ', indent);
|
prefix_len += try bw.splatByteAllCount(' ', indent);
|
||||||
try ttyconf.setColor(bw, .bold);
|
try ttyconf.setColor(bw, .bold);
|
||||||
try counting_bw.print("{s}:{d}:{d}: ", .{
|
prefix_len += try bw.printCount("{s}:{d}:{d}: ", .{
|
||||||
eb.nullTerminatedString(src.data.src_path),
|
eb.nullTerminatedString(src.data.src_path),
|
||||||
src.data.line + 1,
|
src.data.line + 1,
|
||||||
src.data.column + 1,
|
src.data.column + 1,
|
||||||
});
|
});
|
||||||
try ttyconf.setColor(bw, color);
|
try ttyconf.setColor(bw, color);
|
||||||
try counting_bw.writeAll(kind);
|
prefix_len += try bw.writeAllCount(kind);
|
||||||
try counting_bw.writeAll(": ");
|
prefix_len += try bw.writeAllCount(": ");
|
||||||
// This is the length of the part before the error message:
|
|
||||||
// e.g. "file.zig:4:5: error: "
|
|
||||||
const prefix_len: usize = @intCast(counting_writer.bytes_written);
|
|
||||||
try ttyconf.setColor(bw, .reset);
|
try ttyconf.setColor(bw, .reset);
|
||||||
try ttyconf.setColor(bw, .bold);
|
try ttyconf.setColor(bw, .bold);
|
||||||
if (err_msg.count == 1) {
|
if (err_msg.count == 1) {
|
||||||
|
|||||||
@ -103,13 +103,13 @@ pub const Zip64Options = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn writeZip(
|
pub fn writeZip(
|
||||||
writer: anytype,
|
writer: *std.io.BufferedWriter,
|
||||||
files: []const File,
|
files: []const File,
|
||||||
store: []FileStore,
|
store: []FileStore,
|
||||||
options: WriteZipOptions,
|
options: WriteZipOptions,
|
||||||
) !void {
|
) !void {
|
||||||
if (store.len < files.len) return error.FileStoreTooSmall;
|
if (store.len < files.len) return error.FileStoreTooSmall;
|
||||||
var zipper = initZipper(writer);
|
var zipper: Zipper = .init(writer);
|
||||||
for (files, 0..) |file, i| {
|
for (files, 0..) |file, i| {
|
||||||
store[i] = try zipper.writeFile(.{
|
store[i] = try zipper.writeFile(.{
|
||||||
.name = file.name,
|
.name = file.name,
|
||||||
@ -126,173 +126,172 @@ pub fn writeZip(
|
|||||||
try zipper.writeEndRecord(if (options.end) |e| e else .{});
|
try zipper.writeEndRecord(if (options.end) |e| e else .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initZipper(writer: anytype) Zipper(@TypeOf(writer)) {
|
|
||||||
return .{ .counting_writer = std.io.countingWriter(writer) };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provides methods to format and write the contents of a zip archive
|
/// Provides methods to format and write the contents of a zip archive
|
||||||
/// to the underlying Writer.
|
/// to the underlying Writer.
|
||||||
pub fn Zipper(comptime Writer: type) type {
|
pub const Zipper = struct {
|
||||||
return struct {
|
writer: *std.io.BufferedWriter,
|
||||||
counting_writer: std.io.CountingWriter(Writer),
|
bytes_written: u64,
|
||||||
central_count: u64 = 0,
|
central_count: u64 = 0,
|
||||||
first_central_offset: ?u64 = null,
|
first_central_offset: ?u64 = null,
|
||||||
last_central_limit: ?u64 = null,
|
last_central_limit: ?u64 = null,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn writeFile(
|
pub fn init(writer: *std.io.BufferedWriter) Zipper {
|
||||||
self: *Self,
|
return .{ .writer = writer, .bytes_written = 0 };
|
||||||
opt: struct {
|
}
|
||||||
name: []const u8,
|
|
||||||
content: []const u8,
|
|
||||||
compression: zip.CompressionMethod,
|
|
||||||
write_options: WriteZipOptions,
|
|
||||||
},
|
|
||||||
) !FileStore {
|
|
||||||
const writer = self.counting_writer.writer();
|
|
||||||
|
|
||||||
const file_offset: u64 = @intCast(self.counting_writer.bytes_written);
|
pub fn writeFile(
|
||||||
const crc32 = std.hash.Crc32.hash(opt.content);
|
self: *Self,
|
||||||
|
opt: struct {
|
||||||
|
name: []const u8,
|
||||||
|
content: []const u8,
|
||||||
|
compression: zip.CompressionMethod,
|
||||||
|
write_options: WriteZipOptions,
|
||||||
|
},
|
||||||
|
) !FileStore {
|
||||||
|
const writer = self.writer;
|
||||||
|
|
||||||
const header_options = opt.write_options.local_header;
|
const file_offset: u64 = @intCast(self.bytes_written);
|
||||||
{
|
const crc32 = std.hash.Crc32.hash(opt.content);
|
||||||
var compressed_size: u32 = 0;
|
|
||||||
var uncompressed_size: u32 = 0;
|
const header_options = opt.write_options.local_header;
|
||||||
var extra_len: u16 = 0;
|
{
|
||||||
if (header_options) |hdr_options| {
|
var compressed_size: u32 = 0;
|
||||||
compressed_size = if (hdr_options.compressed_size) |size| size else 0;
|
var uncompressed_size: u32 = 0;
|
||||||
uncompressed_size = if (hdr_options.uncompressed_size) |size| size else @intCast(opt.content.len);
|
var extra_len: u16 = 0;
|
||||||
extra_len = if (hdr_options.extra_len) |len| len else 0;
|
if (header_options) |hdr_options| {
|
||||||
}
|
compressed_size = if (hdr_options.compressed_size) |size| size else 0;
|
||||||
const hdr: zip.LocalFileHeader = .{
|
uncompressed_size = if (hdr_options.uncompressed_size) |size| size else @intCast(opt.content.len);
|
||||||
.signature = zip.local_file_header_sig,
|
extra_len = if (hdr_options.extra_len) |len| len else 0;
|
||||||
.version_needed_to_extract = 10,
|
|
||||||
.flags = .{ .encrypted = false, ._ = 0 },
|
|
||||||
.compression_method = opt.compression,
|
|
||||||
.last_modification_time = 0,
|
|
||||||
.last_modification_date = 0,
|
|
||||||
.crc32 = crc32,
|
|
||||||
.compressed_size = compressed_size,
|
|
||||||
.uncompressed_size = uncompressed_size,
|
|
||||||
.filename_len = @intCast(opt.name.len),
|
|
||||||
.extra_len = extra_len,
|
|
||||||
};
|
|
||||||
try writer.writeStructEndian(hdr, .little);
|
|
||||||
}
|
}
|
||||||
try writer.writeAll(opt.name);
|
const hdr: zip.LocalFileHeader = .{
|
||||||
|
.signature = zip.local_file_header_sig,
|
||||||
if (header_options) |hdr| {
|
.version_needed_to_extract = 10,
|
||||||
if (hdr.zip64) |options| {
|
|
||||||
try writer.writeInt(u16, 0x0001, .little);
|
|
||||||
const data_size = if (options.data_size) |size| size else 8;
|
|
||||||
try writer.writeInt(u16, data_size, .little);
|
|
||||||
try writer.writeInt(u64, 0, .little);
|
|
||||||
try writer.writeInt(u64, @intCast(opt.content.len), .little);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var compressed_size: u32 = undefined;
|
|
||||||
switch (opt.compression) {
|
|
||||||
.store => {
|
|
||||||
try writer.writeAll(opt.content);
|
|
||||||
compressed_size = @intCast(opt.content.len);
|
|
||||||
},
|
|
||||||
.deflate => {
|
|
||||||
const offset = self.counting_writer.bytes_written;
|
|
||||||
var fbs = std.io.fixedBufferStream(opt.content);
|
|
||||||
try std.compress.flate.deflate.compress(.raw, fbs.reader(), writer, .{});
|
|
||||||
std.debug.assert(fbs.pos == opt.content.len);
|
|
||||||
compressed_size = @intCast(self.counting_writer.bytes_written - offset);
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
return .{
|
|
||||||
.compression = opt.compression,
|
|
||||||
.file_offset = file_offset,
|
|
||||||
.crc32 = crc32,
|
|
||||||
.compressed_size = compressed_size,
|
|
||||||
.uncompressed_size = opt.content.len,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writeCentralRecord(
|
|
||||||
self: *Self,
|
|
||||||
store: FileStore,
|
|
||||||
opt: struct {
|
|
||||||
name: []const u8,
|
|
||||||
version_needed_to_extract: u16 = 10,
|
|
||||||
},
|
|
||||||
) !void {
|
|
||||||
if (self.first_central_offset == null) {
|
|
||||||
self.first_central_offset = self.counting_writer.bytes_written;
|
|
||||||
}
|
|
||||||
self.central_count += 1;
|
|
||||||
|
|
||||||
const hdr: zip.CentralDirectoryFileHeader = .{
|
|
||||||
.signature = zip.central_file_header_sig,
|
|
||||||
.version_made_by = 0,
|
|
||||||
.version_needed_to_extract = opt.version_needed_to_extract,
|
|
||||||
.flags = .{ .encrypted = false, ._ = 0 },
|
.flags = .{ .encrypted = false, ._ = 0 },
|
||||||
.compression_method = store.compression,
|
.compression_method = opt.compression,
|
||||||
.last_modification_time = 0,
|
.last_modification_time = 0,
|
||||||
.last_modification_date = 0,
|
.last_modification_date = 0,
|
||||||
.crc32 = store.crc32,
|
.crc32 = crc32,
|
||||||
.compressed_size = store.compressed_size,
|
.compressed_size = compressed_size,
|
||||||
.uncompressed_size = @intCast(store.uncompressed_size),
|
.uncompressed_size = uncompressed_size,
|
||||||
.filename_len = @intCast(opt.name.len),
|
.filename_len = @intCast(opt.name.len),
|
||||||
.extra_len = 0,
|
.extra_len = extra_len,
|
||||||
.comment_len = 0,
|
|
||||||
.disk_number = 0,
|
|
||||||
.internal_file_attributes = 0,
|
|
||||||
.external_file_attributes = 0,
|
|
||||||
.local_file_header_offset = @intCast(store.file_offset),
|
|
||||||
};
|
};
|
||||||
try self.counting_writer.writer().writeStructEndian(hdr, .little);
|
self.bytes_written += try writer.writeStructEndian(hdr, .little);
|
||||||
try self.counting_writer.writer().writeAll(opt.name);
|
|
||||||
self.last_central_limit = self.counting_writer.bytes_written;
|
|
||||||
}
|
}
|
||||||
|
self.bytes_written += try writer.writeAll(opt.name);
|
||||||
|
|
||||||
pub fn writeEndRecord(self: *Self, opt: EndRecordOptions) !void {
|
if (header_options) |hdr| {
|
||||||
const cd_offset = self.first_central_offset orelse 0;
|
if (hdr.zip64) |options| {
|
||||||
const cd_end = self.last_central_limit orelse 0;
|
self.bytes_written += try writer.writeInt(u16, 0x0001, .little);
|
||||||
|
const data_size = if (options.data_size) |size| size else 8;
|
||||||
if (opt.zip64) |zip64| {
|
self.bytes_written += try writer.writeInt(u16, data_size, .little);
|
||||||
const end64_off = cd_end;
|
self.bytes_written += try writer.writeInt(u64, 0, .little);
|
||||||
const fixed: zip.EndRecord64 = .{
|
self.bytes_written += try writer.writeInt(u64, @intCast(opt.content.len), .little);
|
||||||
.signature = zip.end_record64_sig,
|
|
||||||
.end_record_size = @sizeOf(zip.EndRecord64) - 12,
|
|
||||||
.version_made_by = 0,
|
|
||||||
.version_needed_to_extract = 45,
|
|
||||||
.disk_number = 0,
|
|
||||||
.central_directory_disk_number = 0,
|
|
||||||
.record_count_disk = @intCast(self.central_count),
|
|
||||||
.record_count_total = @intCast(self.central_count),
|
|
||||||
.central_directory_size = @intCast(cd_end - cd_offset),
|
|
||||||
.central_directory_offset = @intCast(cd_offset),
|
|
||||||
};
|
|
||||||
try self.counting_writer.writer().writeStructEndian(fixed, .little);
|
|
||||||
const locator: zip.EndLocator64 = .{
|
|
||||||
.signature = if (zip64.locator_sig) |s| s else zip.end_locator64_sig,
|
|
||||||
.zip64_disk_count = if (zip64.locator_zip64_disk_count) |c| c else 0,
|
|
||||||
.record_file_offset = if (zip64.locator_record_file_offset) |o| o else @intCast(end64_off),
|
|
||||||
.total_disk_count = if (zip64.locator_total_disk_count) |c| c else 1,
|
|
||||||
};
|
|
||||||
try self.counting_writer.writer().writeStructEndian(locator, .little);
|
|
||||||
}
|
}
|
||||||
const hdr: zip.EndRecord = .{
|
|
||||||
.signature = if (opt.sig) |s| s else zip.end_record_sig,
|
|
||||||
.disk_number = if (opt.disk_number) |n| n else 0,
|
|
||||||
.central_directory_disk_number = if (opt.central_directory_disk_number) |n| n else 0,
|
|
||||||
.record_count_disk = if (opt.record_count_disk) |c| c else @intCast(self.central_count),
|
|
||||||
.record_count_total = if (opt.record_count_total) |c| c else @intCast(self.central_count),
|
|
||||||
.central_directory_size = if (opt.central_directory_size) |s| s else @intCast(cd_end - cd_offset),
|
|
||||||
.central_directory_offset = if (opt.central_directory_offset) |o| o else @intCast(cd_offset),
|
|
||||||
.comment_len = if (opt.comment_len) |l| l else (if (opt.comment) |c| @as(u16, @intCast(c.len)) else 0),
|
|
||||||
};
|
|
||||||
try self.counting_writer.writer().writeStructEndian(hdr, .little);
|
|
||||||
if (opt.comment) |c|
|
|
||||||
try self.counting_writer.writer().writeAll(c);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
var compressed_size: u32 = undefined;
|
||||||
|
switch (opt.compression) {
|
||||||
|
.store => {
|
||||||
|
self.bytes_written += try writer.writeAll(opt.content);
|
||||||
|
compressed_size = @intCast(opt.content.len);
|
||||||
|
},
|
||||||
|
.deflate => {
|
||||||
|
const offset = self.bytes_written;
|
||||||
|
var fbs = std.io.fixedBufferStream(opt.content);
|
||||||
|
self.bytes_written += try std.compress.flate.deflate.compress(.raw, fbs.reader(), writer, .{});
|
||||||
|
std.debug.assert(fbs.pos == opt.content.len);
|
||||||
|
compressed_size = @intCast(self.bytes_written - offset);
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
return .{
|
||||||
|
.compression = opt.compression,
|
||||||
|
.file_offset = file_offset,
|
||||||
|
.crc32 = crc32,
|
||||||
|
.compressed_size = compressed_size,
|
||||||
|
.uncompressed_size = opt.content.len,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeCentralRecord(
|
||||||
|
self: *Self,
|
||||||
|
store: FileStore,
|
||||||
|
opt: struct {
|
||||||
|
name: []const u8,
|
||||||
|
version_needed_to_extract: u16 = 10,
|
||||||
|
},
|
||||||
|
) !void {
|
||||||
|
if (self.first_central_offset == null) {
|
||||||
|
self.first_central_offset = self.bytes_written;
|
||||||
|
}
|
||||||
|
self.central_count += 1;
|
||||||
|
|
||||||
|
const hdr: zip.CentralDirectoryFileHeader = .{
|
||||||
|
.signature = zip.central_file_header_sig,
|
||||||
|
.version_made_by = 0,
|
||||||
|
.version_needed_to_extract = opt.version_needed_to_extract,
|
||||||
|
.flags = .{ .encrypted = false, ._ = 0 },
|
||||||
|
.compression_method = store.compression,
|
||||||
|
.last_modification_time = 0,
|
||||||
|
.last_modification_date = 0,
|
||||||
|
.crc32 = store.crc32,
|
||||||
|
.compressed_size = store.compressed_size,
|
||||||
|
.uncompressed_size = @intCast(store.uncompressed_size),
|
||||||
|
.filename_len = @intCast(opt.name.len),
|
||||||
|
.extra_len = 0,
|
||||||
|
.comment_len = 0,
|
||||||
|
.disk_number = 0,
|
||||||
|
.internal_file_attributes = 0,
|
||||||
|
.external_file_attributes = 0,
|
||||||
|
.local_file_header_offset = @intCast(store.file_offset),
|
||||||
|
};
|
||||||
|
self.bytes_written += try self.writer.writeStructEndian(hdr, .little);
|
||||||
|
self.bytes_written += try self.writer.writeAll(opt.name);
|
||||||
|
self.last_central_limit = self.bytes_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeEndRecord(self: *Self, opt: EndRecordOptions) !void {
|
||||||
|
const cd_offset = self.first_central_offset orelse 0;
|
||||||
|
const cd_end = self.last_central_limit orelse 0;
|
||||||
|
|
||||||
|
if (opt.zip64) |zip64| {
|
||||||
|
const end64_off = cd_end;
|
||||||
|
const fixed: zip.EndRecord64 = .{
|
||||||
|
.signature = zip.end_record64_sig,
|
||||||
|
.end_record_size = @sizeOf(zip.EndRecord64) - 12,
|
||||||
|
.version_made_by = 0,
|
||||||
|
.version_needed_to_extract = 45,
|
||||||
|
.disk_number = 0,
|
||||||
|
.central_directory_disk_number = 0,
|
||||||
|
.record_count_disk = @intCast(self.central_count),
|
||||||
|
.record_count_total = @intCast(self.central_count),
|
||||||
|
.central_directory_size = @intCast(cd_end - cd_offset),
|
||||||
|
.central_directory_offset = @intCast(cd_offset),
|
||||||
|
};
|
||||||
|
self.bytes_written += try self.writer.writeStructEndian(fixed, .little);
|
||||||
|
const locator: zip.EndLocator64 = .{
|
||||||
|
.signature = if (zip64.locator_sig) |s| s else zip.end_locator64_sig,
|
||||||
|
.zip64_disk_count = if (zip64.locator_zip64_disk_count) |c| c else 0,
|
||||||
|
.record_file_offset = if (zip64.locator_record_file_offset) |o| o else @intCast(end64_off),
|
||||||
|
.total_disk_count = if (zip64.locator_total_disk_count) |c| c else 1,
|
||||||
|
};
|
||||||
|
self.bytes_written += try self.writer.writeStructEndian(locator, .little);
|
||||||
|
}
|
||||||
|
const hdr: zip.EndRecord = .{
|
||||||
|
.signature = if (opt.sig) |s| s else zip.end_record_sig,
|
||||||
|
.disk_number = if (opt.disk_number) |n| n else 0,
|
||||||
|
.central_directory_disk_number = if (opt.central_directory_disk_number) |n| n else 0,
|
||||||
|
.record_count_disk = if (opt.record_count_disk) |c| c else @intCast(self.central_count),
|
||||||
|
.record_count_total = if (opt.record_count_total) |c| c else @intCast(self.central_count),
|
||||||
|
.central_directory_size = if (opt.central_directory_size) |s| s else @intCast(cd_end - cd_offset),
|
||||||
|
.central_directory_offset = if (opt.central_directory_offset) |o| o else @intCast(cd_offset),
|
||||||
|
.comment_len = if (opt.comment_len) |l| l else (if (opt.comment) |c| @as(u16, @intCast(c.len)) else 0),
|
||||||
|
};
|
||||||
|
self.bytes_written += try self.writer.writeStructEndian(hdr, .little);
|
||||||
|
if (opt.comment) |c|
|
||||||
|
self.bytes_written += try self.writer.writeAll(c);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
177
src/Type.zig
177
src/Type.zig
@ -121,11 +121,10 @@ pub fn eql(a: Type, b: Type, zcu: *const Zcu) bool {
|
|||||||
return a.toIntern() == b.toIntern();
|
return a.toIntern() == b.toIntern();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(ty: Type, comptime unused_fmt_string: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
pub fn format(ty: Type, bw: *std.io.BufferedWriter, comptime f: []const u8) anyerror!usize {
|
||||||
_ = ty;
|
_ = ty;
|
||||||
_ = unused_fmt_string;
|
_ = f;
|
||||||
_ = options;
|
_ = bw;
|
||||||
_ = writer;
|
|
||||||
@compileError("do not format types directly; use either ty.fmtDebug() or ty.fmt()");
|
@compileError("do not format types directly; use either ty.fmtDebug() or ty.fmt()");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,15 +142,9 @@ const FormatContext = struct {
|
|||||||
pt: Zcu.PerThread,
|
pt: Zcu.PerThread,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn format2(
|
fn format2(ctx: FormatContext, bw: *std.io.BufferedWriter, comptime f: []const u8) anyerror!usize {
|
||||||
ctx: FormatContext,
|
comptime assert(f.len == 0);
|
||||||
comptime unused_format_string: []const u8,
|
return print(ctx.ty, bw, ctx.pt);
|
||||||
options: std.fmt.FormatOptions,
|
|
||||||
writer: anytype,
|
|
||||||
) !void {
|
|
||||||
comptime assert(unused_format_string.len == 0);
|
|
||||||
_ = options;
|
|
||||||
return print(ctx.ty, writer, ctx.pt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmtDebug(ty: Type) std.fmt.Formatter(dump) {
|
pub fn fmtDebug(ty: Type) std.fmt.Formatter(dump) {
|
||||||
@ -173,7 +166,7 @@ pub fn dump(
|
|||||||
|
|
||||||
/// Prints a name suitable for `@typeName`.
|
/// Prints a name suitable for `@typeName`.
|
||||||
/// TODO: take an `opt_sema` to pass to `fmtValue` when printing sentinels.
|
/// TODO: take an `opt_sema` to pass to `fmtValue` when printing sentinels.
|
||||||
pub fn print(ty: Type, writer: *std.io.BufferedWriter, pt: Zcu.PerThread) anyerror!void {
|
pub fn print(ty: Type, bw: *std.io.BufferedWriter, pt: Zcu.PerThread) anyerror!usize {
|
||||||
const zcu = pt.zcu;
|
const zcu = pt.zcu;
|
||||||
const ip = &zcu.intern_pool;
|
const ip = &zcu.intern_pool;
|
||||||
switch (ip.indexToKey(ty.toIntern())) {
|
switch (ip.indexToKey(ty.toIntern())) {
|
||||||
@ -183,22 +176,23 @@ pub fn print(ty: Type, writer: *std.io.BufferedWriter, pt: Zcu.PerThread) anyerr
|
|||||||
.signed => 'i',
|
.signed => 'i',
|
||||||
.unsigned => 'u',
|
.unsigned => 'u',
|
||||||
};
|
};
|
||||||
return writer.print("{c}{d}", .{ sign_char, int_type.bits });
|
return bw.print("{c}{d}", .{ sign_char, int_type.bits });
|
||||||
},
|
},
|
||||||
.ptr_type => {
|
.ptr_type => {
|
||||||
|
var n: usize = 0;
|
||||||
const info = ty.ptrInfo(zcu);
|
const info = ty.ptrInfo(zcu);
|
||||||
|
|
||||||
if (info.sentinel != .none) switch (info.flags.size) {
|
if (info.sentinel != .none) switch (info.flags.size) {
|
||||||
.one, .c => unreachable,
|
.one, .c => unreachable,
|
||||||
.many => try writer.print("[*:{}]", .{Value.fromInterned(info.sentinel).fmtValue(pt)}),
|
.many => n += try bw.print("[*:{}]", .{Value.fromInterned(info.sentinel).fmtValue(pt)}),
|
||||||
.slice => try writer.print("[:{}]", .{Value.fromInterned(info.sentinel).fmtValue(pt)}),
|
.slice => n += try bw.print("[:{}]", .{Value.fromInterned(info.sentinel).fmtValue(pt)}),
|
||||||
} else switch (info.flags.size) {
|
} else switch (info.flags.size) {
|
||||||
.one => try writer.writeAll("*"),
|
.one => n += try bw.writeAll("*"),
|
||||||
.many => try writer.writeAll("[*]"),
|
.many => n += try bw.writeAll("[*]"),
|
||||||
.c => try writer.writeAll("[*c]"),
|
.c => n += try bw.writeAll("[*c]"),
|
||||||
.slice => try writer.writeAll("[]"),
|
.slice => n += try bw.writeAll("[]"),
|
||||||
}
|
}
|
||||||
if (info.flags.is_allowzero and info.flags.size != .c) try writer.writeAll("allowzero ");
|
if (info.flags.is_allowzero and info.flags.size != .c) n += try bw.writeAll("allowzero ");
|
||||||
if (info.flags.alignment != .none or
|
if (info.flags.alignment != .none or
|
||||||
info.packed_offset.host_size != 0 or
|
info.packed_offset.host_size != 0 or
|
||||||
info.flags.vector_index != .none)
|
info.flags.vector_index != .none)
|
||||||
@ -207,76 +201,83 @@ pub fn print(ty: Type, writer: *std.io.BufferedWriter, pt: Zcu.PerThread) anyerr
|
|||||||
info.flags.alignment
|
info.flags.alignment
|
||||||
else
|
else
|
||||||
Type.fromInterned(info.child).abiAlignment(pt.zcu);
|
Type.fromInterned(info.child).abiAlignment(pt.zcu);
|
||||||
try writer.print("align({d}", .{alignment.toByteUnits() orelse 0});
|
n += try bw.print("align({d}", .{alignment.toByteUnits() orelse 0});
|
||||||
|
|
||||||
if (info.packed_offset.bit_offset != 0 or info.packed_offset.host_size != 0) {
|
if (info.packed_offset.bit_offset != 0 or info.packed_offset.host_size != 0) {
|
||||||
try writer.print(":{d}:{d}", .{
|
n += try bw.print(":{d}:{d}", .{
|
||||||
info.packed_offset.bit_offset, info.packed_offset.host_size,
|
info.packed_offset.bit_offset, info.packed_offset.host_size,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (info.flags.vector_index == .runtime) {
|
if (info.flags.vector_index == .runtime) {
|
||||||
try writer.writeAll(":?");
|
n += try bw.writeAll(":?");
|
||||||
} else if (info.flags.vector_index != .none) {
|
} else if (info.flags.vector_index != .none) {
|
||||||
try writer.print(":{d}", .{@intFromEnum(info.flags.vector_index)});
|
n += try bw.print(":{d}", .{@intFromEnum(info.flags.vector_index)});
|
||||||
}
|
}
|
||||||
try writer.writeAll(") ");
|
n += try bw.writeAll(") ");
|
||||||
}
|
}
|
||||||
if (info.flags.address_space != .generic) {
|
if (info.flags.address_space != .generic) {
|
||||||
try writer.print("addrspace(.{s}) ", .{@tagName(info.flags.address_space)});
|
n += try bw.print("addrspace(.{s}) ", .{@tagName(info.flags.address_space)});
|
||||||
}
|
}
|
||||||
if (info.flags.is_const) try writer.writeAll("const ");
|
if (info.flags.is_const) n += try bw.writeAll("const ");
|
||||||
if (info.flags.is_volatile) try writer.writeAll("volatile ");
|
if (info.flags.is_volatile) n += try bw.writeAll("volatile ");
|
||||||
|
|
||||||
try print(Type.fromInterned(info.child), writer, pt);
|
n += try print(Type.fromInterned(info.child), bw, pt);
|
||||||
return;
|
return n;
|
||||||
},
|
},
|
||||||
.array_type => |array_type| {
|
.array_type => |array_type| {
|
||||||
|
var n: usize = 0;
|
||||||
if (array_type.sentinel == .none) {
|
if (array_type.sentinel == .none) {
|
||||||
try writer.print("[{d}]", .{array_type.len});
|
n += try bw.print("[{d}]", .{array_type.len});
|
||||||
try print(Type.fromInterned(array_type.child), writer, pt);
|
n += try print(Type.fromInterned(array_type.child), bw, pt);
|
||||||
} else {
|
} else {
|
||||||
try writer.print("[{d}:{}]", .{
|
n += try bw.print("[{d}:{}]", .{
|
||||||
array_type.len,
|
array_type.len,
|
||||||
Value.fromInterned(array_type.sentinel).fmtValue(pt),
|
Value.fromInterned(array_type.sentinel).fmtValue(pt),
|
||||||
});
|
});
|
||||||
try print(Type.fromInterned(array_type.child), writer, pt);
|
n += try print(Type.fromInterned(array_type.child), bw, pt);
|
||||||
}
|
}
|
||||||
return;
|
return n;
|
||||||
},
|
},
|
||||||
.vector_type => |vector_type| {
|
.vector_type => |vector_type| {
|
||||||
try writer.print("@Vector({d}, ", .{vector_type.len});
|
var n: usize = 0;
|
||||||
try print(Type.fromInterned(vector_type.child), writer, pt);
|
n += try bw.print("@Vector({d}, ", .{vector_type.len});
|
||||||
try writer.writeAll(")");
|
n += try print(Type.fromInterned(vector_type.child), bw, pt);
|
||||||
return;
|
n += try bw.writeAll(")");
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
.opt_type => |child| {
|
.opt_type => |child| {
|
||||||
try writer.writeByte('?');
|
var n: usize = 0;
|
||||||
return print(Type.fromInterned(child), writer, pt);
|
n += try bw.writeByte('?');
|
||||||
|
n += try print(Type.fromInterned(child), bw, pt);
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
.error_union_type => |error_union_type| {
|
.error_union_type => |error_union_type| {
|
||||||
try print(Type.fromInterned(error_union_type.error_set_type), writer, pt);
|
var n: usize = 0;
|
||||||
try writer.writeByte('!');
|
n += try print(Type.fromInterned(error_union_type.error_set_type), bw, pt);
|
||||||
|
n += try bw.writeByte('!');
|
||||||
if (error_union_type.payload_type == .generic_poison_type) {
|
if (error_union_type.payload_type == .generic_poison_type) {
|
||||||
try writer.writeAll("anytype");
|
n += try bw.writeAll("anytype");
|
||||||
} else {
|
} else {
|
||||||
try print(Type.fromInterned(error_union_type.payload_type), writer, pt);
|
n += try print(Type.fromInterned(error_union_type.payload_type), bw, pt);
|
||||||
}
|
}
|
||||||
return;
|
return n;
|
||||||
},
|
},
|
||||||
.inferred_error_set_type => |func_index| {
|
.inferred_error_set_type => |func_index| {
|
||||||
const func_nav = ip.getNav(zcu.funcInfo(func_index).owner_nav);
|
const func_nav = ip.getNav(zcu.funcInfo(func_index).owner_nav);
|
||||||
try writer.print("@typeInfo(@typeInfo(@TypeOf({})).@\"fn\".return_type.?).error_union.error_set", .{
|
return bw.print("@typeInfo(@typeInfo(@TypeOf({})).@\"fn\".return_type.?).error_union.error_set", .{
|
||||||
func_nav.fqn.fmt(ip),
|
func_nav.fqn.fmt(ip),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
.error_set_type => |error_set_type| {
|
.error_set_type => |error_set_type| {
|
||||||
|
var n: usize = 0;
|
||||||
const names = error_set_type.names;
|
const names = error_set_type.names;
|
||||||
try writer.writeAll("error{");
|
n += try bw.writeAll("error{");
|
||||||
for (names.get(ip), 0..) |name, i| {
|
for (names.get(ip), 0..) |name, i| {
|
||||||
if (i != 0) try writer.writeByte(',');
|
if (i != 0) n += try bw.writeByte(',');
|
||||||
try writer.print("{}", .{name.fmt(ip)});
|
n += try bw.print("{}", .{name.fmt(ip)});
|
||||||
}
|
}
|
||||||
try writer.writeAll("}");
|
n += try bw.writeAll("}");
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
.simple_type => |s| switch (s) {
|
.simple_type => |s| switch (s) {
|
||||||
.f16,
|
.f16,
|
||||||
@ -305,97 +306,103 @@ pub fn print(ty: Type, writer: *std.io.BufferedWriter, pt: Zcu.PerThread) anyerr
|
|||||||
.comptime_float,
|
.comptime_float,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
.adhoc_inferred_error_set,
|
.adhoc_inferred_error_set,
|
||||||
=> return writer.writeAll(@tagName(s)),
|
=> return bw.writeAll(@tagName(s)),
|
||||||
|
|
||||||
.null,
|
.null,
|
||||||
.undefined,
|
.undefined,
|
||||||
=> try writer.print("@TypeOf({s})", .{@tagName(s)}),
|
=> return bw.print("@TypeOf({s})", .{@tagName(s)}),
|
||||||
|
|
||||||
.enum_literal => try writer.writeAll("@Type(.enum_literal)"),
|
.enum_literal => return bw.writeAll("@Type(.enum_literal)"),
|
||||||
|
|
||||||
.generic_poison => unreachable,
|
.generic_poison => unreachable,
|
||||||
},
|
},
|
||||||
.struct_type => {
|
.struct_type => {
|
||||||
const name = ip.loadStructType(ty.toIntern()).name;
|
const name = ip.loadStructType(ty.toIntern()).name;
|
||||||
try writer.print("{}", .{name.fmt(ip)});
|
return bw.print("{}", .{name.fmt(ip)});
|
||||||
},
|
},
|
||||||
.tuple_type => |tuple| {
|
.tuple_type => |tuple| {
|
||||||
if (tuple.types.len == 0) {
|
if (tuple.types.len == 0) {
|
||||||
return writer.writeAll("@TypeOf(.{})");
|
return bw.writeAll("@TypeOf(.{})");
|
||||||
}
|
}
|
||||||
try writer.writeAll("struct {");
|
var n: usize = 0;
|
||||||
|
n += try bw.writeAll("struct {");
|
||||||
for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty, val, i| {
|
for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty, val, i| {
|
||||||
try writer.writeAll(if (i == 0) " " else ", ");
|
n += try bw.writeAll(if (i == 0) " " else ", ");
|
||||||
if (val != .none) try writer.writeAll("comptime ");
|
if (val != .none) n += try bw.writeAll("comptime ");
|
||||||
try print(Type.fromInterned(field_ty), writer, pt);
|
n += try print(Type.fromInterned(field_ty), bw, pt);
|
||||||
if (val != .none) try writer.print(" = {}", .{Value.fromInterned(val).fmtValue(pt)});
|
if (val != .none) n += try bw.print(" = {}", .{Value.fromInterned(val).fmtValue(pt)});
|
||||||
}
|
}
|
||||||
try writer.writeAll(" }");
|
n += try bw.writeAll(" }");
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
|
|
||||||
.union_type => {
|
.union_type => {
|
||||||
const name = ip.loadUnionType(ty.toIntern()).name;
|
const name = ip.loadUnionType(ty.toIntern()).name;
|
||||||
try writer.print("{}", .{name.fmt(ip)});
|
return bw.print("{}", .{name.fmt(ip)});
|
||||||
},
|
},
|
||||||
.opaque_type => {
|
.opaque_type => {
|
||||||
const name = ip.loadOpaqueType(ty.toIntern()).name;
|
const name = ip.loadOpaqueType(ty.toIntern()).name;
|
||||||
try writer.print("{}", .{name.fmt(ip)});
|
return bw.print("{}", .{name.fmt(ip)});
|
||||||
},
|
},
|
||||||
.enum_type => {
|
.enum_type => {
|
||||||
const name = ip.loadEnumType(ty.toIntern()).name;
|
const name = ip.loadEnumType(ty.toIntern()).name;
|
||||||
try writer.print("{}", .{name.fmt(ip)});
|
return bw.print("{}", .{name.fmt(ip)});
|
||||||
},
|
},
|
||||||
.func_type => |fn_info| {
|
.func_type => |fn_info| {
|
||||||
|
var n: usize = 0;
|
||||||
if (fn_info.is_noinline) {
|
if (fn_info.is_noinline) {
|
||||||
try writer.writeAll("noinline ");
|
n += try bw.writeAll("noinline ");
|
||||||
}
|
}
|
||||||
try writer.writeAll("fn (");
|
n += try bw.writeAll("fn (");
|
||||||
const param_types = fn_info.param_types.get(&zcu.intern_pool);
|
const param_types = fn_info.param_types.get(&zcu.intern_pool);
|
||||||
for (param_types, 0..) |param_ty, i| {
|
for (param_types, 0..) |param_ty, i| {
|
||||||
if (i != 0) try writer.writeAll(", ");
|
if (i != 0) n += try bw.writeAll(", ");
|
||||||
if (std.math.cast(u5, i)) |index| {
|
if (std.math.cast(u5, i)) |index| {
|
||||||
if (fn_info.paramIsComptime(index)) {
|
if (fn_info.paramIsComptime(index)) {
|
||||||
try writer.writeAll("comptime ");
|
n += try bw.writeAll("comptime ");
|
||||||
}
|
}
|
||||||
if (fn_info.paramIsNoalias(index)) {
|
if (fn_info.paramIsNoalias(index)) {
|
||||||
try writer.writeAll("noalias ");
|
n += try bw.writeAll("noalias ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (param_ty == .generic_poison_type) {
|
if (param_ty == .generic_poison_type) {
|
||||||
try writer.writeAll("anytype");
|
n += try bw.writeAll("anytype");
|
||||||
} else {
|
} else {
|
||||||
try print(Type.fromInterned(param_ty), writer, pt);
|
n += try print(Type.fromInterned(param_ty), bw, pt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fn_info.is_var_args) {
|
if (fn_info.is_var_args) {
|
||||||
if (param_types.len != 0) {
|
if (param_types.len != 0) {
|
||||||
try writer.writeAll(", ");
|
n += try bw.writeAll(", ");
|
||||||
}
|
}
|
||||||
try writer.writeAll("...");
|
n += try bw.writeAll("...");
|
||||||
}
|
}
|
||||||
try writer.writeAll(") ");
|
n += try bw.writeAll(") ");
|
||||||
if (fn_info.cc != .auto) print_cc: {
|
if (fn_info.cc != .auto) print_cc: {
|
||||||
if (zcu.getTarget().cCallingConvention()) |ccc| {
|
if (zcu.getTarget().cCallingConvention()) |ccc| {
|
||||||
if (fn_info.cc.eql(ccc)) {
|
if (fn_info.cc.eql(ccc)) {
|
||||||
try writer.writeAll("callconv(.c) ");
|
n += try bw.writeAll("callconv(.c) ");
|
||||||
break :print_cc;
|
break :print_cc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch (fn_info.cc) {
|
switch (fn_info.cc) {
|
||||||
.auto, .@"async", .naked, .@"inline" => try writer.print("callconv(.{}) ", .{std.zig.fmtId(@tagName(fn_info.cc))}),
|
.auto, .@"async", .naked, .@"inline" => n += try bw.print("callconv(.{}) ", .{std.zig.fmtId(@tagName(fn_info.cc))}),
|
||||||
else => try writer.print("callconv({any}) ", .{fn_info.cc}),
|
else => n += try bw.print("callconv({any}) ", .{fn_info.cc}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fn_info.return_type == .generic_poison_type) {
|
if (fn_info.return_type == .generic_poison_type) {
|
||||||
try writer.writeAll("anytype");
|
n += try bw.writeAll("anytype");
|
||||||
} else {
|
} else {
|
||||||
try print(Type.fromInterned(fn_info.return_type), writer, pt);
|
n += try print(Type.fromInterned(fn_info.return_type), bw, pt);
|
||||||
}
|
}
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
.anyframe_type => |child| {
|
.anyframe_type => |child| {
|
||||||
if (child == .none) return writer.writeAll("anyframe");
|
if (child == .none) return bw.writeAll("anyframe");
|
||||||
try writer.writeAll("anyframe->");
|
var n: usize = 0;
|
||||||
return print(Type.fromInterned(child), writer, pt);
|
n += try bw.writeAll("anyframe->");
|
||||||
|
n += print(Type.fromInterned(child), bw, pt);
|
||||||
|
return n;
|
||||||
},
|
},
|
||||||
|
|
||||||
// values, not types
|
// values, not types
|
||||||
|
|||||||
@ -1270,7 +1270,7 @@ pub const DeclGen = struct {
|
|||||||
}
|
}
|
||||||
const ai = ty.arrayInfo(zcu);
|
const ai = ty.arrayInfo(zcu);
|
||||||
if (ai.elem_type.eql(.u8, zcu)) {
|
if (ai.elem_type.eql(.u8, zcu)) {
|
||||||
var literal = stringLiteral(writer, ty.arrayLenIncludingSentinel(zcu));
|
var literal: StringLiteral = .init(writer, ty.arrayLenIncludingSentinel(zcu));
|
||||||
try literal.start();
|
try literal.start();
|
||||||
var index: usize = 0;
|
var index: usize = 0;
|
||||||
while (index < ai.len) : (index += 1) {
|
while (index < ai.len) : (index += 1) {
|
||||||
@ -1829,7 +1829,7 @@ pub const DeclGen = struct {
|
|||||||
const ai = ty.arrayInfo(zcu);
|
const ai = ty.arrayInfo(zcu);
|
||||||
if (ai.elem_type.eql(.u8, zcu)) {
|
if (ai.elem_type.eql(.u8, zcu)) {
|
||||||
const c_len = ty.arrayLenIncludingSentinel(zcu);
|
const c_len = ty.arrayLenIncludingSentinel(zcu);
|
||||||
var literal = stringLiteral(writer, c_len);
|
var literal: StringLiteral = .init(writer, c_len);
|
||||||
try literal.start();
|
try literal.start();
|
||||||
var index: u64 = 0;
|
var index: u64 = 0;
|
||||||
while (index < c_len) : (index += 1)
|
while (index < c_len) : (index += 1)
|
||||||
@ -8111,7 +8111,12 @@ fn compareOperatorC(operator: std.math.CompareOperator) []const u8 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn StringLiteral(comptime WriterType: type) type {
|
const StringLiteral = struct {
|
||||||
|
len: usize,
|
||||||
|
cur_len: usize,
|
||||||
|
bytes_written: usize,
|
||||||
|
writer: *std.io.BufferedWriter,
|
||||||
|
|
||||||
// MSVC throws C2078 if an array of size 65536 or greater is initialized with a string literal,
|
// MSVC throws C2078 if an array of size 65536 or greater is initialized with a string literal,
|
||||||
// regardless of the length of the string literal initializing it. Array initializer syntax is
|
// regardless of the length of the string literal initializing it. Array initializer syntax is
|
||||||
// used instead.
|
// used instead.
|
||||||
@ -8123,81 +8128,68 @@ fn StringLiteral(comptime WriterType: type) type {
|
|||||||
const max_char_len = 4;
|
const max_char_len = 4;
|
||||||
const max_literal_len = @min(16380 - max_char_len, 4095);
|
const max_literal_len = @min(16380 - max_char_len, 4095);
|
||||||
|
|
||||||
return struct {
|
fn init(writer: *std.io.BufferedWriter, len: usize) StringLiteral {
|
||||||
len: u64,
|
return .{
|
||||||
cur_len: u64 = 0,
|
.cur_len = 0,
|
||||||
counting_writer: std.io.CountingWriter(WriterType),
|
.len = len,
|
||||||
|
.writer = writer,
|
||||||
|
.bytes_written = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub const Error = WriterType.Error;
|
pub fn start(self: *StringLiteral) anyerror!void {
|
||||||
|
const writer = self.writer;
|
||||||
const Self = @This();
|
if (self.len <= max_string_initializer_len) {
|
||||||
|
self.bytes_written += try writer.writeByteCount('\"');
|
||||||
pub fn start(self: *Self) Error!void {
|
} else {
|
||||||
const writer = self.counting_writer.writer();
|
self.bytes_written += try writer.writeByteCount('{');
|
||||||
if (self.len <= max_string_initializer_len) {
|
|
||||||
try writer.writeByte('\"');
|
|
||||||
} else {
|
|
||||||
try writer.writeByte('{');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn end(self: *Self) Error!void {
|
pub fn end(self: *StringLiteral) anyerror!void {
|
||||||
const writer = self.counting_writer.writer();
|
const writer = self.writer;
|
||||||
if (self.len <= max_string_initializer_len) {
|
if (self.len <= max_string_initializer_len) {
|
||||||
try writer.writeByte('\"');
|
self.bytes_written += try writer.writeByteCount('\"');
|
||||||
} else {
|
} else {
|
||||||
try writer.writeByte('}');
|
self.bytes_written += try writer.writeByteCount('}');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn writeStringLiteralChar(writer: anytype, c: u8) !void {
|
fn writeStringLiteralChar(writer: *std.io.BufferedWriter, c: u8) anyerror!usize {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
7 => try writer.writeAll("\\a"),
|
7 => return writer.writeAllCount("\\a"),
|
||||||
8 => try writer.writeAll("\\b"),
|
8 => return writer.writeAllCount("\\b"),
|
||||||
'\t' => try writer.writeAll("\\t"),
|
'\t' => return writer.writeAllCount("\\t"),
|
||||||
'\n' => try writer.writeAll("\\n"),
|
'\n' => return writer.writeAllCount("\\n"),
|
||||||
11 => try writer.writeAll("\\v"),
|
11 => return writer.writeAllCount("\\v"),
|
||||||
12 => try writer.writeAll("\\f"),
|
12 => return writer.writeAllCount("\\f"),
|
||||||
'\r' => try writer.writeAll("\\r"),
|
'\r' => return writer.writeAllCount("\\r"),
|
||||||
'"', '\'', '?', '\\' => try writer.print("\\{c}", .{c}),
|
'"', '\'', '?', '\\' => return writer.printCount("\\{c}", .{c}),
|
||||||
else => switch (c) {
|
else => switch (c) {
|
||||||
' '...'~' => try writer.writeByte(c),
|
' '...'~' => return writer.writeByteCount(c),
|
||||||
else => try writer.print("\\{o:0>3}", .{c}),
|
else => return writer.printCount("\\{o:0>3}", .{c}),
|
||||||
},
|
},
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn writeChar(self: *Self, c: u8) Error!void {
|
pub fn writeChar(self: *StringLiteral, c: u8) anyerror!void {
|
||||||
const writer = self.counting_writer.writer();
|
const writer = self.writer;
|
||||||
if (self.len <= max_string_initializer_len) {
|
if (self.len <= max_string_initializer_len) {
|
||||||
if (self.cur_len == 0 and self.counting_writer.bytes_written > 1)
|
if (self.cur_len == 0 and self.bytes_written > 1)
|
||||||
try writer.writeAll("\"\"");
|
self.bytes_written += try writer.writeAllCount("\"\"");
|
||||||
|
|
||||||
const len = self.counting_writer.bytes_written;
|
const char_length = try writeStringLiteralChar(writer, c);
|
||||||
try writeStringLiteralChar(writer, c);
|
self.bytes_written += char_length;
|
||||||
|
assert(char_length <= max_char_len);
|
||||||
|
self.cur_len += char_length;
|
||||||
|
|
||||||
const char_length = self.counting_writer.bytes_written - len;
|
if (self.cur_len >= max_literal_len) self.cur_len = 0;
|
||||||
assert(char_length <= max_char_len);
|
} else {
|
||||||
self.cur_len += char_length;
|
if (self.bytes_written > 1) self.bytes_written += try writer.writeByteCount(',');
|
||||||
|
self.bytes_written += try writer.printCount("'\\x{x}'", .{c});
|
||||||
if (self.cur_len >= max_literal_len) self.cur_len = 0;
|
|
||||||
} else {
|
|
||||||
if (self.counting_writer.bytes_written > 1) try writer.writeByte(',');
|
|
||||||
try writer.print("'\\x{x}'", .{c});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
fn stringLiteral(
|
|
||||||
child_stream: anytype,
|
|
||||||
len: u64,
|
|
||||||
) StringLiteral(@TypeOf(child_stream)) {
|
|
||||||
return .{
|
|
||||||
.len = len,
|
|
||||||
.counting_writer = std.io.countingWriter(child_stream),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const FormatStringContext = struct { str: []const u8, sentinel: ?u8 };
|
const FormatStringContext = struct { str: []const u8, sentinel: ?u8 };
|
||||||
fn formatStringLiteral(
|
fn formatStringLiteral(
|
||||||
@ -8208,7 +8200,7 @@ fn formatStringLiteral(
|
|||||||
) @TypeOf(writer).Error!void {
|
) @TypeOf(writer).Error!void {
|
||||||
if (fmt.len != 1 or fmt[0] != 's') @compileError("Invalid fmt: " ++ fmt);
|
if (fmt.len != 1 or fmt[0] != 's') @compileError("Invalid fmt: " ++ fmt);
|
||||||
|
|
||||||
var literal = stringLiteral(writer, data.str.len + @intFromBool(data.sentinel != null));
|
var literal: StringLiteral = .init(writer, data.str.len + @intFromBool(data.sentinel != null));
|
||||||
try literal.start();
|
try literal.start();
|
||||||
for (data.str) |c| try literal.writeChar(c);
|
for (data.str) |c| try literal.writeChar(c);
|
||||||
if (data.sentinel) |sentinel| if (sentinel != 0) try literal.writeChar(sentinel);
|
if (data.sentinel) |sentinel| if (sentinel != 0) try literal.writeChar(sentinel);
|
||||||
|
|||||||
@ -1768,34 +1768,36 @@ pub const WipNav = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ExprLocCounter = struct {
|
const ExprLocCounter = struct {
|
||||||
const Stream = std.io.CountingWriter(std.io.NullWriter);
|
stream: *std.io.BufferedWriter,
|
||||||
stream: Stream,
|
|
||||||
section_offset_bytes: u32,
|
section_offset_bytes: u32,
|
||||||
address_size: AddressSize,
|
address_size: AddressSize,
|
||||||
fn init(dwarf: *Dwarf) ExprLocCounter {
|
counter: usize,
|
||||||
|
fn init(dwarf: *Dwarf, stream: *std.io.BufferedWriter) ExprLocCounter {
|
||||||
return .{
|
return .{
|
||||||
.stream = std.io.countingWriter(std.io.null_writer),
|
.stream = stream,
|
||||||
.section_offset_bytes = dwarf.sectionOffsetBytes(),
|
.section_offset_bytes = dwarf.sectionOffsetBytes(),
|
||||||
.address_size = dwarf.address_size,
|
.address_size = dwarf.address_size,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn writer(counter: *ExprLocCounter) Stream.Writer {
|
fn writer(counter: *ExprLocCounter) *std.io.BufferedWriter {
|
||||||
return counter.stream.writer();
|
return counter.stream;
|
||||||
}
|
}
|
||||||
fn endian(_: ExprLocCounter) std.builtin.Endian {
|
fn endian(_: ExprLocCounter) std.builtin.Endian {
|
||||||
return @import("builtin").cpu.arch.endian();
|
return @import("builtin").cpu.arch.endian();
|
||||||
}
|
}
|
||||||
fn addrSym(counter: *ExprLocCounter, _: u32) error{}!void {
|
fn addrSym(counter: *ExprLocCounter, _: u32) error{}!void {
|
||||||
counter.stream.bytes_written += @intFromEnum(counter.address_size);
|
counter.count += @intFromEnum(counter.address_size);
|
||||||
}
|
}
|
||||||
fn infoEntry(counter: *ExprLocCounter, _: Unit.Index, _: Entry.Index) error{}!void {
|
fn infoEntry(counter: *ExprLocCounter, _: Unit.Index, _: Entry.Index) error{}!void {
|
||||||
counter.stream.bytes_written += counter.section_offset_bytes;
|
counter.count += counter.section_offset_bytes;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn infoExprLoc(wip_nav: *WipNav, loc: Loc) UpdateError!void {
|
fn infoExprLoc(wip_nav: *WipNav, loc: Loc) UpdateError!void {
|
||||||
var counter: ExprLocCounter = .init(wip_nav.dwarf);
|
var buffer: [std.atomic.cache_line]u8 = undefined;
|
||||||
try loc.write(&counter);
|
var counter_bw = std.io.Writer.null.buffered(&buffer);
|
||||||
|
var counter: ExprLocCounter = .init(wip_nav.dwarf, &counter_bw);
|
||||||
|
counter.count += try loc.write(&counter);
|
||||||
|
|
||||||
const adapter: struct {
|
const adapter: struct {
|
||||||
wip_nav: *WipNav,
|
wip_nav: *WipNav,
|
||||||
@ -1812,8 +1814,8 @@ pub const WipNav = struct {
|
|||||||
try ctx.wip_nav.infoSectionOffset(.debug_info, unit, entry, 0);
|
try ctx.wip_nav.infoSectionOffset(.debug_info, unit, entry, 0);
|
||||||
}
|
}
|
||||||
} = .{ .wip_nav = wip_nav };
|
} = .{ .wip_nav = wip_nav };
|
||||||
try uleb128(adapter.writer(), counter.stream.bytes_written);
|
try uleb128(adapter.writer(), counter.count);
|
||||||
try loc.write(adapter);
|
_ = try loc.write(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infoAddrSym(wip_nav: *WipNav, sym_index: u32, sym_off: u64) UpdateError!void {
|
fn infoAddrSym(wip_nav: *WipNav, sym_index: u32, sym_off: u64) UpdateError!void {
|
||||||
@ -1826,8 +1828,10 @@ pub const WipNav = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn frameExprLoc(wip_nav: *WipNav, loc: Loc) UpdateError!void {
|
fn frameExprLoc(wip_nav: *WipNav, loc: Loc) UpdateError!void {
|
||||||
var counter: ExprLocCounter = .init(wip_nav.dwarf);
|
var buffer: [std.atomic.cache_line]u8 = undefined;
|
||||||
try loc.write(&counter);
|
var counter_bw = std.io.Writer.null.buffered(&buffer);
|
||||||
|
var counter: ExprLocCounter = .init(wip_nav.dwarf, &counter_bw);
|
||||||
|
counter.count += try loc.write(&counter);
|
||||||
|
|
||||||
const adapter: struct {
|
const adapter: struct {
|
||||||
wip_nav: *WipNav,
|
wip_nav: *WipNav,
|
||||||
@ -1844,8 +1848,8 @@ pub const WipNav = struct {
|
|||||||
try ctx.wip_nav.sectionOffset(.debug_frame, .debug_info, unit, entry, 0);
|
try ctx.wip_nav.sectionOffset(.debug_frame, .debug_info, unit, entry, 0);
|
||||||
}
|
}
|
||||||
} = .{ .wip_nav = wip_nav };
|
} = .{ .wip_nav = wip_nav };
|
||||||
try uleb128(adapter.writer(), counter.stream.bytes_written);
|
try uleb128(adapter.writer(), counter.count);
|
||||||
try loc.write(adapter);
|
_ = try loc.write(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frameAddrSym(wip_nav: *WipNav, sym_index: u32, sym_off: u64) UpdateError!void {
|
fn frameAddrSym(wip_nav: *WipNav, sym_index: u32, sym_off: u64) UpdateError!void {
|
||||||
@ -6015,15 +6019,21 @@ fn sectionOffsetBytes(dwarf: *Dwarf) u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn uleb128Bytes(value: anytype) u32 {
|
fn uleb128Bytes(value: anytype) u32 {
|
||||||
var cw = std.io.countingWriter(std.io.null_writer);
|
var buffer: [std.atomic.cache_line]u8 = undefined;
|
||||||
try uleb128(cw.writer(), value);
|
var bw: std.io.BufferedWriter = .{
|
||||||
return @intCast(cw.bytes_written);
|
.unbuffered_writer = .null,
|
||||||
|
.buffer = .initBuffer(&buffer),
|
||||||
|
};
|
||||||
|
return try std.leb.writeUleb128Count(&bw, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sleb128Bytes(value: anytype) u32 {
|
fn sleb128Bytes(value: anytype) u32 {
|
||||||
var cw = std.io.countingWriter(std.io.null_writer);
|
var buffer: [std.atomic.cache_line]u8 = undefined;
|
||||||
try sleb128(cw.writer(), value);
|
var bw: std.io.BufferedWriter = .{
|
||||||
return @intCast(cw.bytes_written);
|
.unbuffered_writer = .null,
|
||||||
|
.buffer = .initBuffer(&buffer),
|
||||||
|
};
|
||||||
|
return try std.leb.writeIleb128Count(&bw, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// overrides `-fno-incremental` for testing incremental debug info until `-fincremental` is functional
|
/// overrides `-fno-incremental` for testing incremental debug info until `-fincremental` is functional
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user