diff --git a/docs/Map.md b/docs/Map.md index 6bfa6b4..c7001aa 100644 --- a/docs/Map.md +++ b/docs/Map.md @@ -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 ) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt index 8ee2665..2d93862 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt @@ -94,6 +94,15 @@ class ObjMap(val map: MutableMap = 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): MutableMap { @@ -121,6 +130,13 @@ class ObjMap(val map: MutableMap = 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) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonDecoder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonDecoder.kt index b431419..0a9db53 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonDecoder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonDecoder.kt @@ -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 { + suspend fun decodeAnyList(scope: Scope,fixedSize: Int?=null): MutableList { 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..) { - 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,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) + } } - } /** diff --git a/lynglib/src/jvmTest/kotlin/LynonTests.kt b/lynglib/src/jvmTest/kotlin/LynonTests.kt index 4c3acfd..c243d8a 100644 --- a/lynglib/src/jvmTest/kotlin/LynonTests.kt +++ b/lynglib/src/jvmTest/kotlin/LynonTests.kt @@ -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()) + } + + }