Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be4f2c7f45 | ||
|
|
7cc80e2433 | ||
|
|
dacdcd7faa | ||
|
|
59a76efdce | ||
|
|
bb862e6cb5 | ||
|
|
aea819b89a | ||
|
|
88974e0f2d | ||
|
|
0981d8370e | ||
|
|
c4122e8243 | ||
|
|
c3bf536bab | ||
|
|
5ed8b2f123 | ||
|
|
6c71f0a2e6 | ||
|
|
95aae0b231 | ||
|
|
b3f08b4cac |
No files matched your search
@@ -0,0 +1,145 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
## finally block
|
||||||
|
|
||||||
|
If `finally` block present, it will be executed after body (until first exception)
|
||||||
|
and catch block, if any will match. finally statement is executed even if the
|
||||||
|
exception will be thrown and not caught locally. It does not alter try/catch block result:
|
||||||
|
|
||||||
|
try {
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
println("called finally")
|
||||||
|
}
|
||||||
|
>>> called finally
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
- and yes, there could be try-finally block, no catching, but perform some guaranteed cleanup.
|
||||||
|
|
||||||
|
# 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 |
|
||||||
|
| | |
|
||||||
|
|
||||||
+140
-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)
|
||||||
@@ -552,6 +552,89 @@ Or, more neat:
|
|||||||
>>> just 3
|
>>> just 3
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
## When
|
||||||
|
|
||||||
|
It is very much like the kotlin's:
|
||||||
|
|
||||||
|
fun type(x) {
|
||||||
|
when(x) {
|
||||||
|
in 'a'..'z', in 'A'..'Z' -> "letter"
|
||||||
|
in '0'..'9' -> "digit"
|
||||||
|
'$' -> "dollar"
|
||||||
|
"EUR" -> "crap"
|
||||||
|
in ['@', '#', '^'] -> "punctuation1"
|
||||||
|
in "*&.," -> "punctuation2"
|
||||||
|
else -> "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals("digit", type('3'))
|
||||||
|
assertEquals("dollar", type('$'))
|
||||||
|
assertEquals("crap", type("EUR"))
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Notice, several conditions can be grouped with a comma.
|
||||||
|
Also, you can check the type too:
|
||||||
|
|
||||||
|
fun type(x) {
|
||||||
|
when(x) {
|
||||||
|
"42", 42 -> "answer to the great question"
|
||||||
|
is Real, is Int -> "number"
|
||||||
|
is String -> {
|
||||||
|
for( d in x ) {
|
||||||
|
if( d !in '0'..'9' )
|
||||||
|
break "unknown"
|
||||||
|
}
|
||||||
|
else "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals("number", type(5))
|
||||||
|
assertEquals("number", type("153"))
|
||||||
|
assertEquals("number", type(π/2))
|
||||||
|
assertEquals("unknown", type("12%"))
|
||||||
|
assertEquals("answer to the great question", type(42))
|
||||||
|
assertEquals("answer to the great question", type("42"))
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
### supported when conditions:
|
||||||
|
|
||||||
|
#### Contains:
|
||||||
|
|
||||||
|
You can thest that _when expression_ is _contained_, or not contained, in some object using `in container` and `!in container`. The container is any object that provides `contains` method, otherwise the runtime exception will be thrown.
|
||||||
|
|
||||||
|
Typical builtin types that are containers (e.g. support `conain`):
|
||||||
|
|
||||||
|
| class | notes |
|
||||||
|
|------------|--------------------------------------------|
|
||||||
|
| Collection | contains an element (1) |
|
||||||
|
| Array | faster maybe that Collection's |
|
||||||
|
| List | faster than Array's |
|
||||||
|
| String | character in string or substring in string |
|
||||||
|
| Range | object is included in the range (2) |
|
||||||
|
|
||||||
|
(1)
|
||||||
|
: Iterable is not the container as it can be infinite
|
||||||
|
|
||||||
|
(2)
|
||||||
|
: Depending on the inclusivity and open/closed range parameters. BE careful here: String range is allowed, but it is usually not what you expect of it:
|
||||||
|
|
||||||
|
assert( "more" in "a".."z") // string range ok
|
||||||
|
assert( 'x' !in "a".."z") // char in string range: probably error
|
||||||
|
assert( 'x' in 'a'..'z') // character range: ok
|
||||||
|
assert( "x" !in 'a'..'z') // string in character range: could be error
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
So we recommend not to mix characters and string ranges; use `ch in str` that works
|
||||||
|
as expected:
|
||||||
|
|
||||||
|
"foo" in "foobar"
|
||||||
|
>>> true
|
||||||
|
|
||||||
|
and also character inclusion:
|
||||||
|
|
||||||
|
'o' in "foobar"
|
||||||
|
>>> true
|
||||||
|
|
||||||
## while
|
## while
|
||||||
|
|
||||||
Regular pre-condition while loop, as expression, loop returns the last expression as everything else:
|
Regular pre-condition while loop, as expression, loop returns the last expression as everything else:
|
||||||
@@ -753,6 +836,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.6.0-SNAPSHOT"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
@@ -62,6 +62,7 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
all {
|
all {
|
||||||
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
|
||||||
|
languageSettings.optIn("kotlin.contracts.ExperimentalContracts::class")
|
||||||
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
|
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
|
||||||
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
|
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,13 +39,6 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.PRIVATE, Token.Type.PROTECTED -> {
|
|
||||||
if (cc.nextIdValue() in setOf("var", "val", "class", "fun", "fn")) {
|
|
||||||
continue
|
|
||||||
} else
|
|
||||||
throw ScriptError(t.pos, "unexpected keyword ${t.value}")
|
|
||||||
}
|
|
||||||
|
|
||||||
Token.Type.PLUS2, Token.Type.MINUS2 -> {
|
Token.Type.PLUS2, Token.Type.MINUS2 -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
parseExpression(cc)
|
parseExpression(cc)
|
||||||
@@ -149,7 +142,7 @@ class Compiler(
|
|||||||
v.invokeInstanceMethod(
|
v.invokeInstanceMethod(
|
||||||
context,
|
context,
|
||||||
next.value,
|
next.value,
|
||||||
args.toArguments(context,false)
|
args.toArguments(context, false)
|
||||||
), isMutable = false
|
), isMutable = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -170,7 +163,7 @@ class Compiler(
|
|||||||
v.invokeInstanceMethod(
|
v.invokeInstanceMethod(
|
||||||
context,
|
context,
|
||||||
next.value,
|
next.value,
|
||||||
Arguments(listOf(lambda),true)
|
Arguments(listOf(lambda), true)
|
||||||
), isMutable = false
|
), isMutable = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -458,17 +451,14 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.NEWLINE -> {}
|
Token.Type.NEWLINE -> {}
|
||||||
Token.Type.PROTECTED, Token.Type.PRIVATE -> {
|
|
||||||
if (!isClassDeclaration) {
|
|
||||||
cc.restorePos(startPos); return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Token.Type.ID -> {
|
Token.Type.ID -> {
|
||||||
// visibility
|
// visibility
|
||||||
val visibility = if (isClassDeclaration)
|
val visibility = if (isClassDeclaration && t.value == "private") {
|
||||||
cc.getVisibility(Visibility.Public)
|
t = cc.next()
|
||||||
else Visibility.Public
|
Visibility.Private
|
||||||
|
} else Visibility.Public
|
||||||
|
|
||||||
// val/var?
|
// val/var?
|
||||||
val access = when (t.value) {
|
val access = when (t.value) {
|
||||||
"val" -> {
|
"val" -> {
|
||||||
@@ -660,7 +650,7 @@ class Compiler(
|
|||||||
"void" -> Accessor { ObjVoid.asReadonly }
|
"void" -> Accessor { ObjVoid.asReadonly }
|
||||||
"null" -> Accessor { ObjNull.asReadonly }
|
"null" -> Accessor { ObjNull.asReadonly }
|
||||||
"true" -> Accessor { ObjBool(true).asReadonly }
|
"true" -> Accessor { ObjBool(true).asReadonly }
|
||||||
"false" -> Accessor { ObjBool(false).asReadonly }
|
"false" -> Accessor { ObjFalse.asReadonly }
|
||||||
else -> {
|
else -> {
|
||||||
Accessor({
|
Accessor({
|
||||||
it.pos = t.pos
|
it.pos = t.pos
|
||||||
@@ -707,8 +697,8 @@ class Compiler(
|
|||||||
* @return parsed statement or null if, for example. [id] is not among keywords
|
* @return parsed statement or null if, for example. [id] is not among keywords
|
||||||
*/
|
*/
|
||||||
private fun parseKeywordStatement(id: Token, cc: CompilerContext): Statement? = when (id.value) {
|
private fun parseKeywordStatement(id: Token, cc: CompilerContext): Statement? = when (id.value) {
|
||||||
"val" -> parseVarDeclaration(id.value, false, cc)
|
"val" -> parseVarDeclaration(false, Visibility.Public, cc)
|
||||||
"var" -> parseVarDeclaration(id.value, true, cc)
|
"var" -> parseVarDeclaration(true, Visibility.Public, cc)
|
||||||
"while" -> parseWhileStatement(cc)
|
"while" -> parseWhileStatement(cc)
|
||||||
"do" -> parseDoWhileStatement(cc)
|
"do" -> parseDoWhileStatement(cc)
|
||||||
"for" -> parseForStatement(cc)
|
"for" -> parseForStatement(cc)
|
||||||
@@ -717,8 +707,256 @@ 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)
|
||||||
else -> null
|
"throw" -> parseThrowStatement(cc)
|
||||||
|
"when" -> parseWhenStatement(cc)
|
||||||
|
else -> {
|
||||||
|
// triples
|
||||||
|
cc.previous()
|
||||||
|
when {
|
||||||
|
cc.matchQualifiers("fun", "private") -> parseFunctionDeclaration(cc, Visibility.Private)
|
||||||
|
cc.matchQualifiers("fn", "private") -> parseFunctionDeclaration(cc, Visibility.Private)
|
||||||
|
cc.matchQualifiers("fun", "open") -> parseFunctionDeclaration(cc, isOpen = true)
|
||||||
|
cc.matchQualifiers("fn", "open") -> parseFunctionDeclaration(cc, isOpen = true)
|
||||||
|
cc.matchQualifiers("val", "private") -> parseVarDeclaration(false, Visibility.Private, cc)
|
||||||
|
cc.matchQualifiers("var", "private") -> parseVarDeclaration(true, Visibility.Private, cc)
|
||||||
|
cc.matchQualifiers("val", "open") -> parseVarDeclaration(false, Visibility.Private, cc, true)
|
||||||
|
cc.matchQualifiers("var", "open") -> parseVarDeclaration(true, Visibility.Private, cc, true)
|
||||||
|
else -> {
|
||||||
|
cc.next()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class WhenCase(val condition: Statement, val block: Statement)
|
||||||
|
|
||||||
|
private fun parseWhenStatement(cc: CompilerContext): Statement {
|
||||||
|
// has a value, when(value) ?
|
||||||
|
var t = cc.skipWsTokens()
|
||||||
|
return if (t.type == Token.Type.LPAREN) {
|
||||||
|
// when(value)
|
||||||
|
val value = parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "when(value) expected")
|
||||||
|
cc.skipTokenOfType(Token.Type.RPAREN)
|
||||||
|
t = cc.next()
|
||||||
|
if (t.type != Token.Type.LBRACE) throw ScriptError(t.pos, "when { ... } expected")
|
||||||
|
val cases = mutableListOf<WhenCase>()
|
||||||
|
var elseCase: Statement? = null
|
||||||
|
lateinit var whenValue: Obj
|
||||||
|
|
||||||
|
// there could be 0+ then clauses
|
||||||
|
// condition could be a value, in and is clauses:
|
||||||
|
// parse several conditions for one then clause
|
||||||
|
|
||||||
|
// loop cases
|
||||||
|
outer@ while (true) {
|
||||||
|
|
||||||
|
var skipParseBody = false
|
||||||
|
val currentCondition = mutableListOf<Statement>()
|
||||||
|
|
||||||
|
// loop conditions
|
||||||
|
while (true) {
|
||||||
|
t = cc.skipWsTokens()
|
||||||
|
|
||||||
|
when (t.type) {
|
||||||
|
Token.Type.IN,
|
||||||
|
Token.Type.NOTIN -> {
|
||||||
|
// we need a copy in the closure:
|
||||||
|
val isIn = t.type == Token.Type.IN
|
||||||
|
val container = parseExpression(cc) ?: throw ScriptError(cc.currentPos(), "type expected")
|
||||||
|
currentCondition += statement {
|
||||||
|
val r = container.execute(this).contains(this, whenValue)
|
||||||
|
ObjBool(if (isIn) r else !r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Token.Type.IS, Token.Type.NOTIS -> {
|
||||||
|
// we need a copy in the closure:
|
||||||
|
val isIn = t.type == Token.Type.IS
|
||||||
|
val caseType = parseExpression(cc) ?: throw ScriptError(cc.currentPos(), "type expected")
|
||||||
|
currentCondition += statement {
|
||||||
|
val r = whenValue.isInstanceOf(caseType.execute(this))
|
||||||
|
ObjBool(if (isIn) r else !r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Token.Type.COMMA ->
|
||||||
|
continue
|
||||||
|
|
||||||
|
Token.Type.ARROW ->
|
||||||
|
break
|
||||||
|
|
||||||
|
Token.Type.RBRACE ->
|
||||||
|
break@outer
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
if (t.value == "else") {
|
||||||
|
cc.skipTokens(Token.Type.ARROW)
|
||||||
|
if (elseCase != null) throw ScriptError(
|
||||||
|
cc.currentPos(),
|
||||||
|
"when else block already defined"
|
||||||
|
)
|
||||||
|
elseCase =
|
||||||
|
parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "when else block expected")
|
||||||
|
skipParseBody = true
|
||||||
|
} else {
|
||||||
|
cc.previous()
|
||||||
|
val x = parseExpression(cc)
|
||||||
|
?: throw ScriptError(cc.currentPos(), "when case condition expected")
|
||||||
|
currentCondition += statement {
|
||||||
|
ObjBool(x.execute(this).compareTo(this, whenValue) == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// parsed conditions?
|
||||||
|
if (!skipParseBody) {
|
||||||
|
val block = parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "when case block expected")
|
||||||
|
for (c in currentCondition) cases += WhenCase(c, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
statement {
|
||||||
|
var result: Obj = ObjVoid
|
||||||
|
// in / is and like uses whenValue from closure:
|
||||||
|
whenValue = value.execute(this)
|
||||||
|
var found = false
|
||||||
|
for (c in cases)
|
||||||
|
if (c.condition.execute(this).toBool()) {
|
||||||
|
result = c.block.execute(this)
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (!found && elseCase != null) result = elseCase.execute(this)
|
||||||
|
result
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// when { cond -> ... }
|
||||||
|
TODO("when without object is not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val finallyClause = if (t.value == "finally") {
|
||||||
|
parseBlock(cc)
|
||||||
|
} else {
|
||||||
|
cc.previous()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (catches.isEmpty() && finallyClause == null)
|
||||||
|
throw ScriptError(cc.currentPos(), "try block must have either catch or finally clause or both")
|
||||||
|
|
||||||
|
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 {
|
||||||
@@ -906,9 +1144,10 @@ 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 {
|
||||||
for (i in start.toLong()..<end.toLong()) {
|
for (i in start.toLong()..<end.toLong()) {
|
||||||
@@ -934,9 +1173,10 @@ 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")
|
||||||
result = body.execute(forContext)
|
result = body.execute(forContext)
|
||||||
@@ -956,7 +1196,7 @@ class Compiler(
|
|||||||
cc.skipTokens(Token.Type.NEWLINE)
|
cc.skipTokens(Token.Type.NEWLINE)
|
||||||
|
|
||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
if( t.type != Token.Type.ID && t.value != "while" )
|
if (t.type != Token.Type.ID && t.value != "while")
|
||||||
cc.skipTokenOfType(Token.Type.LPAREN, "expected '(' here")
|
cc.skipTokenOfType(Token.Type.LPAREN, "expected '(' here")
|
||||||
|
|
||||||
val conditionStart = ensureLparen(cc)
|
val conditionStart = ensureLparen(cc)
|
||||||
@@ -981,19 +1221,19 @@ class Compiler(
|
|||||||
doContext = it.copy().apply { skipContextCreation = true }
|
doContext = it.copy().apply { skipContextCreation = true }
|
||||||
try {
|
try {
|
||||||
result = body.execute(doContext)
|
result = body.execute(doContext)
|
||||||
}
|
} catch (e: LoopBreakContinueException) {
|
||||||
catch( e: LoopBreakContinueException) {
|
if (e.label == label || e.label == null) {
|
||||||
if( e.label == label || e.label == null ) {
|
if (e.doContinue) continue
|
||||||
if( e.doContinue ) continue
|
|
||||||
else {
|
else {
|
||||||
result = e.result
|
result = e.result
|
||||||
wasBroken = true
|
wasBroken = true
|
||||||
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) }
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1147,9 +1387,11 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseFunctionDeclaration(tokens: CompilerContext): Statement {
|
private fun parseFunctionDeclaration(
|
||||||
val visibility = tokens.getVisibility()
|
tokens: CompilerContext,
|
||||||
|
visibility: Visibility = Visibility.Public,
|
||||||
|
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false
|
||||||
|
): Statement {
|
||||||
var t = tokens.next()
|
var t = tokens.next()
|
||||||
val start = t.pos
|
val start = t.pos
|
||||||
val name = if (t.type != Token.Type.ID)
|
val name = if (t.type != Token.Type.ID)
|
||||||
@@ -1203,7 +1445,7 @@ class Compiler(
|
|||||||
val block = parseScript(startPos, cc)
|
val block = parseScript(startPos, cc)
|
||||||
return statement(startPos) {
|
return statement(startPos) {
|
||||||
// block run on inner context:
|
// block run on inner context:
|
||||||
block.execute(if( it.skipContextCreation ) it else it.copy(startPos))
|
block.execute(if (it.skipContextCreation) it else it.copy(startPos))
|
||||||
}.also {
|
}.also {
|
||||||
val t1 = cc.next()
|
val t1 = cc.next()
|
||||||
if (t1.type != Token.Type.RBRACE)
|
if (t1.type != Token.Type.RBRACE)
|
||||||
@@ -1211,26 +1453,22 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement {
|
private fun parseVarDeclaration(
|
||||||
// we are just after var/val, visibility if exists is 2 steps behind
|
isMutable: Boolean,
|
||||||
val visibility = when (tokens.atOffset(-2)?.type) {
|
visibility: Visibility,
|
||||||
Token.Type.PRIVATE ->
|
tokens: CompilerContext,
|
||||||
Visibility.Private
|
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false
|
||||||
|
): Statement {
|
||||||
Token.Type.PROTECTED -> Visibility.Protected
|
|
||||||
else -> Visibility.Public
|
|
||||||
}
|
|
||||||
|
|
||||||
val nameToken = tokens.next()
|
val nameToken = tokens.next()
|
||||||
val start = nameToken.pos
|
val start = nameToken.pos
|
||||||
if (nameToken.type != Token.Type.ID)
|
if (nameToken.type != Token.Type.ID)
|
||||||
throw ScriptError(nameToken.pos, "Expected identifier after '$kind'")
|
throw ScriptError(nameToken.pos, "Expected identifier here")
|
||||||
val name = nameToken.value
|
val name = nameToken.value
|
||||||
|
|
||||||
val eqToken = tokens.next()
|
val eqToken = tokens.next()
|
||||||
var setNull = false
|
var setNull = false
|
||||||
if (eqToken.type != Token.Type.ASSIGN) {
|
if (eqToken.type != Token.Type.ASSIGN) {
|
||||||
if (!mutable)
|
if (!isMutable)
|
||||||
throw ScriptError(start, "val must be initialized")
|
throw ScriptError(start, "val must be initialized")
|
||||||
else {
|
else {
|
||||||
tokens.previous()
|
tokens.previous()
|
||||||
@@ -1249,7 +1487,7 @@ class Compiler(
|
|||||||
// create a separate copy:
|
// create a separate copy:
|
||||||
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
||||||
|
|
||||||
context.addItem(name, mutable, initValue, visibility)
|
context.addItem(name, isMutable, initValue, visibility)
|
||||||
initValue
|
initValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1393,7 +1631,8 @@ class Compiler(
|
|||||||
/**
|
/**
|
||||||
* The keywords that stop processing of expression term
|
* The keywords that stop processing of expression term
|
||||||
*/
|
*/
|
||||||
val stopKeywords = setOf("do", "break", "continue", "return", "if", "when", "do", "while", "for", "class", "struct")
|
val stopKeywords =
|
||||||
|
setOf("do", "break", "continue", "return", "if", "when", "do", "while", "for", "class", "struct")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
|
|
||||||
fun hasNext() = currentIndex < tokens.size
|
fun hasNext() = currentIndex < tokens.size
|
||||||
fun hasPrevious() = currentIndex > 0
|
fun hasPrevious() = currentIndex > 0
|
||||||
fun next() = tokens.getOrElse(currentIndex) { throw IllegalStateException("No next token") }.also { currentIndex++ }
|
fun next() =
|
||||||
|
if( currentIndex < tokens.size ) tokens[currentIndex++]
|
||||||
|
else Token("", tokens.last().pos, Token.Type.EOF)
|
||||||
|
// throw IllegalStateException("No more tokens")
|
||||||
|
|
||||||
fun previous() = if (!hasPrevious()) throw IllegalStateException("No previous token") else tokens[--currentIndex]
|
fun previous() = if (!hasPrevious()) throw IllegalStateException("No previous token") else tokens[--currentIndex]
|
||||||
|
|
||||||
fun savePos() = currentIndex
|
fun savePos() = currentIndex
|
||||||
@@ -47,9 +51,7 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
throw ScriptError(at, message)
|
throw ScriptError(at, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun currentPos() =
|
fun currentPos(): Pos = tokens[currentIndex].pos
|
||||||
if (hasNext()) next().pos.also { previous() }
|
|
||||||
else previous().pos.also { next() }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skips next token if its type is `tokenType`, returns `true` if so.
|
* Skips next token if its type is `tokenType`, returns `true` if so.
|
||||||
@@ -102,6 +104,7 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
* Return value of the next token if it is an identifier, null otherwise.
|
* Return value of the next token if it is an identifier, null otherwise.
|
||||||
* Does not change position.
|
* Does not change position.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
fun nextIdValue(): String? {
|
fun nextIdValue(): String? {
|
||||||
return if (hasNext()) {
|
return if (hasNext()) {
|
||||||
val nt = tokens[currentIndex]
|
val nt = tokens[currentIndex]
|
||||||
@@ -117,36 +120,43 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
/**
|
/**
|
||||||
* If the token at current position plus offset (could be negative) exists, returns it, otherwise returns null.
|
* If the token at current position plus offset (could be negative) exists, returns it, otherwise returns null.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
fun atOffset(offset: Int): Token? =
|
fun atOffset(offset: Int): Token? =
|
||||||
if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null
|
if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null
|
||||||
|
|
||||||
|
fun matchQualifiers(keyword: String, vararg qualifiers: String): Boolean {
|
||||||
|
val pos = savePos()
|
||||||
|
var count = 0
|
||||||
|
while( count < qualifiers.size) {
|
||||||
|
val t = next()
|
||||||
|
when(t.type) {
|
||||||
|
Token.Type.ID -> {
|
||||||
|
if( t.value in qualifiers ) count++
|
||||||
|
else { restorePos(pos); return false }
|
||||||
|
}
|
||||||
|
Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT, Token.Type.NEWLINE -> {}
|
||||||
|
else -> { restorePos(pos); return false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val t = next()
|
||||||
|
if( t.type == Token.Type.ID && t.value == keyword ) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
restorePos(pos)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan backwards as deep as specified looking for visibility token. Does not change position.
|
* Skip newlines and comments. Returns (and reads) first non-whitespace token.
|
||||||
|
* Note that [Token.Type.EOF] is not considered a whitespace token.
|
||||||
*/
|
*/
|
||||||
fun getVisibility(default: Visibility = Visibility.Public, depths: Int = 2): Visibility {
|
fun skipWsTokens(): Token {
|
||||||
for( i in -depths .. -1) {
|
while( current().type in wstokens ) next()
|
||||||
when( atOffset(i)?.type) {
|
return next()
|
||||||
Token.Type.PROTECTED -> return Visibility.Protected
|
|
||||||
Token.Type.PRIVATE -> return Visibility.Private
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return default
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fun expectKeyword(vararg keyword: String): String {
|
companion object {
|
||||||
// val t = next()
|
val wstokens = setOf(Token.Type.NEWLINE, Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT)
|
||||||
// if (t.type != Token.Type.ID && t.value !in keyword) {
|
}
|
||||||
// throw ScriptError(t.pos, "expected one of ${keyword.joinToString()}")
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
// data class ReturnScope(val needCatch: Boolean = false)
|
|
||||||
|
|
||||||
// private val
|
|
||||||
|
|
||||||
// fun startReturnScope(): ReturnScope {
|
|
||||||
// return ReturnScope()
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
@@ -86,7 +89,7 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun contains(context: Context, other: Obj): Boolean {
|
open suspend fun contains(context: Context, other: Obj): Boolean {
|
||||||
context.raiseNotImplemented()
|
return invokeInstanceMethod(context, "contains", other).toBool()
|
||||||
}
|
}
|
||||||
|
|
||||||
open val asStr: ObjString by lazy {
|
open val asStr: ObjString by lazy {
|
||||||
@@ -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() {
|
||||||
@@ -42,7 +42,8 @@ class ObjClass(
|
|||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
pos: Pos = Pos.builtIn
|
pos: Pos = Pos.builtIn
|
||||||
) {
|
) {
|
||||||
if (name in members || allParentsSet.any { name in it.members })
|
val existing = members[name] ?: allParentsSet.firstNotNullOfOrNull { it.members[name] }
|
||||||
|
if( existing?.isMutable == false)
|
||||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||||
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
||||||
}
|
}
|
||||||
@@ -97,7 +98,19 @@ val ObjIterable by lazy {
|
|||||||
*/
|
*/
|
||||||
val ObjCollection by lazy {
|
val ObjCollection by lazy {
|
||||||
val i: ObjClass = ObjIterable
|
val i: ObjClass = ObjIterable
|
||||||
ObjClass("Collection", i)
|
ObjClass("Collection", i).apply {
|
||||||
|
// it is not effective, but it is open:
|
||||||
|
addFn("contains", isOpen = true) {
|
||||||
|
val obj = args.firstAndOnly()
|
||||||
|
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||||
|
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||||
|
if( obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0 )
|
||||||
|
return@addFn ObjTrue
|
||||||
|
}
|
||||||
|
ObjFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val ObjIterator by lazy { ObjClass("Iterator") }
|
val ObjIterator by lazy { ObjClass("Iterator") }
|
||||||
@@ -122,7 +135,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>()
|
||||||
@@ -145,6 +158,15 @@ val ObjArray by lazy {
|
|||||||
addFn("iterator") {
|
addFn("iterator") {
|
||||||
ObjArrayIterator(thisObj).also { it.init(this) }
|
ObjArrayIterator(thisObj).also { it.init(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addFn("contains", isOpen = true) {
|
||||||
|
val obj = args.firstAndOnly()
|
||||||
|
for( i in 0..< thisObj.invokeInstanceMethod(this, "size").toInt()) {
|
||||||
|
if( thisObj.getAt(this, i).compareTo(this, obj) == 0 ) return@addFn ObjTrue
|
||||||
|
}
|
||||||
|
ObjFalse
|
||||||
|
}
|
||||||
|
|
||||||
addFn("isample") { "ok".toObj() }
|
addFn("isample") { "ok".toObj() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||||
|
return list.contains(other)
|
||||||
|
}
|
||||||
|
|
||||||
override val objClass: ObjClass
|
override val objClass: ObjClass
|
||||||
get() = type
|
get() = type
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
return ObjChar(value[index])
|
return ObjChar(value[index])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||||
|
return if (other is ObjString)
|
||||||
|
value.contains(other.value)
|
||||||
|
else if (other is ObjChar)
|
||||||
|
value.contains(other.value)
|
||||||
|
else context.raiseArgumentError("String.contains can't take $other")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = ObjClass("String").apply {
|
val type = ObjClass("String").apply {
|
||||||
addConst("startsWith",
|
addConst("startsWith",
|
||||||
|
|||||||
@@ -137,15 +137,19 @@ private class Parser(fromPos: Pos) {
|
|||||||
if (currentChar == '.') {
|
if (currentChar == '.') {
|
||||||
pos.advance()
|
pos.advance()
|
||||||
// .. already parsed:
|
// .. already parsed:
|
||||||
if (currentChar == '.') {
|
when (currentChar) {
|
||||||
|
'.' -> {
|
||||||
pos.advance()
|
pos.advance()
|
||||||
Token("...", from, Token.Type.ELLIPSIS)
|
Token("...", from, Token.Type.ELLIPSIS)
|
||||||
} else if (currentChar == '<') {
|
}
|
||||||
|
'<' -> {
|
||||||
pos.advance()
|
pos.advance()
|
||||||
Token("..<", from, Token.Type.DOTDOTLT)
|
Token("..<", from, Token.Type.DOTDOTLT)
|
||||||
} else {
|
}
|
||||||
|
else -> {
|
||||||
Token("..", from, Token.Type.DOTDOT)
|
Token("..", from, Token.Type.DOTDOT)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else
|
} else
|
||||||
Token(".", from, Token.Type.DOT)
|
Token(".", from, Token.Type.DOT)
|
||||||
}
|
}
|
||||||
@@ -280,8 +284,6 @@ private class Parser(fromPos: Pos) {
|
|||||||
when (text) {
|
when (text) {
|
||||||
"in" -> Token("in", from, Token.Type.IN)
|
"in" -> Token("in", from, Token.Type.IN)
|
||||||
"is" -> Token("is", from, Token.Type.IS)
|
"is" -> Token("is", from, Token.Type.IS)
|
||||||
"protected" -> Token("protected", from, Token.Type.PROTECTED)
|
|
||||||
"private" -> Token("private", from, Token.Type.PRIVATE)
|
|
||||||
else -> Token(text, from, Token.Type.ID)
|
else -> Token(text, from, Token.Type.ID)
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
@@ -338,7 +340,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
// could be integer, also hex:
|
// could be integer, also hex:
|
||||||
if (currentChar == 'x' && p1 == "0") {
|
if (currentChar == 'x' && p1 == "0") {
|
||||||
pos.advance()
|
pos.advance()
|
||||||
Token(loadChars({ it in hexDigits }), start, Token.Type.HEX).also {
|
Token(loadChars { it in hexDigits }, start, Token.Type.HEX).also {
|
||||||
if (currentChar.isLetter())
|
if (currentChar.isLetter())
|
||||||
raise("invalid hex literal")
|
raise("invalid hex literal")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -17,7 +17,6 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
|||||||
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
|
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
|
||||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||||
LABEL, ATLABEL, // label@ at@label
|
LABEL, ATLABEL, // label@ at@label
|
||||||
PRIVATE, PROTECTED,
|
|
||||||
//PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS,
|
//PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS,
|
||||||
ELLIPSIS, DOTDOT, DOTDOTLT,
|
ELLIPSIS, DOTDOT, DOTDOTLT,
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
|
|||||||
@@ -298,9 +298,9 @@ class ScriptTest {
|
|||||||
@Test
|
@Test
|
||||||
fun eqNeqTest() = runTest {
|
fun eqNeqTest() = runTest {
|
||||||
assertEquals(ObjBool(true), eval("val x = 2; x == 2"))
|
assertEquals(ObjBool(true), eval("val x = 2; x == 2"))
|
||||||
assertEquals(ObjBool(false), eval("val x = 3; x == 2"))
|
assertEquals(ObjFalse, eval("val x = 3; x == 2"))
|
||||||
assertEquals(ObjBool(true), eval("val x = 3; x != 2"))
|
assertEquals(ObjBool(true), eval("val x = 3; x != 2"))
|
||||||
assertEquals(ObjBool(false), eval("val x = 3; x != 3"))
|
assertEquals(ObjFalse, eval("val x = 3; x != 3"))
|
||||||
|
|
||||||
assertTrue { eval("1 == 1").toBool() }
|
assertTrue { eval("1 == 1").toBool() }
|
||||||
assertTrue { eval("true == true").toBool() }
|
assertTrue { eval("true == true").toBool() }
|
||||||
@@ -313,17 +313,17 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun logicTest() = runTest {
|
fun logicTest() = runTest {
|
||||||
assertEquals(ObjBool(false), eval("true && false"))
|
assertEquals(ObjFalse, eval("true && false"))
|
||||||
assertEquals(ObjBool(false), eval("false && false"))
|
assertEquals(ObjFalse, eval("false && false"))
|
||||||
assertEquals(ObjBool(false), eval("false && true"))
|
assertEquals(ObjFalse, eval("false && true"))
|
||||||
assertEquals(ObjBool(true), eval("true && true"))
|
assertEquals(ObjBool(true), eval("true && true"))
|
||||||
|
|
||||||
assertEquals(ObjBool(true), eval("true || false"))
|
assertEquals(ObjBool(true), eval("true || false"))
|
||||||
assertEquals(ObjBool(false), eval("false || false"))
|
assertEquals(ObjFalse, eval("false || false"))
|
||||||
assertEquals(ObjBool(true), eval("false || true"))
|
assertEquals(ObjBool(true), eval("false || true"))
|
||||||
assertEquals(ObjBool(true), eval("true || true"))
|
assertEquals(ObjBool(true), eval("true || true"))
|
||||||
|
|
||||||
assertEquals(ObjBool(false), eval("!true"))
|
assertEquals(ObjFalse, eval("!true"))
|
||||||
assertEquals(ObjBool(true), eval("!false"))
|
assertEquals(ObjBool(true), eval("!false"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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,437 @@ 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())
|
""".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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTryFinally() = runTest {
|
||||||
|
val c = Context()
|
||||||
|
assertFails {
|
||||||
|
c.eval(
|
||||||
|
"""
|
||||||
|
var resource = "used"
|
||||||
|
try {
|
||||||
|
throw "test"
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
resource = "freed"
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
c.eval(
|
||||||
|
"""
|
||||||
|
assertEquals("freed", resource)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testThrowFromKotlin() = runTest {
|
||||||
|
val c = Context()
|
||||||
|
c.addFn("callThrow") {
|
||||||
|
raiseArgumentError("fromKotlin")
|
||||||
|
}
|
||||||
|
c.eval(
|
||||||
|
"""
|
||||||
|
val result = try {
|
||||||
|
callThrow()
|
||||||
|
"fail"
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
println("caught:"+e)
|
||||||
|
println(e.message)
|
||||||
|
assert( e is IllegalArgumentException )
|
||||||
|
assertEquals("fromKotlin", e.message)
|
||||||
|
"ok"
|
||||||
|
}
|
||||||
|
assertEquals(result, "ok")
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testReturnValue1() = runTest {
|
||||||
|
val r = eval(
|
||||||
|
"""
|
||||||
|
class Point(x,y) {
|
||||||
|
println("1")
|
||||||
|
fun length() { sqrt(d2()) }
|
||||||
|
println("2")
|
||||||
|
private fun d2() {x*x + y*y}
|
||||||
|
println("3")
|
||||||
|
}
|
||||||
|
println("Helluva")
|
||||||
|
val p = Point(3,4)
|
||||||
|
// assertEquals( 5, p.length() )
|
||||||
|
// assertThrows { p.d2() }
|
||||||
|
"111"
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
assertEquals("111", r.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun doWhileValuesTest() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var count = 0
|
||||||
|
val result = do {
|
||||||
|
count++
|
||||||
|
if( count < 10 )
|
||||||
|
continue
|
||||||
|
if( count % 2 == 1 )
|
||||||
|
break "found "+count
|
||||||
|
} while(count < 100)
|
||||||
|
else "no"
|
||||||
|
assertEquals("found 11", result)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var count = 0
|
||||||
|
val result = do {
|
||||||
|
count++
|
||||||
|
if( count < 10 )
|
||||||
|
continue
|
||||||
|
if( count % 2 == 1 )
|
||||||
|
break "found "+count
|
||||||
|
} while(count < 5)
|
||||||
|
else "no"
|
||||||
|
assertEquals("no", result)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var count = 0
|
||||||
|
val result = do {
|
||||||
|
count++
|
||||||
|
if( count % 2 == 3 )
|
||||||
|
break "found "+count
|
||||||
|
"proc "+count
|
||||||
|
} while(count < 5)
|
||||||
|
assertEquals("proc 5", result)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun doWhileValuesLabelTest() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var count = 0
|
||||||
|
var count2 = 0
|
||||||
|
var count3 = 0
|
||||||
|
val result = outer@ do {
|
||||||
|
count2++
|
||||||
|
count = 0
|
||||||
|
do {
|
||||||
|
count++
|
||||||
|
if( count < 10 || count2 < 5 ) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if( count % 2 == 1 )
|
||||||
|
break@outer "found "+count + "/" + count2
|
||||||
|
} while(count < 14)
|
||||||
|
count3++
|
||||||
|
} while( count2 < 100 )
|
||||||
|
else "no"
|
||||||
|
assertEquals("found 11/5", result)
|
||||||
|
assertEquals( 4, count3)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSimpleWhen() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var result = when("a") {
|
||||||
|
"a" -> "ok"
|
||||||
|
else -> "fail"
|
||||||
|
}
|
||||||
|
assertEquals(result, "ok")
|
||||||
|
result = when(5) {
|
||||||
|
3 -> "fail1"
|
||||||
|
4 -> "fail2"
|
||||||
|
else -> "ok2"
|
||||||
|
}
|
||||||
|
assert(result == "ok2")
|
||||||
|
result = when(5) {
|
||||||
|
3 -> "fail"
|
||||||
|
4 -> "fail2"
|
||||||
|
}
|
||||||
|
assert(result == void)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWhenIs() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var result = when("a") {
|
||||||
|
is Int -> "fail2"
|
||||||
|
is String -> "ok"
|
||||||
|
else -> "fail"
|
||||||
|
}
|
||||||
|
assertEquals(result, "ok")
|
||||||
|
result = when(5) {
|
||||||
|
3 -> "fail1"
|
||||||
|
4 -> "fail2"
|
||||||
|
else -> "ok2"
|
||||||
|
}
|
||||||
|
assert(result == "ok2")
|
||||||
|
result = when(5) {
|
||||||
|
3 -> "fail"
|
||||||
|
4 -> "fail2"
|
||||||
|
}
|
||||||
|
assert(result == void)
|
||||||
|
result = when(5) {
|
||||||
|
!is String -> "ok"
|
||||||
|
4 -> "fail2"
|
||||||
|
}
|
||||||
|
assert(result == "ok")
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWhenIn() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var result = when('e') {
|
||||||
|
in 'a'..'c' -> "fail2"
|
||||||
|
in 'a'..'z' -> "ok"
|
||||||
|
else -> "fail"
|
||||||
|
}
|
||||||
|
// assertEquals(result, "ok")
|
||||||
|
result = when(5) {
|
||||||
|
in [1,2,3,4,6] -> "fail1"
|
||||||
|
in [7, 0, 9] -> "fail2"
|
||||||
|
else -> "ok2"
|
||||||
|
}
|
||||||
|
assert(result == "ok2")
|
||||||
|
result = when(5) {
|
||||||
|
in [1,2,3,4,6] -> "fail1"
|
||||||
|
in [7, 0, 9] -> "fail2"
|
||||||
|
in [-1, 5, 11] -> "ok3"
|
||||||
|
else -> "fail3"
|
||||||
|
}
|
||||||
|
assert(result == "ok3")
|
||||||
|
result = when(5) {
|
||||||
|
!in [1,2,3,4,6, 5] -> "fail1"
|
||||||
|
!in [7, 0, 9, 5] -> "fail2"
|
||||||
|
!in [-1, 15, 11] -> "ok4"
|
||||||
|
else -> "fail3"
|
||||||
|
}
|
||||||
|
assert(result == "ok4")
|
||||||
|
result = when(5) {
|
||||||
|
in [1,3] -> "fail"
|
||||||
|
in 2..4 -> "fail2"
|
||||||
|
}
|
||||||
|
assert(result == void)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWhenSample1() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun type(x) {
|
||||||
|
when(x) {
|
||||||
|
in 'a'..'z', in 'A'..'Z' -> "letter"
|
||||||
|
in '0'..'9' -> "digit"
|
||||||
|
in "$%&" -> "hate char"
|
||||||
|
else -> "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals("digit", type('3'))
|
||||||
|
assertEquals("letter", type('E'))
|
||||||
|
assertEquals("hate char", type('%'))
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWhenSample2() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun type(x) {
|
||||||
|
when(x) {
|
||||||
|
"42", 42 -> "answer to the great question"
|
||||||
|
is Real, is Int -> "number"
|
||||||
|
is String -> {
|
||||||
|
for( d in x ) {
|
||||||
|
if( d !in '0'..'9' )
|
||||||
|
break "unknown"
|
||||||
|
}
|
||||||
|
else "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals("number", type(5))
|
||||||
|
""".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