This commit is contained in:
Sergey Chernov 2025-05-31 18:53:42 +04:00
parent 3908b8ee9f
commit b82a97ed20
11 changed files with 501 additions and 206 deletions

61
docs/Range.md Normal file
View File

@ -0,0 +1,61 @@
# Range
Range is diapason between two values. Open range has at least one end open, e.g. ±∞, closed range has both ends open.
## Closed ranges
The syntax is intuitive and adopted from Kotlin:
// end inclusive:
val r = 1..5
assert(5 in r)
assert(6 !in r)
assert(0 !in r)
assert(2 in r)
>>> void
Exclusive end ranges are adopted from kotlin either:
// end inclusive:
val r = 1..<5
assert(5 !in r)
assert(6 !in r)
assert(0 !in r)
assert(2 in r)
assert(4 in r)
>>> void
In any case, we can test an object to belong to using `in` and `!in` and
access limits:
val r = 0..5
(r.end - r.start)/2
>>> 2
Notice, start and end are ints, so midpoint here is int too.
It is possible to test that one range is included in another range too,
one range is defined as _contained_ in another ifm and only if, it begin and end
are equal or within another, taking into account the end-inclusiveness:
assert( (1..3) in (1..3) )
assert( (0..3) !in (1..3) )
assert( (1..2) in (1..<3) )
assert( (1..<2) in (1..<3) )
assert( (1..<3) in (1..3) )
>>> void
# Instance members
| member | description | args |
|-----------------|----------------------------|---------------|
| contains(other) | used in `in` | Range, or Any |
| inclusiveEnd | true for '..' | Bool |
| isOpen | at any end | Bool |
| isIntRange | both start and end are Int | Bool |
| start | | Bool |
| end | | Bool |
| | | |
| | | |
| | | |

View File

