maps serialization
This commit is contained in:
parent
2339130241
commit
790cce0d24
@ -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`)
|
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 is Map)
|
||||||
assert(map.size == 2)
|
assert(map.size == 2)
|
||||||
assert(map is Iterable)
|
assert(map is Iterable)
|
||||||
>>> void
|
>>> 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:
|
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"] )
|
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()`
|
hold nulls in the map - this is not a recommended practice when using `remove` returned value. `clear()`
|
||||||
removes all.
|
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") )
|
assertEquals( 1, map.remove("foo") )
|
||||||
assert( map.getOrNull("foo") == null)
|
assert( map.getOrNull("foo") == null)
|
||||||
assert( map.size == 2 )
|
assert( map.size == 2 )
|
||||||
|
@ -94,6 +94,15 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
|||||||
return map == other.map
|
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 {
|
companion object {
|
||||||
|
|
||||||
suspend fun listToMap(scope: Scope, list: List<Obj>): MutableMap<Obj, Obj> {
|
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 {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
return ObjMap(listToMap(scope, scope.args.list))
|
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 {
|
}.apply {
|
||||||
addFn("getOrNull") {
|
addFn("getOrNull") {
|
||||||
val key = args.firstAndOnly(pos)
|
val key = args.firstAndOnly(pos)
|
||||||
|
@ -56,7 +56,7 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
|
|||||||
} ?: scope.raiseSymbolNotFound("can't deserialize: not found type $className")
|
} ?: 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) {
|
return if (bin.getBit() == 1) {
|
||||||
// homogenous
|
// homogenous
|
||||||
val type = LynonType.entries[getBitsAsInt(4)]
|
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)
|
val objClass = if (type == LynonType.Other)
|
||||||
decodeClassObj(scope).also { println("detected class obj: $it") }
|
decodeClassObj(scope).also { println("detected class obj: $it") }
|
||||||
else type.objClass
|
else type.objClass
|
||||||
val size = bin.unpackUnsigned().toInt()
|
val size = fixedSize ?: bin.unpackUnsigned().toInt()
|
||||||
println("detected homogenous list type $type, $size items")
|
println("detected homogenous list type $type, $size items")
|
||||||
for (i in 0..<size) {
|
for (i in 0..<size) {
|
||||||
list += decodeObject(scope, objClass, type).also {
|
list += decodeObject(scope, objClass, type).also {
|
||||||
@ -73,7 +73,7 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
|
|||||||
}
|
}
|
||||||
list
|
list
|
||||||
} else {
|
} else {
|
||||||
val size = unpackUnsigned().toInt()
|
val size = fixedSize ?: unpackUnsigned().toInt()
|
||||||
(0..<size).map { decodeAny(scope) }.toMutableList()
|
(0..<size).map { decodeAny(scope) }.toMutableList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,30 +108,38 @@ open class LynonEncoder(val bout: BitOutput, val settings: LynonSettings = Lynon
|
|||||||
* for each item.
|
* for each item.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
suspend fun encodeAnyList(scope: Scope, list: List<Obj>) {
|
suspend fun encodeAnyList(scope: Scope, list: List<Obj>,fixedSize: Boolean = false) {
|
||||||
val objClass = list[0].objClass
|
if( list.isEmpty()) {
|
||||||
var type = list[0].lynonType()
|
// any-typed, empty, save space
|
||||||
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)
|
putBit(0)
|
||||||
encodeUnsigned(list.size.toULong())
|
encodeUnsigned(0u)
|
||||||
for (i in list) encodeAny(scope, i)
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -508,6 +508,7 @@ class LynonTests {
|
|||||||
testEncode(["the", "the", "wall", "the", "wall", "wall"])
|
testEncode(["the", "the", "wall", "the", "wall", "wall"])
|
||||||
testEncode([1,2,3, "the", "wall", "wall"])
|
testEncode([1,2,3, "the", "wall", "wall"])
|
||||||
testEncode([false, false, false, true,true])
|
testEncode([false, false, false, true,true])
|
||||||
|
testEncode([])
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -534,6 +535,28 @@ class LynonTests {
|
|||||||
testEncode( [1 => "one", 1 => "one", "foo" => "MapEntry"] )
|
testEncode( [1 => "one", 1 => "one", "foo" => "MapEntry"] )
|
||||||
""".trimIndent())
|
""".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())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user