User exception classes, unified exception class architecture
This commit is contained in:
1 parent
2ce6d8e482
commit
1d089db9ff
10 files changed
+307
-75
No files matched your search
@@ -1719,19 +1719,25 @@ class Compiler(
|
||||
var errorObject = throwStatement.execute(sc)
|
||||
// Rebind error scope to the throw-site position so ScriptError.pos is accurate
|
||||
val throwScope = sc.createChildScope(pos = start)
|
||||
errorObject = when (errorObject) {
|
||||
is ObjString -> ObjException(throwScope, errorObject.value)
|
||||
is ObjException -> ObjException(
|
||||
if (errorObject is ObjString) {
|
||||
errorObject = ObjException(throwScope, errorObject.value)
|
||||
}
|
||||
if (!errorObject.isInstanceOf(ObjException.Root)) {
|
||||
throwScope.raiseError("this is not an exception object: $errorObject")
|
||||
}
|
||||
if (errorObject is ObjException) {
|
||||
errorObject = ObjException(
|
||||
errorObject.exceptionClass,
|
||||
throwScope,
|
||||
errorObject.message,
|
||||
errorObject.extraData,
|
||||
errorObject.useStackTrace
|
||||
)
|
||||
|
||||
else -> throwScope.raiseError("this is not an exception object: $errorObject")
|
||||
throwScope.raiseError(errorObject)
|
||||
} else {
|
||||
val msg = errorObject.invokeInstanceMethod(sc, "message").toString(sc).value
|
||||
throwScope.raiseError(errorObject, start, msg)
|
||||
}
|
||||
throwScope.raiseError(errorObject)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1814,25 +1820,25 @@ class Compiler(
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
// convert to appropriate exception
|
||||
val objException = when (e) {
|
||||
val caughtObj = when (e) {
|
||||
is ExecutionError -> e.errorObject
|
||||
else -> ObjUnknownException(this, e.message ?: e.toString())
|
||||
}
|
||||
// let's see if we should catch it:
|
||||
var isCaught = false
|
||||
for (cdata in catches) {
|
||||
var exceptionObject: ObjException? = null
|
||||
var match: Obj? = null
|
||||
for (exceptionClassName in cdata.classNames) {
|
||||
val exObj = ObjException.getErrorClass(exceptionClassName)
|
||||
?: raiseSymbolNotFound("error clas not exists: $exceptionClassName")
|
||||
if (objException.isInstanceOf(exObj)) {
|
||||
exceptionObject = objException
|
||||
val exObj = this[exceptionClassName]?.value as? ObjClass
|
||||
?: raiseSymbolNotFound("error class does not exist or is not a class: $exceptionClassName")
|
||||
if (caughtObj.isInstanceOf(exObj)) {
|
||||
match = caughtObj
|
||||
break
|
||||
}
|
||||
}
|
||||
if (exceptionObject != null) {
|
||||
if (match != null) {
|
||||
val catchContext = this.createChildScope(pos = cdata.catchVar.pos)
|
||||
catchContext.addItem(cdata.catchVar.value, false, objException)
|
||||
catchContext.addItem(cdata.catchVar.value, false, caughtObj)
|
||||
result = cdata.block.execute(catchContext)
|
||||
isCaught = true
|
||||
break
|
||||
|
||||
@@ -277,16 +277,22 @@ open class Scope(
|
||||
raiseError(ObjSymbolNotDefinedException(this, "symbol is not defined: $name"))
|
||||
|
||||
fun raiseError(message: String): Nothing {
|
||||
throw ExecutionError(ObjException(this, message))
|
||||
val ex = ObjException(this, message)
|
||||
throw ExecutionError(ex, pos, ex.message.value)
|
||||
}
|
||||
|
||||
fun raiseError(obj: ObjException): Nothing {
|
||||
throw ExecutionError(obj)
|
||||
throw ExecutionError(obj, obj.scope.pos, obj.message.value)
|
||||
}
|
||||
|
||||
fun raiseError(obj: Obj, pos: Pos, message: String): Nothing {
|
||||
throw ExecutionError(obj, pos, message)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseNotFound(message: String = "not found"): Nothing {
|
||||
throw ExecutionError(ObjNotFoundException(this, message))
|
||||
val ex = ObjNotFoundException(this, message)
|
||||
throw ExecutionError(ex, ex.scope.pos, ex.message.value)
|
||||
}
|
||||
|
||||
inline fun <reified T : Obj> requiredArg(index: Int): T {
|
||||
|
||||
@@ -248,10 +248,9 @@ class Script(
|
||||
)
|
||||
)
|
||||
expectedClass?.let {
|
||||
if (result !is ObjException)
|
||||
raiseError("Expected $expectedClass, got non-lyng exception $result")
|
||||
if (result.exceptionClass != expectedClass) {
|
||||
raiseError("Expected $expectedClass, got ${result.exceptionClass}")
|
||||
if (!result.isInstanceOf(it)) {
|
||||
val actual = if (result is ObjException) result.exceptionClass else result.objClass
|
||||
raiseError("Expected $it, got $actual")
|
||||
}
|
||||
}
|
||||
result
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.lyng.obj.ObjException
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
|
||||
open class ScriptError(val pos: Pos, val errorMessage: String, cause: Throwable? = null) : Exception(
|
||||
"""
|
||||
@@ -33,6 +33,6 @@ open class ScriptError(val pos: Pos, val errorMessage: String, cause: Throwable?
|
||||
|
||||
class ScriptFlowIsNoMoreCollected: Exception()
|
||||
|
||||
class ExecutionError(val errorObject: ObjException) : ScriptError(errorObject.scope.pos, errorObject.message.value)
|
||||
class ExecutionError(val errorObject: Obj, pos: Pos, message: String) : ScriptError(pos, message)
|
||||
|
||||
class ImportException(pos: Pos, message: String) : ScriptError(pos, message)
|
||||
@@ -41,7 +41,7 @@ open class ObjException(
|
||||
val exceptionClass: ExceptionClass,
|
||||
val scope: Scope,
|
||||
val message: ObjString,
|
||||
@Suppress("unused") val extraData: Obj = ObjNull,
|
||||
val extraData: Obj = ObjNull,
|
||||
val useStackTrace: ObjList? = null
|
||||
) : Obj() {
|
||||
constructor(name: String, scope: Scope, message: String) : this(
|
||||
@@ -54,37 +54,14 @@ open class ObjException(
|
||||
|
||||
suspend fun getStackTrace(): ObjList {
|
||||
return cachedStackTrace.get {
|
||||
val result = ObjList()
|
||||
val maybeCls = scope.get("StackTraceEntry")?.value as? ObjClass
|
||||
var s: Scope? = scope
|
||||
var lastPos: Pos? = null
|
||||
while (s != null) {
|
||||
val pos = s.pos
|
||||
if (pos != lastPos && !pos.currentLine.isEmpty()) {
|
||||
if (maybeCls != null) {
|
||||
result.list += maybeCls.callWithArgs(
|
||||
scope,
|
||||
pos.source.objSourceName,
|
||||
ObjInt(pos.line.toLong()),
|
||||
ObjInt(pos.column.toLong()),
|
||||
ObjString(pos.currentLine)
|
||||
)
|
||||
} else {
|
||||
// Fallback textual entry if StackTraceEntry class is not available in this scope
|
||||
result.list += ObjString("${pos.source.objSourceName}:${pos.line}:${pos.column}: ${pos.currentLine}")
|
||||
}
|
||||
}
|
||||
s = s.parent
|
||||
lastPos = pos
|
||||
}
|
||||
result
|
||||
captureStackTrace(scope)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(scope: Scope, message: String) : this(Root, scope, ObjString(message))
|
||||
|
||||
fun raise(): Nothing {
|
||||
throw ExecutionError(this)
|
||||
throw ExecutionError(this, scope.pos, message.value)
|
||||
}
|
||||
|
||||
override val objClass: ObjClass = exceptionClass
|
||||
@@ -116,7 +93,41 @@ open class ObjException(
|
||||
|
||||
companion object {
|
||||
|
||||
suspend fun captureStackTrace(scope: Scope): ObjList {
|
||||
val result = ObjList()
|
||||
val maybeCls = scope.get("StackTraceEntry")?.value as? ObjClass
|
||||
var s: Scope? = scope
|
||||
var lastPos: Pos? = null
|
||||
while (s != null) {
|
||||
val pos = s.pos
|
||||
if (pos != lastPos && !pos.currentLine.isEmpty()) {
|
||||
if (maybeCls != null) {
|
||||
result.list += maybeCls.callWithArgs(
|
||||
scope,
|
||||
pos.source.objSourceName,
|
||||
ObjInt(pos.line.toLong()),
|
||||
ObjInt(pos.column.toLong()),
|
||||
ObjString(pos.currentLine)
|
||||
)
|
||||
} else {
|
||||
// Fallback textual entry if StackTraceEntry class is not available in this scope
|
||||
result.list += ObjString("${pos.source.objSourceName}:${pos.line}:${pos.column}: ${pos.currentLine}")
|
||||
}
|
||||
}
|
||||
s = s.parent
|
||||
lastPos = pos
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
|
||||
init {
|
||||
constructorMeta = ArgsDeclaration(
|
||||
listOf(ArgsDeclaration.Item("message", defaultValue = statement { ObjString(name) })),
|
||||
Token.Type.RPAREN
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun callOn(scope: Scope): Obj {
|
||||
val message = scope.args.getOrNull(0)?.toString(scope) ?: ObjString(name)
|
||||
return ObjException(this, scope, message)
|
||||
@@ -148,22 +159,77 @@ open class ObjException(
|
||||
}
|
||||
|
||||
val Root = ExceptionClass("Exception").apply {
|
||||
instanceConstructor = statement {
|
||||
val msg = args.getOrNull(0) ?: ObjString("Exception")
|
||||
if (thisObj is ObjInstance) {
|
||||
(thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg)
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
instanceInitializers.add(statement {
|
||||
if (thisObj is ObjInstance) {
|
||||
val stack = captureStackTrace(this)
|
||||
(thisObj as ObjInstance).instanceScope.addItem("Exception::stackTrace", false, stack)
|
||||
}
|
||||
ObjVoid
|
||||
})
|
||||
addConstDoc(
|
||||
name = "message",
|
||||
value = statement {
|
||||
(thisObj as ObjException).message.toObj()
|
||||
when (val t = thisObj) {
|
||||
is ObjException -> t.message
|
||||
is ObjInstance -> t.instanceScope.get("Exception::message")?.value ?: ObjNull
|
||||
else -> ObjNull
|
||||
}
|
||||
},
|
||||
doc = "Human‑readable error message.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
)
|
||||
addConstDoc(
|
||||
name = "extraData",
|
||||
value = statement {
|
||||
when (val t = thisObj) {
|
||||
is ObjException -> t.extraData
|
||||
else -> ObjNull
|
||||
}
|
||||
},
|
||||
doc = "Extra data associated with the exception.",
|
||||
type = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
)
|
||||
addFnDoc(
|
||||
name = "stackTrace",
|
||||
doc = "Stack trace captured at throw site as a list of `StackTraceEntry`.",
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
(thisObj as ObjException).getStackTrace()
|
||||
when (val t = thisObj) {
|
||||
is ObjException -> t.getStackTrace()
|
||||
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||
else -> ObjList()
|
||||
}
|
||||
}
|
||||
addFnDoc(
|
||||
name = "toString",
|
||||
doc = "Human‑readable string representation of the error.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val msg = when (val t = thisObj) {
|
||||
is ObjException -> t.message.value
|
||||
is ObjInstance -> (t.instanceScope.get("Exception::message")?.value as? ObjString)?.value
|
||||
?: t.objClass.className
|
||||
|
||||
else -> t.objClass.className
|
||||
}
|
||||
val stack = when (val t = thisObj) {
|
||||
is ObjException -> t.getStackTrace()
|
||||
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||
else -> ObjList()
|
||||
}
|
||||
val at = stack.list.firstOrNull()?.toString(this) ?: ObjString("(unknown)")
|
||||
ObjString("${thisObj.objClass.className}: $msg at $at")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user