@ -23,9 +23,9 @@ class Compiler(
return Script(start, statements)
}
private fun parseStatement(tokens: CompilerContext): Statement? {
private fun parseStatement(cc: CompilerContext): Statement? {
while (true) {
val t = tokens.next()
val t = cc.next()
return when (t.type) {
Token.Type.ID -> {
// could be keyword, assignment or just the expression
@ -44,16 +44,16 @@ class Compiler(
// get back the token which is not '=':
// tokens.previous()
// try keyword statement
parseKeywordStatement(t, tokens)
parseKeywordStatement(t, cc)
?: run {
tokens.previous()
parseExpression(tokens)
cc.previous()
parseExpression(cc)
}
}
Token.Type.PLUS2, Token.Type.MINUS2 -> {
tokens.previous()
parseExpression(tokens)
cc.previous()
parseExpression(cc)
}
Token.Type.LABEL -> continue
@ -64,12 +64,12 @@ class Compiler(
Token.Type.SEMICOLON -> continue
Token.Type.LBRACE -> {
tokens.previous()
parseBlock(tokens)
cc.previous()
parseBlock(cc)
}
Token.Type.RBRACE -> {
tokens.previous()
cc.previous()
return null
}
@ -77,8 +77,8 @@ class Compiler(
else -> {
// could be expression
tokens.previous()
parseExpression(tokens)
cc.previous()
parseExpression(cc)
}
}
}
@ -299,7 +299,20 @@ class Compiler(
}.value.decrementAndGet(ctx).asReadonly
}
}
}
Token.Type.DOTDOT, Token.Type.DOTDOTLT -> {
// closed-range operator
val inclusiveEnd = t.type == Token.Type.DOTDOT
val left = operand
val right = parseStatement(cc)
operand = Accessor {
ObjRange(
left?.getter?.invoke(it)?.value ?: ObjNull,
right?.execute(it) ?: ObjNull,
inclusiveEnd = inclusiveEnd
).asReadonly
}
}
@ -560,7 +573,7 @@ class Compiler(
current = sourceObj.getAt(forContext, index)
}
}
if( !breakCaught && elseStatement != null) {
if (!breakCaught && elseStatement != null) {
result = elseStatement.execute(it)
}
result
@ -859,7 +872,7 @@ class Compiler(
private var lastPrty = 0
val allOps = listOf(
// assignments
// assignments, lowest priority
Operator(Token.Type.ASSIGN, lastPrty) { pos, a, b ->
Accessor {
val value = b.getter(it).value
@ -942,6 +955,9 @@ class Compiler(
Operator.simple(Token.Type.LT, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) < 0) },
Operator.simple(Token.Type.GTE, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) >= 0) },
Operator.simple(Token.Type.GT, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) > 0) },
// in, is:
Operator.simple(Token.Type.IN, lastPrty) { c, a, b -> ObjBool(b.contains(c, a)) },
Operator.simple(Token.Type.NOTIN, lastPrty) { c, a, b -> ObjBool(!b.contains(c, a)) },
// shuttle <=> 6
// bit shifts 7
Operator.simple(Token.Type.PLUS, ++lastPrty) { ctx, a, b -> a.plus(ctx, b) },

View File

@ -4,8 +4,6 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlin.math.floor
import kotlin.math.roundToLong
//typealias InstanceMethod = (Context, Obj) -> Obj
@ -67,6 +65,10 @@ sealed class Obj {
context.raiseNotImplemented()
}
open suspend fun contains(context: Context, other: Obj): Boolean {
context.raiseNotImplemented()
}
open val asStr: ObjString by lazy {
if (this is ObjString) this else ObjString(this.toString())
}
@ -75,7 +77,16 @@ sealed class Obj {
* Class of the object: definition of member functions (top-level), etc.
* Note that using lazy allows to avoid endless recursion here
*/
open val objClass: ObjClass by lazy { ObjClass("Obj") }
open val objClass: ObjClass by lazy {
ObjClass("Obj").apply {
addFn("toString") {
thisObj.asStr
}
addFn("contains") {
ObjBool(thisObj.contains(this, args.firstAndOnly()))
}
}
}
open suspend fun plus(context: Context, other: Obj): Obj {
context.raiseNotImplemented()
@ -198,6 +209,8 @@ sealed class Obj {
suspend fun invoke(context: Context, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
callOn(context.copy(atPos, args = args, newThisObj = thisObj))
val asReadonly: WithAccess<Obj> by lazy { WithAccess(this, false) }
val asMutable: WithAccess<Obj> by lazy { WithAccess(this, true) }
@ -274,176 +287,83 @@ fun Obj.toBool(): Boolean =
(this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean $this")
data class ObjReal(val value: Double) : Obj(), Numeric {
override val asStr by lazy { ObjString(value.toString()) }
override val longValue: Long by lazy { floor(value).toLong() }
override val doubleValue: Double by lazy { value }
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
override val toObjReal: ObjReal by lazy { ObjReal(value) }
override fun byValueCopy(): Obj = ObjReal(value)
override suspend fun compareTo(context: Context, other: Obj): Int {
if (other !is Numeric) return -2
return value.compareTo(other.doubleValue)
}
override fun toString(): String = value.toString()
class ObjRange(val start: Obj?, val end: Obj?,val inclusiveEnd: Boolean) : Obj() {
override val objClass: ObjClass = type
override suspend fun plus(context: Context, other: Obj): Obj =
ObjReal(this.value + other.toDouble())
override suspend fun minus(context: Context, other: Obj): Obj =
ObjReal(this.value - other.toDouble())
override suspend fun mul(context: Context, other: Obj): Obj =
ObjReal(this.value * other.toDouble())
override suspend fun div(context: Context, other: Obj): Obj =
ObjReal(this.value / other.toDouble())
override suspend fun mod(context: Context, other: Obj): Obj =
ObjReal(this.value % other.toDouble())
companion object {
val type: ObjClass = ObjClass("Real").apply {
createField(
"roundToInt",
statement(Pos.builtIn) {
(it.thisObj as ObjReal).value.roundToLong().toObj()
},
)
}
}
}
data class ObjInt(var value: Long) : Obj(), Numeric {
override val asStr get() = ObjString(value.toString())
override val longValue get() = value
override val doubleValue get() = value.toDouble()
override val toObjInt get() = this
override val toObjReal = ObjReal(doubleValue)
override fun byValueCopy(): Obj = ObjInt(value)
override suspend fun getAndIncrement(context: Context): Obj {
return ObjInt(value).also { value++ }
override fun toString(): String {
val result = StringBuilder()
result.append("${start ?: '∞'} ..")
if( !inclusiveEnd) result.append('<')
result.append(" ${end ?: '∞'}")
return result.toString()
}
override suspend fun getAndDecrement(context: Context): Obj {
return ObjInt(value).also { value-- }
suspend fun containsRange(context: Context, other: ObjRange): Boolean {
if( start != null ) {
// our start is not -∞ so other start should be GTE or is not contained:
if( other.start != null && start.compareTo(context, other.start) > 0) return false
}
if( end != null ) {
// same with the end: if it is open, it can't be contained in ours:
if( other.end == null ) return false
// both exists, now there could be 4 cases:
return when {
other.inclusiveEnd && inclusiveEnd ->
end.compareTo(context, other.end) >= 0
!other.inclusiveEnd && !inclusiveEnd ->
end.compareTo(context, other.end) >= 0
other.inclusiveEnd && !inclusiveEnd ->
end.compareTo(context, other.end) > 0
!other.inclusiveEnd && inclusiveEnd ->
end.compareTo(context, other.end) >= 0
else -> throw IllegalStateException("unknown comparison")
}
}
return true
}
override suspend fun incrementAndGet(context: Context): Obj {
return ObjInt(++value)
override suspend fun contains(context: Context, other: Obj): Boolean {
if( other is ObjRange)
return containsRange(context, other)
if (start == null && end == null) return true
if (start != null) {
if (start.compareTo(context, other) > 0) return false
}
if (end != null) {
val cmp = end.compareTo(context, other)
if (inclusiveEnd && cmp < 0 || !inclusiveEnd && cmp <= 0) return false
}
return true
}
override suspend fun decrementAndGet(context: Context): Obj {
return ObjInt(--value)
}
override suspend fun compareTo(context: Context, other: Obj): Int {
if (other !is Numeric) return -2
return value.compareTo(other.doubleValue)
}
override fun toString(): String = value.toString()
override val objClass: ObjClass = type
override suspend fun plus(context: Context, other: Obj): Obj =
if (other is ObjInt)
ObjInt(this.value + other.value)
else
ObjReal(this.doubleValue + other.toDouble())
override suspend fun minus(context: Context, other: Obj): Obj =
if (other is ObjInt)
ObjInt(this.value - other.value)
else
ObjReal(this.doubleValue - other.toDouble())
override suspend fun mul(context: Context, other: Obj): Obj =
if (other is ObjInt) {
ObjInt(this.value * other.value)
} else ObjReal(this.value * other.toDouble())
override suspend fun div(context: Context, other: Obj): Obj =
if (other is ObjInt)
ObjInt(this.value / other.value)
else ObjReal(this.value / other.toDouble())
override suspend fun mod(context: Context, other: Obj): Obj =
if (other is ObjInt)
ObjInt(this.value % other.value)
else ObjReal(this.value.toDouble() % other.toDouble())
/**
* We are by-value type ([byValueCopy] is implemented) so we can do in-place
* assignment
*/
override suspend fun assign(context: Context, other: Obj): Obj? {
return if (other is ObjInt) {
value = other.value
this
} else null
val isIntRange: Boolean by lazy {
start is ObjInt && end is ObjInt
}
companion object {
val type = ObjClass("Int")
val type = ObjClass("Range").apply {
addFn("start" ) {
thisAs<ObjRange>().start ?: ObjNull
}
}
data class ObjBool(val value: Boolean) : Obj() {
override val asStr by lazy { ObjString(value.toString()) }
override suspend fun compareTo(context: Context, other: Obj): Int {
if (other !is ObjBool) return -2
return value.compareTo(other.value)
addFn("end") {
thisAs<ObjRange>().end ?: ObjNull
}
override fun toString(): String = value.toString()
override val objClass: ObjClass = type
override suspend fun logicalNot(context: Context): Obj = ObjBool(!value)
override suspend fun logicalAnd(context: Context, other: Obj): Obj = ObjBool(value && other.toBool())
override suspend fun logicalOr(context: Context, other: Obj): Obj = ObjBool(value || other.toBool())
companion object {
val type = ObjClass("Bool")
addFn("isOpen") {
thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj()
}
addFn("isIntRange") {
thisAs<ObjRange>().isIntRange.toObj()
}
addFn("inclusiveEnd") {
thisAs<ObjRange>().inclusiveEnd.toObj()
}
}
//open class ObjProperty(var value: Obj =ObjVoid) {
// open suspend fun get(context: Context): Obj = value
// open suspend fun set(context: Context,newValue: Obj): Obj {
// 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() {
override fun toString(): String {
return "namespace ${name}"

View File

@ -0,0 +1,24 @@
package net.sergeych.ling
data class ObjBool(val value: Boolean) : Obj() {
override val asStr by lazy { ObjString(value.toString()) }
override suspend fun compareTo(context: Context, other: Obj): Int {
if (other !is ObjBool) return -2
return value.compareTo(other.value)
}
override fun toString(): String = value.toString()
override val objClass: ObjClass = type
override suspend fun logicalNot(context: Context): Obj = ObjBool(!value)
override suspend fun logicalAnd(context: Context, other: Obj): Obj = ObjBool(value && other.toBool())
override suspend fun logicalOr(context: Context, other: Obj): Obj = ObjBool(value || other.toBool())
companion object {
val type = ObjClass("Bool")
}
}

View File

@ -0,0 +1,78 @@
package net.sergeych.ling
data class ObjInt(var value: Long) : Obj(), Numeric {
override val asStr get() = ObjString(value.toString())
override val longValue get() = value
override val doubleValue get() = value.toDouble()
override val toObjInt get() = this
override val toObjReal = ObjReal(doubleValue)
override fun byValueCopy(): Obj = ObjInt(value)
override suspend fun getAndIncrement(context: Context): Obj {
return ObjInt(value).also { value++ }
}
override suspend fun getAndDecrement(context: Context): Obj {
return ObjInt(value).also { value-- }
}
override suspend fun incrementAndGet(context: Context): Obj {
return ObjInt(++value)
}
override suspend fun decrementAndGet(context: Context): Obj {
return ObjInt(--value)
}
override suspend fun compareTo(context: Context, other: Obj): Int {
if (other !is Numeric) return -2
return value.compareTo(other.doubleValue)
}
override fun toString(): String = value.toString()
override val objClass: ObjClass = type
override suspend fun plus(context: Context, other: Obj): Obj =
if (other is ObjInt)
ObjInt(this.value + other.value)
else
ObjReal(this.doubleValue + other.toDouble())
override suspend fun minus(context: Context, other: Obj): Obj =
if (other is ObjInt)
ObjInt(this.value - other.value)
else
ObjReal(this.doubleValue - other.toDouble())
override suspend fun mul(context: Context, other: Obj): Obj =
if (other is ObjInt) {
ObjInt(this.value * other.value)
} else ObjReal(this.value * other.toDouble())
override suspend fun div(context: Context, other: Obj): Obj =
if (other is ObjInt)
ObjInt(this.value / other.value)
else ObjReal(this.value / other.toDouble())
override suspend fun mod(context: Context, other: Obj): Obj =
if (other is ObjInt)
ObjInt(this.value % other.value)
else ObjReal(this.value.toDouble() % other.toDouble())
/**
* We are by-value type ([byValueCopy] is implemented) so we can do in-place
* assignment
*/
override suspend fun assign(context: Context, other: Obj): Obj? {
return if (other is ObjInt) {
value = other.value
this
} else null
}
companion object {
val type = ObjClass("Int")
}
}

View File

@ -0,0 +1,49 @@
package net.sergeych.ling
import kotlin.math.floor
import kotlin.math.roundToLong
data class ObjReal(val value: Double) : Obj(), Numeric {
override val asStr by lazy { ObjString(value.toString()) }
override val longValue: Long by lazy { floor(value).toLong() }
override val doubleValue: Double by lazy { value }
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
override val toObjReal: ObjReal by lazy { ObjReal(value) }
override fun byValueCopy(): Obj = ObjReal(value)
override suspend fun compareTo(context: Context, other: Obj): Int {
if (other !is Numeric) return -2
return value.compareTo(other.doubleValue)
}
override fun toString(): String = value.toString()
override val objClass: ObjClass = type
override suspend fun plus(context: Context, other: Obj): Obj =
ObjReal(this.value + other.toDouble())
override suspend fun minus(context: Context, other: Obj): Obj =
ObjReal(this.value - other.toDouble())
override suspend fun mul(context: Context, other: Obj): Obj =
ObjReal(this.value * other.toDouble())
override suspend fun div(context: Context, other: Obj): Obj =
ObjReal(this.value / other.toDouble())
override suspend fun mod(context: Context, other: Obj): Obj =
ObjReal(this.value % other.toDouble())
companion object {
val type: ObjClass = ObjClass("Real").apply {
createField(
"roundToInt",
statement(Pos.builtIn) {
(it.thisObj as ObjReal).value.roundToLong().toObj()
},
)
}
}
}

View File

@ -45,11 +45,10 @@ private class Parser(fromPos: Pos) {
'=' -> {
if (pos.currentChar == '=') {
pos.advance()
if( currentChar == '=' ) {
if (currentChar == '=') {
pos.advance()
Token("===", from, Token.Type.REF_EQ)
}
else
} else
Token("==", from, Token.Type.EQ)
} else
Token("=", from, Token.Type.ASSIGN)
@ -127,9 +126,9 @@ private class Parser(fromPos: Pos) {
pos.advance()
Token("...", from, Token.Type.ELLIPSIS)
} else if (currentChar == '<') {
pos.advance()
Token("..<", from, Token.Type.DOTDOTLT)
} else {
pos.back()
Token("..", from, Token.Type.DOTDOT)
}
} else
@ -153,13 +152,22 @@ private class Parser(fromPos: Pos) {
}
'!' -> {
if (currentChar == 'i') {
pos.advance()
if( currentChar == 'n') {
pos.advance()
Token("!in", from, Token.Type.NOTIN)
} else {
pos.back()
Token("!", from, Token.Type.NOT)
}
} else
if (currentChar == '=') {
pos.advance()
if( currentChar == '=' ) {
if (currentChar == '=') {
pos.advance()
Token("!==", from, Token.Type.REF_NEQ)
}
else
} else
Token("!=", from, Token.Type.NEQ)
} else
Token("!", from, Token.Type.NOT)
@ -210,7 +218,7 @@ private class Parser(fromPos: Pos) {
if (currentChar == '\\') {
value = currentChar
pos.advance()
value = when(value) {
value = when (value) {
'n' -> '\n'
'r' -> '\r'
't' -> '\t'
@ -218,12 +226,13 @@ private class Parser(fromPos: Pos) {
else -> throw ScriptError(currentPos, "unsupported escape character: $value")
}
}
if( currentChar != '\'' ) throw ScriptError(currentPos, "expected end of character literal: '")
if (currentChar != '\'') throw ScriptError(currentPos, "expected end of character literal: '")
pos.advance()
Token(value.toString(), start, Token.Type.CHAR)
}
else -> {
// text infix operators:
// Labels processing is complicated!
// some@ statement: label 'some', ID 'statement'
// statement@some: ID 'statement', LABEL 'some'!
@ -238,7 +247,10 @@ private class Parser(fromPos: Pos) {
} else
Token(text, from, Token.Type.LABEL)
} else
Token(text, from, Token.Type.ID)
when (text) {
"in" -> Token("in", from, Token.Type.IN)
else -> Token(text, from, Token.Type.ID)
}
} else
raise("can't parse token")
}

View File

@ -59,6 +59,7 @@ class Script(
addConst("Bool", ObjBool.type)
addConst("Char", ObjChar.type)
addConst("List", ObjList.type)
addConst("Range", ObjRange.type)
val pi = ObjReal(PI)
addConst("π", pi)
getOrCreateNamespace("Math").apply {

View File

@ -11,6 +11,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
PLUS, MINUS, STAR, SLASH, PERCENT,
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
PLUS2, MINUS2,
IN, NOTIN, IS, NOTIS,
EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ,
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,

View File

@ -65,6 +65,36 @@ class ScriptTest {
}
@Test
fun parseRangeTest() {
var tt = parseLing("5 .. 4".toSource())
assertEquals(Token.Type.INT, tt[0].type)
assertEquals(Token.Type.DOTDOT, tt[1].type)
assertEquals(Token.Type.INT, tt[2].type)
tt = parseLing("5 ..< 4".toSource())
assertEquals(Token.Type.INT, tt[0].type)
assertEquals(Token.Type.DOTDOTLT, tt[1].type)
assertEquals(Token.Type.INT, tt[2].type)
}
@Test
fun parseInTest() {
var tt = parseLing("5 in 4".toSource())
assertEquals(Token.Type.INT, tt[0].type)
assertEquals(Token.Type.IN, tt[1].type)
assertEquals(Token.Type.INT, tt[2].type)
tt = parseLing("5 ..< 4".toSource())
assertEquals(Token.Type.INT, tt[0].type)
assertEquals(Token.Type.DOTDOTLT, tt[1].type)
assertEquals(Token.Type.INT, tt[2].type)
}
@Test
fun parserLabelsTest() {
val src = "label@ break@label".toSource()
@ -748,35 +778,42 @@ class ScriptTest {
@Test
fun testListLiteral() = runTest {
eval("""
eval(
"""
val list = [1,22,3]
assert(list[0] == 1)
assert(list[1] == 22)
assert(list[2] == 3)
""".trimIndent())
""".trimIndent()
)
eval("""
eval(
"""
val x0 = 100
val list = [x0 + 1, x0 * 10, 3]
assert(list[0] == 101)
assert(list[1] == 1000)
assert(list[2] == 3)
""".trimIndent())
""".trimIndent()
)
eval("""
eval(
"""
val x0 = 100
val list = [x0 + 1, x0 * 10, if(x0 < 100) "low" else "high", 5]
assert(list[0] == 101)
assert(list[1] == 1000)
assert(list[2] == "high")
assert(list[3] == 5)
""".trimIndent())
""".trimIndent()
)
}
@Test
fun testListLiteralSpread() = runTest {
eval("""
eval(
"""
val list1 = [1,22,3]
val list = ["start", ...list1, "end"]
assert(list[0] == "start")
@ -784,40 +821,48 @@ class ScriptTest {
assert(list[2] == 22)
assert(list[3] == 3)
assert(list[4] == "end")
""".trimIndent())
""".trimIndent()
)
}
@Test
fun testListSize() = runTest {
eval("""
eval(
"""
val a = [4,3]
assert(a.size == 2)
""".trimIndent())
""".trimIndent()
)
}
@Test
fun testArrayCompare() = runTest {
eval("""
eval(
"""
val a = [4,3]
val b = [4,3]
assert(a == b)
assert( a === a )
assert( !(a === b) )
assert( a !== b )
""".trimIndent())
""".trimIndent()
)
}
@Test
fun forLoop1() = runTest {
eval("""
eval(
"""
var sum = 0
for(i in [1,2,3]) {
println(i)
sum += i
}
assert(sum == 6)
""".trimIndent())
eval("""
""".trimIndent()
)
eval(
"""
fun test1(array) {
var sum = 0
for(i in array) {
@ -827,12 +872,14 @@ class ScriptTest {
}
println("result=",test1([1,2]))
println("result=",test1([1,2,3]))
""".trimIndent())
""".trimIndent()
)
}
@Test
fun forLoop2() = runTest {
println(eval(
println(
eval(
"""
fun search(haystack, needle) {
for(ch in haystack) {
@ -844,7 +891,88 @@ class ScriptTest {
assert( search("hello", 'l') == "found")
assert( search("hello", 'z') == null)
""".trimIndent()
).toString())
).toString()
)
}
@Test
fun testIntOpenRangeInclusive() = runTest {
eval(
"""
val r = 10 .. 20
assert( r::class == Range)
assert(r.isOpen == false)
assert(r.start == 10)
assert(r.end == 20)
assert(r.inclusiveEnd == true)
assert(r.isIntRange)
assert(12 in r)
assert(10 in r)
assert(20 in r)
assert(9 !in r)
assert(21 !in r)
assert( (11..12) in r)
assert( (10..11) in r)
assert( (11..20) in r)
assert( (10..20) in r)
assert( (9..12) !in r)
assert( (1..9) !in r)
assert( (17..22) !in r)
assert( (21..22) !in r)
// assert(r.size == 11)
""".trimIndent()
)
}
@Test
fun testIntOpenRangeExclusive() = runTest {
eval(
"""
val r = 10 ..< 20
assert( r::class == Range)
assert(r.isOpen == false)
assert(r.start == 10)
assert(r.end == 20)
assert(r.inclusiveEnd == false)
assert(r.isIntRange)
assert(12 in r)
assert(10 in r)
assert(20 !in r)
assert(9 !in r)
assert(21 !in r)
assert( (11..12) in r)
assert( (10..11) in r)
assert( (11..20) !in r)
assert( (10..20) !in r)
assert( (10..<20) in r)
assert( (9..12) !in r)
assert( (1..9) !in r)
assert( (17..22) !in r)
assert( (21..22) !in r)
""".trimIndent()
)
}
@Test
fun testIntOpenRangeInExclusive() = runTest {
eval(
"""
assert( (1..3) !in (1..<3) )
assert( (1..<3) in (1..3) )
""".trimIndent()
)
}
// @Test

View File

@ -205,4 +205,9 @@ class BookTest {
runDocTests("../docs/List.md")
}
@Test
fun testFromRange() = runTest {
runDocTests("../docs/Range.md")
}
}