fix #18 basic exceptions handling
This commit is contained in:
parent
95aae0b231
commit
6c71f0a2e6
@ -753,6 +753,34 @@ We can use labels too:
|
|||||||
assert( search(["hello", "world"], 'z') == null)
|
assert( search(["hello", "world"], 'z') == null)
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
# Exception handling
|
||||||
|
|
||||||
|
Very much like in Kotlin. Try block returns its body block result, if no exception was cauht, or the result from the catch block that caught the exception:
|
||||||
|
|
||||||
|
var error = "not caught"
|
||||||
|
var finallyCaught = false
|
||||||
|
val result = try {
|
||||||
|
throw IllegalArgumentException()
|
||||||
|
"invalid"
|
||||||
|
}
|
||||||
|
catch(nd: SymbolNotDefinedException) {
|
||||||
|
error = "bad catch"
|
||||||
|
}
|
||||||
|
catch(x: IllegalArgumentException) {
|
||||||
|
error = "no error"
|
||||||
|
"OK"
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// finally does not affect returned value
|
||||||
|
"too bad"
|
||||||
|
}
|
||||||
|
assertEquals( "no error", error)
|
||||||
|
assertEquals( "OK", result)
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
It is possible to catch several exceptions in the same block (TBD)
|
||||||
|
|
||||||
|
|
||||||
# Self-assignments in expression
|
# Self-assignments in expression
|
||||||
|
|
||||||
There are auto-increments and auto-decrements:
|
There are auto-increments and auto-decrements:
|
||||||
|
@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "0.4.0-SNAPSHOT"
|
version = "0.5.0-SNAPSHOT"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
@ -62,6 +62,7 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
all {
|
all {
|
||||||
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
||||||
|
languageSettings.optIn("kotlin.contracts.ExperimentalContracts::class")
|
||||||
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
|
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
|
||||||
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
|
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
|
||||||
}
|
}
|
||||||
|
@ -717,10 +717,115 @@ class Compiler(
|
|||||||
"fn", "fun" -> parseFunctionDeclaration(cc)
|
"fn", "fun" -> parseFunctionDeclaration(cc)
|
||||||
"if" -> parseIfStatement(cc)
|
"if" -> parseIfStatement(cc)
|
||||||
"class" -> parseClassDeclaration(cc, false)
|
"class" -> parseClassDeclaration(cc, false)
|
||||||
"struct" -> parseClassDeclaration(cc, true)
|
"try" -> parseTryStatement(cc)
|
||||||
|
"throw" -> parseThrowStatement(cc)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun parseThrowStatement(cc: CompilerContext): Statement {
|
||||||
|
val throwStatement = parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "throw object expected")
|
||||||
|
return statement {
|
||||||
|
val errorObject = throwStatement.execute(this)
|
||||||
|
if( errorObject is ObjException )
|
||||||
|
raiseError(errorObject)
|
||||||
|
else raiseError("this is not an exception object: $errorObject")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class CatchBlockData(
|
||||||
|
val catchVar: Token,
|
||||||
|
val classNames: List<String>,
|
||||||
|
val block: Statement
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun parseTryStatement(cc: CompilerContext): Statement {
|
||||||
|
val body = parseBlock(cc)
|
||||||
|
val catches = mutableListOf<CatchBlockData>()
|
||||||
|
cc.skipTokens(Token.Type.NEWLINE)
|
||||||
|
var t = cc.next()
|
||||||
|
while( t.value == "catch" ) {
|
||||||
|
ensureLparen(cc)
|
||||||
|
t = cc.next()
|
||||||
|
if( t.type != Token.Type.ID ) throw ScriptError(t.pos, "expected catch variable")
|
||||||
|
val catchVar = t
|
||||||
|
cc.skipTokenOfType(Token.Type.COLON)
|
||||||
|
// load list of exception classes
|
||||||
|
val exClassNames = mutableListOf<String>()
|
||||||
|
do {
|
||||||
|
t = cc.next()
|
||||||
|
if( t.type != Token.Type.ID )
|
||||||
|
throw ScriptError(t.pos, "expected exception class name")
|
||||||
|
exClassNames += t.value
|
||||||
|
t = cc.next()
|
||||||
|
when(t.type) {
|
||||||
|
Token.Type.COMMA -> {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
Token.Type.RPAREN -> {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
else -> throw ScriptError(t.pos, "syntax error: expected ',' or ')'")
|
||||||
|
}
|
||||||
|
} while(true)
|
||||||
|
val block = parseBlock(cc)
|
||||||
|
catches += CatchBlockData(catchVar, exClassNames, block)
|
||||||
|
cc.skipTokens(Token.Type.NEWLINE)
|
||||||
|
t = cc.next()
|
||||||
|
}
|
||||||
|
if( catches.isEmpty() )
|
||||||
|
throw ScriptError(cc.currentPos(), "try block must have at least one catch clause")
|
||||||
|
val finallyClause = if( t.value == "finally" ) {
|
||||||
|
parseBlock(cc)
|
||||||
|
} else {
|
||||||
|
cc.previous()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
return statement {
|
||||||
|
var result: Obj = ObjVoid
|
||||||
|
try {
|
||||||
|
// body is a parsed block, it already has separate context
|
||||||
|
result = body.execute(this)
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
// convert to appropriate exception
|
||||||
|
val objException = 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
|
||||||
|
for (exceptionClassName in cdata.classNames) {
|
||||||
|
val exObj = ObjException.getErrorClass(exceptionClassName)
|
||||||
|
?: raiseSymbolNotFound("error clas not exists: $exceptionClassName")
|
||||||
|
println("exObj: $exObj")
|
||||||
|
println("objException: ${objException.objClass}")
|
||||||
|
if( objException.objClass == exObj )
|
||||||
|
exceptionObject = objException
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if( exceptionObject != null ) {
|
||||||
|
val catchContext = this.copy(pos = cdata.catchVar.pos)
|
||||||
|
catchContext.addItem(cdata.catchVar.value, false, objException)
|
||||||
|
result = cdata.block.execute(catchContext)
|
||||||
|
isCaught = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// rethrow if not caught this exception
|
||||||
|
if( !isCaught )
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// finally clause does not alter result!
|
||||||
|
finallyClause?.execute(this)
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseClassDeclaration(cc: CompilerContext, isStruct: Boolean): Statement {
|
private fun parseClassDeclaration(cc: CompilerContext, isStruct: Boolean): Statement {
|
||||||
val nameToken = cc.requireToken(Token.Type.ID)
|
val nameToken = cc.requireToken(Token.Type.ID)
|
||||||
val constructorArgsDeclaration =
|
val constructorArgsDeclaration =
|
||||||
|
@ -16,27 +16,27 @@ class Context(
|
|||||||
fun raiseNotImplemented(what: String = "operation"): Nothing = raiseError("$what is not implemented")
|
fun raiseNotImplemented(what: String = "operation"): Nothing = raiseError("$what is not implemented")
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun raiseNPE(): Nothing = raiseError(ObjNullPointerError(this))
|
fun raiseNPE(): Nothing = raiseError(ObjNullPointerException(this))
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun raiseIndexOutOfBounds(message: String = "Index out of bounds"): Nothing =
|
fun raiseIndexOutOfBounds(message: String = "Index out of bounds"): Nothing =
|
||||||
raiseError(ObjIndexOutOfBoundsError(this, message))
|
raiseError(ObjIndexOutOfBoundsException(this, message))
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun raiseArgumentError(message: String = "Illegal argument error"): Nothing =
|
fun raiseArgumentError(message: String = "Illegal argument error"): Nothing =
|
||||||
raiseError(ObjIllegalArgumentError(this, message))
|
raiseError(ObjIllegalArgumentException(this, message))
|
||||||
|
|
||||||
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastError(this, msg))
|
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastException(this, msg))
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun raiseSymbolNotFound(name: String): Nothing =
|
fun raiseSymbolNotFound(name: String): Nothing =
|
||||||
raiseError(ObjSymbolNotDefinedError(this, "symbol is not defined: $name"))
|
raiseError(ObjSymbolNotDefinedException(this, "symbol is not defined: $name"))
|
||||||
|
|
||||||
fun raiseError(message: String): Nothing {
|
fun raiseError(message: String): Nothing {
|
||||||
throw ExecutionError(ObjError(this, message))
|
throw ExecutionError(ObjException(this, message))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun raiseError(obj: ObjError): Nothing {
|
fun raiseError(obj: ObjException): Nothing {
|
||||||
throw ExecutionError(obj)
|
throw ExecutionError(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,10 @@ import kotlinx.coroutines.sync.Mutex
|
|||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import net.sergeych.bintools.encodeToHex
|
||||||
import net.sergeych.synctools.ProtectedOp
|
import net.sergeych.synctools.ProtectedOp
|
||||||
|
import net.sergeych.synctools.withLock
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record to store object with access rules, e.g. [isMutable] and access level [visibility].
|
* Record to store object with access rules, e.g. [isMutable] and access level [visibility].
|
||||||
@ -63,7 +66,7 @@ open class Obj {
|
|||||||
suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
|
suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
|
||||||
invokeInstanceMethod(context, name, Arguments(args.toList()))
|
invokeInstanceMethod(context, name, Arguments(args.toList()))
|
||||||
|
|
||||||
inline suspend fun <reified T : Obj> callMethod(
|
suspend inline fun <reified T : Obj> callMethod(
|
||||||
context: Context,
|
context: Context,
|
||||||
name: String,
|
name: String,
|
||||||
args: Arguments = Arguments.EMPTY
|
args: Arguments = Arguments.EMPTY
|
||||||
@ -179,11 +182,10 @@ open class Obj {
|
|||||||
|
|
||||||
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
||||||
|
|
||||||
suspend open fun readField(context: Context, name: String): ObjRecord {
|
open suspend fun readField(context: Context, name: String): ObjRecord {
|
||||||
// could be property or class field:
|
// could be property or class field:
|
||||||
val obj = objClass.getInstanceMemberOrNull(name) ?: context.raiseError("no such field: $name")
|
val obj = objClass.getInstanceMemberOrNull(name) ?: context.raiseError("no such field: $name")
|
||||||
val value = obj.value
|
return when (val value = obj.value) {
|
||||||
return when (value) {
|
|
||||||
is Statement -> {
|
is Statement -> {
|
||||||
ObjRecord(value.execute(context.copy(context.pos, newThisObj = this)), obj.isMutable)
|
ObjRecord(value.execute(context.copy(context.pos, newThisObj = this)), obj.isMutable)
|
||||||
}
|
}
|
||||||
@ -327,21 +329,97 @@ data class ObjNamespace(val name: String) : Obj() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open class ObjError(val context: Context, val message: String) : Obj() {
|
open class ObjException(exceptionClass: ExceptionClass, val context: Context, val message: String) : Obj() {
|
||||||
override val asStr: ObjString by lazy { ObjString("Error: $message") }
|
constructor(name: String,context: Context, message: String) : this(getOrCreateExceptionClass(name), context, message)
|
||||||
|
constructor(context: Context, message: String) : this(Root, context, message)
|
||||||
|
|
||||||
fun raise(): Nothing {
|
fun raise(): Nothing {
|
||||||
throw ExecutionError(this)
|
throw ExecutionError(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val objClass: ObjClass = exceptionClass
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ObjException:${objClass.className}:${context.pos}@${hashCode().encodeToHex()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
class ExceptionClass(val name: String,vararg parents: ObjClass) : ObjClass(name, *parents) {
|
||||||
|
override suspend fun callOn(context: Context): Obj {
|
||||||
|
return ObjException(this, context, name).apply {
|
||||||
|
println(">>>> "+this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun toString(): String = "ExceptionClass[$name]@${hashCode().encodeToHex()}"
|
||||||
|
}
|
||||||
|
val Root = ExceptionClass("Throwable")
|
||||||
|
|
||||||
|
private val op = ProtectedOp()
|
||||||
|
private val existingErrorClasses = mutableMapOf<String, ExceptionClass>()
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
protected fun getOrCreateExceptionClass(name: String): ExceptionClass {
|
||||||
|
return op.withLock {
|
||||||
|
existingErrorClasses.getOrPut(name) {
|
||||||
|
ExceptionClass(name, Root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get [ObjClass] for error class by name if exists.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
fun getErrorClass(name: String): ObjClass? = op.withLock {
|
||||||
|
existingErrorClasses[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addExceptionsToContext(context: Context) {
|
||||||
|
context.addConst("Exception", Root)
|
||||||
|
existingErrorClasses["Exception"] = Root
|
||||||
|
for (name in listOf(
|
||||||
|
"NullPointerException",
|
||||||
|
"AssertionFailedException",
|
||||||
|
"ClassCastException",
|
||||||
|
"IndexOutOfBoundsException",
|
||||||
|
"IllegalArgumentException",
|
||||||
|
"IllegalAssignmentException",
|
||||||
|
"SymbolNotDefinedException",
|
||||||
|
"IterationEndException",
|
||||||
|
"AccessException",
|
||||||
|
"UnknownException",
|
||||||
|
)) {
|
||||||
|
context.addConst(name, getOrCreateExceptionClass(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjNullPointerError(context: Context) : ObjError(context, "object is null")
|
class ObjNullPointerException(context: Context) : ObjException("NullPointerException", context, "object is null")
|
||||||
|
|
||||||
class ObjAssertionError(context: Context, message: String) : ObjError(context, message)
|
class ObjAssertionFailedException(context: Context, message: String) :
|
||||||
class ObjClassCastError(context: Context, message: String) : ObjError(context, message)
|
ObjException("AssertionFailedException", context, message)
|
||||||
class ObjIndexOutOfBoundsError(context: Context, message: String = "index out of bounds") : ObjError(context, message)
|
|
||||||
class ObjIllegalArgumentError(context: Context, message: String = "illegal argument") : ObjError(context, message)
|
class ObjClassCastException(context: Context, message: String) : ObjException("ClassCastException", context, message)
|
||||||
class ObjIllegalAssignmentError(context: Context, message: String = "illegal assignment") : ObjError(context, message)
|
class ObjIndexOutOfBoundsException(context: Context, message: String = "index out of bounds") :
|
||||||
class ObjSymbolNotDefinedError(context: Context, message: String = "symbol is not defined") : ObjError(context, message)
|
ObjException("IndexOutOfBoundsException", context, message)
|
||||||
class ObjIterationFinishedError(context: Context) : ObjError(context, "iteration finished")
|
|
||||||
class ObjAccessError(context: Context, message: String = "access not allowed error") : ObjError(context, message)
|
class ObjIllegalArgumentException(context: Context, message: String = "illegal argument") :
|
||||||
|
ObjException("IllegalArgumentException", context, message)
|
||||||
|
|
||||||
|
class ObjIllegalAssignmentException(context: Context, message: String = "illegal assignment") :
|
||||||
|
ObjException("IllegalAssignmentException", context, message)
|
||||||
|
|
||||||
|
class ObjSymbolNotDefinedException(context: Context, message: String = "symbol is not defined") :
|
||||||
|
ObjException("SymbolNotDefinedException", context, message)
|
||||||
|
|
||||||
|
class ObjIterationFinishedException(context: Context) :
|
||||||
|
ObjException("IterationEndException", context, "iteration finished")
|
||||||
|
|
||||||
|
class ObjAccessException(context: Context, message: String = "access not allowed error") :
|
||||||
|
ObjException("AccessException", context, message)
|
||||||
|
|
||||||
|
class ObjUnknownException(context: Context, message: String = "access not allowed error") :
|
||||||
|
ObjException("UnknownException", context, message)
|
||||||
|
@ -2,7 +2,7 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
val ObjClassType by lazy { ObjClass("Class") }
|
val ObjClassType by lazy { ObjClass("Class") }
|
||||||
|
|
||||||
class ObjClass(
|
open class ObjClass(
|
||||||
val className: String,
|
val className: String,
|
||||||
vararg val parents: ObjClass,
|
vararg val parents: ObjClass,
|
||||||
) : Obj() {
|
) : Obj() {
|
||||||
@ -122,7 +122,7 @@ class ObjArrayIterator(val array: Obj) : Obj() {
|
|||||||
val self = thisAs<ObjArrayIterator>()
|
val self = thisAs<ObjArrayIterator>()
|
||||||
if (self.nextIndex < self.lastIndex) {
|
if (self.nextIndex < self.lastIndex) {
|
||||||
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
|
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
|
||||||
} else raiseError(ObjIterationFinishedError(this))
|
} else raiseError(ObjIterationFinishedException(this))
|
||||||
}
|
}
|
||||||
addFn("hasNext") {
|
addFn("hasNext") {
|
||||||
val self = thisAs<ObjArrayIterator>()
|
val self = thisAs<ObjArrayIterator>()
|
||||||
|
@ -9,7 +9,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
if (it.visibility.isPublic)
|
if (it.visibility.isPublic)
|
||||||
it
|
it
|
||||||
else
|
else
|
||||||
context.raiseError(ObjAccessError(context, "can't access non-public field $name"))
|
context.raiseError(ObjAccessException(context, "can't access non-public field $name"))
|
||||||
}
|
}
|
||||||
?: super.readField(context, name)
|
?: super.readField(context, name)
|
||||||
}
|
}
|
||||||
@ -17,8 +17,8 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
override suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
override suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
||||||
instanceContext[name]?.let { f ->
|
instanceContext[name]?.let { f ->
|
||||||
if (!f.visibility.isPublic)
|
if (!f.visibility.isPublic)
|
||||||
ObjIllegalAssignmentError(context, "can't assign to non-public field $name")
|
ObjIllegalAssignmentException(context, "can't assign to non-public field $name")
|
||||||
if (!f.isMutable) ObjIllegalAssignmentError(context, "can't reassign val $name").raise()
|
if (!f.isMutable) ObjIllegalAssignmentException(context, "can't reassign val $name").raise()
|
||||||
if (f.value.assign(context, newValue) == null)
|
if (f.value.assign(context, newValue) == null)
|
||||||
f.value = newValue
|
f.value = newValue
|
||||||
} ?: super.writeField(context, name, newValue)
|
} ?: super.writeField(context, name, newValue)
|
||||||
@ -29,7 +29,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
if (it.visibility.isPublic)
|
if (it.visibility.isPublic)
|
||||||
it.value.invoke(context, this, args)
|
it.value.invoke(context, this, args)
|
||||||
else
|
else
|
||||||
context.raiseError(ObjAccessError(context, "can't invoke non-public method $name"))
|
context.raiseError(ObjAccessException(context, "can't invoke non-public method $name"))
|
||||||
}
|
}
|
||||||
?: super.invokeInstanceMethod(context, name, args)
|
?: super.invokeInstanceMethod(context, name, args)
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
|||||||
if( isCharRange ) ObjChar(x.toInt().toChar()) else ObjInt(x)
|
if( isCharRange ) ObjChar(x.toInt().toChar()) else ObjInt(x)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
context.raiseError(ObjIterationFinishedError(context))
|
context.raiseError(ObjIterationFinishedException(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -21,6 +21,7 @@ class Script(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val defaultContext: Context = Context().apply {
|
val defaultContext: Context = Context().apply {
|
||||||
|
ObjException.addExceptionsToContext(this)
|
||||||
addFn("println") {
|
addFn("println") {
|
||||||
for ((i, a) in args.withIndex()) {
|
for ((i, a) in args.withIndex()) {
|
||||||
if (i > 0) print(' ' + a.asStr.value)
|
if (i > 0) print(' ' + a.asStr.value)
|
||||||
@ -117,14 +118,14 @@ class Script(
|
|||||||
addVoidFn("assert") {
|
addVoidFn("assert") {
|
||||||
val cond = requiredArg<ObjBool>(0)
|
val cond = requiredArg<ObjBool>(0)
|
||||||
if( !cond.value == true )
|
if( !cond.value == true )
|
||||||
raiseError(ObjAssertionError(this,"Assertion failed"))
|
raiseError(ObjAssertionFailedException(this,"Assertion failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
addVoidFn("assertEquals") {
|
addVoidFn("assertEquals") {
|
||||||
val a = requiredArg<Obj>(0)
|
val a = requiredArg<Obj>(0)
|
||||||
val b = requiredArg<Obj>(1)
|
val b = requiredArg<Obj>(1)
|
||||||
if( a.compareTo(this, b) != 0 )
|
if( a.compareTo(this, b) != 0 )
|
||||||
raiseError(ObjAssertionError(this,"Assertion failed: ${a.inspect()} == ${b.inspect()}"))
|
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect()} == ${b.inspect()}"))
|
||||||
}
|
}
|
||||||
addFn("assertThrows") {
|
addFn("assertThrows") {
|
||||||
val code = requireOnlyArg<Statement>()
|
val code = requireOnlyArg<Statement>()
|
||||||
@ -138,7 +139,7 @@ class Script(
|
|||||||
catch (e: ScriptError) {
|
catch (e: ScriptError) {
|
||||||
ObjNull
|
ObjNull
|
||||||
}
|
}
|
||||||
result ?: raiseError(ObjAssertionError(this,"Expected exception but nothing was thrown"))
|
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
|
||||||
}
|
}
|
||||||
|
|
||||||
addVoidFn("delay") {
|
addVoidFn("delay") {
|
||||||
|
@ -12,4 +12,4 @@ open class ScriptError(val pos: Pos, val errorMessage: String,cause: Throwable?=
|
|||||||
cause
|
cause
|
||||||
)
|
)
|
||||||
|
|
||||||
class ExecutionError(val errorObject: ObjError) : ScriptError(errorObject.context.pos, errorObject.message)
|
class ExecutionError(val errorObject: ObjException) : ScriptError(errorObject.context.pos, errorObject.message)
|
||||||
|
@ -1748,4 +1748,36 @@ class ScriptTest {
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testThrowExisting()= runTest {
|
||||||
|
eval("""
|
||||||
|
val x = IllegalArgumentException("test")
|
||||||
|
println("instance class",x::class)
|
||||||
|
println("instance", x)
|
||||||
|
println("Exception object",Exception)
|
||||||
|
println("... and it's class",Exception::class)
|
||||||
|
assert( x is Exception )
|
||||||
|
println(x)
|
||||||
|
|
||||||
|
var t = 0
|
||||||
|
var finallyCaught = false
|
||||||
|
try {
|
||||||
|
t = 1
|
||||||
|
throw x
|
||||||
|
t = 2
|
||||||
|
}
|
||||||
|
catch( e: SymbolNotDefinedException ) {
|
||||||
|
t = 101
|
||||||
|
}
|
||||||
|
catch( e: IllegalArgumentException ) {
|
||||||
|
t = 3
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
finallyCaught = true
|
||||||
|
}
|
||||||
|
assertEquals(3, t)
|
||||||
|
assert(finallyCaught)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user