fix #94 overridden toString() now is called correctly in known cases, default implementation is still generated for ObjInstances and some other classes

This commit is contained in:
Sergey Chernov 2025-12-22 05:32:19 +01:00
parent b7838b45ec
commit 3acd56f55a
9 changed files with 48 additions and 22 deletions

View File

@ -142,15 +142,19 @@ open class Obj {
* @param calledFromLyng true if called from Lyng's `toString`. Normally this parameter should be ignored,
* but it is used to avoid endless recursion in [Obj.toString] base implementation
*/
open suspend fun toString(scope: Scope,calledFromLyng: Boolean=false): ObjString {
return if (this is ObjString) this
else if( !calledFromLyng ) {
invokeInstanceMethod(scope, "toString") {
ObjString(this.toString())
open suspend fun toString(scope: Scope, calledFromLyng: Boolean = false): ObjString {
if (this is ObjString) return this
return if (!calledFromLyng) {
invokeInstanceMethod(scope, "toString", Arguments.EMPTY) {
defaultToString(scope)
} as ObjString
} else { ObjString(this.toString()) }
} else {
defaultToString(scope)
}
}
open suspend fun defaultToString(scope: Scope): ObjString = ObjString(this.toString())
/**
* Class of the object: definition of member functions (top-level), etc.
* Note that using lazy allows to avoid endless recursion here

View File

@ -89,7 +89,7 @@ open class ObjException(
override val objClass: ObjClass = exceptionClass
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
override suspend fun defaultToString(scope: Scope): ObjString {
val at = getStackTrace().list.firstOrNull()?.toString(scope)
?: ObjString("(unknown)")
return ObjString("${objClass.className}: $message at $at")

View File

@ -187,16 +187,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
!it.key.contains("::") && it.value.visibility.isPublic && it.value.type.serializable
}
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
override suspend fun defaultToString(scope: Scope): 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(")")
})
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 {

View File

@ -43,7 +43,7 @@ class ObjInstanceClass(val name: String, vararg parents: ObjClass) : ObjClass(na
init {
addFn("toString", true) {
ObjString(thisObj.toString())
thisObj.toString(this, true)
}
}

View File

@ -198,7 +198,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
return JsonArray(list.map { it.toJson(scope) })
}
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
override suspend fun defaultToString(scope: Scope): ObjString {
return ObjString(buildString {
append("[")
var first = true

View File

@ -52,7 +52,7 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
else -> scope.raiseIndexOutOfBounds()
}
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
override suspend fun defaultToString(scope: Scope): ObjString {
return ObjString("(${key.toString(scope).value} => ${value.toString(scope).value})")
}
@ -124,7 +124,7 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
return -1
}
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString {
override suspend fun defaultToString(scope: Scope): ObjString {
val reusult = buildString {
append("Map(")
var first = true

View File

@ -29,7 +29,7 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
override val objClass: ObjClass = type
override suspend fun toString(scope: Scope,calledFromLyng: Boolean): ObjString {
override suspend fun defaultToString(scope: Scope): ObjString {
val result = StringBuilder()
result.append("${start?.inspect(scope) ?: '∞'} ..")
if (!isEndInclusive) result.append('<')

View File

@ -103,7 +103,7 @@ class ObjRegexMatch(val match: MatchResult) : Obj() {
)
}
override suspend fun toString(scope: Scope,calledFromLyng: Boolean): ObjString {
override suspend fun defaultToString(scope: Scope): ObjString {
return ObjString("RegexMath(${objRange.toString(scope)},${objGroups.toString(scope)})")
}

View File

@ -4311,6 +4311,28 @@ class ScriptTest {
""".trimIndent())
}
@Test
fun testCustomToStringBug() = runTest {
eval("""
class A(x,y)
class B(x,y) {
fun toString() {
"B(%d,%d)"(x,y)
}
}
assertEquals("B(1,2)", B(1,2).toString())
assertEquals("A(x=1,y=2)", A(1,2).toString())
// now tricky part: this _should_ cakk custom toString()
assertEquals(":B(1,2)", ":" + B(1,2).toString())
// and this must be exactly same:
assertEquals(":B(1,2)", ":" + B(1,2))
""".trimIndent())
}
// @Test
// fun testSplatAssignemnt() = runTest {