186 lines
5.5 KiB
Kotlin
186 lines
5.5 KiB
Kotlin
package net.sergeych.lynon
|
|
|
|
import net.sergeych.bintools.ByteChunk
|
|
import net.sergeych.lyng.Scope
|
|
import net.sergeych.lyng.obj.*
|
|
|
|
enum class LynonType(val objClass: ObjClass, val defaultFrequency: Int = 1) {
|
|
Null(ObjNull.objClass, 80),
|
|
Int0(ObjInt.type, 70),
|
|
IntNegative(ObjInt.type, 50),
|
|
IntPositive(ObjInt.type, 100),
|
|
IntSigned(ObjInt.type, 30),
|
|
String(ObjString.type, 100),
|
|
Real(ObjReal.type),
|
|
Bool(ObjBool.type, 80),
|
|
List(ObjList.type, 70),
|
|
Map(ObjMap.type, 40),
|
|
Set(ObjSet.type),
|
|
Buffer(ObjBuffer.type, 50),
|
|
Instant(ObjInstant.type, 30),
|
|
Duration(ObjDuration.type),
|
|
Other(Obj.rootObjectType, 60);
|
|
|
|
fun generalizeTo(other: LynonType): LynonType? {
|
|
if (this == other) return this
|
|
return (if (this.isInt && other.isInt) {
|
|
when {
|
|
this == Int0 -> other // upgrade 0 to some other int
|
|
other == Int0 -> this // 0 is member of our class, ignore
|
|
// different signum propagate to signed
|
|
else -> IntSigned
|
|
}
|
|
} else
|
|
// impossible to generalize
|
|
null
|
|
).also { println("Gen $this + $other -> $it") }
|
|
}
|
|
|
|
val isInt by lazy {
|
|
when (this) {
|
|
Int0, IntSigned, IntPositive, IntNegative -> true
|
|
else -> false
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
open class LynonEncoder(val bout: BitOutput, val settings: LynonSettings = LynonSettings.default) {
|
|
|
|
val cache = mutableMapOf<Any, Int>()
|
|
|
|
suspend fun encodeCached(item: Any, packer: suspend LynonEncoder.() -> Unit) {
|
|
|
|
suspend fun serializeAndCache(key: Any = item) {
|
|
cache[key]?.let { cacheId ->
|
|
// println("encode: Cache hit: ${cacheId}: $item: ${item::class.simpleName}")
|
|
val size = sizeInBits(cache.size)
|
|
bout.putBit(1)
|
|
bout.putBits(cacheId.toULong(), size)
|
|
} ?: run {
|
|
bout.putBit(0)
|
|
if (settings.shouldCache(item)) {
|
|
// println("encode add cache: ${cache.size}: $item: ${item::class.simpleName}")
|
|
packer()
|
|
cache[key] = cache.size
|
|
} else {
|
|
// println("encode but not cache $item")
|
|
packer()
|
|
}
|
|
}
|
|
}
|
|
|
|
when (item) {
|
|
is ByteArray -> serializeAndCache(ByteChunk(item.asUByteArray()))
|
|
is UByteArray -> serializeAndCache(ByteChunk(item))
|
|
else -> serializeAndCache(item)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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, obj: Obj) {
|
|
encodeCached(obj) {
|
|
val type = putTypeRecord(scope, obj, obj.lynonType())
|
|
obj.serialize(scope, this, type)
|
|
}
|
|
}
|
|
|
|
private suspend fun putTypeRecord(scope: Scope, obj: Obj, type: LynonType): LynonType {
|
|
putType(type)
|
|
if( type == LynonType.Other) {
|
|
encodeObject(scope, obj.objClass.classNameObj)
|
|
}
|
|
return type
|
|
}
|
|
|
|
private fun putType(type: LynonType) {
|
|
bout.putBits(type.ordinal.toULong(), 4)
|
|
}
|
|
|
|
/**
|
|
* AnyList could be homogenous (first bit=1) and heterogeneous (first bit=0). Homogenous list
|
|
* has a single type record that precedes the list, heterogeneous hash typed record
|
|
* for each item.
|
|
*
|
|
*/
|
|
suspend fun encodeAnyList(scope: Scope, list: List<Obj>) {
|
|
val objClass = list[0].objClass
|
|
var type = list[0].lynonType()
|
|
var isHomogeneous = true
|
|
for (i in list.drop(1))
|
|
if (i.objClass != objClass) {
|
|
isHomogeneous = false
|
|
break
|
|
} else {
|
|
// same class but type might need generalization
|
|
type = type.generalizeTo(i.lynonType())
|
|
?: scope.raiseError("inner error: can't generalize lynon type $type to ${i.lynonType()}")
|
|
}
|
|
if (isHomogeneous) {
|
|
putBit(1)
|
|
putTypeRecord(scope, list[0], type)
|
|
encodeUnsigned(list.size.toULong())
|
|
for (i in list) encodeObject(scope, i, type)
|
|
} else {
|
|
putBit(0)
|
|
encodeUnsigned(list.size.toULong())
|
|
for (i in list) encodeAny(scope, i)
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Write object _with no type record_: type is known
|
|
*/
|
|
suspend fun encodeObject(scope: Scope, obj: Obj,overrideType: LynonType? = null) {
|
|
encodeCached(obj) {
|
|
obj.serialize(scope, this, overrideType)
|
|
}
|
|
}
|
|
|
|
suspend fun encodeCachedBytes(bytes: ByteArray) {
|
|
encodeCached(bytes) {
|
|
bout.compress(bytes)
|
|
}
|
|
}
|
|
|
|
fun encodeBinaryData(data: ByteArray) {
|
|
bout.compress(data)
|
|
}
|
|
|
|
fun encodeSigned(value: Long) {
|
|
bout.packSigned(value)
|
|
}
|
|
|
|
@Suppress("unused")
|
|
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 encodeBoolean(value: Boolean) {
|
|
bout.putBit(if (value) 1 else 0)
|
|
}
|
|
|
|
fun putBits(value: Int, sizeInBits: Int) {
|
|
bout.putBits(value.toULong(), sizeInBits)
|
|
}
|
|
|
|
fun putBit(bit: Int) {
|
|
bout.putBit(bit)
|
|
}
|
|
|
|
} |