This commit is contained in:
sergeych committed 2025-05-31 18:53:42 +04:00
1 parent 3908b8ee9f
commit b82a97ed20
11 files changed
+501 -206

No files matched your search

@@ -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) },
@@ -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 fun toString(): String {
val result = StringBuilder()
result.append("${start ?: '∞'} ..")
if( !inclusiveEnd) result.append('<')
result.append(" ${end ?: '∞'}")
return result.toString()
}
override suspend fun minus(context: Context, other: Obj): Obj =
ObjReal(this.value - other.toDouble())
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 mul(context: Context, other: Obj): Obj =
ObjReal(this.value * other.toDouble())
override suspend fun contains(context: Context, other: Obj): Boolean {
override suspend fun div(context: Context, other: Obj): Obj =
ObjReal(this.value / other.toDouble())
if( other is ObjRange)
return containsRange(context, other)
override suspend fun mod(context: Context, other: Obj): Obj =
ObjReal(this.value % other.toDouble())
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
}
val isIntRange: Boolean by lazy {
start is ObjInt && end is ObjInt
}
companion object {
val type: ObjClass = ObjClass("Real").apply {
createField(
"roundToInt",
statement(Pos.builtIn) {
(it.thisObj as ObjReal).value.roundToLong().toObj()
},
)
val type = ObjClass("Range").apply {
addFn("start" ) {
thisAs<ObjRange>().start ?: ObjNull
}
addFn("end") {
thisAs<ObjRange>().end ?: ObjNull
}
addFn("isOpen") {
thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj()
}
addFn("isIntRange") {
thisAs<ObjRange>().isIntRange.toObj()
}
addFn("inclusiveEnd") {
thisAs<ObjRange>().inclusiveEnd.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 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")
}
}
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")
}
}
//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}"
@@ -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")
}
}
@@ -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")
}
}
@@ -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()
},
)
}
}
}
@@ -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,16 +152,25 @@ private class Parser(fromPos: Pos) {
}
'!' -> {
if (currentChar == '=') {
if (currentChar == 'i') {
pos.advance()
if( currentChar == '=' ) {
if( currentChar == 'n') {
pos.advance()
Token("!==", from, Token.Type.REF_NEQ)
Token("!in", from, Token.Type.NOTIN)
} else {
pos.back()
Token("!", from, Token.Type.NOT)
}
else
Token("!=", from, Token.Type.NEQ)
} else
Token("!", from, Token.Type.NOT)
if (currentChar == '=') {
pos.advance()
if (currentChar == '=') {
pos.advance()
Token("!==", from, Token.Type.REF_NEQ)
} 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")
}
@@ -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 {
@@ -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,
+147 -19
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,13 +872,15 @@ 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) {
if( ch == needle)
@@ -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
+5
View File
@@ -205,4 +205,9 @@ class BookTest {
runDocTests("../docs/List.md")
}
@Test
fun testFromRange() = runTest {
runDocTests("../docs/Range.md")
}
}