more tools to deal with Lyng exeption in Kotlin

This commit is contained in:
Sergey Chernov 2026-01-08 12:42:29 +01:00
parent c12804a806
commit 2acb60697d
6 changed files with 53 additions and 5 deletions

View File

@ -298,11 +298,12 @@ To simplify handling these objects from Kotlin, several extension methods are pr
| :--- | :--- | | :--- | :--- |
| `obj.isLyngException()` | Returns `true` if the object is an instance of `Exception`. | | `obj.isLyngException()` | Returns `true` if the object is an instance of `Exception`. |
| `obj.isInstanceOf("ClassName")` | Returns `true` if the object is an instance of the named Lyng class or its ancestors. | | `obj.isInstanceOf("ClassName")` | Returns `true` if the object is an instance of the named Lyng class or its ancestors. |
| `obj.getLyngExceptionMessage(scope)` | Returns the exception message as a Kotlin `String`. | | `obj.getLyngExceptionMessage(scope?=null)` | Returns the exception message as a Kotlin `String`. |
| `obj.getLyngExceptionMessageWithStackTrace(scope?=null)` | Returns a detailed message with a formatted stack trace. |
| `obj.getLyngExceptionString(scope)` | Returns a formatted string including the class name, message, and primary throw site. | | `obj.getLyngExceptionString(scope)` | Returns a formatted string including the class name, message, and primary throw site. |
| `obj.getLyngExceptionStackTrace(scope)` | Returns the stack trace as an `ObjList` of `StackTraceEntry`. | | `obj.getLyngExceptionStackTrace(scope)` | Returns the stack trace as an `ObjList` of `StackTraceEntry`. |
| `obj.getLyngExceptionExtraData(scope)` | Returns the extra data associated with the exception. | | `obj.getLyngExceptionExtraData(scope)` | Returns the extra data associated with the exception. |
| `obj.raiseAsExecutionError(scope)` | Rethrows the object as a Kotlin `ExecutionError`. | | `obj.raiseAsExecutionError(scope?=null)` | Rethrows the object as a Kotlin `ExecutionError`. |
#### Example: Serialization and Rethrowing #### Example: Serialization and Rethrowing

View File

@ -132,6 +132,8 @@ Serializable class that conveys information about the exception. Important membe
| stackTrace() | lyng stack trace, list of `StackTraceEntry`, see below | | stackTrace() | lyng stack trace, list of `StackTraceEntry`, see below |
| printStackTrace() | format and print stack trace using println() | | printStackTrace() | format and print stack trace using println() |
> **Note for Kotlin users**: When working with Lyng exceptions from Kotlin, you can use extension methods like `getLyngExceptionMessageWithStackTrace()`. See [Embedding Lyng](embedding.md#12-handling-and-serializing-exceptions) for the full API.
## StackTraceEntry ## StackTraceEntry
A simple structire that stores single entry in Lyng stack, it is created automatically on exception creation: A simple structire that stores single entry in Lyng stack, it is created automatically on exception creation:

View File

@ -183,3 +183,6 @@ lyng fmt --check MyFile.lyng # Check if file needs formatting
Experimental lightweight autocompletion is now available in the IntelliJ plugin. It features type-aware member suggestions and inheritance-aware completion. Experimental lightweight autocompletion is now available in the IntelliJ plugin. It features type-aware member suggestions and inheritance-aware completion.
You can enable it in **Settings | Lyng Formatter | Enable Lyng autocompletion**. You can enable it in **Settings | Lyng Formatter | Enable Lyng autocompletion**.
### Kotlin API: Exception Handling
The `Obj.getLyngExceptionMessageWithStackTrace()` extension method has been added to simplify retrieving detailed error information from Lyng exception objects in Kotlin. Additionally, `getLyngExceptionMessage()` and `raiseAsExecutionError()` now accept an optional `Scope`, making it easier to use them when a scope is not immediately available.

View File

@ -337,8 +337,37 @@ fun Obj.isLyngException(): Boolean = isInstanceOf("Exception")
/** /**
* Get the exception message. * Get the exception message.
*/ */
suspend fun Obj.getLyngExceptionMessage(scope: Scope): String = suspend fun Obj.getLyngExceptionMessage(scope: Scope?=null): String {
invokeInstanceMethod(scope, "message").toString(scope).value require( this.isLyngException() )
val s = scope ?: Script.newScope()
return invokeInstanceMethod(s, "message").toString(s).value
}
/**
* Retrieves a detailed exception message including the stack trace for a Lyng exception.
* This function is designed to handle objects identified as Lyng exceptions.
*
* @param scope the scope to be used for fetching the exception message and stack trace.
* If null, a new scope will be created.
* @return a string combining the exception message, the location ("at"),
* and the formatted stack trace information.
* The stack trace details each frame using indentation for clarity.
* @throws IllegalArgumentException if the object is not a Lyng exception.
*/
suspend fun Obj.getLyngExceptionMessageWithStackTrace(scope: Scope?=null): String {
require( this.isLyngException() )
val s = scope ?: Script.newScope()
val msg = getLyngExceptionMessage(s)
val trace = getLyngExceptionStackTrace(s)
var at = "unknown"
val stack = if( !trace.list.isEmpty() ) {
val first = trace.list[0]
at = (first.readField(s, "at").value as ObjString).value
"\n" + trace.list.map { " at " + it.toString(s).value }.joinToString("\n")
}
else ""
return "$at: $msg$stack"
}
/** /**
* Get the exception stack trace. * Get the exception stack trace.

View File

@ -4671,5 +4671,17 @@ class ScriptTest {
// source name, in our case, is is "tc2": // source name, in our case, is is "tc2":
assertContains(x1.message!!, "tc2") assertContains(x1.message!!, "tc2")
} }
@Test
fun testLyngToKotlinExceptionHelpers() = runTest {
var x = evalNamed( "tc1","""
IllegalArgumentException("test3")
""".trimIndent())
assertEquals("""
tc1:1:1: test3
at tc1:1:1: IllegalArgumentException("test3")
""".trimIndent(),
x.getLyngExceptionMessageWithStackTrace()
)
}
} }

View File

@ -256,9 +256,10 @@ class StackTraceEntry(
val column: Int, val column: Int,
val sourceString: String val sourceString: String
) { ) {
val at by lazy { "%s:%s:%s"(sourceName,line+1,column+1) }
/* Formatted representation: source:line:column: text. */ /* Formatted representation: source:line:column: text. */
override fun toString() { override fun toString() {
"%s:%d:%d: %s"(sourceName, line, column, sourceString.trim()) "%s: %s"(at, sourceString.trim())
} }
} }