refs #35 bits in BitArray/input/output reordered for better performance; started typed serialization
This commit is contained in:
		
							parent
							
								
									7aee25ffef
								
							
						
					
					
						commit
						cffe4eaffc
					
				@ -8,6 +8,7 @@ kotlinx-coroutines = "1.10.1"
 | 
			
		||||
mp_bintools = "0.1.12"
 | 
			
		||||
firebaseCrashlyticsBuildtools = "3.0.3"
 | 
			
		||||
okioVersion = "3.10.2"
 | 
			
		||||
compiler = "3.2.0-alpha11"
 | 
			
		||||
 | 
			
		||||
[libraries]
 | 
			
		||||
clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" }
 | 
			
		||||
@ -19,6 +20,7 @@ mp_bintools = { module = "net.sergeych:mp_bintools", version.ref = "mp_bintools"
 | 
			
		||||
firebase-crashlytics-buildtools = { group = "com.google.firebase", name = "firebase-crashlytics-buildtools", version.ref = "firebaseCrashlyticsBuildtools" }
 | 
			
		||||
okio = { module = "com.squareup.okio:okio", version.ref = "okioVersion" }
 | 
			
		||||
okio-fakefilesystem = { module = "com.squareup.okio:okio-fakefilesystem", version.ref = "okioVersion" }
 | 
			
		||||
compiler = { group = "androidx.databinding", name = "compiler", version.ref = "compiler" }
 | 
			
		||||
 | 
			
		||||
[plugins]
 | 
			
		||||
androidLibrary = { id = "com.android.library", version.ref = "agp" }
 | 
			
		||||
 | 
			
		||||
@ -99,6 +99,7 @@ android {
 | 
			
		||||
}
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation(libs.firebase.crashlytics.buildtools)
 | 
			
		||||
    implementation(libs.compiler)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
publishing {
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ package net.sergeych.lyng
 | 
			
		||||
import kotlinx.coroutines.delay
 | 
			
		||||
import net.sergeych.lyng.obj.*
 | 
			
		||||
import net.sergeych.lyng.pacman.ImportManager
 | 
			
		||||
import net.sergeych.lynon.ObjLynonClass
 | 
			
		||||
import kotlin.math.*
 | 
			
		||||
 | 
			
		||||
class Script(
 | 
			
		||||
@ -181,6 +182,9 @@ class Script(
 | 
			
		||||
                    it.addConst("Buffer", ObjBuffer.type)
 | 
			
		||||
                    it.addConst("MutableBuffer", ObjMutableBuffer.type)
 | 
			
		||||
                }
 | 
			
		||||
                addPackage("lyng.serialization") {
 | 
			
		||||
                    it.addConst("Lynon", ObjLynonClass)
 | 
			
		||||
                }
 | 
			
		||||
                addPackage("lyng.time") {
 | 
			
		||||
                    it.addConst("Instant", ObjInstant.type)
 | 
			
		||||
                    it.addConst("Duration", ObjDuration.type)
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ package net.sergeych.lyng.obj
 | 
			
		||||
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
import kotlinx.coroutines.flow.toList
 | 
			
		||||
import net.sergeych.bintools.toDump
 | 
			
		||||
import net.sergeych.lyng.Scope
 | 
			
		||||
import net.sergeych.lyng.statement
 | 
			
		||||
import kotlin.math.min
 | 
			
		||||
@ -138,6 +139,12 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
 | 
			
		||||
                requireNoArgs()
 | 
			
		||||
                ObjMutableBuffer(thisAs<ObjBuffer>().byteArray.copyOf())
 | 
			
		||||
            }
 | 
			
		||||
            addFn("toDump") {
 | 
			
		||||
                requireNoArgs()
 | 
			
		||||
                ObjString(
 | 
			
		||||
                    thisAs<ObjBuffer>().byteArray.toByteArray().toDump()
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -69,6 +69,9 @@ open class ObjClass(
 | 
			
		||||
 | 
			
		||||
    fun addConst(name: String, value: Obj) = createField(name, value, isMutable = false)
 | 
			
		||||
    fun addClassConst(name: String, value: Obj) = createClassField(name, value)
 | 
			
		||||
    fun addClassFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
 | 
			
		||||
        createClassField(name, statement { code() }, isOpen)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -91,6 +94,10 @@ open class ObjClass(
 | 
			
		||||
        return super.readField(scope, name)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments): Obj {
 | 
			
		||||
        return classMembers[name]?.value?.invoke(scope, this, args) ?: super.invokeInstanceMethod(scope, name, args)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open fun deserialize(scope: Scope, decoder: LynonDecoder): Obj = scope.raiseNotImplemented()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -57,7 +57,10 @@ data class ObjString(val value: String) : Obj() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun callOn(scope: Scope): Obj {
 | 
			
		||||
        return ObjString(this.value.sprintf(*scope.args.toKotlinList(scope).toTypedArray()))
 | 
			
		||||
        return ObjString(this.value.sprintf(*scope.args
 | 
			
		||||
            .toKotlinList(scope)
 | 
			
		||||
            .map { if( it == null) "null" else it }
 | 
			
		||||
            .toTypedArray()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun contains(scope: Scope, other: Obj): Boolean {
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,8 @@ package net.sergeych.lynon
 | 
			
		||||
import net.sergeych.lyng.Scope
 | 
			
		||||
import net.sergeych.lyng.obj.Obj
 | 
			
		||||
import net.sergeych.lyng.obj.ObjClass
 | 
			
		||||
import net.sergeych.lyng.obj.ObjInt
 | 
			
		||||
import net.sergeych.lyng.obj.ObjNull
 | 
			
		||||
 | 
			
		||||
open class LynonDecoder(val bin: BitInput,val settings: LynonSettings = LynonSettings.default) {
 | 
			
		||||
 | 
			
		||||
@ -24,6 +26,17 @@ open class LynonDecoder(val bin: BitInput,val settings: LynonSettings = LynonSet
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun decodeAny(scope: Scope): Obj = decodeCached {
 | 
			
		||||
        val type = LynonType.entries[bin.getBits(4).toInt()]
 | 
			
		||||
        return when(type) {
 | 
			
		||||
            LynonType.Null -> ObjNull
 | 
			
		||||
            LynonType.Int0 -> ObjInt.Zero
 | 
			
		||||
            else -> {
 | 
			
		||||
                scope.raiseNotImplemented("lynon type $type")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun unpackObject(scope: Scope, type: ObjClass): Obj {
 | 
			
		||||
        return decodeCached { type.deserialize(scope, this) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -2,26 +2,84 @@ package net.sergeych.lynon
 | 
			
		||||
 | 
			
		||||
import net.sergeych.lyng.Scope
 | 
			
		||||
import net.sergeych.lyng.obj.Obj
 | 
			
		||||
import net.sergeych.lyng.obj.ObjInt
 | 
			
		||||
import net.sergeych.lyng.obj.ObjNull
 | 
			
		||||
 | 
			
		||||
enum class LynonType {
 | 
			
		||||
    Null,
 | 
			
		||||
    Int0,
 | 
			
		||||
    IntNegative,
 | 
			
		||||
    IntPositive,
 | 
			
		||||
    String,
 | 
			
		||||
    Real,
 | 
			
		||||
    Bool,
 | 
			
		||||
    List,
 | 
			
		||||
    Map,
 | 
			
		||||
    Set,
 | 
			
		||||
    Buffer,
 | 
			
		||||
    Instant,
 | 
			
		||||
    Duration,
 | 
			
		||||
    Other;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
open class LynonEncoder(val bout: BitOutput,val settings: LynonSettings = LynonSettings.default) {
 | 
			
		||||
 | 
			
		||||
    val cache = mutableMapOf<Any, Int>()
 | 
			
		||||
 | 
			
		||||
    private inline fun encodeCached(item: Any, packer: LynonEncoder.() -> Unit) {
 | 
			
		||||
        if (item is Obj) {
 | 
			
		||||
            cache[item]?.let { cacheId ->
 | 
			
		||||
    private suspend fun encodeCached(item: Any, packer: suspend LynonEncoder.() -> Unit) {
 | 
			
		||||
 | 
			
		||||
        suspend fun serializeAndCache(key: Any=item) {
 | 
			
		||||
            bout.putBit(0)
 | 
			
		||||
            if( settings.shouldCache(item) )
 | 
			
		||||
                cache[key] = cache.size
 | 
			
		||||
            packer()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        when(item) {
 | 
			
		||||
            is Obj -> cache[item]?.let { cacheId ->
 | 
			
		||||
                val size = sizeInBits(cache.size)
 | 
			
		||||
                bout.putBit(1)
 | 
			
		||||
                bout.putBits(cacheId.toULong(), size)
 | 
			
		||||
            } ?: run {
 | 
			
		||||
                bout.putBit(0)
 | 
			
		||||
                if (settings.shouldCache(item))
 | 
			
		||||
                    cache[item] = cache.size
 | 
			
		||||
                packer()
 | 
			
		||||
            } ?: serializeAndCache()
 | 
			
		||||
 | 
			
		||||
            is ByteArray, is UByteArray ->  serializeAndCache()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Encode any Lyng object [Obj], which can be serialized, using type record. This allow to
 | 
			
		||||
     * encode any object with the overhead of type record.
 | 
			
		||||
     *
 | 
			
		||||
     * Caching is used automatically.
 | 
			
		||||
     */
 | 
			
		||||
    suspend fun encodeAny(scope: Scope,value: Obj) {
 | 
			
		||||
        encodeCached(value) {
 | 
			
		||||
            when(value) {
 | 
			
		||||
                is ObjNull -> putType(LynonType.Null)
 | 
			
		||||
                is ObjInt -> {
 | 
			
		||||
                    when {
 | 
			
		||||
                        value.value == 0L -> putType(LynonType.Int0)
 | 
			
		||||
                        value.value < 0 -> {
 | 
			
		||||
                            putType(LynonType.IntNegative)
 | 
			
		||||
                            encodeUnsigned((-value.value).toULong())
 | 
			
		||||
                        }
 | 
			
		||||
                        else -> {
 | 
			
		||||
                            putType(LynonType.IntPositive)
 | 
			
		||||
                            encodeUnsigned(value.value.toULong())
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else -> {
 | 
			
		||||
                    TODO()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun putType(type: LynonType) {
 | 
			
		||||
        bout.putBits(type.ordinal.toULong(), 4)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun encodeObj(scope: Scope, obj: Obj) {
 | 
			
		||||
        encodeCached(obj) {
 | 
			
		||||
            obj.serialize(scope, this)
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,20 @@
 | 
			
		||||
package net.sergeych.lynon
 | 
			
		||||
 | 
			
		||||
import net.sergeych.lyng.obj.Obj
 | 
			
		||||
import net.sergeych.lyng.obj.ObjBool
 | 
			
		||||
import net.sergeych.lyng.obj.ObjChar
 | 
			
		||||
import net.sergeych.lyng.obj.ObjInt
 | 
			
		||||
import net.sergeych.lyng.obj.ObjNull
 | 
			
		||||
import kotlin.math.absoluteValue
 | 
			
		||||
 | 
			
		||||
open class LynonSettings() {
 | 
			
		||||
 | 
			
		||||
    open fun shouldCache(obj: Obj): Boolean = when (obj) {
 | 
			
		||||
    open fun shouldCache(obj: Any): Boolean = when (obj) {
 | 
			
		||||
        is ObjChar -> false
 | 
			
		||||
        is ObjInt -> obj.value > 0x10000FF
 | 
			
		||||
        is ObjInt -> obj.value.absoluteValue > 0x10000FF
 | 
			
		||||
        is ObjBool -> false
 | 
			
		||||
        is ObjNull -> false
 | 
			
		||||
        is ByteArray -> obj.size > 2
 | 
			
		||||
        is UByteArray -> obj.size > 2
 | 
			
		||||
        else -> true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,30 +8,31 @@ class MemoryBitInput(val packedBits: UByteArray, val lastByteBits: Int) : BitInp
 | 
			
		||||
 | 
			
		||||
    private var index = 0
 | 
			
		||||
 | 
			
		||||
    private var isEndOfStream: Boolean = packedBits.isEmpty() || (packedBits.size == 1 && lastByteBits == 0)
 | 
			
		||||
        private set
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return next byte, int in 0..255 range, or -1 if end of stream reached
 | 
			
		||||
     */
 | 
			
		||||
    private var accumulator = 0
 | 
			
		||||
    private var accumulator = if( isEndOfStream ) 0 else packedBits[0].toInt()
 | 
			
		||||
 | 
			
		||||
    private var isEndOfStream: Boolean = false
 | 
			
		||||
        private set
 | 
			
		||||
 | 
			
		||||
    private var mask = 0
 | 
			
		||||
    private var bitCounter = 0
 | 
			
		||||
 | 
			
		||||
    override fun getBitOrNull(): Int? {
 | 
			
		||||
        if (isEndOfStream) return null
 | 
			
		||||
        if (mask == 0) {
 | 
			
		||||
            if (index < packedBits.size) {
 | 
			
		||||
                accumulator = packedBits[index++].toInt()
 | 
			
		||||
                val n = if (index == packedBits.size) lastByteBits else 8
 | 
			
		||||
                mask = 1 shl (n - 1)
 | 
			
		||||
            } else {
 | 
			
		||||
                isEndOfStream = true
 | 
			
		||||
                return null
 | 
			
		||||
        val result = accumulator and 1
 | 
			
		||||
        accumulator = accumulator shr 1
 | 
			
		||||
        bitCounter++
 | 
			
		||||
        // is end?
 | 
			
		||||
        if( index == packedBits.lastIndex && bitCounter == lastByteBits ) {
 | 
			
		||||
            isEndOfStream = true
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            if( bitCounter == 8 ) {
 | 
			
		||||
                bitCounter = 0
 | 
			
		||||
                accumulator = packedBits[++index].toInt()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        val result = if (0 == accumulator and mask) 0 else 1
 | 
			
		||||
        mask = mask shr 1
 | 
			
		||||
        return result
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,11 +4,13 @@ import kotlin.math.min
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * BitList implementation as fixed suze array of bits; indexing works exactly same as if
 | 
			
		||||
 * [MemoryBitInput] is used with [MemoryBitInput.getBit].
 | 
			
		||||
 * [MemoryBitInput] is used with [MemoryBitInput.getBit]. See [MemoryBitOutput] for
 | 
			
		||||
 * bits order and more information.
 | 
			
		||||
 */
 | 
			
		||||
class BitArray(val bytes: UByteArray, val lastByteBits: Int) : BitList {
 | 
			
		||||
 | 
			
		||||
    val bytesSize: Int get() = bytes.size
 | 
			
		||||
 | 
			
		||||
    override val size by lazy { bytes.size * 8L - (8 - lastByteBits) }
 | 
			
		||||
 | 
			
		||||
    override val indices by lazy { 0..<size }
 | 
			
		||||
@ -23,15 +25,9 @@ class BitArray(val bytes: UByteArray, val lastByteBits: Int) : BitList {
 | 
			
		||||
        if (byteIndex !in bytes.indices)
 | 
			
		||||
            throw IndexOutOfBoundsException("$bitIndex is out of bounds")
 | 
			
		||||
        val i = (bitIndex % 8).toInt()
 | 
			
		||||
        return byteIndex to (
 | 
			
		||||
                if (byteIndex == bytes.lastIndex) {
 | 
			
		||||
                    if (i >= lastByteBits)
 | 
			
		||||
                        throw IndexOutOfBoundsException("$bitIndex is out of bounds (last)")
 | 
			
		||||
                    1 shl (lastByteBits - i - 1)
 | 
			
		||||
                } else {
 | 
			
		||||
                    1 shl (7 - i)
 | 
			
		||||
                }
 | 
			
		||||
                )
 | 
			
		||||
        if (byteIndex == bytes.lastIndex && i >= lastByteBits)
 | 
			
		||||
                throw IndexOutOfBoundsException("$bitIndex is out of bounds (last)")
 | 
			
		||||
        return byteIndex to (1 shl i)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override operator fun get(bitIndex: Long): Int =
 | 
			
		||||
@ -56,6 +52,10 @@ class BitArray(val bytes: UByteArray, val lastByteBits: Int) : BitList {
 | 
			
		||||
        return result.toString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun asByteArray(): ByteArray = bytes.asByteArray()
 | 
			
		||||
 | 
			
		||||
    fun asUbyteArray(): UByteArray = bytes
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        fun withBitSize(size: Long): BitArray {
 | 
			
		||||
@ -75,35 +75,40 @@ class BitArray(val bytes: UByteArray, val lastByteBits: Int) : BitList {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * [BitOutput] implementation that writes to a memory buffer, LSB first.
 | 
			
		||||
 *
 | 
			
		||||
 * Bits are stored in the least significant bits of the bytes. E.g. the first bit
 | 
			
		||||
 * added by [putBit] will be stored in the bit 0x01 of the first byte, the second bit
 | 
			
		||||
 * in the bit 0x02 of the first byte, etc.
 | 
			
		||||
 *
 | 
			
		||||
 * This allow automatic fill of the last byte with zeros. This is important when
 | 
			
		||||
 * using bytes stored from [asByteArray] or [asUbyteArray]. When converting to
 | 
			
		||||
 * bytes, automatic padding to byte size is applied. With such bit order, constrinting
 | 
			
		||||
 * [BitInput] to read from [asByteArray] result only provides 0 to 7 extra zeroes bits
 | 
			
		||||
 * at teh end which is often acceptable. To avoid this, use [toBitArray]; the [BitArray]
 | 
			
		||||
 * stores exact number of bits and [BitArray.toBitInput] provides [BitInput] that
 | 
			
		||||
 * decodes exactly same bits.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class MemoryBitOutput : BitOutput {
 | 
			
		||||
    private val buffer = mutableListOf<UByte>()
 | 
			
		||||
 | 
			
		||||
    private var accumulator = 0
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Number of bits in accumulator. After output is closed by [close] this value is
 | 
			
		||||
     * not changed and represents the number of bits in the last byte; this should
 | 
			
		||||
     * be used to properly calculate end of the bit stream
 | 
			
		||||
     */
 | 
			
		||||
    private var accumulatorBits = 0
 | 
			
		||||
        private set
 | 
			
		||||
 | 
			
		||||
//    /**
 | 
			
		||||
//     * When [close] is called, represents the number of used bits in the last byte;
 | 
			
		||||
//     * bits after this number are the garbage and should be ignored
 | 
			
		||||
//     */
 | 
			
		||||
//    val lastByteBits: Int
 | 
			
		||||
//        get() {
 | 
			
		||||
//            if (!isClosed) throw IllegalStateException("BitOutput is not closed")
 | 
			
		||||
//            return accumulatorBits
 | 
			
		||||
//        }
 | 
			
		||||
    private var mask = 1
 | 
			
		||||
 | 
			
		||||
    override fun putBit(bit: Int) {
 | 
			
		||||
        accumulator = (accumulator shl 1) or bit
 | 
			
		||||
        if (++accumulatorBits >= 8) {
 | 
			
		||||
        when (bit) {
 | 
			
		||||
            0 -> {}
 | 
			
		||||
            1 -> accumulator = accumulator or mask
 | 
			
		||||
            else -> throw IllegalArgumentException("Bit must be 0 or 1")
 | 
			
		||||
        }
 | 
			
		||||
        mask = mask shl 1
 | 
			
		||||
        if(mask == 0x100) {
 | 
			
		||||
            mask = 1
 | 
			
		||||
            outputByte(accumulator.toUByte())
 | 
			
		||||
            accumulator = accumulator shr 8
 | 
			
		||||
            accumulatorBits = 0
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -112,19 +117,34 @@ class MemoryBitOutput : BitOutput {
 | 
			
		||||
 | 
			
		||||
    fun close(): BitArray {
 | 
			
		||||
        if (!isClosed) {
 | 
			
		||||
            if (accumulatorBits > 0) {
 | 
			
		||||
            if (mask != 0x01) {
 | 
			
		||||
                outputByte(accumulator.toUByte())
 | 
			
		||||
            } else accumulatorBits = 8
 | 
			
		||||
            }
 | 
			
		||||
            isClosed = true
 | 
			
		||||
        }
 | 
			
		||||
        return toBitArray()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun lastBits(): Int {
 | 
			
		||||
        check(isClosed)
 | 
			
		||||
        return when(mask) {
 | 
			
		||||
            0x01 -> 8   // means that all bits of the last byte are in use
 | 
			
		||||
            0x02 -> 1
 | 
			
		||||
            0x04 -> 2
 | 
			
		||||
            0x08 -> 3
 | 
			
		||||
            0x10 -> 4
 | 
			
		||||
            0x20 -> 5
 | 
			
		||||
            0x40 -> 6
 | 
			
		||||
            0x80 -> 7
 | 
			
		||||
            else -> throw IllegalStateException("Invalid state, mask=${mask.toString(16)}")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun toBitArray(): BitArray {
 | 
			
		||||
        if (!isClosed) {
 | 
			
		||||
            close()
 | 
			
		||||
        }
 | 
			
		||||
        return BitArray(buffer.toTypedArray().toUByteArray(), accumulatorBits)
 | 
			
		||||
        return BitArray(buffer.toTypedArray().toUByteArray(), lastBits())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun toBitInput(): BitInput = toBitArray().toBitInput()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										35
									
								
								lynglib/src/commonMain/kotlin/net/sergeych/lynon/packer.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								lynglib/src/commonMain/kotlin/net/sergeych/lynon/packer.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
package net.sergeych.lynon
 | 
			
		||||
 | 
			
		||||
import net.sergeych.lyng.Scope
 | 
			
		||||
import net.sergeych.lyng.obj.Obj
 | 
			
		||||
import net.sergeych.lyng.obj.ObjBuffer
 | 
			
		||||
import net.sergeych.lyng.obj.ObjClass
 | 
			
		||||
import net.sergeych.lyng.obj.ObjString
 | 
			
		||||
 | 
			
		||||
// Most often used types:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
val ObjLynonClass = object : ObjClass("Lynon") {
 | 
			
		||||
 | 
			
		||||
    suspend fun Scope.encodeAny(obj: Obj): Obj {
 | 
			
		||||
        val bout = MemoryBitOutput()
 | 
			
		||||
        val serializer = LynonEncoder(bout)
 | 
			
		||||
        serializer.encodeAny(this, obj)
 | 
			
		||||
        return ObjBuffer(bout.toBitArray().bytes)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun Scope.decodeAny(buffer: ObjBuffer): Obj {
 | 
			
		||||
        val bin = BitArray(buffer.byteArray,8).toInput()
 | 
			
		||||
        val deserializer = LynonDecoder(bin)
 | 
			
		||||
        return deserializer.decodeAny(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}.apply {
 | 
			
		||||
    addClassConst("test", ObjString("test_const"))
 | 
			
		||||
    addClassFn("encode") {
 | 
			
		||||
        encodeAny(requireOnlyArg<Obj>())
 | 
			
		||||
    }
 | 
			
		||||
    addClassFn("decode") {
 | 
			
		||||
        decodeAny(requireOnlyArg<ObjBuffer>())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
import junit.framework.TestCase.*
 | 
			
		||||
import kotlinx.coroutines.test.runTest
 | 
			
		||||
import net.sergeych.bintools.encodeToHex
 | 
			
		||||
import net.sergeych.lyng.Scope
 | 
			
		||||
import net.sergeych.lyng.eval
 | 
			
		||||
import net.sergeych.lyng.obj.*
 | 
			
		||||
import net.sergeych.lynon.*
 | 
			
		||||
import java.nio.file.Files
 | 
			
		||||
@ -30,6 +32,62 @@ class LynonTests {
 | 
			
		||||
        assertEquals(4, sizeInBits(15u))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testBitOutputSmall() {
 | 
			
		||||
        val bout = MemoryBitOutput()
 | 
			
		||||
        bout.putBit(1)
 | 
			
		||||
        bout.putBit(1)
 | 
			
		||||
        bout.putBit(0)
 | 
			
		||||
        bout.putBit(1)
 | 
			
		||||
        val x = bout.toBitArray()
 | 
			
		||||
        assertEquals(1, x[0])
 | 
			
		||||
        assertEquals(1, x[1])
 | 
			
		||||
        assertEquals(0, x[2])
 | 
			
		||||
        assertEquals(1, x[3])
 | 
			
		||||
        assertEquals(4, x.size)
 | 
			
		||||
        assertEquals("1101", x.toString())
 | 
			
		||||
        val bin = MemoryBitInput(x)
 | 
			
		||||
        assertEquals(1, bin.getBit())
 | 
			
		||||
        assertEquals(1, bin.getBit())
 | 
			
		||||
        assertEquals(0, bin.getBit())
 | 
			
		||||
        assertEquals(1, bin.getBit())
 | 
			
		||||
        assertEquals(null, bin.getBitOrNull())
 | 
			
		||||
    }
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testBitOutputMedium() {
 | 
			
		||||
        val bout = MemoryBitOutput()
 | 
			
		||||
        bout.putBit(1)
 | 
			
		||||
        bout.putBit(1)
 | 
			
		||||
        bout.putBit(0)
 | 
			
		||||
        bout.putBit(1)
 | 
			
		||||
        bout.putBits( 0, 7)
 | 
			
		||||
        bout.putBits( 3, 2)
 | 
			
		||||
        val x = bout.toBitArray()
 | 
			
		||||
        assertEquals(1, x[0])
 | 
			
		||||
        assertEquals(1, x[1])
 | 
			
		||||
        assertEquals(0, x[2])
 | 
			
		||||
        assertEquals(1, x[3])
 | 
			
		||||
        assertEquals(13, x.size)
 | 
			
		||||
        assertEquals("1101000000011", x.toString())
 | 
			
		||||
        println(x.bytes.encodeToHex())
 | 
			
		||||
        val bin = MemoryBitInput(x)
 | 
			
		||||
        assertEquals(1, bin.getBit())
 | 
			
		||||
        assertEquals(1, bin.getBit())
 | 
			
		||||
        assertEquals(0, bin.getBit())
 | 
			
		||||
        assertEquals(1, bin.getBit())
 | 
			
		||||
 | 
			
		||||
//        assertEquals(0, bin.getBit())
 | 
			
		||||
//        assertEquals(0, bin.getBit())
 | 
			
		||||
//        assertEquals(0, bin.getBit())
 | 
			
		||||
//        assertEquals(0, bin.getBit())
 | 
			
		||||
//        assertEquals(0, bin.getBit())
 | 
			
		||||
//        assertEquals(0, bin.getBit())
 | 
			
		||||
//        assertEquals(0, bin.getBit())
 | 
			
		||||
        assertEquals(0UL, bin.getBits(7))
 | 
			
		||||
        assertEquals(3UL, bin.getBits(2))
 | 
			
		||||
        assertEquals(null, bin.getBitOrNull())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testBitStreams() {
 | 
			
		||||
 | 
			
		||||
@ -213,6 +271,45 @@ class LynonTests {
 | 
			
		||||
 | 
			
		||||
    val original = Files.readString(Path.of("../sample_texts/dikkens_hard_times.txt"))
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testEncodeNullsAndInts() = runTest{
 | 
			
		||||
        testScope().eval("""
 | 
			
		||||
            testEncode(null)
 | 
			
		||||
            testEncode(0)
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testBufferEncoderInterop() = runTest{
 | 
			
		||||
        val bout = MemoryBitOutput()
 | 
			
		||||
        bout.putBits(0, 1)
 | 
			
		||||
        bout.putBits(1, 4)
 | 
			
		||||
        val bin = MemoryBitInput(bout.toBitArray().bytes, 8)
 | 
			
		||||
        assertEquals(0UL, bin.getBits(1))
 | 
			
		||||
        assertEquals(1UL, bin.getBits(4))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun testScope() =
 | 
			
		||||
        Scope().apply { eval("""
 | 
			
		||||
            import lyng.serialization
 | 
			
		||||
            fun testEncode(value) {
 | 
			
		||||
                val encoded = Lynon.encode(value)
 | 
			
		||||
                println(encoded.toDump())
 | 
			
		||||
                println("Encoded size %d: %s"(encoded.size, value))
 | 
			
		||||
                assertEquals( value, Lynon.decode(encoded) )
 | 
			
		||||
            }
 | 
			
		||||
            """.trimIndent())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testIntsNulls() = runTest{
 | 
			
		||||
        eval("""
 | 
			
		||||
            import lyng.serialization
 | 
			
		||||
            assertEquals( null, Lynon.decode(Lynon.encode(null)) )
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testLzw() {
 | 
			
		||||
        // Example usage
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user