I made my own bitReader to better fit my exact use case:
/// A bit reader with an API loosely similar to std.io.bitReader, but with some customizations
/// to our use case:
///
/// - only supports little endian
/// - skip bits by using read with type void.
/// - readBitsNoEof(u16, 16) -> u16
/// - readBitsNoEof(u16, 32) -> u16 (ignores higher bits from bitstream)
/// - readBitsNoEof(u16, 8) -> u16 (higher bits are zero filled)
/// - readBitsNoEof(boo1, 1) -> bool
/// - readBitsNoEof(bool, 0) -> bool (false)
/// - readBitsNoEof(bool, 2) -> bool (ignores higher bits from bitsream)
/// - readBitsNoEof(void, 2) -> void (skips 2 bits in bitstream)
pub fn LossyBitReader(comptime Reader: type) type {
return struct {
reader: Reader,
current_byte: u8 = 0,
pos_in_byte: u3 = 0,
pub fn readBitsNoEof(self: *@This(), comptime T: type, num: u16) !T {
var rval: @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(T) } }) = 0;
var used_bits: u16 = 0;
var bits_remaining = num;
while (bits_remaining > 0) : ({
bits_remaining -= 1;
self.pos_in_byte +%= 1;
}) {
if (self.pos_in_byte == 0) {
self.current_byte = try self.reader.readByte();
}
if (used_bits < @bitSizeOf(T)) {
const bit_value: u1 = @intCast((self.current_byte >> self.pos_in_byte) & 0b00000001);
const mask = @as(@TypeOf(rval), @intCast(1)) << @intCast(used_bits);
switch (bit_value) {
0 => rval = rval & ~mask,
1 => rval = rval | mask,
}
used_bits += 1;
}
}
return switch (T) {
void => void{},
else => return @bitCast(rval),
};
}
pub fn reset(self: *@This()) void {
self.pos_in_byte = 0;
self.current_byte = 0; // not strictly necessary?
}
};
}
pub fn lossyBitReader(reader: anytype) LossyBitReader(@TypeOf(reader)) {
return .{ .reader = reader };
}
test lossyBitReader {
const t: []const u8 = &.{1};
var fbs = std.io.fixedBufferStream(t);
const reader = fbs.reader();
var bit_reader = lossyBitReader(reader);
try std.testing.expectEqual(true, try bit_reader.readBitsNoEof(bool, 1));
}
test "lossyBitReader 0 bytes" {
const t: []const u8 = &.{};
var fbs = std.io.fixedBufferStream(t);
const reader = fbs.reader();
var bit_reader = lossyBitReader(reader);
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 0));
try std.testing.expectEqual(0, bit_reader.pos_in_byte);
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0, try bit_reader.readBitsNoEof(u4, 0));
try std.testing.expectEqual(0, bit_reader.pos_in_byte);
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0, try bit_reader.readBitsNoEof(u22, 0));
try std.testing.expectEqual(0, bit_reader.pos_in_byte);
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0, try bit_reader.readBitsNoEof(u0, 0));
try std.testing.expectEqual(0, bit_reader.pos_in_byte);
fbs.reset();
bit_reader.reset();
try std.testing.expectError(error.EndOfStream, bit_reader.readBitsNoEof(u0, 1));
fbs.reset();
bit_reader.reset();
try std.testing.expectError(error.EndOfStream, bit_reader.readBitsNoEof(u0, 400));
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0, try bit_reader.readBitsNoEof(u1, 0));
try std.testing.expectEqual(0, bit_reader.pos_in_byte);
fbs.reset();
bit_reader.reset();
}
test "lossyBitReader 1 byte" {
const t: []const u8 = &.{0b00001101};
var fbs = std.io.fixedBufferStream(t);
const reader = fbs.reader();
var bit_reader = lossyBitReader(reader);
try std.testing.expectEqual(0, bit_reader.pos_in_byte);
try std.testing.expectEqual(true, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(true, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(true, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectError(error.EndOfStream, bit_reader.readBitsNoEof(bool, 1));
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0b00001101, try bit_reader.readBitsNoEof(u8, 8));
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0b1, try bit_reader.readBitsNoEof(u4, 1));
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0b01, try bit_reader.readBitsNoEof(u4, 2));
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0b101, try bit_reader.readBitsNoEof(u4, 3));
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0b1101, try bit_reader.readBitsNoEof(u4, 4));
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0b01101, try bit_reader.readBitsNoEof(u4, 5));
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0b1101, try bit_reader.readBitsNoEof(u4, 8));
fbs.reset();
bit_reader.reset();
try std.testing.expectError(error.EndOfStream, bit_reader.readBitsNoEof(u4, 9));
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0b00000000_00001101, try bit_reader.readBitsNoEof(u16, 8));
fbs.reset();
bit_reader.reset();
}
test "lossyBitReader 2 bytes" {
const t: []const u8 = &.{ 0b00001101, 0b00100110 };
var fbs = std.io.fixedBufferStream(t);
const reader = fbs.reader();
var bit_reader = lossyBitReader(reader);
try std.testing.expectEqual(true, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(true, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(true, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(true, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(true, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(true, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectEqual(false, try bit_reader.readBitsNoEof(bool, 1));
try std.testing.expectError(error.EndOfStream, bit_reader.readBitsNoEof(bool, 1));
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0b0100110_00001101, try bit_reader.readBitsNoEof(u15, 16));
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0b00100110_00001101, try bit_reader.readBitsNoEof(u16, 16));
fbs.reset();
bit_reader.reset();
try std.testing.expectEqual(0b00100110_00001101, try bit_reader.readBitsNoEof(u17, 16));
fbs.reset();
bit_reader.reset();
}