[ all classes ]
[ lzwpack.data ]
Coverage Summary for Class: BufferInstances (lzwpack.data)
| Class | Method, % | Line, % |
|---|---|---|
| BufferInstances | 100% (1/ 1) | 100% (2/ 2) |
| BufferInstances$BufferMonoid$ | 100% (3/ 3) | 100% (3/ 3) |
| total | 100% (4/ 4) | 100% (5/ 5) |
1 package lzwpack.data
2
3 import lzwpack._
4 import cats._
5 import cats.implicits._
6 import java.lang.{Long => JavaLong}
7
8 import scala.util.Try
9
10 /**
11 * A `BitBuffer` is a bit sequence backed by a 64-bit [[scala.Long]]. This means that it can hold at most 64 bits.
12 * It provides sequential access (head, append etc.) to manipulate variable-sized chunks.
13 */
14 case class BitBuffer(private[data] val data: Long, size: Int) {
15 assert(size >= 0)
16
17 /**
18 * Reads n bits from input and returns a tuple of (read bits, remaining bits).
19 *
20 * @param bits amount of bits to read
21 * @param allowFewer whether to successfully read and return a value with fewer read bits than requested
22 * @throws scala.IndexOutOfBoundsException if this BitBuffer does not contain enough bits to be read
23 */
24 def read(bits: Int, allowFewer: Boolean = false): (BitBuffer, Code) = {
25 if (!allowFewer && bits > size) throw new IndexOutOfBoundsException(s"Tried to read $bits bits from a buffer with $size bits")
26 (drop(bits), take(bits).toInt)
27 }
28
29 /**
30 * Like [[read]], but returns [[scala.None]] instead of throwing an exception if the read failed.
31 */
32 def readOption(bits: Int): Option[(BitBuffer, Int)] = Try(read(bits)).toOption
33
34 /**
35 * Reads a [[scala.Byte]] from this BitBuffer.
36 * @return a tuple of remaining and read bytes
37 * @throws scala.IndexOutOfBoundsException if this BitBuffer does not contain enough bits to be read
38 */
39 def readByte: (BitBuffer, Byte) = read(8) match {
40 case (remaining, read) => (remaining, read.toByte)
41 }
42
43 /**
44 * Returns a new BitBuffer with `n` bits discarded from the beginning of the buffer.
45 */
46 def drop(n: Int): BitBuffer = {
47 val tail = data >>> n
48 BitBuffer(tail, Math.max(size - n, 0))
49 }
50
51 /**
52 * Returns a new BitBuffer with all but the first `n` bits discarded from the buffer.
53 */
54 def take(n: Int): BitBuffer = {
55 val mask = (1 << n) - 1
56 val head = data & mask
57 BitBuffer(head, Math.min(size, n))
58 }
59
60 /**
61 * Converts this BitBuffer to an [[scala.Int]].
62 * Note that if the data exceeds 32 bits then it will be truncated to fit an integer.
63 */
64 def toInt: Int = data.toInt
65
66 /**
67 * Converts this BitBuffer to a [[scala.Byte]].
68 * Note that if the data exceeds 8 bits then it will be truncated to fit a byte.
69 */
70 def toByte: Byte = data.toByte
71
72 /**
73 * Returns a new buffer that contains this buffer preceded by the other buffer.
74 */
75 def append(other: BitBuffer): BitBuffer = {
76 require(other.size + size < JavaLong.SIZE, s"${other.size} + ${size} < 64")
77 val bb = other.data
78 BitBuffer((bb << size) ^ data, other.size + size)
79 }
80
81 /**
82 * Alias for [[append]]
83 */
84 def ++(other: BitBuffer): BitBuffer = this append other
85
86 /**
87 * Extracts as many values from this buffer as possible. Trailing zero-filled chunks are discarded.
88 * @param chunkSize the size of each read value in bytes
89 * @return an array of values
90 */
91 def drain(chunkSize: Int): (BitBuffer, Array[Int]) = {
92 assert(chunkSize > 0)
93 val chunkCount = size / chunkSize
94 val values = new Array[Int](chunkCount)
95
96 @annotation.tailrec
97 def go(i: Int, buffer: BitBuffer): Unit = {
98 if (i < chunkCount) {
99 buffer.read(chunkSize) match {
100 case (rest, value) =>
101 values(i) = value
102 if (rest.data > 0) go(i + 1, rest)
103 case _ =>
104 }
105 }
106 }
107
108 go(0, this)
109 (drop(chunkCount * chunkSize), values)
110 }
111
112 override def toString(): String = s"BitBuffer(${data.bin(size)}, $size bits)"
113 }
114
115 trait BufferInstances {
116 /**
117 * A BitBuffer forms a monoid under concatenation and an empty buffer.
118 */
119 implicit object BufferMonoid extends Monoid[BitBuffer] {
120 override def empty: BitBuffer = BitBuffer.empty
121 override def combine(a: BitBuffer, b: BitBuffer): BitBuffer = a append b
122 }
123
124 implicit val bitBufferShow: Show[BitBuffer] = Show.fromToString[BitBuffer]
125 }
126
127 object BitBuffer {
128 /**
129 * Creates a buffer from a [[scala.Byte]] with an initial size of 8.
130 */
131 @inline def apply(b: Byte): BitBuffer = BitBuffer(b.unsigned, 8)
132
133 /**
134 * Creates a buffer from a [[scala.Int]] with an initial size of the bytes required to represent the integer.
135 */
136 @inline def apply(data: Int): BitBuffer = BitBuffer(data, data.bitsize)
137
138 def tupled(t: (Int, Int)): BitBuffer = BitBuffer(t._1, t._2)
139
140 /**
141 * Returns an empty BitBuffer.
142 */
143 lazy val empty: BitBuffer = BitBuffer(0, 0)
144 }