Compare commits

...
14 Commits
18 changed files with 1219 additions and 146 deletions

No files matched your search

+145
View File
@@ -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
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: __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:
+2 -1
View File
@@ -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,
+440 -23
View File
@@ -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()
)
}
} }
+5
View File
@@ -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")
}
} }