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, * @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 * but it is used to avoid endless recursion in [Obj.toString] base implementation
*/ */
open suspend fun toString(scope: Scope,calledFromLyng: Boolean=false): ObjString { open suspend fun toString(scope: Scope, calledFromLyng: Boolean = false): ObjString {
return if (this is ObjString) this if (this is ObjString) return this
else if( !calledFromLyng ) { return if (!calledFromLyng) {
invokeInstanceMethod(scope, "toString") { invokeInstanceMethod(scope, "toString", Arguments.EMPTY) {
ObjString(this.toString()) defaultToString(scope)
} as ObjString } 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. * Class of the object: definition of member functions (top-level), etc.
* Note that using lazy allows to avoid endless recursion here * 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 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) val at = getStackTrace().list.firstOrNull()?.toString(scope)
?: ObjString("(unknown)") ?: ObjString("(unknown)")
return ObjString("${objClass.className}: $message at $at") 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 !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 { return ObjString(buildString {
append("${objClass.className}(") append("${objClass.className}(")
var first = true var first = true
for ((name, value) in publicFields) { for ((name, value) in publicFields) {
if (first) first = false else append(",") if (first) first = false else append(",")
append("$name=${value.value.toString(scope)}") append("$name=${value.value.toString(scope)}")
} }
append(")") append(")")
}) })
} }
override suspend fun inspect(scope: Scope): String { override suspend fun inspect(scope: Scope): String {

View File

@ -43,7 +43,7 @@ class ObjInstanceClass(val name: String, vararg parents: ObjClass) : ObjClass(na
init { init {
addFn("toString", true) { 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) }) 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 { return ObjString(buildString {
append("[") append("[")
var first = true var first = true

View File

@ -52,7 +52,7 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
else -> scope.raiseIndexOutOfBounds() 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})") 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 return -1
} }
override suspend fun toString(scope: Scope, calledFromLyng: Boolean): ObjString { override suspend fun defaultToString(scope: Scope): ObjString {
val reusult = buildString { val reusult = buildString {
append("Map(") append("Map(")
var first = true 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 val objClass: ObjClass = type
override suspend fun toString(scope: Scope,calledFromLyng: Boolean): ObjString { override suspend fun defaultToString(scope: Scope): ObjString {
val result = StringBuilder() val result = StringBuilder()
result.append("${start?.inspect(scope) ?: '∞'} ..") result.append("${start?.inspect(scope) ?: '∞'} ..")
if (!isEndInclusive) result.append('<') 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)})") return ObjString("RegexMath(${objRange.toString(scope)},${objGroups.toString(scope)})")
} }

View File

@ -4311,6 +4311,28 @@ class ScriptTest {
""".trimIndent()) """.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 // @Test
// fun testSplatAssignemnt() = runTest { // fun testSplatAssignemnt() = runTest {