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:
|
||||
|
||||
- [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)
|
||||
- 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)
|
||||
@@ -753,6 +753,62 @@ We can use labels too:
|
||||
assert( search(["hello", "world"], 'z') == null)
|
||||
>>> 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
|
||||
|
||||
There are auto-increments and auto-decrements:
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.4.0-SNAPSHOT"
|
||||
version = "0.5.1-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
@@ -62,6 +62,7 @@ kotlin {
|
||||
sourceSets {
|
||||
all {
|
||||
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
||||
languageSettings.optIn("kotlin.contracts.ExperimentalContracts::class")
|
||||
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
|
||||
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
|
||||
}
|
||||
|
||||
@@ -717,10 +717,132 @@ class Compiler(
|
||||
"fn", "fun" -> parseFunctionDeclaration(cc)
|
||||
"if" -> parseIfStatement(cc)
|
||||
"class" -> parseClassDeclaration(cc, false)
|
||||
"struct" -> parseClassDeclaration(cc, true)
|
||||
"try" -> parseTryStatement(cc)
|
||||
"throw" -> parseThrowStatement(cc)
|
||||
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 {
|
||||
val nameToken = cc.requireToken(Token.Type.ID)
|
||||
val constructorArgsDeclaration =
|
||||
@@ -906,9 +1028,10 @@ class Compiler(
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
if (lbe.doContinue) continue
|
||||
}
|
||||
return lbe.result
|
||||
}
|
||||
throw lbe
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i in start.toLong()..<end.toLong()) {
|
||||
@@ -934,9 +1057,10 @@ class Compiler(
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
if (lbe.doContinue) continue
|
||||
}
|
||||
return lbe.result
|
||||
}
|
||||
throw lbe
|
||||
}
|
||||
else {
|
||||
loopVar.value = iterObj.invokeInstanceMethod(forContext, "next")
|
||||
result = body.execute(forContext)
|
||||
@@ -991,6 +1115,7 @@ class Compiler(
|
||||
break
|
||||
}
|
||||
}
|
||||
throw e
|
||||
}
|
||||
} while( condition.execute(doContext).toBool() )
|
||||
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")
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseNPE(): Nothing = raiseError(ObjNullPointerError(this))
|
||||
fun raiseNPE(): Nothing = raiseError(ObjNullPointerException(this))
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseIndexOutOfBounds(message: String = "Index out of bounds"): Nothing =
|
||||
raiseError(ObjIndexOutOfBoundsError(this, message))
|
||||
raiseError(ObjIndexOutOfBoundsException(this, message))
|
||||
|
||||
@Suppress("unused")
|
||||
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")
|
||||
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 {
|
||||
throw ExecutionError(ObjError(this, message))
|
||||
throw ExecutionError(ObjException(this, message))
|
||||
}
|
||||
|
||||
fun raiseError(obj: ObjError): Nothing {
|
||||
fun raiseError(obj: ObjException): Nothing {
|
||||
throw ExecutionError(obj)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.sergeych.bintools.encodeToHex
|
||||
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].
|
||||
@@ -63,7 +66,7 @@ open class Obj {
|
||||
suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
|
||||
invokeInstanceMethod(context, name, Arguments(args.toList()))
|
||||
|
||||
inline suspend fun <reified T : Obj> callMethod(
|
||||
suspend inline fun <reified T : Obj> callMethod(
|
||||
context: Context,
|
||||
name: String,
|
||||
args: Arguments = Arguments.EMPTY
|
||||
@@ -179,11 +182,10 @@ open class Obj {
|
||||
|
||||
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:
|
||||
val obj = objClass.getInstanceMemberOrNull(name) ?: context.raiseError("no such field: $name")
|
||||
val value = obj.value
|
||||
return when (value) {
|
||||
return when (val value = obj.value) {
|
||||
is Statement -> {
|
||||
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() {
|
||||
override val asStr: ObjString by lazy { ObjString("Error: $message") }
|
||||
open class ObjException(exceptionClass: ExceptionClass, val context: Context, val message: String) : Obj() {
|
||||
constructor(name: String,context: Context, message: String) : this(getOrCreateExceptionClass(name), context, message)
|
||||
constructor(context: Context, message: String) : this(Root, context, message)
|
||||
|
||||
fun raise(): Nothing {
|
||||
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 ObjClassCastError(context: Context, message: String) : ObjError(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 ObjIllegalAssignmentError(context: Context, message: String = "illegal assignment") : ObjError(context, message)
|
||||
class ObjSymbolNotDefinedError(context: Context, message: String = "symbol is not defined") : ObjError(context, message)
|
||||
class ObjIterationFinishedError(context: Context) : ObjError(context, "iteration finished")
|
||||
class ObjAccessError(context: Context, message: String = "access not allowed error") : ObjError(context, message)
|
||||
class ObjAssertionFailedException(context: Context, message: String) :
|
||||
ObjException("AssertionFailedException", context, message)
|
||||
|
||||
class ObjClassCastException(context: Context, message: String) : ObjException("ClassCastException", context, message)
|
||||
class ObjIndexOutOfBoundsException(context: Context, message: String = "index out of bounds") :
|
||||
ObjException("IndexOutOfBoundsException", 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") }
|
||||
|
||||
class ObjClass(
|
||||
open class ObjClass(
|
||||
val className: String,
|
||||
vararg val parents: ObjClass,
|
||||
) : Obj() {
|
||||
@@ -122,7 +122,7 @@ class ObjArrayIterator(val array: Obj) : Obj() {
|
||||
val self = thisAs<ObjArrayIterator>()
|
||||
if (self.nextIndex < self.lastIndex) {
|
||||
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
|
||||
} else raiseError(ObjIterationFinishedError(this))
|
||||
} else raiseError(ObjIterationFinishedException(this))
|
||||
}
|
||||
addFn("hasNext") {
|
||||
val self = thisAs<ObjArrayIterator>()
|
||||
|
||||
@@ -9,7 +9,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (it.visibility.isPublic)
|
||||
it
|
||||
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)
|
||||
}
|
||||
@@ -17,8 +17,8 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
override suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
||||
instanceContext[name]?.let { f ->
|
||||
if (!f.visibility.isPublic)
|
||||
ObjIllegalAssignmentError(context, "can't assign to non-public field $name")
|
||||
if (!f.isMutable) ObjIllegalAssignmentError(context, "can't reassign val $name").raise()
|
||||
ObjIllegalAssignmentException(context, "can't assign to non-public field $name")
|
||||
if (!f.isMutable) ObjIllegalAssignmentException(context, "can't reassign val $name").raise()
|
||||
if (f.value.assign(context, newValue) == null)
|
||||
f.value = newValue
|
||||
} ?: super.writeField(context, name, newValue)
|
||||
@@ -29,7 +29,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (it.visibility.isPublic)
|
||||
it.value.invoke(context, this, args)
|
||||
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)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||
if( isCharRange ) ObjChar(x.toInt().toChar()) else ObjInt(x)
|
||||
}
|
||||
else {
|
||||
context.raiseError(ObjIterationFinishedError(context))
|
||||
context.raiseError(ObjIterationFinishedException(context))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -21,6 +21,7 @@ class Script(
|
||||
|
||||
companion object {
|
||||
val defaultContext: Context = Context().apply {
|
||||
ObjException.addExceptionsToContext(this)
|
||||
addFn("println") {
|
||||
for ((i, a) in args.withIndex()) {
|
||||
if (i > 0) print(' ' + a.asStr.value)
|
||||
@@ -117,14 +118,14 @@ class Script(
|
||||
addVoidFn("assert") {
|
||||
val cond = requiredArg<ObjBool>(0)
|
||||
if( !cond.value == true )
|
||||
raiseError(ObjAssertionError(this,"Assertion failed"))
|
||||
raiseError(ObjAssertionFailedException(this,"Assertion failed"))
|
||||
}
|
||||
|
||||
addVoidFn("assertEquals") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
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") {
|
||||
val code = requireOnlyArg<Statement>()
|
||||
@@ -138,7 +139,7 @@ class Script(
|
||||
catch (e: ScriptError) {
|
||||
ObjNull
|
||||
}
|
||||
result ?: raiseError(ObjAssertionError(this,"Expected exception but nothing was thrown"))
|
||||
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
|
||||
}
|
||||
|
||||
addVoidFn("delay") {
|
||||
|
||||
@@ -12,4 +12,4 @@ open class ScriptError(val pos: Pos, val errorMessage: String,cause: Throwable?=
|
||||
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
|
||||
fun whileAssignTest() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
var t = 0
|
||||
val x = while( t < 5 ) { t++ }
|
||||
// last returned value is 4 - when t was 5 body was not executed
|
||||
assertEquals( 4, x )
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1597,32 +1599,37 @@ class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun testCallLastBlockAfterDetault() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
// this means last is lambda:
|
||||
fun f(e=1, f) {
|
||||
"e="+e+"f="+f()
|
||||
}
|
||||
assertEquals("e=1f=xx", f { "xx" })
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCallLastBlockWithEllipsis() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
// this means last is lambda:
|
||||
fun f(e..., f) {
|
||||
"e="+e+"f="+f()
|
||||
}
|
||||
assertEquals("e=[]f=xx", f { "xx" })
|
||||
assertEquals("e=[1, 2]f=xx", f(1,2) { "xx" })
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMethodCallLastBlockAfterDefault() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
class Foo {
|
||||
// this means last is lambda:
|
||||
fun f(e=1, f) {
|
||||
@@ -1631,13 +1638,15 @@ class ScriptTest {
|
||||
}
|
||||
val f = Foo()
|
||||
assertEquals("e=1f=xx", f.f { "xx" })
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMethodCallLastBlockWithEllipsis() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
class Foo {
|
||||
// this means last is lambda:
|
||||
fun f(e..., f) {
|
||||
@@ -1647,13 +1656,15 @@ class ScriptTest {
|
||||
val f = Foo()
|
||||
assertEquals("e=[]f=xx", f.f { "xx" })
|
||||
assertEquals("e=[1, 2]f=xx", f.f(1,2) { "xx" })
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nationalCharsTest() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
fun сумма_ряда(x, погрешность=0.0001, f) {
|
||||
var сумма = 0
|
||||
for( n in 1..100000) {
|
||||
@@ -1669,30 +1680,156 @@ class ScriptTest {
|
||||
sign * pow(x, n) / n
|
||||
}
|
||||
assert( x - ln(2) < 0.001 )
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doWhileSimpleTest() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
var sum = 0
|
||||
var x = do {
|
||||
val s = sum
|
||||
sum += 1
|
||||
} while( s < 10 )
|
||||
assertEquals(11, x)
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFailDoWhileSample1() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
fun readLine() { "done: result" }
|
||||
val result = do {
|
||||
val line = readLine()
|
||||
} while( !line.startsWith("done:") )
|
||||
assertEquals("result", result.drop(6))
|
||||
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())
|
||||
}
|
||||
|
||||
@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 {
|
||||
runDocTests("../docs/declaring_arguments.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExceptionsBooks() = runTest {
|
||||
runDocTests("../docs/exceptions_handling.md")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user