zig/lib/std/io/bit_writer.zig
mlugg f26dda2117 all: migrate code to new cast builtin syntax
Most of this migration was performed automatically with `zig fmt`. There
were a few exceptions which I had to manually fix:

* `@alignCast` and `@addrSpaceCast` cannot be automatically rewritten
* `@truncate`'s fixup is incorrect for vectors
* Test cases are not formatted, and their error locations change
2023-06-24 16:56:39 -07:00

197 lines
7.4 KiB
Zig

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;
/// 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,
pub const Error = WriterType.Error;
pub const Writer = io.Writer(*Self, Error, write);
const Self = @This();
const u8_bit_count = @bitSizeOf(u8);
const u4_bit_count = @bitSizeOf(u4);
pub fn init(forward_writer: WriterType) Self {
return Self{
.forward_writer = forward_writer,
.bit_buffer = 0,
.bit_count = 0,
};
}
/// 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;
const U = @TypeOf(value);
comptime assert(trait.isUnsignedInt(U));
//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);
const buf_value = @as(Buf, @intCast(value));
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);
}