maps serialization

This commit is contained in:
Sergey Chernov 2025-08-03 15:06:46 +03:00
parent 2339130241
commit 790cce0d24
5 changed files with 76 additions and 27 deletions

View File

@ -7,12 +7,14 @@ Important thing is that maps can't contain `null`: it is used to return from mis
Constructed map instance is of class `Map` and implements `Collection` (and therefore `Iterable`)
val map = Map( ["foo", 1], ["bar", "buzz"] )
val map = Map( "foo" => 1, "bar" => "buzz" )
assert(map is Map)
assert(map.size == 2)
assert(map is Iterable)
>>> void
Notice usage of the `=>` operator that creates `MapEntry`, which implements also [Collection] of
two items, first, at index zero, is a key, second, at index 1, is the value. You can use lists too.
Map keys could be any objects (hashable, e.g. with reasonable hashCode, most of standard types are). You can access elements with indexing operator:
val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
@ -31,7 +33,7 @@ To remove item from the collection. use `remove`. It returns last removed item o
hold nulls in the map - this is not a recommended practice when using `remove` returned value. `clear()`
removes all.
val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
val map = Map( "foo" => 1, "bar" => "buzz", [42, "answer"] )
assertEquals( 1, map.remove("foo") )
assert( map.getOrNull("foo") == null)
assert( map.size == 2 )

View File

@ -94,6 +94,15 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
return map == other.map
}
override suspend fun lynonType(): LynonType = LynonType.Map
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
val keys = map.keys.map { it.toObj() }
val values = map.values.map { it.toObj() }
encoder.encodeAnyList(scope, keys)
encoder.encodeAnyList(scope, values, fixedSize = true)
}
companion object {
suspend fun listToMap(scope: Scope, list: List<Obj>): MutableMap<Obj, Obj> {
@ -121,6 +130,13 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
override suspend fun callOn(scope: Scope): Obj {
return ObjMap(listToMap(scope, scope.args.list))
}
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
val keys = decoder.decodeAnyList(scope)
val values = decoder.decodeAnyList(scope,fixedSize = keys.size)
if( keys.size != values.size) scope.raiseIllegalArgument("map keys and values should be same size")
return ObjMap(keys.zip(values).toMap().toMutableMap())
}
}.apply {
addFn("getOrNull") {
val key = args.firstAndOnly(pos)

View File

@ -56,7 +56,7 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
} ?: scope.raiseSymbolNotFound("can't deserialize: not found type $className")
}
suspend fun decodeAnyList(scope: Scope): MutableList<Obj> {
suspend fun decodeAnyList(scope: Scope,fixedSize: Int?=null): MutableList<Obj> {
return if (bin.getBit() == 1) {
// homogenous
val type = LynonType.entries[getBitsAsInt(4)]
@ -64,7 +64,7 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
val objClass = if (type == LynonType.Other)
decodeClassObj(scope).also { println("detected class obj: $it") }
else type.objClass
val size = bin.unpackUnsigned().toInt()
val size = fixedSize ?: bin.unpackUnsigned().toInt()
println("detected homogenous list type $type, $size items")
for (i in 0..<size) {
list += decodeObject(scope, objClass, type).also {
@ -73,7 +73,7 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
}
list
} else {
val size = unpackUnsigned().toInt()
val size = fixedSize ?: unpackUnsigned().toInt()
(0..<size).map { decodeAny(scope) }.toMutableList()
}
}

View File

@ -108,30 +108,38 @@ open class LynonEncoder(val bout: BitOutput, val settings: LynonSettings = Lynon
* 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 {
suspend fun encodeAnyList(scope: Scope, list: List<Obj>,fixedSize: Boolean = false) {
if( list.isEmpty()) {
// any-typed, empty, save space
putBit(0)
encodeUnsigned(list.size.toULong())
for (i in list) encodeAny(scope, i)
encodeUnsigned(0u)
}
else {
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)
if (!fixedSize)
encodeUnsigned(list.size.toULong())
for (i in list) encodeObject(scope, i, type)
} else {
putBit(0)
if (!fixedSize)
encodeUnsigned(list.size.toULong())
for (i in list) encodeAny(scope, i)
}
}
}
/**

View File

@ -508,6 +508,7 @@ class LynonTests {
testEncode(["the", "the", "wall", "the", "wall", "wall"])
testEncode([1,2,3, "the", "wall", "wall"])
testEncode([false, false, false, true,true])
testEncode([])
""".trimIndent()
)
}
@ -534,6 +535,28 @@ class LynonTests {
testEncode( [1 => "one", 1 => "one", "foo" => "MapEntry"] )
""".trimIndent())
}
@Test
fun testHomogenousMap() = runTest {
val s = testScope()
s.eval("""
testEncode( Map("one" => 1, "two" => 2) )
testEncode( Map() )
""".trimIndent())
}
@Test
fun testHeterogeneousMap() = runTest {
val s = testScope()
s.eval("""
// testEncode(["one", 2])
// testEncode([1, "2"])
// testEncode( Map("one" => 1, 2 => 2) )
testEncode( Map("one" => 1, 2 => "2") )
""".trimIndent())
}
}