Refactor toString implementations to support Scope context, add inspect, and improve assertions readability.

This commit is contained in:
Sergey Chernov 2025-12-13 13:48:57 +01:00
parent 2737aaa14e
commit fba44622e5
8 changed files with 95 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

@ -155,52 +155,65 @@ class Script(
sqrt(args.firstAndOnly().toDouble())
)
}
addFn( "abs" ) {
addFn("abs") {
val x = args.firstAndOnly()
if( x is ObjInt) ObjInt( x.value.absoluteValue ) else ObjReal( x.toDouble().absoluteValue )
if (x is ObjInt) ObjInt(x.value.absoluteValue) else ObjReal(x.toDouble().absoluteValue)
}
addVoidFn("assert") {
val cond = requiredArg<ObjBool>(0)
val message = if( args.size > 1 )
val message = if (args.size > 1)
": " + (args[1] as Statement).execute(this).toString(this).value
else ""
if( !cond.value == true )
if (!cond.value == true)
raiseError(ObjAssertionFailedException(this, "Assertion failed$message"))
}
addVoidFn("assertEquals") {
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)}"))
if (a.compareTo(this, b) != 0)
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)}"))
if (a.compareTo(this, b) != 0)
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)}"))
if (a.compareTo(this, b) == 0)
raiseError(
ObjAssertionFailedException(
this,
"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}"
)
)
}
addFn("assertThrows") {
val code = requireOnlyArg<Statement>()
val result =try {
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"))
result ?: raiseError(ObjAssertionFailedException(this, "Expected exception but nothing was thrown"))
}
addFn("dynamic") {
@ -209,7 +222,7 @@ class Script(
addFn("require") {
val condition = requiredArg<ObjBool>(0)
if( !condition.value ) {
if (!condition.value) {
val message = args.list.getOrNull(1)?.toString() ?: "requirement not met"
raiseIllegalArgument(message)
}
@ -217,7 +230,7 @@ class Script(
}
addFn("check") {
val condition = requiredArg<ObjBool>(0)
if( !condition.value ) {
if (!condition.value) {
val message = args.list.getOrNull(1)?.toString() ?: "check failed"
raiseIllegalState(message)
}
@ -340,7 +353,7 @@ class Script(
doc = "Suspend for the given time. Accepts Duration, Int seconds, or Real seconds."
) {
val a = args.firstAndOnly()
when(a) {
when (a) {
is ObjInt -> delay(a.value * 1000)
is ObjReal -> delay((a.value * 1000).roundToLong())
is ObjDuration -> delay(a.duration)

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

@ -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,7 +82,11 @@ 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")
} ?: 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")
}
suspend fun decodeAnyList(scope: Scope, fixedSize: Int? = null): MutableList<Obj> {