diff --git a/.run/lyng_site [jsBrowserDevelopmentRun].run.xml b/.run/lyng_site [jsBrowserDevelopmentRun].run.xml
index 548e319..5518148 100644
--- a/.run/lyng_site [jsBrowserDevelopmentRun].run.xml
+++ b/.run/lyng_site [jsBrowserDevelopmentRun].run.xml
@@ -1,3 +1,20 @@
+
+
@@ -17,8 +34,11 @@
true
true
+ false
false
false
+ true
+ true
\ No newline at end of file
diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonDecoder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonDecoder.kt
index 6e230d1..40d3268 100644
--- a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonDecoder.kt
+++ b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/LynonDecoder.kt
@@ -78,15 +78,72 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
private suspend fun decodeClassObj(scope: Scope): ObjClass {
val className = decodeObject(scope, ObjString.type, null) as ObjString
- return scope.get(className.value)?.value?.let {
+ val name = className.value
+ // 1) Try direct lookup in this scope (locals/parents/this members)
+ scope.get(name)?.value?.let {
if (it !is ObjClass)
scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
- it
- } ?: scope.also {
- println("Class not found: $className")
- println("::: ${runCatching { scope.eval("Vault")}.getOrNull() }")
- println("::2 [${className}]: ${scope.get(className.value)}")
- }.raiseSymbolNotFound("can't deserialize: not found type $className")
+ return it
+ }
+ // 2) Try ancestry lookup including instance/class members, but without invoking overridden get
+ scope.chainLookupWithMembers(name)?.value?.let {
+ if (it !is ObjClass)
+ scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
+ return it
+ }
+ // 3) Try to find nearest ModuleScope and check its locals and its parent (root) locals
+ run {
+ var s: Scope? = scope
+ val visited = HashSet(4)
+ while (s != null) {
+ if (!visited.add(s.frameId)) break
+ if (s is net.sergeych.lyng.ModuleScope) {
+ s.objects[name]?.value?.let {
+ if (it !is ObjClass)
+ scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
+ return it
+ }
+ s.localBindings[name]?.value?.let {
+ if (it !is ObjClass)
+ scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
+ return it
+ }
+ s.parent?.let { p ->
+ p.objects[name]?.value?.let {
+ if (it !is ObjClass)
+ scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
+ return it
+ }
+ p.localBindings[name]?.value?.let {
+ if (it !is ObjClass)
+ scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
+ return it
+ }
+ p.thisObj.objClass.getInstanceMemberOrNull(name)?.value?.let {
+ if (it !is ObjClass)
+ scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
+ return it
+ }
+ }
+ break
+ }
+ s = s.parent
+ }
+ }
+ // 4) Try ImportProvider root scope globals (e.g., stdlib)
+ runCatching { scope.currentImportProvider.rootScope.objects[name]?.value }.getOrNull()?.let {
+ if (it !is ObjClass)
+ scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
+ return it
+ }
+// // 5) As a final fallback, try to evaluate the name in this scope using the compiler
+// runCatching { scope.eval(name) }.getOrNull()?.let {
+// if (it !is ObjClass)
+// scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
+// return it
+// }
+ // If everything failed, raise an informative error
+ scope.raiseSymbolNotFound("can't deserialize: not found type ${className}")
}
suspend fun decodeAnyList(scope: Scope, fixedSize: Int? = null): MutableList {
diff --git a/lynglib/src/jvmTest/kotlin/LynonTests.kt b/lynglib/src/jvmTest/kotlin/LynonTests.kt
index f597c5d..cf8eb0e 100644
--- a/lynglib/src/jvmTest/kotlin/LynonTests.kt
+++ b/lynglib/src/jvmTest/kotlin/LynonTests.kt
@@ -25,10 +25,7 @@ import net.sergeych.lyng.obj.*
import net.sergeych.lynon.*
import java.nio.file.Files
import java.nio.file.Path
-import kotlin.test.Test
-import kotlin.test.assertContentEquals
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
+import kotlin.test.*
class LynonTests {
@@ -44,6 +41,73 @@ class LynonTests {
assertEquals(3, sizeInTetrades(257u))
}
+ @Ignore("This is not yet implemented")
+ @Test
+ fun decodeClassObj_shouldResolveDottedQualifiedNames() = runTest {
+ // Define nested namespaces and a class with a qualified name via eval
+ val module = net.sergeych.lyng.Script.defaultImportManager.newModule()
+ module.eval(
+ """
+ package ns.sub
+ class Vault() { fun toString() { "ns.sub.Vault" } }
+ """.trimIndent()
+ )
+
+ val child = module.createChildScope(module.pos)
+
+ // Sanity: eval resolves both qualified and unqualified in the same module context
+ val qualified = child.eval("ns.sub.Vault")
+ assertTrue(qualified is ObjClass)
+
+ val inst = child.eval("ns.sub.Vault()")
+ assertTrue(inst is ObjInstance)
+
+ // Encode and decode instance; decoder should resolve class by its encoded name
+ val bout = MemoryBitOutput()
+ val enc = LynonEncoder(bout)
+ enc.encodeAny(child, inst)
+ val bin = MemoryBitInput(bout.toBitArray())
+ val dec = LynonDecoder(bin)
+
+ val decoded = dec.decodeAny(child)
+ assertTrue(decoded is ObjInstance)
+ val decObj = decoded as ObjInstance
+ assertEquals("Vault", decObj.objClass.className)
+ }
+
+ @Test
+ fun decodeClassObj_shouldResolveWhenEvalFindsButGetMisses() = runTest {
+ // Build a module scope and define a class there via eval (simulating imported/user code)
+ val module = net.sergeych.lyng.Script.defaultImportManager.newModule()
+ module.eval("class Vault() { fun toString() { \"Vault\" } }")
+
+ // Build a child scope where local bindings do not include the class name explicitly
+ val child = module.createChildScope(module.pos)
+
+ // Sanity: eval in the child must resolve the class by name
+ val evalResolved = child.eval("Vault")
+ assertTrue(evalResolved is ObjClass)
+
+ // Create an instance so that encoder will write type Other + class name and constructor args
+ val inst = child.eval("Vault()")
+ assertTrue(inst is ObjInstance)
+
+ // Encode the instance and then decode it with our decoder that contains robust class lookup
+ val bout = MemoryBitOutput()
+ val enc = LynonEncoder(bout)
+ enc.encodeAny(child, inst)
+ val bin = MemoryBitInput(bout.toBitArray())
+ val dec = LynonDecoder(bin)
+
+ val decoded = dec.decodeAny(child)
+ assertTrue(decoded is ObjInstance)
+ // Class should be resolvable and preserved
+ val instObj = inst as ObjInstance
+ val decObj = decoded as ObjInstance
+ assertEquals(instObj.objClass.className, decObj.objClass.className)
+ assertEquals("Vault", decObj.objClass.className)
+ }
+
@Test
fun testSizeInBits() {
assertEquals(1, sizeInBits(0u))