std.io.BitReader, skipping bits

When I am using std.io.BitReader, I am wishing for the following:

  1. Skipping bits.
  2. Reading a larger integers from a smaller set of bits. For example, reading a u32 from 16 bits, and having the 16 bits just go to the least significant bits of the u32. Ensuring the bit count to read can be run-time known.

This does not work:

// skip 5 bits
_ = bit_reader.readBitsNoEof(u0, 5) catch unreachable;
const my_u32 = bit_reader.readBitsNoEof(u32, 16) catch unreachable;
thread 520453 panic: integer cast truncated bits
/home/jeff/zig/0.14.0/files/lib/std/io/bit_reader.zig:96:53: 0x1772888 in readBitsTuple__anon_32319 (gatorcat)
                        const pos = @as(U, byte) << @intCast(out_count);
                                                    ^
/home/jeff/zig/0.14.0/files/lib/std/io/bit_reader.zig:53:54: 0x1772ec1 in readBitsNoEof__anon_32302 (gatorcat)
            const b, const c = try self.readBitsTuple(T, num);
                                                     ^
/home/jeff/repos/gatorcat/src/cli/run.zig:405:61: 0x17742d5 in publishInputs (gatorcat)
                        else => _ = bit_reader.readBitsNoEof(u0, entry.bits) catch unreachable,

I think you should use at least u5 to skip 5 bits:

_ = bit_reader.readBitsNoEof(u5, 5) catch unreachable;

for now I have just put a large integer to allow skipping up to 256 bits.

// TODO: this panics on larger than 256
_ = bit_reader.readBitsNoEof(u256, some_runtime_number_of_bits) catch unreachable;

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();
}