Compare commits

...

12 Commits

Author SHA1 Message Date
5d8fdce637 Move qualified identifier resolution to Scope as resolveQualifiedIdentifier, replace inline logic in LynonDecoder. 2025-12-14 00:20:43 +01:00
5a8881bfd5 Refactor decodeClassObj to mimic compiler behavior for qualified names, add evaluateQualifiedNameAsCompiled. 2025-12-14 00:06:46 +01:00
d487886c8f some more trace on strange decpdeClassObj behavior 2025-12-13 23:41:52 +01:00
180471e4cd Merge remote-tracking branch 'origin/fix_decodeClassObj' 2025-12-13 23:20:55 +01:00
71a37a2906 Revert "Improve decodeClassObj class resolution in LynonDecoder, add fallback lookup mechanisms, and refine related tests"
This reverts commit dd1a1544c6d49641783d221b15e23c7150010161.
2025-12-13 23:14:12 +01:00
ab05f83e77 Revert "Add documentation for Lynon class-name resolution behavior and future plans for fully-qualified name support"
This reverts commit a2d26fc7775508c4fc9c5bb62496ad4b88d74662.
2025-12-13 23:13:56 +01:00
9e11519608 revert to wirking ugly fix for decodeClassObj 2025-12-13 23:11:28 +01:00
a2d26fc777 Add documentation for Lynon class-name resolution behavior and future plans for fully-qualified name support 2025-12-13 17:16:10 +01:00
dd1a1544c6 Improve decodeClassObj class resolution in LynonDecoder, add fallback lookup mechanisms, and refine related tests 2025-12-13 17:12:44 +01:00
fba44622e5 Refactor toString implementations to support Scope context, add inspect, and improve assertions readability. 2025-12-13 13:48:57 +01:00
2737aaa14e add mapNotNull to ObjIterable with documentation 2025-12-12 13:48:02 +01:00
bce88ced43 Merge pull request 'fix/scope-parent-cycle' (#92) from fix/scope-parent-cycle into master
Reviewed-on: #92
2025-12-11 03:09:45 +03:00
11 changed files with 147 additions and 27 deletions

View File

@ -0,0 +1,28 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Tests in 'lyng.lynglib.jvmTest'" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":lynglib:cleanJvmTest" />
<option value=":lynglib:jvmTest" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<ExternalSystemDebugDisabled>false</ExternalSystemDebugDisabled>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>true</RunAsTest>
<GradleProfilingDisabled>false</GradleProfilingDisabled>
<GradleCoverageDisabled>false</GradleCoverageDisabled>
<method v="2" />
</configuration>
</component>

View File

@ -68,6 +68,20 @@ These, again, does the thing:
>>> void
## map and mapNotNull
Used to transform either the whole iterable stream or also skipping som elements from it:
val source = [1,2,3,4]
// transform every element to string or null:
assertEquals(["n1", "n2", null, "n4"], source.map { if( it == 3 ) null else "n"+it } )
// transform every element to stirng, skipping 3:
assertEquals(["n1", "n2", "n4"], source.mapNotNull { if( it == 3 ) null else "n"+it } )
>>> void
## Instance methods:
| fun/method | description |

View File

@ -516,6 +516,23 @@ open class Scope(
open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure)
/**
* Resolve and evaluate a qualified identifier exactly as compiled code would.
* For input like `A.B.C`, it builds the same ObjRef chain the compiler emits:
* `LocalVarRef("A", Pos.builtIn)` followed by `FieldRef` for each segment, then evaluates it.
* This mirrors `eval("A.B.C")` resolution semantics without invoking the compiler.
*/
suspend fun resolveQualifiedIdentifier(qualifiedName: String): Obj {
val trimmed = qualifiedName.trim()
if (trimmed.isEmpty()) raiseSymbolNotFound("empty identifier")
val parts = trimmed.split('.')
var ref: ObjRef = LocalVarRef(parts[0], Pos.builtIn)
for (i in 1 until parts.size) {
ref = FieldRef(ref, parts[i], false)
}
return ref.evalValue(this)
}
companion object {
fun new(): Scope =

View File

@ -173,31 +173,44 @@ class Script(
val a = requiredArg<Obj>(0)
val b = requiredArg<Obj>(1)
if (a.compareTo(this, b) != 0)
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"))
raiseError(
ObjAssertionFailedException(
this,
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
)
)
}
// alias used in tests
addVoidFn("assertEqual") {
val a = requiredArg<Obj>(0)
val b = requiredArg<Obj>(1)
if (a.compareTo(this, b) != 0)
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"))
raiseError(
ObjAssertionFailedException(
this,
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
)
)
}
addVoidFn("assertNotEquals") {
val a = requiredArg<Obj>(0)
val b = requiredArg<Obj>(1)
if (a.compareTo(this, b) == 0)
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}"))
raiseError(
ObjAssertionFailedException(
this,
"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}"
)
)
}
addFn("assertThrows") {
val code = requireOnlyArg<Statement>()
val result = try {
code.execute(this)
null
}
catch( e: ExecutionError ) {
} catch (e: ExecutionError) {
e.errorObject
}
catch (_: ScriptError) {
} catch (_: ScriptError) {
ObjNull
}
result ?: raiseError(ObjAssertionFailedException(this, "Expected exception but nothing was thrown"))

View File

@ -131,7 +131,7 @@ object CompletionEngineLight {
}
is MiniClassDecl -> add(CompletionItem(d.name, Kind.Class_))
is MiniValDecl -> add(CompletionItem(d.name, Kind.Value, typeText = typeOf(d.type)))
else -> add(CompletionItem(d.name, Kind.Value))
// else -> add(CompletionItem(d.name, Kind.Value))
}
}

