If performance is not an issue, then you can use the proper BitVector
methods and things will be a lot easier. I still think it’s best to keep the state (the number of bits consumed) in the IO
object.
There were several problems with my previous post.
Here’s one I actually tested:
mutable struct BitIOStream <: IO
io::IO
last
offset
end
BitIOStream(io::IO) = BitIOStream(io, 0, 0)
bit(byte, ix, ::Val{true}) = byte & (0x80 >> ix) != 0
bit(byte, ix, ::Val{false}) = byte & (1 << ix) != 0
function readbitvector(bio::BitIOStream, nbits; ltor=true)
i = bio.offset # bit-index in `byte`
byte = bio.offset == 0 ? read(bio.io, UInt8) : bio.last
j = 1 # index in `r`
r = BitVector(undef, nbits)
while true
r[j] = bit(byte, i, Val(ltor))
i += 1
j == nbits && break
j += 1
if i == 8
byte = read(bio.io, UInt8)
i = 0
end
end
bio.offset = i & 7
bio.last = byte
return r
end
Your example, somewhat modified:
bio = BitIOStream(IOBuffer("hello world"))
println(Char(read(bio.io,1)[1]))
println(Char(read(bio.io,1)[1]))
println(readbitvector(bio,4))
println(readbitvector(bio,4))
println(readbitvector(bio,4))
println(readbitvector(bio,4))
println(Char(read(bio.io,1)[1]))
now outputs
h
e
Bool[0, 1, 1, 0]
Bool[1, 1, 0, 0]
Bool[0, 1, 1, 0]
Bool[1, 1, 0, 0]
o