diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/BitInput.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/BitInput.kt new file mode 100644 index 0000000..16e5fc3 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/BitInput.kt @@ -0,0 +1,61 @@ +package net.sergeych.lynon + +abstract class BitInput { + + /** + * Return next byte, int in 0..255 range, or -1 if end of stream reached + */ + abstract fun getByte(): Int + + private var accumulator = 0 + + var isEndOfStream: Boolean = false + private set + + private var mask = 0 + + fun getBitOrNull(): Int? { + if (isEndOfStream) return null + if (mask == 0) { + accumulator = getByte() + if (accumulator == -1) { + isEndOfStream = true + return null + } + mask = 0x80 + } + val result = if (0 == accumulator and mask) 0 else 1 + mask = mask shr 1 + return result + } + + fun getBitsOrNull(count: Int): ULong? { + var result = 0UL + var mask = 1UL + for( i in 0 ..< count) { + when(getBitOrNull()) { + null -> return null + 1 -> result = result or mask + 0 -> {} + } + mask = mask shl 1 + } + return result + } + + fun getBits(count: Int): ULong { + return getBitsOrNull(count) ?: throw IllegalStateException("Unexpected end of stream") + } + + fun unpackUnsigned(): ULong { + val tetrades = getBits(4).toInt() + var result = 0UL + var shift = 0 + for (i in 0..= 8 ) { + outputByte(accumulator.toUByte()) + accumulator = accumulator shr 0 + accumulatorBits = 0 + } + } + + fun packUnsigned(value: ULong) { + val tetrades = sizeInTetrades(value) + putBits(tetrades, 4) + var rest = value + for( i in 0.. 0) { + while (accumulatorBits != 0) putBit(0) + } + isClosed = true + } + } +} \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/MemoryBitInput.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/MemoryBitInput.kt new file mode 100644 index 0000000..0f91712 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/MemoryBitInput.kt @@ -0,0 +1,14 @@ +package net.sergeych.lynon + +class MemoryBitInput(val packedBits: UByteArray): BitInput() { + private var index = 0 + + override fun getByte(): Int { + if( index < packedBits.size ) { + return packedBits[index++].toInt() + } else { + return -1 + } + } + +} \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/MemoryBitOutput.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/MemoryBitOutput.kt new file mode 100644 index 0000000..d028e24 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/MemoryBitOutput.kt @@ -0,0 +1,14 @@ +package net.sergeych.lynon + +class MemoryBitOutput: BitOutput() { + private val buffer = mutableListOf() + + fun toUByteArray(): UByteArray { + close() + return buffer.toTypedArray().toUByteArray() + } + + override fun outputByte(byte: UByte) { + buffer.add(byte) + } +} \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/bit_tools.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/bit_tools.kt new file mode 100644 index 0000000..bbc5068 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/bit_tools.kt @@ -0,0 +1,33 @@ +package net.sergeych.lynon + +/** + * Hoq many tetrades needed to store the value. It is faster to use this function + * than to use sizeInBits + * + * Size for 0 is 1 + */ +fun sizeInTetrades(value: ULong): Int { + if( value == 0UL ) return 1 + var size = 0 + var rest = value + while( rest != 0UL ) { + size++ + rest = rest shr 4 + } + return size +} + +/** + * How many bits needed to store the value. Size for 0 is 1, + */ +@Suppress("unused") +fun sizeInBits(value: ULong): Int { + if( value == 0UL ) return 1 + var size = 0 + var rest = value + while( rest != 0UL ) { + size++ + rest = rest shr 1 + } + return size +} \ No newline at end of file diff --git a/lynglib/src/jvmTest/kotlin/LynonTests.kt b/lynglib/src/jvmTest/kotlin/LynonTests.kt new file mode 100644 index 0000000..685153d --- /dev/null +++ b/lynglib/src/jvmTest/kotlin/LynonTests.kt @@ -0,0 +1,49 @@ +import junit.framework.TestCase.assertEquals +import net.sergeych.bintools.toDump +import net.sergeych.lynon.MemoryBitInput +import net.sergeych.lynon.MemoryBitOutput +import net.sergeych.lynon.sizeInTetrades +import kotlin.test.Test + +class LynonTests { + + @Test + fun testSizeInTetrades() { + assertEquals(1, sizeInTetrades(0u)) + assertEquals(1, sizeInTetrades(1u)) + assertEquals(1, sizeInTetrades(15u)) + assertEquals(2, sizeInTetrades(16u)) + assertEquals(2, sizeInTetrades(254u)) + assertEquals(2, sizeInTetrades(255u)) + assertEquals(3, sizeInTetrades(256u)) + assertEquals(3, sizeInTetrades(257u)) + } + + @Test + fun testBitStreams() { + + val bout = MemoryBitOutput() + bout.putBits(2, 3) + bout.putBits(1, 7) + bout.putBits( 197, 8) + bout.putBits( 3, 4) + bout.close() + + val bin = MemoryBitInput(bout.toUByteArray()) + assertEquals(2UL, bin.getBits(3)) + assertEquals(1UL, bin.getBits(7)) + assertEquals(197UL, bin.getBits(8)) + assertEquals(3UL, bin.getBits(4)) + } + + @Test + fun testPackInteger() { + val bout = MemoryBitOutput() + bout.packUnsigned(147179UL) + bout.close() + println(bout.toUByteArray().toDump()) + val bin = MemoryBitInput(bout.toUByteArray()) + assertEquals(147179UL, bin.unpackUnsigned()) + } + +} \ No newline at end of file