View File

@ -197,9 +197,20 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
!it.key.contains("::") && it.value.visibility.isPublic && it.value.type.serializable
}
override fun toString(): String {
val fields = publicFields.map { "${it.key}=${it.value.value}" }.joinToString(",")
return "${objClass.className}($fields)"
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
return ObjString(buildString {
append("${objClass.className}(")
var first = true
for ((name, value) in publicFields) {
if (first) first = false else append(",")
append("$name=${value.value.toString(scope)}")
}
append(")")
})
}
override suspend fun inspect(scope: Scope): String {
return toString(scope).value
}
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {

View File

@ -158,6 +158,23 @@ val ObjIterable by lazy {
ObjList(result)
}
addFnDoc(
name = "mapNotNull",
doc = "Transform elements by applying the given lambda unless it returns null.",
params = listOf(ParamDoc("transform")),
returns = type("lyng.List"),
isOpen = true,
moduleName = "lyng.stdlib"
) {
val fn = requiredArg<Statement>(0)
val result = mutableListOf<Obj>()
thisObj.toFlow(this).collect {
val transformed = fn.call(this, it)
if( transformed != ObjNull) result += transformed
}
ObjList(result)
}
addFnDoc(
name = "take",
doc = "Take the first N elements and return them as a list.",

View File

@ -198,6 +198,18 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
return JsonArray(list.map { it.toJson(scope) })
}
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
return ObjString(buildString {
append("[")
var first = true
for (v in list) {
if (first) first = false else append(",")
append(v.toString(scope).value)
}
append("]")
})
}
companion object {
val type = object : ObjClass("List", ObjArray) {
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {

View File

@ -52,8 +52,8 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
else -> scope.raiseIndexOutOfBounds()
}
override fun toString(): String {
return "$key=>$value"
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
return ObjString("(${key.toString(scope).value} => ${value.toString(scope).value})")
}
override val objClass = type

View File

@ -1625,7 +1625,7 @@ class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef {
when (elements) {
is ObjList -> {
// Grow underlying array once when possible
if (list is ArrayList) list.ensureCapacity(list.size + elements.list.size)
list.ensureCapacity(list.size + elements.list.size)
list.addAll(elements.list)
}
else -> scope.raiseError("Spread element must be list")

View File

@ -82,8 +82,16 @@ open class LynonDecoder(val bin: BitInput, val settings: LynonSettings = LynonSe
if (it !is ObjClass)
scope.raiseClassCastError("Expected obj class but got ${it::class.simpleName}")
it
} ?: scope.raiseSymbolNotFound("can't deserialize: not found type $className")
} ?: run {
// Use Scope API that mirrors compiler-emitted ObjRef chain for qualified identifiers
val evaluated = scope.resolveQualifiedIdentifier(className.value)
if (evaluated !is ObjClass)
scope.raiseClassCastError("Expected obj class but got ${evaluated::class.simpleName}")
evaluated
}
}
// helper moved to Scope as resolveQualifiedIdentifier
suspend fun decodeAnyList(scope: Scope, fixedSize: Int? = null): MutableList<Obj> {
return if (bin.getBit() == 1) {