diff --git a/docs/exceptions_handling.md b/docs/exceptions_handling.md new file mode 100644 index 0000000..a6a61bf --- /dev/null +++ b/docs/exceptions_handling.md @@ -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 | +| | | + diff --git a/docs/tutorial.md b/docs/tutorial.md index fa58422..e2b39ae 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -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) diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index a644e30..87d8920 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -725,7 +725,9 @@ class Compiler( private fun parseThrowStatement(cc: CompilerContext): Statement { val throwStatement = parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "throw object expected") return statement { - val errorObject = throwStatement.execute(this) + 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") @@ -816,11 +818,10 @@ class Compiler( 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.isInstanceOf(exObj) ) + if( objException.isInstanceOf(exObj) ) { exceptionObject = objException break + } } if( exceptionObject != null ) { val catchContext = this.copy(pos = cdata.catchVar.pos) diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt index d76a19b..3f7a44e 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt @@ -347,13 +347,16 @@ open class ObjException(exceptionClass: ExceptionClass, val context: Context, va 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) - } + 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") + val Root = ExceptionClass("Throwable").apply { + addConst("message", statement { + (thisObj as ObjException).message.toObj() + }) + } private val op = ProtectedOp() private val existingErrorClasses = mutableMapOf() diff --git a/library/src/commonTest/kotlin/ScriptTest.kt b/library/src/commonTest/kotlin/ScriptTest.kt index 532f99e..53098d0 100644 --- a/library/src/commonTest/kotlin/ScriptTest.kt +++ b/library/src/commonTest/kotlin/ScriptTest.kt @@ -1813,4 +1813,24 @@ class ScriptTest { """.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()) + } + } \ No newline at end of file diff --git a/library/src/jvmTest/kotlin/BookTest.kt b/library/src/jvmTest/kotlin/BookTest.kt index efe0af2..d30f3bb 100644 --- a/library/src/jvmTest/kotlin/BookTest.kt +++ b/library/src/jvmTest/kotlin/BookTest.kt @@ -268,4 +268,9 @@ class BookTest { fun testArgumentBooks() = runTest { runDocTests("../docs/declaring_arguments.md") } + + @Test + fun testExceptionsBooks() = runTest { + runDocTests("../docs/exceptions_handling.md") + } } \ No newline at end of file