trivial implementation of class name = based serialization (LynonType.Other)

This commit is contained in:
Sergey Chernov 2025-08-03 14:42:23 +03:00
parent d7bd159fcb
commit 2339130241
5 changed files with 132 additions and 49 deletions

View File

@ -11,6 +11,8 @@ open class ObjClass(
vararg parents: ObjClass, vararg parents: ObjClass,
) : Obj() { ) : Obj() {
val classNameObj by lazy { ObjString(className) }
var constructorMeta: ArgsDeclaration? = null var constructorMeta: ArgsDeclaration? = null
var instanceConstructor: Statement? = null var instanceConstructor: Statement? = null
@ -100,6 +102,7 @@ open class ObjClass(
} }
open suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj = scope.raiseNotImplemented() open suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj = scope.raiseNotImplemented()
} }

View File

@ -2,6 +2,9 @@ package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement import net.sergeych.lyng.Statement
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType
class ObjMapEntry(val key: Obj, val value: Obj) : Obj() { class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
@ -12,6 +15,14 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
return value.compareTo(scope, other.value) return value.compareTo(scope, other.value)
} }
override fun hashCode(): Int {
return key.hashCode() + value.hashCode()
}
override fun equals(other: Any?): Boolean {
return other is ObjMapEntry && key == other.key && value == other.value
}
override suspend fun getAt(scope: Scope, index: Obj): Obj = when (index.toInt()) { override suspend fun getAt(scope: Scope, index: Obj): Obj = when (index.toInt()) {
0 -> key 0 -> key
1 -> value 1 -> value
@ -24,11 +35,23 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
override val objClass = type override val objClass = type
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
encoder.encodeAny(scope,key)
encoder.encodeAny(scope,value)
}
companion object { companion object {
val type = object : ObjClass("MapEntry", ObjArray) { val type = object : ObjClass("MapEntry", ObjArray) {
override suspend fun callOn(scope: Scope): Obj { override suspend fun callOn(scope: Scope): Obj {
return ObjMapEntry(scope.requiredArg<Obj>(0), scope.requiredArg<Obj>(1)) return ObjMapEntry(scope.requiredArg<Obj>(0), scope.requiredArg<Obj>(1))
} }
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
return ObjMapEntry(
decoder.decodeAny(scope),
decoder.decodeAny(scope)
)
}
}.apply { }.apply {
addFn("key") { thisAs<ObjMapEntry>().key } addFn("key") { thisAs<ObjMapEntry>().key }
addFn("value") { thisAs<ObjMapEntry>().value } addFn("value") { thisAs<ObjMapEntry>().value }

View File

@ -3,6 +3,7 @@ package net.sergeych.lynon
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjString
open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSettings.default) { open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSettings.default) {
@ -17,7 +18,7 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
val cache = mutableListOf<Any>() val cache = mutableListOf<Any>()
inline fun <reified T : Any>decodeCached(f: LynonDecoder.() -> T): T { inline fun <reified T : Any> decodeCached(f: LynonDecoder.() -> T): T {
return if (bin.getBit() == 0) { return if (bin.getBit() == 0) {
// unpack and cache // unpack and cache
f().also { f().also {
@ -38,31 +39,46 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
suspend fun decodeAny(scope: Scope): Obj = decodeCached { suspend fun decodeAny(scope: Scope): Obj = decodeCached {
val type = LynonType.entries[bin.getBits(4).toInt()] val type = LynonType.entries[bin.getBits(4).toInt()]
if (type != LynonType.Other) {
type.objClass.deserialize(scope, this, type) type.objClass.deserialize(scope, this, type)
} else {
decodeClassObj(scope).deserialize(scope, this, null)
}
}
private suspend fun decodeClassObj(scope: Scope): ObjClass {
val className = decodeObject(scope, ObjString.type, null) as ObjString
println("expected class name $className")
return scope.get(className.value)?.value?.let {
if (it !is ObjClass)
scope.raiseClassCastError("Expected obj class but got ${it::class.qualifiedName}")
it
} ?: scope.raiseSymbolNotFound("can't deserialize: not found type $className")
} }
suspend fun decodeAnyList(scope: Scope): MutableList<Obj> { suspend fun decodeAnyList(scope: Scope): 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)]
val list = mutableListOf<Obj>()
val objClass = if (type == LynonType.Other)
decodeClassObj(scope).also { println("detected class obj: $it") }
else type.objClass
val size = bin.unpackUnsigned().toInt() val size = bin.unpackUnsigned().toInt()
println("detected homogenous list type $type, $size items") println("detected homogenous list type $type, $size items")
val list = mutableListOf<Obj>() for (i in 0..<size) {
val objClass = type.objClass
for( i in 0 ..< size) {
list += decodeObject(scope, objClass, type).also { list += decodeObject(scope, objClass, type).also {
println("decoded: $it") println("decoded: $it")
} }
} }
list list
} } else {
else {
val size = unpackUnsigned().toInt() val size = unpackUnsigned().toInt()
(0..<size).map { decodeAny(scope) }.toMutableList() (0..<size).map { decodeAny(scope) }.toMutableList()
} }
} }
suspend fun decodeObject(scope: Scope, type: ObjClass,overrideType: LynonType?=null): Obj { suspend fun decodeObject(scope: Scope, type: ObjClass, overrideType: LynonType? = null): Obj {
return decodeCached { type.deserialize(scope, this, overrideType) } return decodeCached { type.deserialize(scope, this, overrideType) }
} }

View File

@ -85,13 +85,16 @@ open class LynonEncoder(val bout: BitOutput, val settings: LynonSettings = Lynon
*/ */
suspend fun encodeAny(scope: Scope, obj: Obj) { suspend fun encodeAny(scope: Scope, obj: Obj) {
encodeCached(obj) { encodeCached(obj) {
val type = putTypeRecord(obj, obj.lynonType()) val type = putTypeRecord(scope, obj, obj.lynonType())
obj.serialize(scope, this, type) obj.serialize(scope, this, type)
} }
} }
private fun putTypeRecord(obj: Obj, type: LynonType): LynonType { private suspend fun putTypeRecord(scope: Scope, obj: Obj, type: LynonType): LynonType {
putType(type) putType(type)
if( type == LynonType.Other) {
encodeObject(scope, obj.objClass.classNameObj)
}
return type return type
} }
@ -120,7 +123,7 @@ open class LynonEncoder(val bout: BitOutput, val settings: LynonSettings = Lynon
} }
if (isHomogeneous) { if (isHomogeneous) {
putBit(1) putBit(1)
putTypeRecord(list[0], type) putTypeRecord(scope, list[0], type)
encodeUnsigned(list.size.toULong()) encodeUnsigned(list.size.toULong())
for (i in list) encodeObject(scope, i, type) for (i in list) encodeObject(scope, i, type)
} else { } else {

View File

@ -11,6 +11,7 @@ import java.nio.file.Path
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertContentEquals import kotlin.test.assertContentEquals
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue
class LynonTests { class LynonTests {
@ -56,6 +57,7 @@ class LynonTests {
assertEquals(1, bin.getBit()) assertEquals(1, bin.getBit())
assertEquals(null, bin.getBitOrNull()) assertEquals(null, bin.getBitOrNull())
} }
@Test @Test
fun testBitOutputMedium() { fun testBitOutputMedium() {
val bout = MemoryBitOutput() val bout = MemoryBitOutput()
@ -63,8 +65,8 @@ class LynonTests {
bout.putBit(1) bout.putBit(1)
bout.putBit(0) bout.putBit(0)
bout.putBit(1) bout.putBit(1)
bout.putBits( 0, 7) bout.putBits(0, 7)
bout.putBits( 3, 2) bout.putBits(3, 2)
val x = bout.toBitArray() val x = bout.toBitArray()
assertEquals(1, x[0]) assertEquals(1, x[0])
assertEquals(1, x[1]) assertEquals(1, x[1])
@ -243,6 +245,7 @@ class LynonTests {
assertEquals(ObjReal(Double.MIN_VALUE), decoder.decodeObject(scope, ObjReal.type)) assertEquals(ObjReal(Double.MIN_VALUE), decoder.decodeObject(scope, ObjReal.type))
assertEquals(ObjReal(Double.MAX_VALUE), decoder.decodeObject(scope, ObjReal.type)) assertEquals(ObjReal(Double.MAX_VALUE), decoder.decodeObject(scope, ObjReal.type))
} }
@Test @Test
fun testUnpackInt() = runTest { fun testUnpackInt() = runTest {
val scope = Scope() val scope = Scope()
@ -284,15 +287,17 @@ class LynonTests {
val original = Files.readString(Path.of("../sample_texts/dikkens_hard_times.txt")) val original = Files.readString(Path.of("../sample_texts/dikkens_hard_times.txt"))
@Test @Test
fun testEncodeNullsAndInts() = runTest{ fun testEncodeNullsAndInts() = runTest {
testScope().eval(""" testScope().eval(
"""
testEncode(null) testEncode(null)
testEncode(0) testEncode(0)
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
fun testBufferEncoderInterop() = runTest{ fun testBufferEncoderInterop() = runTest {
val bout = MemoryBitOutput() val bout = MemoryBitOutput()
bout.putBits(0, 1) bout.putBits(0, 1)
bout.putBits(1, 4) bout.putBits(1, 4)
@ -302,7 +307,9 @@ class LynonTests {
} }
suspend fun testScope() = suspend fun testScope() =
Scope().apply { eval(""" Scope().apply {
eval(
"""
import lyng.serialization import lyng.serialization
fun testEncode(value) { fun testEncode(value) {
val encoded = Lynon.encode(value) val encoded = Lynon.encode(value)
@ -310,21 +317,25 @@ class LynonTests {
println("Encoded size %d: %s"(encoded.size, value)) println("Encoded size %d: %s"(encoded.size, value))
assertEquals( value, Lynon.decode(encoded) ) assertEquals( value, Lynon.decode(encoded) )
} }
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
fun testUnaryMinus() = runTest{ fun testUnaryMinus() = runTest {
eval(""" eval(
"""
assertEquals( -1 * π, 0 - π ) assertEquals( -1 * π, 0 - π )
assertEquals( -1 * π, -π ) assertEquals( -1 * π, -π )
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
fun testSimpleTypes() = runTest{ fun testSimpleTypes() = runTest {
testScope().eval(""" testScope().eval(
"""
testEncode(null) testEncode(null)
testEncode(0) testEncode(0)
testEncode(47) testEncode(47)
@ -342,7 +353,8 @@ class LynonTests {
testEncode("Hello, world".encodeUtf8()) testEncode("Hello, world".encodeUtf8())
testEncode("Hello, world") testEncode("Hello, world")
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
@ -383,7 +395,7 @@ class LynonTests {
assertEquals("011101", a0.toString()) assertEquals("011101", a0.toString())
val bin = MemoryBitInput(MemoryBitOutput().apply { putBits(a0) }) val bin = MemoryBitInput(MemoryBitOutput().apply { putBits(a0) })
var result = TinyBits() var result = TinyBits()
for( i in a0.indices) result = result.insertBit(bin.getBit()) for (i in a0.indices) result = result.insertBit(bin.getBit())
assertEquals(a0, result) assertEquals(a0, result)
} }
@ -399,8 +411,8 @@ class LynonTests {
println("Huffman : ${huff.size}") println("Huffman : ${huff.size}")
val lzwhuff = Huffman.compress(lzw, Huffman.byteAlphabet).bytes val lzwhuff = Huffman.compress(lzw, Huffman.byteAlphabet).bytes
println("LZW+HUFF : ${lzwhuff.size}") println("LZW+HUFF : ${lzwhuff.size}")
val compressed = Huffman.compress(x,Huffman.byteAlphabet) val compressed = Huffman.compress(x, Huffman.byteAlphabet)
val decompressed = Huffman.decompress(compressed.toBitInput(),Huffman.byteAlphabet) val decompressed = Huffman.decompress(compressed.toBitInput(), Huffman.byteAlphabet)
assertContentEquals(x, decompressed) assertContentEquals(x, decompressed)
} }
@ -420,7 +432,7 @@ class LynonTests {
override fun ordinalOf(value: LynonType): Int = value.ordinal override fun ordinalOf(value: LynonType): Int = value.ordinal
} }
for(code in Huffman.generateCanonicalCodes(frequencies, alphabet)) { for (code in Huffman.generateCanonicalCodes(frequencies, alphabet)) {
println("${code?.bits}: ${code?.ordinal?.let { LynonType.entries[it] }}") println("${code?.bits}: ${code?.ordinal?.let { LynonType.entries[it] }}")
} }
} }
@ -428,19 +440,19 @@ class LynonTests {
@Test @Test
fun testBitListSmall() { fun testBitListSmall() {
var t = TinyBits() var t = TinyBits()
for( i in listOf(1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1) ) for (i in listOf(1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1))
t = t.insertBit(i) t = t.insertBit(i)
assertEquals(1, t[0]) assertEquals(1, t[0])
assertEquals(1, t[1]) assertEquals(1, t[1])
assertEquals(0, t[2]) assertEquals(0, t[2])
assertEquals("1101000111101",t.toString()) assertEquals("1101000111101", t.toString())
t[0] = 0 t[0] = 0
t[1] = 0 t[1] = 0
t[2] = 1 t[2] = 1
assertEquals("0011000111101",t.toString()) assertEquals("0011000111101", t.toString())
t[12] = 0 t[12] = 0
t[11] = 1 t[11] = 1
assertEquals("0011000111110",t.toString()) assertEquals("0011000111110", t.toString())
} }
@Test @Test
@ -449,10 +461,10 @@ class LynonTests {
val bout = MemoryBitOutput() val bout = MemoryBitOutput()
assertEquals("1101", bitListOf(1, 1, 0, 1).toString()) assertEquals("1101", bitListOf(1, 1, 0, 1).toString())
bout.putBits(bitListOf(1, 1, 0, 1)) bout.putBits(bitListOf(1, 1, 0, 1))
bout.putBits(bitListOf( 0, 0)) bout.putBits(bitListOf(0, 0))
bout.putBits(bitListOf( 0, 1, 1, 1, 1, 0, 1)) bout.putBits(bitListOf(0, 1, 1, 1, 1, 0, 1))
val x = bout.toBitArray() val x = bout.toBitArray()
assertEquals("1101000111101",x.toString()) assertEquals("1101000111101", x.toString())
} }
@ -486,17 +498,43 @@ class LynonTests {
@Test @Test
fun testIntList() = runTest { fun testIntList() = runTest {
testScope().eval(""" testScope().eval(
// testEncode([1,2,3]) """
// testEncode([-1,-2,-3]) testEncode([1,2,3])
// testEncode([1,-2,-3]) testEncode([-1,-2,-3])
// testEncode([0,1]) testEncode([1,-2,-3])
// testEncode([0,0,0]) testEncode([0,1])
// testEncode(["the", "the", "wall", "the", "wall", "wall"]) testEncode([0,0,0])
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])
""".trimIndent()) """.trimIndent()
)
} }
@Test
fun testNamedObject() = runTest {
val s = testScope()
val x = s.eval("""5 => 6""")
assert(x is ObjMapEntry)
val bout = MemoryBitOutput()
val e = LynonEncoder(bout)
e.encodeAny(s, x)
val bin = bout.toBitInput()
val d = LynonDecoder(bin)
val x2 = d.decodeAny(s)
println(x)
println(x2)
assertTrue { x.compareTo(s, x2) == 0}
assertEquals(x, x2)
s.eval("""
testEncode( 1 => "one" )
testEncode( [1 => "one", 1 => "one"] )
testEncode( [1 => "one", 1 => "one", "foo" => "MapEntry"] )
""".trimIndent())
}
} }