Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0981d8370e | ||
|
|
c4122e8243 | ||
|
|
c3bf536bab | ||
|
|
5ed8b2f123 | ||
|
|
6c71f0a2e6 | ||
|
|
95aae0b231 | ||
|
|
b3f08b4cac |
No files matched your search
@@ -0,0 +1,129 @@
|
|||||||
|
# Exceptions handling
|
||||||
|
|
||||||
|
Exceptions are widely used in modern programming languages, so
|
||||||
|
they are implemented also in Lyng and in the most complete way.
|
||||||
|
|
||||||
|
# Exception classes
|
||||||
|
|
||||||
|
Exceptions are throwing instances of some class that inherits `Exception`
|
||||||
|
across the code. Below is the list of built-in exceptions. Note that
|
||||||
|
only objects that inherit `Exception` can be thrown. For example:
|
||||||
|
|
||||||
|
assert( IllegalArgumentException() is Exception)
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
# Try statement: catching exceptions
|
||||||
|
|
||||||
|
There general pattern is:
|
||||||
|
|
||||||
|
```
|
||||||
|
try_statement = try_clause, [catch_clause, ...], [finally_clause]
|
||||||
|
|
||||||
|
try_clause = "try", "{", statements, "}"
|
||||||
|
|
||||||
|
catch_clause = "catch", [(full_catch | shorter_catch)], "{", statements "}"
|
||||||
|
|
||||||
|
full_catch = "(", catch_var, ":", exception_class [, excetpion_class...], ")
|
||||||
|
|
||||||
|
shorter_catch = "(", catch_var, ")"
|
||||||
|
|
||||||
|
finally_clause = "{", statements, "}"
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's in details.
|
||||||
|
|
||||||
|
## Full catch block:
|
||||||
|
|
||||||
|
val result = try {
|
||||||
|
throw IllegalArgumentException("the test")
|
||||||
|
}
|
||||||
|
catch( x: IndexOutOfBoundsException, IllegalArgumentException) {
|
||||||
|
x.message
|
||||||
|
}
|
||||||
|
catch(x: Exception) {
|
||||||
|
"bad"
|
||||||
|
}
|
||||||
|
assertEquals(result, "the test")
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Because our exception is listed in a first catch block, it is processed there.
|
||||||
|
|
||||||
|
The full form allow a single catch block to process exceptions with specified classes and bind actual caught object to
|
||||||
|
the given variable. This is most common and well known form, implemented like this or similar in many other languages,
|
||||||
|
like Kotlin, Java or C++.
|
||||||
|
|
||||||
|
## Shorter form
|
||||||
|
|
||||||
|
When you want to catch _all_ the exceptions, you should write `catch(e: Exception)`,
|
||||||
|
but it is somewhat redundant, so there is simpler variant:
|
||||||
|
|
||||||
|
val sample2 = try {
|
||||||
|
throw IllegalArgumentException("sample 2")
|
||||||
|
}
|
||||||
|
catch(x) {
|
||||||
|
x.message
|
||||||
|
}
|
||||||
|
assertEquals( sample2, "sample 2" )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
But well most likely you will find default variable `it`, like in Kotlin, more than enough
|
||||||
|
to catch all exceptions to, then you can write it even shorter:
|
||||||
|
|
||||||
|
val sample2 = try {
|
||||||
|
throw IllegalArgumentException("sample 3")
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
it.message
|
||||||
|
}
|
||||||
|
assertEquals( sample2, "sample 3" )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
You can even check the type of the `it` and create more convenient and sophisticated processing logic. Such approach is
|
||||||
|
used, for example, in Scala.
|
||||||
|
|
||||||
|
# Conveying data with exceptions
|
||||||
|
|
||||||
|
The simplest way is to provide exception string and `Exception` class:
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw Exception("this is my exception")
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
it.message
|
||||||
|
}
|
||||||
|
>>> "this is my exception"
|
||||||
|
|
||||||
|
This way, in turn, can also be shortened, as it is overly popular:
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw "this is my exception"
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
it.message
|
||||||
|
}
|
||||||
|
>>> "this is my exception"
|
||||||
|
|
||||||
|
The trick, though, works with strings only, and always provide `Exception` instances, which is good for debugging but
|
||||||
|
most often not enough.
|
||||||
|
|
||||||
|
# Custom error classes
|
||||||
|
|
||||||
|
_this functionality is not yet released_
|
||||||
|
|
||||||
|
# Standard exception classes
|
||||||
|
|
||||||
|
| class | notes |
|
||||||
|
|----------------------------|-------------------------------------------------------|
|
||||||
|
| Exception | root of al throwable objects |
|
||||||
|
| NullPointerException | |
|
||||||
|
| AssertionFailedException | |
|
||||||
|
| ClassCastException | |
|
||||||
|
| IndexOutOfBoundsException | |
|
||||||
|
| IllegalArgumentException | |
|
||||||
|
| IllegalAssignmentException | assigning to val, etc. |
|
||||||
|
| SymbolNotDefinedException | |
|
||||||
|
| IterationEndException | attempt to read iterator past end, `hasNext == false` |
|
||||||
|
| AccessException | attempt to access private members or like |
|
||||||
|
| UnknownException | unexpected kotlin exception caught |
|
||||||
|
| | |
|
||||||
|
|
||||||
+57
-1
@@ -12,7 +12,7 @@ In other word, the code usually works as expected when you see it. So, nothing u
|
|||||||
__Other documents to read__ maybe after this one:
|
__Other documents to read__ maybe after this one:
|
||||||
|
|
||||||
- [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md)
|
- [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md)
|
||||||
- [OOP notes](OOP.md)
|
- [OOP notes](OOP.md), [exception handling](exceptions_handling.md)
|
||||||
- [math in Lyng](math.md)
|
- [math in Lyng](math.md)
|
||||||
- Some class references: [List], [Real], [Range], [Iterable], [Iterator]
|
- Some class references: [List], [Real], [Range], [Iterable], [Iterator]
|
||||||
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
|
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
|
||||||
@@ -753,6 +753,62 @@ 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
|
||||||
|
|
||||||
|
There is shorter form of catch block when you want to catch any exception:
|
||||||
|
|
||||||
|
var caught = null
|
||||||
|
try {
|
||||||
|
throw IllegalArgumentException()
|
||||||
|
}
|
||||||
|
catch(t) { // same as catch(t: Exception), but simpler
|
||||||
|
caught = t
|
||||||
|
}
|
||||||
|
assert( caught is IllegalArgumentException )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
And even shortest, for the Lying lang tradition, missing var is `it`:
|
||||||
|
|
||||||
|
var caught = null
|
||||||
|
try {
|
||||||
|
throw IllegalArgumentException()
|
||||||
|
}
|
||||||
|
catch { // same as catch(it: Exception), but simpler
|
||||||
|
caught = it
|
||||||
|
}
|
||||||
|
assert( caught is IllegalArgumentException )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
It is possible to catch several exceptions in the same block too, use
|
||||||
|
`catch( varName: ExceptionClass1, ExceptionClass2)`, etc, use short form of throw and
|
||||||
|
many more.
|
||||||
|
|
||||||
|
- see [exception handling](exceptions_handling.md) for detailed exceptions tutorial and reference.
|
||||||
|
|
||||||
|
|
||||||
# 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.1-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,132 @@ 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 {
|
||||||
|
var errorObject = throwStatement.execute(this)
|
||||||
|
if( errorObject is ObjString )
|
||||||
|
errorObject = ObjException(this, errorObject.value)
|
||||||
|
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" ) {
|
||||||
|
|
||||||
|
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
|
||||||
|
t = cc.next()
|
||||||
|
if (t.type != Token.Type.ID) throw ScriptError(t.pos, "expected catch variable")
|
||||||
|
val catchVar = t
|
||||||
|
|
||||||
|
val exClassNames = mutableListOf<String>()
|
||||||
|
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
|
||||||
|
// load list of exception classes
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
// no type!
|
||||||
|
exClassNames += "Exception"
|
||||||
|
cc.skipTokenOfType(Token.Type.RPAREN)
|
||||||
|
}
|
||||||
|
val block = parseBlock(cc)
|
||||||
|
catches += CatchBlockData(catchVar, exClassNames, block)
|
||||||
|
cc.skipTokens(Token.Type.NEWLINE)
|
||||||
|
t = cc.next()
|
||||||
|
} else {
|
||||||
|
// no (e: Exception) block: should be shortest variant `catch { ... }`
|
||||||
|
cc.skipTokenOfType(Token.Type.LBRACE, "expected catch(...) or catch { ... } here")
|
||||||
|
catches += CatchBlockData(Token("it", cc.currentPos(), Token.Type.ID), listOf("Exception"),
|
||||||
|
parseBlock(cc,true))
|
||||||
|
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")
|
||||||
|
if( objException.isInstanceOf(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 =
|
||||||
@@ -906,8 +1028,9 @@ class Compiler(
|
|||||||
} catch (lbe: LoopBreakContinueException) {
|
} catch (lbe: LoopBreakContinueException) {
|
||||||
if (lbe.label == label || lbe.label == null) {
|
if (lbe.label == label || lbe.label == null) {
|
||||||
if (lbe.doContinue) continue
|
if (lbe.doContinue) continue
|
||||||
|
return lbe.result
|
||||||
}
|
}
|
||||||
return lbe.result
|
throw lbe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -934,8 +1057,9 @@ class Compiler(
|
|||||||
} catch (lbe: LoopBreakContinueException) {
|
} catch (lbe: LoopBreakContinueException) {
|
||||||
if (lbe.label == label || lbe.label == null) {
|
if (lbe.label == label || lbe.label == null) {
|
||||||
if (lbe.doContinue) continue
|
if (lbe.doContinue) continue
|
||||||
|
return lbe.result
|
||||||
}
|
}
|
||||||
return lbe.result
|
throw lbe
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
loopVar.value = iterObj.invokeInstanceMethod(forContext, "next")
|
loopVar.value = iterObj.invokeInstanceMethod(forContext, "next")
|
||||||
@@ -991,6 +1115,7 @@ class Compiler(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
} while( condition.execute(doContext).toBool() )
|
} while( condition.execute(doContext).toBool() )
|
||||||
if( !wasBroken ) elseStatement?.let { s -> result = s.execute(it) }
|
if( !wasBroken ) elseStatement?.let { s -> result = s.execute(it) }
|
||||||
|
|||||||
@@ -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,100 @@ 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 {
|
||||||
|
val message = context.args.getOrNull(0)?.toString() ?: name
|
||||||
|
return ObjException(this, context, message)
|
||||||
|
}
|
||||||
|
override fun toString(): String = "ExceptionClass[$name]@${hashCode().encodeToHex()}"
|
||||||
|
}
|
||||||
|
val Root = ExceptionClass("Throwable").apply {
|
||||||
|
addConst("message", statement {
|
||||||
|
(thisObj as ObjException).message.toObj()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -436,12 +436,14 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun whileAssignTest() = runTest {
|
fun whileAssignTest() = runTest {
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
var t = 0
|
var t = 0
|
||||||
val x = while( t < 5 ) { t++ }
|
val x = while( t < 5 ) { t++ }
|
||||||
// last returned value is 4 - when t was 5 body was not executed
|
// last returned value is 4 - when t was 5 body was not executed
|
||||||
assertEquals( 4, x )
|
assertEquals( 4, x )
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1597,32 +1599,37 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCallLastBlockAfterDetault() = runTest {
|
fun testCallLastBlockAfterDetault() = runTest {
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
// this means last is lambda:
|
// this means last is lambda:
|
||||||
fun f(e=1, f) {
|
fun f(e=1, f) {
|
||||||
"e="+e+"f="+f()
|
"e="+e+"f="+f()
|
||||||
}
|
}
|
||||||
assertEquals("e=1f=xx", f { "xx" })
|
assertEquals("e=1f=xx", f { "xx" })
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCallLastBlockWithEllipsis() = runTest {
|
fun testCallLastBlockWithEllipsis() = runTest {
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
// this means last is lambda:
|
// this means last is lambda:
|
||||||
fun f(e..., f) {
|
fun f(e..., f) {
|
||||||
"e="+e+"f="+f()
|
"e="+e+"f="+f()
|
||||||
}
|
}
|
||||||
assertEquals("e=[]f=xx", f { "xx" })
|
assertEquals("e=[]f=xx", f { "xx" })
|
||||||
assertEquals("e=[1, 2]f=xx", f(1,2) { "xx" })
|
assertEquals("e=[1, 2]f=xx", f(1,2) { "xx" })
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testMethodCallLastBlockAfterDefault() = runTest {
|
fun testMethodCallLastBlockAfterDefault() = runTest {
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
class Foo {
|
class Foo {
|
||||||
// this means last is lambda:
|
// this means last is lambda:
|
||||||
fun f(e=1, f) {
|
fun f(e=1, f) {
|
||||||
@@ -1631,13 +1638,15 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
val f = Foo()
|
val f = Foo()
|
||||||
assertEquals("e=1f=xx", f.f { "xx" })
|
assertEquals("e=1f=xx", f.f { "xx" })
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testMethodCallLastBlockWithEllipsis() = runTest {
|
fun testMethodCallLastBlockWithEllipsis() = runTest {
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
class Foo {
|
class Foo {
|
||||||
// this means last is lambda:
|
// this means last is lambda:
|
||||||
fun f(e..., f) {
|
fun f(e..., f) {
|
||||||
@@ -1647,13 +1656,15 @@ class ScriptTest {
|
|||||||
val f = Foo()
|
val f = Foo()
|
||||||
assertEquals("e=[]f=xx", f.f { "xx" })
|
assertEquals("e=[]f=xx", f.f { "xx" })
|
||||||
assertEquals("e=[1, 2]f=xx", f.f(1,2) { "xx" })
|
assertEquals("e=[1, 2]f=xx", f.f(1,2) { "xx" })
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun nationalCharsTest() = runTest {
|
fun nationalCharsTest() = runTest {
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
fun сумма_ряда(x, погрешность=0.0001, f) {
|
fun сумма_ряда(x, погрешность=0.0001, f) {
|
||||||
var сумма = 0
|
var сумма = 0
|
||||||
for( n in 1..100000) {
|
for( n in 1..100000) {
|
||||||
@@ -1669,31 +1680,157 @@ class ScriptTest {
|
|||||||
sign * pow(x, n) / n
|
sign * pow(x, n) / n
|
||||||
}
|
}
|
||||||
assert( x - ln(2) < 0.001 )
|
assert( x - ln(2) < 0.001 )
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun doWhileSimpleTest() = runTest {
|
fun doWhileSimpleTest() = runTest {
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
var sum = 0
|
var sum = 0
|
||||||
var x = do {
|
var x = do {
|
||||||
val s = sum
|
val s = sum
|
||||||
sum += 1
|
sum += 1
|
||||||
} while( s < 10 )
|
} while( s < 10 )
|
||||||
assertEquals(11, x)
|
assertEquals(11, x)
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFailDoWhileSample1() = runTest {
|
fun testFailDoWhileSample1() = runTest {
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
fun readLine() { "done: result" }
|
fun readLine() { "done: result" }
|
||||||
val result = do {
|
val result = do {
|
||||||
val line = readLine()
|
val line = readLine()
|
||||||
} while( !line.startsWith("done:") )
|
} while( !line.startsWith("done:") )
|
||||||
assertEquals("result", result.drop(6))
|
assertEquals("result", result.drop(6))
|
||||||
result
|
result
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testForContinue() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var x = 0
|
||||||
|
for( i in 1..10 ) {
|
||||||
|
if( i % 2 == 0 ) continue
|
||||||
|
println(i)
|
||||||
|
x++
|
||||||
|
}
|
||||||
|
assertEquals(5, x)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testForLabelNreakTest() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var x = 0
|
||||||
|
var y = 0
|
||||||
|
FOR0@ for( i in 1..10 ) {
|
||||||
|
x = i
|
||||||
|
for( j in 1..20 ) {
|
||||||
|
y = j
|
||||||
|
if( i == 3 && j == 5 ) {
|
||||||
|
println("bb")
|
||||||
|
break@FOR0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals( 5, y )
|
||||||
|
assertEquals( 3, x )
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testThrowExisting()= runTest {
|
||||||
|
eval("""
|
||||||
|
val x = IllegalArgumentException("test")
|
||||||
|
assert( x is Exception )
|
||||||
|
|
||||||
|
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())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCatchShort1()= runTest {
|
||||||
|
eval("""
|
||||||
|
val x = IllegalArgumentException("test")
|
||||||
|
var t = 0
|
||||||
|
var finallyCaught = false
|
||||||
|
try {
|
||||||
|
t = 1
|
||||||
|
throw x
|
||||||
|
t = 2
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
t = 31
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
finallyCaught = true
|
||||||
|
}
|
||||||
|
assertEquals(31, t)
|
||||||
|
assert(finallyCaught)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCatchShort2()= runTest {
|
||||||
|
eval("""
|
||||||
|
val x = IllegalArgumentException("test")
|
||||||
|
var caught = null
|
||||||
|
try {
|
||||||
|
throw x
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
caught = it
|
||||||
|
}
|
||||||
|
assert( caught is IllegalArgumentException )
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAccessEHData() = runTest {
|
||||||
|
eval("""
|
||||||
|
val x = IllegalArgumentException("test")
|
||||||
|
val m = try {
|
||||||
|
throw x
|
||||||
|
null
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
println(e)
|
||||||
|
println(e::class)
|
||||||
|
println(e.message)
|
||||||
|
println("--------------")
|
||||||
|
e.message
|
||||||
|
}
|
||||||
|
println(m)
|
||||||
|
assert( m == "test" )
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -268,4 +268,9 @@ class BookTest {
|
|||||||
fun testArgumentBooks() = runTest {
|
fun testArgumentBooks() = runTest {
|
||||||
runDocTests("../docs/declaring_arguments.md")
|
runDocTests("../docs/declaring_arguments.md")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExceptionsBooks() = runTest {
|
||||||
|
runDocTests("../docs/exceptions_handling.md")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user