Compare commits

...
7 Commits
Author SHA1 Message Date
sergeych 0981d8370e v0.5.1-SNAPSHOT: Exceptions Handling! 2025-06-13 01:36:21 +04:00
sergeych c4122e8243 refs #22 more docs 2025-06-13 01:32:40 +04:00
sergeych c3bf536bab refs #22 more docs, fixed EH bug
+shortcut throw "message"
2025-06-13 01:30:26 +04:00
sergeych 5ed8b2f123 refs #22 @35m 2 shorter forms for catch block, fixed catch class instance type checks 2025-06-13 00:12:21 +04:00
sergeych 6c71f0a2e6 fix #18 basic exceptions handling 2025-06-12 19:17:35 +04:00
sergeych 95aae0b231 fix #21 fixed loop break with label behavior 2025-06-12 12:34:03 +04:00
sergeych b3f08b4cac fix #1 for-continue test 2025-06-12 12:23:50 +04:00
13 changed files with 588 additions and 53 deletions

No files matched your search

+129
View File
@@ -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
View File
@@ -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:
+2 -1
View File
@@ -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()}"
}
class ObjNullPointerError(context: Context) : ObjError(context, "object is null")
companion object {
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 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 ObjNullPointerException(context: Context) : ObjException("NullPointerException", context, "object is null")
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)
+152 -15
View File
@@ -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())
}
+5
View File
@@ -268,4 +268,9 @@ class BookTest {
fun testArgumentBooks() = runTest {
runDocTests("../docs/declaring_arguments.md")
}
@Test
fun testExceptionsBooks() = runTest {
runDocTests("../docs/exceptions_handling.md")
}
}