+for-else-labels-break is running
+String now is indexed with Char instances, size supported +Char type and char constants
This commit is contained in:
parent
f7b35c7576
commit
3908b8ee9f
@ -51,6 +51,7 @@ Note `Real` class: it is global variable for Real class; there are such class in
|
|||||||
assert("Hello"::class == String)
|
assert("Hello"::class == String)
|
||||||
assert(1970::class == Int)
|
assert(1970::class == Int)
|
||||||
assert(true::class == Bool)
|
assert(true::class == Bool)
|
||||||
|
assert('$'::class == Char)
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
More complex is singleton classes, because you don't need to compare their class
|
More complex is singleton classes, because you don't need to compare their class
|
||||||
|
@ -507,34 +507,51 @@ We can skip the rest of the loop and restart it, as usual, with `continue` opera
|
|||||||
Notice that `total` remains 0 as the end of the outerLoop@ is not reachable: `continue` is always called and always make
|
Notice that `total` remains 0 as the end of the outerLoop@ is not reachable: `continue` is always called and always make
|
||||||
Ling to skip it.
|
Ling to skip it.
|
||||||
|
|
||||||
## Labels@
|
## else statement
|
||||||
|
|
||||||
The label can be any valid identifier, even a keyword, labels exist in their own, isolated world, so no risk of
|
The while and for loops can be followed by the else block, which is executed when the loop
|
||||||
occasional clash. Labels are also scoped to their context and do not exist outside it.
|
|
||||||
|
|
||||||
Right now labels are implemented only for the while loop. It is intended to be implemented for all loops and returns.
|
|
||||||
|
|
||||||
## while - else statement
|
|
||||||
|
|
||||||
The while loop can be followed by the else block, which is executed when the loop
|
|
||||||
ends normally, without breaks. It allows override loop result value, for example,
|
ends normally, without breaks. It allows override loop result value, for example,
|
||||||
to not calculate it in every iteration. Here is the sample:
|
to not calculate it in every iteration. See for loop example just below.
|
||||||
|
|
||||||
### Else, labels, and break practical sample
|
## For loops
|
||||||
|
|
||||||
// Get a first word that starts with a given previx and return it:
|
For loop are intended to traverse collections, and all other objects that supports
|
||||||
fun findPrefix(prefix,words) {
|
size and index access, like lists:
|
||||||
var index = 0
|
|
||||||
while( index < words.size ) {
|
var letters = 0
|
||||||
val w = words[index++]
|
for( w in ["hello", "wolrd"]) {
|
||||||
if( w.startsWith(prefix) ) break w
|
letters += w.length
|
||||||
}
|
}
|
||||||
|
"total letters: "+letters
|
||||||
|
>>> "total letters: 10"
|
||||||
|
|
||||||
|
For loop support breaks the same as while loops above:
|
||||||
|
|
||||||
|
fun search(haystack, needle) {
|
||||||
|
for(ch in haystack) {
|
||||||
|
if( ch == needle)
|
||||||
|
break "found"
|
||||||
|
}
|
||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
val words = ["hello", "world", "foobar", "end"]
|
assert( search("hello", 'l') == "found")
|
||||||
assert( findPrefix("bad", words) == null )
|
assert( search("hello", 'z') == null)
|
||||||
findPrefix("foo", words )
|
>>> void
|
||||||
>>> "foobar"
|
|
||||||
|
We can use labels too:
|
||||||
|
|
||||||
|
fun search(haystacks, needle) {
|
||||||
|
exit@ for( hs in haystacks ) {
|
||||||
|
for(ch in hs ) {
|
||||||
|
if( ch == needle)
|
||||||
|
break@exit "found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
assert( search(["hello", "world"], 'l') == "found")
|
||||||
|
assert( search(["hello", "world"], 'z') == null)
|
||||||
|
>>> void
|
||||||
|
|
||||||
# Self-assignments in expression
|
# Self-assignments in expression
|
||||||
|
|
||||||
@ -581,6 +598,7 @@ There are self-assigning version for operators too:
|
|||||||
| Int | 64 bit signed | `1` `-22` `0x1FF` |
|
| Int | 64 bit signed | `1` `-22` `0x1FF` |
|
||||||
| Real | 64 bit double | `1.0`, `2e-11` |
|
| Real | 64 bit double | `1.0`, `2e-11` |
|
||||||
| Bool | boolean | `true` `false` |
|
| Bool | boolean | `true` `false` |
|
||||||
|
| Char | single unicode character | `'S'`, `'\n'` |
|
||||||
| String | unicode string, no limits | "hello" (see below) |
|
| String | unicode string, no limits | "hello" (see below) |
|
||||||
| List | mutable list | [1, "two", 3] |
|
| List | mutable list | [1, "two", 3] |
|
||||||
| Void | no value could exist, singleton | void |
|
| Void | no value could exist, singleton | void |
|
||||||
@ -589,6 +607,29 @@ There are self-assigning version for operators too:
|
|||||||
|
|
||||||
See also [math operations](math.md)
|
See also [math operations](math.md)
|
||||||
|
|
||||||
|
## Character details
|
||||||
|
|
||||||
|
The type for the character objects is `Char`.
|
||||||
|
|
||||||
|
### Char literal escapes
|
||||||
|
|
||||||
|
Are the same as in string literals with little difference:
|
||||||
|
|
||||||
|
| escape | ASCII value |
|
||||||
|
|--------|-------------------|
|
||||||
|
| \n | 0x10, newline |
|
||||||
|
| \t | 0x07, tabulation |
|
||||||
|
| \\ | \ slash character |
|
||||||
|
| \' | ' apostrophe |
|
||||||
|
|
||||||
|
### Char instance members
|
||||||
|
|
||||||
|
| member | type | meaning |
|
||||||
|
|--------|------|--------------------------------|
|
||||||
|
| code | Int | Unicode code for the character |
|
||||||
|
| | | |
|
||||||
|
|
||||||
|
|
||||||
## String details
|
## String details
|
||||||
|
|
||||||
### String operations
|
### String operations
|
||||||
|
@ -112,7 +112,6 @@ class Compiler(
|
|||||||
return lvalue
|
return lvalue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun parseTerm(cc: CompilerContext): Accessor? {
|
private fun parseTerm(cc: CompilerContext): Accessor? {
|
||||||
var operand: Accessor? = null
|
var operand: Accessor? = null
|
||||||
|
|
||||||
@ -227,7 +226,7 @@ class Compiler(
|
|||||||
Token.Type.ID -> {
|
Token.Type.ID -> {
|
||||||
// there could be terminal operators or keywords:// variable to read or like
|
// there could be terminal operators or keywords:// variable to read or like
|
||||||
when (t.value) {
|
when (t.value) {
|
||||||
"if", "when", "do", "while", "return" -> {
|
in stopKeywords -> {
|
||||||
if (operand != null) throw ScriptError(t.pos, "unexpected keyword")
|
if (operand != null) throw ScriptError(t.pos, "unexpected keyword")
|
||||||
cc.previous()
|
cc.previous()
|
||||||
val s = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting valid statement")
|
val s = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting valid statement")
|
||||||
@ -405,6 +404,8 @@ class Compiler(
|
|||||||
|
|
||||||
Token.Type.STRING -> Accessor { ObjString(t.value).asReadonly }
|
Token.Type.STRING -> Accessor { ObjString(t.value).asReadonly }
|
||||||
|
|
||||||
|
Token.Type.CHAR -> Accessor { ObjChar(t.value[0]).asReadonly }
|
||||||
|
|
||||||
Token.Type.PLUS -> {
|
Token.Type.PLUS -> {
|
||||||
val n = parseNumber(true, cc)
|
val n = parseNumber(true, cc)
|
||||||
Accessor { n.asReadonly }
|
Accessor { n.asReadonly }
|
||||||
@ -470,6 +471,7 @@ class Compiler(
|
|||||||
"val" -> parseVarDeclaration(id.value, false, cc)
|
"val" -> parseVarDeclaration(id.value, false, cc)
|
||||||
"var" -> parseVarDeclaration(id.value, true, cc)
|
"var" -> parseVarDeclaration(id.value, true, cc)
|
||||||
"while" -> parseWhileStatement(cc)
|
"while" -> parseWhileStatement(cc)
|
||||||
|
"for" -> parseForStatement(cc)
|
||||||
"break" -> parseBreakStatement(id.pos, cc)
|
"break" -> parseBreakStatement(id.pos, cc)
|
||||||
"continue" -> parseContinueStatement(id.pos, cc)
|
"continue" -> parseContinueStatement(id.pos, cc)
|
||||||
"fn", "fun" -> parseFunctionDeclaration(cc)
|
"fn", "fun" -> parseFunctionDeclaration(cc)
|
||||||
@ -492,6 +494,83 @@ class Compiler(
|
|||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun parseForStatement(cc: CompilerContext): Statement {
|
||||||
|
val label = getLabel(cc)?.also { cc.labels += it }
|
||||||
|
val start = ensureLparen(cc)
|
||||||
|
|
||||||
|
// for - in?
|
||||||
|
val tVar = cc.next()
|
||||||
|
if (tVar.type != Token.Type.ID)
|
||||||
|
throw ScriptError(tVar.pos, "Bad for statement: expected loop variable")
|
||||||
|
val tOp = cc.next()
|
||||||
|
if (tOp.value == "in") {
|
||||||
|
// in loop
|
||||||
|
val source = parseStatement(cc) ?: throw ScriptError(start, "Bad for statement: expected expression")
|
||||||
|
ensureRparen(cc)
|
||||||
|
val body = parseStatement(cc) ?: throw ScriptError(start, "Bad for statement: expected loop body")
|
||||||
|
|
||||||
|
// possible else clause
|
||||||
|
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||||
|
val elseStatement = if (cc.next().let { it.type == Token.Type.ID && it.value == "else" }) {
|
||||||
|
parseStatement(cc)
|
||||||
|
} else {
|
||||||
|
cc.previous()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return statement(body.pos) {
|
||||||
|
val forContext = it.copy(start)
|
||||||
|
|
||||||
|
// loop var: StoredObject
|
||||||
|
val loopSO = forContext.addItem(tVar.value, true, ObjNull)
|
||||||
|
|
||||||
|
// insofar we suggest source object is enumerable. Later we might need to add checks
|
||||||
|
val sourceObj = source.execute(forContext)
|
||||||
|
val size = runCatching { sourceObj.callInstanceMethod(forContext, "size").toInt() }
|
||||||
|
.getOrElse { throw ScriptError(tOp.pos, "object is not enumerable: no size") }
|
||||||
|
var result: Obj = ObjVoid
|
||||||
|
var breakCaught = false
|
||||||
|
if (size > 0) {
|
||||||
|
var current = runCatching { sourceObj.getAt(forContext, 0) }
|
||||||
|
.getOrElse {
|
||||||
|
throw ScriptError(
|
||||||
|
tOp.pos,
|
||||||
|
"object is not enumerable: no index access for ${sourceObj.inspect()}",
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var index = 0
|
||||||
|
while (true) {
|
||||||
|
loopSO.value = current
|
||||||
|
try {
|
||||||
|
result = body.execute(forContext)
|
||||||
|
} catch (lbe: LoopBreakContinueException) {
|
||||||
|
if (lbe.label == label || lbe.label == null) {
|
||||||
|
breakCaught = true
|
||||||
|
if (lbe.doContinue) continue
|
||||||
|
else {
|
||||||
|
result = lbe.result
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
throw lbe
|
||||||
|
}
|
||||||
|
if (++index >= size) break
|
||||||
|
current = sourceObj.getAt(forContext, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( !breakCaught && elseStatement != null) {
|
||||||
|
result = elseStatement.execute(it)
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// maybe other loops?
|
||||||
|
throw ScriptError(tOp.pos, "Unsupported for-loop syntax")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseWhileStatement(cc: CompilerContext): Statement {
|
private fun parseWhileStatement(cc: CompilerContext): Statement {
|
||||||
val label = getLabel(cc)?.also { cc.labels += it }
|
val label = getLabel(cc)?.also { cc.labels += it }
|
||||||
val start = ensureLparen(cc)
|
val start = ensureLparen(cc)
|
||||||
@ -887,6 +966,11 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun compile(code: String): Script = Compiler().compile(Source("<eval>", code))
|
fun compile(code: String): Script = Compiler().compile(Source("<eval>", code))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The keywords that stop processing of expression term
|
||||||
|
*/
|
||||||
|
val stopKeywords = setOf("break", "continue", "return", "if", "when", "do", "while", "for")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ class Context(
|
|||||||
return requiredArg(0)
|
return requiredArg(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
fun requireExactCount(count: Int) {
|
fun requireExactCount(count: Int) {
|
||||||
if( args.list.size != count ) {
|
if( args.list.size != count ) {
|
||||||
raiseError("Expected exactly $count arguments, got ${args.list.size}")
|
raiseError("Expected exactly $count arguments, got ${args.list.size}")
|
||||||
@ -56,8 +57,8 @@ class Context(
|
|||||||
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY,newThisObj: Obj? = null): Context =
|
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY,newThisObj: Obj? = null): Context =
|
||||||
Context(this, args, pos, newThisObj ?: thisObj)
|
Context(this, args, pos, newThisObj ?: thisObj)
|
||||||
|
|
||||||
fun addItem(name: String, isMutable: Boolean, value: Obj?) {
|
fun addItem(name: String, isMutable: Boolean, value: Obj?): StoredObj {
|
||||||
objects.put(name, StoredObj(value, isMutable))
|
return StoredObj(value, isMutable).also { objects.put(name, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrCreateNamespace(name: String): ObjNamespace =
|
fun getOrCreateNamespace(name: String): ObjNamespace =
|
||||||
|
@ -54,7 +54,10 @@ sealed class Obj {
|
|||||||
getInstanceMemberOrNull(name)
|
getInstanceMemberOrNull(name)
|
||||||
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
||||||
|
|
||||||
suspend fun callInstanceMethod(context: Context, name: String, args: Arguments): Obj =
|
suspend fun callInstanceMethod(context: Context,
|
||||||
|
name: String,
|
||||||
|
args: Arguments = Arguments.EMPTY
|
||||||
|
): Obj =
|
||||||
// note that getInstanceMember traverses the hierarchy
|
// note that getInstanceMember traverses the hierarchy
|
||||||
objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args)
|
objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args)
|
||||||
|
|
||||||
@ -422,6 +425,24 @@ data class ObjBool(val value: Boolean) : Obj() {
|
|||||||
// return value.also { value = newValue }
|
// return value.also { value = newValue }
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
class ObjChar(val value: Char): Obj() {
|
||||||
|
|
||||||
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
|
override suspend fun compareTo(context: Context, other: Obj): Int =
|
||||||
|
(other as? ObjChar)?.let { value.compareTo(it.value) } ?: -1
|
||||||
|
|
||||||
|
override fun toString(): String = value.toString()
|
||||||
|
|
||||||
|
override fun inspect(): String = "'$value'"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val type = ObjClass("Char").apply {
|
||||||
|
addFn("toInt") { ObjInt(thisAs<ObjChar>().value.code.toLong()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
data class ObjNamespace(val name: String) : Obj() {
|
data class ObjNamespace(val name: String) : Obj() {
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
|
@ -27,6 +27,10 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
return ObjString(value + other.asStr.value)
|
return ObjString(value + other.asStr.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getAt(context: Context, index: Int): Obj {
|
||||||
|
return ObjChar(value[index])
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = ObjClass("String").apply {
|
val type = ObjClass("String").apply {
|
||||||
addConst("startsWith",
|
addConst("startsWith",
|
||||||
@ -36,6 +40,7 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
addConst("length",
|
addConst("length",
|
||||||
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||||
)
|
)
|
||||||
|
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -203,6 +203,26 @@ private class Parser(fromPos: Pos) {
|
|||||||
decodeNumber(loadChars(digits), from)
|
decodeNumber(loadChars(digits), from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
'\'' -> {
|
||||||
|
val start = pos.toPos()
|
||||||
|
var value = currentChar
|
||||||
|
pos.advance()
|
||||||
|
if (currentChar == '\\') {
|
||||||
|
value = currentChar
|
||||||
|
pos.advance()
|
||||||
|
value = when(value) {
|
||||||
|
'n' -> '\n'
|
||||||
|
'r' -> '\r'
|
||||||
|
't' -> '\t'
|
||||||
|
'\'', '\\' -> value
|
||||||
|
else -> throw ScriptError(currentPos, "unsupported escape character: $value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( currentChar != '\'' ) throw ScriptError(currentPos, "expected end of character literal: '")
|
||||||
|
pos.advance()
|
||||||
|
Token(value.toString(), start, Token.Type.CHAR)
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
// Labels processing is complicated!
|
// Labels processing is complicated!
|
||||||
// some@ statement: label 'some', ID 'statement'
|
// some@ statement: label 'some', ID 'statement'
|
||||||
|
@ -57,6 +57,7 @@ class Script(
|
|||||||
addConst("String", ObjString.type)
|
addConst("String", ObjString.type)
|
||||||
addConst("Int", ObjInt.type)
|
addConst("Int", ObjInt.type)
|
||||||
addConst("Bool", ObjBool.type)
|
addConst("Bool", ObjBool.type)
|
||||||
|
addConst("Char", ObjChar.type)
|
||||||
addConst("List", ObjList.type)
|
addConst("List", ObjList.type)
|
||||||
val pi = ObjReal(PI)
|
val pi = ObjReal(PI)
|
||||||
addConst("π", pi)
|
addConst("π", pi)
|
||||||
|
@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
package net.sergeych.ling
|
package net.sergeych.ling
|
||||||
|
|
||||||
open class ScriptError(val pos: Pos, val errorMessage: String) : Exception(
|
open class ScriptError(val pos: Pos, val errorMessage: String,cause: Throwable?=null) : Exception(
|
||||||
"""
|
"""
|
||||||
$pos: Error: $errorMessage
|
$pos: Error: $errorMessage
|
||||||
${pos.currentLine}
|
${pos.currentLine}
|
||||||
${"-".repeat(pos.column)}^
|
${"-".repeat(pos.column)}^
|
||||||
""".trimIndent()
|
""".trimIndent(),
|
||||||
|
cause
|
||||||
)
|
)
|
||||||
|
|
||||||
class ExecutionError(val errorObject: ObjError) : ScriptError(errorObject.context.pos, errorObject.message)
|
class ExecutionError(val errorObject: ObjError) : ScriptError(errorObject.context.pos, errorObject.message)
|
||||||
|
@ -5,7 +5,8 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
|||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
enum class Type {
|
enum class Type {
|
||||||
ID, INT, REAL, HEX, STRING, LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET, COMMA,
|
ID, INT, REAL, HEX, STRING, CHAR,
|
||||||
|
LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET, COMMA,
|
||||||
SEMICOLON, COLON,
|
SEMICOLON, COLON,
|
||||||
PLUS, MINUS, STAR, SLASH, PERCENT,
|
PLUS, MINUS, STAR, SLASH, PERCENT,
|
||||||
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
|
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
|
||||||
|
@ -807,6 +807,46 @@ class ScriptTest {
|
|||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forLoop1() = runTest {
|
||||||
|
eval("""
|
||||||
|
var sum = 0
|
||||||
|
for(i in [1,2,3]) {
|
||||||
|
println(i)
|
||||||
|
sum += i
|
||||||
|
}
|
||||||
|
assert(sum == 6)
|
||||||
|
""".trimIndent())
|
||||||
|
eval("""
|
||||||
|
fun test1(array) {
|
||||||
|
var sum = 0
|
||||||
|
for(i in array) {
|
||||||
|
if( i > 2 ) break "too much"
|
||||||
|
sum += i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("result=",test1([1,2]))
|
||||||
|
println("result=",test1([1,2,3]))
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forLoop2() = runTest {
|
||||||
|
println(eval(
|
||||||
|
"""
|
||||||
|
fun search(haystack, needle) {
|
||||||
|
for(ch in haystack) {
|
||||||
|
if( ch == needle)
|
||||||
|
break "found"
|
||||||
|
}
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
assert( search("hello", 'l') == "found")
|
||||||
|
assert( search("hello", 'z') == null)
|
||||||
|
""".trimIndent()
|
||||||
|
).toString())
|
||||||
|
}
|
||||||
|
|
||||||
// @Test
|
// @Test
|
||||||
// fun testLambda1() = runTest {
|
// fun testLambda1() = runTest {
|
||||||
// val l = eval("""
|
// val l = eval("""
|
||||||
|
@ -158,7 +158,9 @@ suspend fun DocTest.test() {
|
|||||||
) {
|
) {
|
||||||
println("Test failed: ${this.detailedString}")
|
println("Test failed: ${this.detailedString}")
|
||||||
}
|
}
|
||||||
error?.let { fail(it.toString()) }
|
error?.let {
|
||||||
|
fail(it.toString(), it)
|
||||||
|
}
|
||||||
assertEquals(expectedOutput, collectedOutput.toString(), "script output do not match")
|
assertEquals(expectedOutput, collectedOutput.toString(), "script output do not match")
|
||||||
assertEquals(expectedResult, result.toString(), "script result does not match")
|
assertEquals(expectedResult, result.toString(), "script result does not match")
|
||||||
// println("OK: $this")
|
// println("OK: $this")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user