Compare commits
No commits in common. "19313841163d881c77caa54efda6fae7f16e6716" and "c12804a8064d90f17bee19cd5a5f4d9a4d6e5125" have entirely different histories.
1931384116
...
c12804a806
@ -298,12 +298,11 @@ 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?=null)` | Returns the exception message as a Kotlin `String`. |
|
| `obj.getLyngExceptionMessage(scope)` | 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?=null)` | Rethrows the object as a Kotlin `ExecutionError`. |
|
| `obj.raiseAsExecutionError(scope)` | Rethrows the object as a Kotlin `ExecutionError`. |
|
||||||
|
|
||||||
#### Example: Serialization and Rethrowing
|
#### Example: Serialization and Rethrowing
|
||||||
|
|
||||||
|
|||||||
@ -132,8 +132,6 @@ 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:
|
||||||
|
|||||||
@ -183,6 +183,3 @@ 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.
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -66,11 +66,6 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1b) Captured locals from the entire closure ancestry. This ensures that parameters
|
|
||||||
// and local variables shadow members of captured receivers, matching standard
|
|
||||||
// lexical scoping rules.
|
|
||||||
closureScope.chainLookupIgnoreClosure(name, followClosure = true)?.let { return it }
|
|
||||||
|
|
||||||
// 2) Members on the captured receiver instance
|
// 2) Members on the captured receiver instance
|
||||||
(closureScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)?.let { inst ->
|
(closureScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)?.let { inst ->
|
||||||
// Check direct locals in instance scope (unmangled)
|
// Check direct locals in instance scope (unmangled)
|
||||||
@ -99,7 +94,7 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3) Closure scope chain (locals/parents + members), ignore ClosureScope overrides to prevent recursion
|
// 3) Closure scope chain (locals/parents + members), ignore ClosureScope overrides to prevent recursion
|
||||||
closureScope.chainLookupWithMembers(name, currentClassCtx, followClosure = true)?.let { return it }
|
closureScope.chainLookupWithMembers(name, currentClassCtx)?.let { return it }
|
||||||
|
|
||||||
// 4) Caller `this` members
|
// 4) Caller `this` members
|
||||||
(callScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)?.let { inst ->
|
(callScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)?.let { inst ->
|
||||||
|
|||||||
@ -120,14 +120,14 @@ open class Scope(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun chainLookupIgnoreClosure(name: String, followClosure: Boolean = false): ObjRecord? {
|
internal fun chainLookupIgnoreClosure(name: String): ObjRecord? {
|
||||||
var s: Scope? = this
|
var s: Scope? = this
|
||||||
// use frameId to detect unexpected structural cycles in the parent chain
|
// use frameId to detect unexpected structural cycles in the parent chain
|
||||||
val visited = HashSet<Long>(4)
|
val visited = HashSet<Long>(4)
|
||||||
while (s != null) {
|
while (s != null) {
|
||||||
if (!visited.add(s.frameId)) return null
|
if (!visited.add(s.frameId)) return null
|
||||||
tryGetLocalRecord(s, name, currentClassCtx)?.let { return it }
|
tryGetLocalRecord(s, name, currentClassCtx)?.let { return it }
|
||||||
s = if (followClosure && s is ClosureScope) s.closureScope else s.parent
|
s = s.parent
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -169,7 +169,7 @@ open class Scope(
|
|||||||
* This completely avoids invoking overridden `get` implementations, preventing
|
* This completely avoids invoking overridden `get` implementations, preventing
|
||||||
* ping-pong recursion between `ClosureScope` frames.
|
* ping-pong recursion between `ClosureScope` frames.
|
||||||
*/
|
*/
|
||||||
internal fun chainLookupWithMembers(name: String, caller: net.sergeych.lyng.obj.ObjClass? = currentClassCtx, followClosure: Boolean = false): ObjRecord? {
|
internal fun chainLookupWithMembers(name: String, caller: net.sergeych.lyng.obj.ObjClass? = currentClassCtx): ObjRecord? {
|
||||||
var s: Scope? = this
|
var s: Scope? = this
|
||||||
val visited = HashSet<Long>(4)
|
val visited = HashSet<Long>(4)
|
||||||
while (s != null) {
|
while (s != null) {
|
||||||
@ -185,7 +185,7 @@ open class Scope(
|
|||||||
} else return rec
|
} else return rec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s = if (followClosure && s is ClosureScope) s.closureScope else s.parent
|
s = s.parent
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@ -337,37 +337,8 @@ fun Obj.isLyngException(): Boolean = isInstanceOf("Exception")
|
|||||||
/**
|
/**
|
||||||
* Get the exception message.
|
* Get the exception message.
|
||||||
*/
|
*/
|
||||||
suspend fun Obj.getLyngExceptionMessage(scope: Scope?=null): String {
|
suspend fun Obj.getLyngExceptionMessage(scope: Scope): String =
|
||||||
require( this.isLyngException() )
|
invokeInstanceMethod(scope, "message").toString(scope).value
|
||||||
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.
|
||||||
|
|||||||
@ -4671,54 +4671,5 @@ 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()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testMapIteralAmbiguity() = runTest {
|
|
||||||
eval("""
|
|
||||||
val m = { a: 1, b: { foo: "bar" } }
|
|
||||||
assertEquals(1, m["a"])
|
|
||||||
assertEquals("bar", m["b"]["foo"])
|
|
||||||
val bar = "foobar"
|
|
||||||
val m2 = { a: 1, b: { bar: } }
|
|
||||||
assert( m2["b"] is Map )
|
|
||||||
assertEquals("foobar", m2["b"]["bar"])
|
|
||||||
""".trimIndent())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun realWorldCaptureProblem() = runTest {
|
|
||||||
eval("""
|
|
||||||
// 61755f07-630c-4181-8d50-1b044d96e1f4
|
|
||||||
class T {
|
|
||||||
static var f1 = null
|
|
||||||
static fun t(name=null) {
|
|
||||||
run {
|
|
||||||
// I expect it will catch the 'name' from
|
|
||||||
// param?
|
|
||||||
f1 = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert(T.f1 == null)
|
|
||||||
println("-- "+T.f1::class)
|
|
||||||
println("-- "+T.f1)
|
|
||||||
T.t("foo")
|
|
||||||
println("2- "+T.f1::class)
|
|
||||||
println("2- "+T.f1)
|
|
||||||
assert(T.f1 == "foo")
|
|
||||||
""".trimIndent())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -256,10 +256,9 @@ 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: %s"(at, sourceString.trim())
|
"%s:%d:%d: %s"(sourceName, line, column, sourceString.trim())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user