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 }