diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBool.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBool.kt index 95e2549..672345e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBool.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBool.kt @@ -31,7 +31,7 @@ data class ObjBool(val value: Boolean) : Obj() { } override suspend fun serialize(scope: Scope, encoder: LynonEncoder) { - encoder.packBoolean(value) + encoder.encodeBoolean(value) } override fun equals(other: Any?): Boolean { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt index e97f098..a058e39 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt @@ -98,7 +98,7 @@ class ObjInt(var value: Long,override val isConst: Boolean = false) : Obj(), Num } override suspend fun serialize(scope: Scope, encoder: LynonEncoder) { - encoder.packSigned(value) + encoder.encodeSigned(value) } companion object { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt index c47bb0f..ae41da9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt @@ -62,7 +62,7 @@ data class ObjReal(val value: Double) : Obj(), Numeric { } override suspend fun serialize(scope: Scope, encoder: LynonEncoder) { - encoder.packReal(value) + encoder.encodeReal(value) } companion object { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt index 42b9cfa..43b4178 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt @@ -78,7 +78,7 @@ data class ObjString(val value: String) : Obj() { } override suspend fun serialize(scope: Scope, encoder: LynonEncoder) { - encoder.packBinaryData(value.encodeToByteArray()) + encoder.encodeBinaryData(value.encodeToByteArray()) } companion object { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonDecoder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonDecoder.kt index ac10b1f..d9517a4 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonDecoder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonDecoder.kt @@ -4,27 +4,31 @@ import net.sergeych.lyng.Scope import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.ObjClass -open class LynonDecoder(private val bin: BitInput) { +open class LynonDecoder(val bin: BitInput) { val cache = mutableListOf() - fun unpackObject(scope: Scope, type: ObjClass): Obj { + inline fun decodeCached(f: LynonDecoder.() -> Obj): Obj { return if( bin.getBit() == 0 ) { // unpack and cache val cached = bin.getBool() - type.deserialize(scope, this).also { + f().also { if( cached ) cache.add(it) } } else { // get cache reference val size = sizeInBits(cache.size) - val id = bin.getBitsOrNull(size)?.toInt() ?: scope.raiseError("Invalid object id: unexpected end of stream") - if( id >= cache.size ) scope.raiseError("Invalid object id: $id should be in 0..<${cache.size}") + val id = bin.getBitsOrNull(size)?.toInt() ?: throw RuntimeException("Invalid object id: unexpected end of stream") + if( id >= cache.size ) throw RuntimeException("Invalid object id: $id should be in 0..<${cache.size}") cache[id] } } + fun unpackObject(scope: Scope, type: ObjClass): Obj { + return decodeCached { type.deserialize(scope, this) } + } + fun unpackBinaryData(): ByteArray? { val size = bin.unpackUnsigned() return bin.getBytes(size.toInt()) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonEncoder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonEncoder.kt index 083e32b..3b706fa 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonEncoder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonEncoder.kt @@ -6,13 +6,13 @@ import net.sergeych.lyng.obj.ObjBool import net.sergeych.lyng.obj.ObjChar import net.sergeych.lyng.obj.ObjInt -class LynonPacker(private val bout: MemoryBitOutput= MemoryBitOutput()) : LynonEncoder(bout) { - fun toUByteArray(): UByteArray = bout.toUByteArray() +class LynonPacker(bout: MemoryBitOutput = MemoryBitOutput()) : LynonEncoder(bout) { + fun toUByteArray(): UByteArray = (bout as MemoryBitOutput).toUByteArray() } class LynonUnpacker(source: UByteArray) : LynonDecoder(MemoryBitInput(source)) -open class LynonEncoder(private val bout: BitOutput) { +open class LynonEncoder(val bout: BitOutput) { fun shouldCache(obj: Obj): Boolean = when (obj) { is ObjChar -> false @@ -21,41 +21,56 @@ open class LynonEncoder(private val bout: BitOutput) { else -> true } - val cache = mutableMapOf() + val cache = mutableMapOf() - suspend fun packObject(scope: Scope,obj: Obj) { - cache[obj]?.let { cacheId -> - val size = sizeInBits(cache.size) - bout.putBit(1) - bout.putBits(cacheId, size) - } ?: run { - bout.putBit(0) - if( shouldCache(obj) ) { + inline fun encodeCached(item: Any, packer: LynonEncoder.() -> Unit) { + if (item is Obj) { + cache[item]?.let { cacheId -> + val size = sizeInBits(cache.size) bout.putBit(1) - cache[obj] = cache.size - } - else + bout.putBits(cacheId.toULong(), size) + } ?: run { bout.putBit(0) + if (shouldCache(item)) { + bout.putBit(1) + cache[item] = cache.size + } else + bout.putBit(0) + packer() + } + } + } + + suspend fun encodeObj(scope: Scope, obj: Obj) { + encodeCached(obj) { obj.serialize(scope, this) } } - fun packBinaryData(data: ByteArray) { + fun encodeBinaryData(data: ByteArray) { bout.packUnsigned(data.size.toULong()) bout.putBytes(data) } - fun packSigned(value: Long) { bout.packSigned(value) } - @Suppress("unused") - fun packUnsigned(value: ULong) { bout.packUnsigned(value) } + fun encodeSigned(value: Long) { + bout.packSigned(value) + } @Suppress("unused") - fun packBool(value: Boolean) { bout.putBit(if (value) 1 else 0) } - fun packReal(value: Double) { + fun encodeUnsigned(value: ULong) { + bout.packUnsigned(value) + } + + @Suppress("unused") + fun encodeBool(value: Boolean) { + bout.putBit(if (value) 1 else 0) + } + + fun encodeReal(value: Double) { bout.putBits(value.toRawBits().toULong(), 64) } - fun packBoolean(value: Boolean) { + fun encodeBoolean(value: Boolean) { bout.putBit(if (value) 1 else 0) } diff --git a/lynglib/src/jvmTest/kotlin/LynonTests.kt b/lynglib/src/jvmTest/kotlin/LynonTests.kt index d29b7c3..b56e8f6 100644 --- a/lynglib/src/jvmTest/kotlin/LynonTests.kt +++ b/lynglib/src/jvmTest/kotlin/LynonTests.kt @@ -90,14 +90,14 @@ class LynonTests { val encoder = LynonEncoder(bout) val s = "Hello, World!".toObj() val scope = Scope() - encoder.packObject(scope, s) // 1 - encoder.packObject(scope, s) - encoder.packObject(scope, s) - encoder.packObject(scope, s) - encoder.packObject(scope, s) - encoder.packObject(scope, s) - encoder.packObject(scope, s) - encoder.packObject(scope, s) // 8 + encoder.encodeObj(scope, s) // 1 + encoder.encodeObj(scope, s) + encoder.encodeObj(scope, s) + encoder.encodeObj(scope, s) + encoder.encodeObj(scope, s) + encoder.encodeObj(scope, s) + encoder.encodeObj(scope, s) + encoder.encodeObj(scope, s) // 8 val decoder = LynonDecoder(MemoryBitInput(bout)) val s1 = decoder.unpackObject(scope, ObjString.type) // 1 @@ -122,7 +122,7 @@ class LynonTests { val encoder = LynonPacker() val scope = Scope() for (s in source) { - encoder.packObject(scope, s) + encoder.encodeObj(scope, s) } val decoder = LynonUnpacker(encoder.toUByteArray()) val restored = mutableListOf() @@ -136,10 +136,10 @@ class LynonTests { fun testUnpackBoolean() = runTest { val scope = Scope() val decoder = LynonUnpacker(LynonPacker().apply { - packObject(scope, ObjBool(true)) - packObject(scope, ObjBool(false)) - packObject(scope, ObjBool(true)) - packObject(scope, ObjBool(true)) + encodeObj(scope, ObjBool(true)) + encodeObj(scope, ObjBool(false)) + encodeObj(scope, ObjBool(true)) + encodeObj(scope, ObjBool(true)) }.toUByteArray()) assertEquals(ObjTrue, decoder.unpackObject(scope, ObjBool.type)) assertEquals(ObjFalse, decoder.unpackObject(scope, ObjBool.type)) @@ -151,15 +151,15 @@ class LynonTests { fun testUnpackReal() = runTest { val scope = Scope() val decoder = LynonUnpacker(LynonPacker().apply { - packObject(scope, ObjReal(-Math.PI)) - packObject(scope, ObjReal(Math.PI)) - packObject(scope, ObjReal(-Math.PI)) - packObject(scope, ObjReal(Math.PI)) - packObject(scope, ObjReal(Double.NaN)) - packObject(scope, ObjReal(Double.NEGATIVE_INFINITY)) - packObject(scope, ObjReal(Double.POSITIVE_INFINITY)) - packObject(scope, ObjReal(Double.MIN_VALUE)) - packObject(scope, ObjReal(Double.MAX_VALUE)) + encodeObj(scope, ObjReal(-Math.PI)) + encodeObj(scope, ObjReal(Math.PI)) + encodeObj(scope, ObjReal(-Math.PI)) + encodeObj(scope, ObjReal(Math.PI)) + encodeObj(scope, ObjReal(Double.NaN)) + encodeObj(scope, ObjReal(Double.NEGATIVE_INFINITY)) + encodeObj(scope, ObjReal(Double.POSITIVE_INFINITY)) + encodeObj(scope, ObjReal(Double.MIN_VALUE)) + encodeObj(scope, ObjReal(Double.MAX_VALUE)) }.toUByteArray()) assertEquals(ObjReal(-Math.PI), decoder.unpackObject(scope, ObjReal.type)) assertEquals(ObjReal(Math.PI), decoder.unpackObject(scope, ObjReal.type)) @@ -175,12 +175,12 @@ class LynonTests { fun testUnpackInt() = runTest { val scope = Scope() val decoder = LynonUnpacker(LynonPacker().apply { - packObject(scope, ObjInt(0)) - packObject(scope, ObjInt(-1)) - packObject(scope, ObjInt(23)) - packObject(scope, ObjInt(Long.MIN_VALUE)) - packObject(scope, ObjInt(Long.MAX_VALUE)) - packObject(scope, ObjInt(Long.MAX_VALUE)) + encodeObj(scope, ObjInt(0)) + encodeObj(scope, ObjInt(-1)) + encodeObj(scope, ObjInt(23)) + encodeObj(scope, ObjInt(Long.MIN_VALUE)) + encodeObj(scope, ObjInt(Long.MAX_VALUE)) + encodeObj(scope, ObjInt(Long.MAX_VALUE)) }.toUByteArray()) assertEquals(ObjInt(0), decoder.unpackObject(scope, ObjInt.type)) assertEquals(ObjInt(-1), decoder.unpackObject(scope, ObjInt.type))