|
const std = @import("../std.zig"); const io = std.io; const testing = std.testing; const assert = std.debug.assert; const trait = std.meta.trait; const meta = std.meta; const math = std.math; |
BitWriter()Write the specified number of bits to the stream from the least significant bits of the specified unsigned int value. Bits will only be written to the stream when there are enough to fill a byte. |
/// Creates a stream which allows for writing bit fields to another stream pub fn BitWriter(comptime endian: std.builtin.Endian, comptime WriterType: type) type { return struct { forward_writer: WriterType, bit_buffer: u8, bit_count: u4, |
ErrorFlush any remaining bits to the stream. |
pub const Error = WriterType.Error; |
Writer |
pub const Writer = io.Writer(*Self, Error, write); |
init() |
const Self = @This(); const u8_bit_count = @bitSizeOf(u8); const u4_bit_count = @bitSizeOf(u4); |
writeBits() |
pub fn init(forward_writer: WriterType) Self { return Self{ .forward_writer = forward_writer, .bit_buffer = 0, .bit_count = 0, }; } |
flushBits() |
/// Write the specified number of bits to the stream from the least significant bits of /// the specified unsigned int value. Bits will only be written to the stream when there /// are enough to fill a byte. pub fn writeBits(self: *Self, value: anytype, bits: usize) Error!void { if (bits == 0) return; |
write() |
const U = @TypeOf(value); comptime assert(trait.isUnsignedInt(U)); |
writer() |
//by extending the buffer to a minimum of u8 we can cover a number of edge cases // related to shifting and casting. const u_bit_count = @bitSizeOf(U); const buf_bit_count = bc: { assert(u_bit_count >= bits); break :bc if (u_bit_count <= u8_bit_count) u8_bit_count else u_bit_count; }; const Buf = std.meta.Int(.unsigned, buf_bit_count); const BufShift = math.Log2Int(Buf); |
bitWriter() |
const buf_value = @as(Buf, @intCast(value)); |
Test:api coverage |
const high_byte_shift = @as(BufShift, @intCast(buf_bit_count - u8_bit_count)); var in_buffer = switch (endian) { .Big => buf_value << @as(BufShift, @intCast(buf_bit_count - bits)), .Little => buf_value, }; var in_bits = bits; if (self.bit_count > 0) { const bits_remaining = u8_bit_count - self.bit_count; const n = @as(u3, @intCast(if (bits_remaining > bits) bits else bits_remaining)); switch (endian) { .Big => { const shift = @as(BufShift, @intCast(high_byte_shift + self.bit_count)); const v = @as(u8, @intCast(in_buffer >> shift)); self.bit_buffer |= v; in_buffer <<= n; }, .Little => { const v = @as(u8, @truncate(in_buffer)) << @as(u3, @intCast(self.bit_count)); self.bit_buffer |= v; in_buffer >>= n; }, } self.bit_count += n; in_bits -= n; //if we didn't fill the buffer, it's because bits < bits_remaining; if (self.bit_count != u8_bit_count) return; try self.forward_writer.writeByte(self.bit_buffer); self.bit_buffer = 0; self.bit_count = 0; } //at this point we know bit_buffer is empty //copy bytes until we can't fill one anymore, then leave the rest in bit_buffer while (in_bits >= u8_bit_count) { switch (endian) { .Big => { const v = @as(u8, @intCast(in_buffer >> high_byte_shift)); try self.forward_writer.writeByte(v); in_buffer <<= @as(u3, @intCast(u8_bit_count - 1)); in_buffer <<= 1; }, .Little => { const v = @as(u8, @truncate(in_buffer)); try self.forward_writer.writeByte(v); in_buffer >>= @as(u3, @intCast(u8_bit_count - 1)); in_buffer >>= 1; }, } in_bits -= u8_bit_count; } if (in_bits > 0) { self.bit_count = @as(u4, @intCast(in_bits)); self.bit_buffer = switch (endian) { .Big => @as(u8, @truncate(in_buffer >> high_byte_shift)), .Little => @as(u8, @truncate(in_buffer)), }; } } /// Flush any remaining bits to the stream. pub fn flushBits(self: *Self) Error!void { if (self.bit_count == 0) return; try self.forward_writer.writeByte(self.bit_buffer); self.bit_buffer = 0; self.bit_count = 0; } pub fn write(self: *Self, buffer: []const u8) Error!usize { // TODO: I'm not sure this is a good idea, maybe flushBits should be forced if (self.bit_count > 0) { for (buffer) |b| try self.writeBits(b, u8_bit_count); return buffer.len; } return self.forward_writer.write(buffer); } pub fn writer(self: *Self) Writer { return .{ .context = self }; } }; } pub fn bitWriter( comptime endian: std.builtin.Endian, underlying_stream: anytype, ) BitWriter(endian, @TypeOf(underlying_stream)) { return BitWriter(endian, @TypeOf(underlying_stream)).init(underlying_stream); } test "api coverage" { var mem_be = [_]u8{0} ** 2; var mem_le = [_]u8{0} ** 2; var mem_out_be = io.fixedBufferStream(&mem_be); var bit_stream_be = bitWriter(.Big, mem_out_be.writer()); try bit_stream_be.writeBits(@as(u2, 1), 1); try bit_stream_be.writeBits(@as(u5, 2), 2); try bit_stream_be.writeBits(@as(u128, 3), 3); try bit_stream_be.writeBits(@as(u8, 4), 4); try bit_stream_be.writeBits(@as(u9, 5), 5); try bit_stream_be.writeBits(@as(u1, 1), 1); try testing.expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001011); mem_out_be.pos = 0; try bit_stream_be.writeBits(@as(u15, 0b110011010000101), 15); try bit_stream_be.flushBits(); try testing.expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001010); mem_out_be.pos = 0; try bit_stream_be.writeBits(@as(u32, 0b110011010000101), 16); try testing.expect(mem_be[0] == 0b01100110 and mem_be[1] == 0b10000101); try bit_stream_be.writeBits(@as(u0, 0), 0); var mem_out_le = io.fixedBufferStream(&mem_le); var bit_stream_le = bitWriter(.Little, mem_out_le.writer()); try bit_stream_le.writeBits(@as(u2, 1), 1); try bit_stream_le.writeBits(@as(u5, 2), 2); try bit_stream_le.writeBits(@as(u128, 3), 3); try bit_stream_le.writeBits(@as(u8, 4), 4); try bit_stream_le.writeBits(@as(u9, 5), 5); try bit_stream_le.writeBits(@as(u1, 1), 1); try testing.expect(mem_le[0] == 0b00011101 and mem_le[1] == 0b10010101); mem_out_le.pos = 0; try bit_stream_le.writeBits(@as(u15, 0b110011010000101), 15); try bit_stream_le.flushBits(); try testing.expect(mem_le[0] == 0b10000101 and mem_le[1] == 0b01100110); mem_out_le.pos = 0; try bit_stream_le.writeBits(@as(u32, 0b1100110100001011), 16); try testing.expect(mem_le[0] == 0b00001011 and mem_le[1] == 0b11001101); try bit_stream_le.writeBits(@as(u0, 0), 0); } |
Generated by zstd-live on 2025-08-12 12:37:59 UTC. |