for in range, range size and indexing
This commit is contained in:
parent
dcab60b7bb
commit
d2c732ceef
@ -51,7 +51,6 @@ This might be confusing, but the range size and limits are used with for loops
|
|||||||
so their meaning is special.
|
so their meaning is special.
|
||||||
|
|
||||||
For open ranges, size throws and exception.
|
For open ranges, size throws and exception.
|
||||||
|
|
||||||
For Int ranges, the `size` is `end` - `start` possibly + 1 for ind-inclusive ranges, and indexing getter returns all values from start to end, probably, inclusive:
|
For Int ranges, the `size` is `end` - `start` possibly + 1 for ind-inclusive ranges, and indexing getter returns all values from start to end, probably, inclusive:
|
||||||
|
|
||||||
val r = 1..3
|
val r = 1..3
|
||||||
@ -86,6 +85,27 @@ but
|
|||||||
>>> 2
|
>>> 2
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
## Character ranges
|
||||||
|
|
||||||
|
You can use Char as both ends of the closed range:
|
||||||
|
|
||||||
|
val r = 'a' .. 'c'
|
||||||
|
assert( 'b' in r)
|
||||||
|
assert( 'e' !in r)
|
||||||
|
assert( 'c' == r[2] )
|
||||||
|
for( ch in r )
|
||||||
|
println(ch)
|
||||||
|
>>> a
|
||||||
|
>>> b
|
||||||
|
>>> c
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Exclusive end char ranges are supported too:
|
||||||
|
|
||||||
|
('a'..<'c').size
|
||||||
|
>>> 2
|
||||||
|
|
||||||
|
|
||||||
# Instance members
|
# Instance members
|
||||||
|
|
||||||
| member | description | args |
|
| member | description | args |
|
||||||
|
@ -645,6 +645,9 @@ Are the same as in string literals with little difference:
|
|||||||
|
|
||||||
### Char instance members
|
### Char instance members
|
||||||
|
|
||||||
|
assert( 'a'.code == 0x61 )
|
||||||
|
>>> void
|
||||||
|
|
||||||
| member | type | meaning |
|
| member | type | meaning |
|
||||||
|--------|------|--------------------------------|
|
|--------|------|--------------------------------|
|
||||||
| code | Int | Unicode code for the character |
|
| code | Int | Unicode code for the character |
|
||||||
|
@ -541,7 +541,7 @@ class Compiler(
|
|||||||
// insofar we suggest source object is enumerable. Later we might need to add checks
|
// insofar we suggest source object is enumerable. Later we might need to add checks
|
||||||
val sourceObj = source.execute(forContext)
|
val sourceObj = source.execute(forContext)
|
||||||
val size = runCatching { sourceObj.callInstanceMethod(forContext, "size").toInt() }
|
val size = runCatching { sourceObj.callInstanceMethod(forContext, "size").toInt() }
|
||||||
.getOrElse { throw ScriptError(tOp.pos, "object is not enumerable: no size") }
|
.getOrElse { throw ScriptError(tOp.pos, "object is not enumerable: no size", it) }
|
||||||
var result: Obj = ObjVoid
|
var result: Obj = ObjVoid
|
||||||
var breakCaught = false
|
var breakCaught = false
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
|
@ -277,9 +277,12 @@ fun Obj.toDouble(): Double =
|
|||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun Obj.toLong(): Long =
|
fun Obj.toLong(): Long =
|
||||||
(this as? Numeric)?.longValue
|
when(this) {
|
||||||
?: (this as? ObjString)?.value?.toLong()
|
is Numeric -> longValue
|
||||||
?: throw IllegalArgumentException("cannot convert to double $this")
|
is ObjString -> value.toLong()
|
||||||
|
is ObjChar -> value.code.toLong()
|
||||||
|
else -> throw IllegalArgumentException("cannot convert to double $this")
|
||||||
|
}
|
||||||
|
|
||||||
fun Obj.toInt(): Int = toLong().toInt()
|
fun Obj.toInt(): Int = toLong().toInt()
|
||||||
|
|
||||||
@ -287,118 +290,6 @@ fun Obj.toBool(): Boolean =
|
|||||||
(this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean $this")
|
(this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean $this")
|
||||||
|
|
||||||
|
|
||||||
class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Obj() {
|
|
||||||
|
|
||||||
override val objClass: ObjClass = type
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
val result = StringBuilder()
|
|
||||||
result.append("${start ?: '∞'} ..")
|
|
||||||
if (!isEndInclusive) result.append('<')
|
|
||||||
result.append(" ${end ?: '∞'}")
|
|
||||||
return result.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
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.isEndInclusive && isEndInclusive ->
|
|
||||||
end.compareTo(context, other.end) >= 0
|
|
||||||
|
|
||||||
!other.isEndInclusive && !isEndInclusive ->
|
|
||||||
end.compareTo(context, other.end) >= 0
|
|
||||||
|
|
||||||
other.isEndInclusive && !isEndInclusive ->
|
|
||||||
end.compareTo(context, other.end) > 0
|
|
||||||
|
|
||||||
!other.isEndInclusive && isEndInclusive ->
|
|
||||||
end.compareTo(context, other.end) >= 0
|
|
||||||
|
|
||||||
else -> throw IllegalStateException("unknown comparison")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (isEndInclusive && cmp < 0 || !isEndInclusive && cmp <= 0) return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getAt(context: Context, index: Int): Obj {
|
|
||||||
if( !isIntRange ) {
|
|
||||||
return when (index) {
|
|
||||||
0 -> start ?: ObjNull
|
|
||||||
1 -> end ?: ObjNull
|
|
||||||
else -> context.raiseIndexOutOfBounds("index out of range: $index for max of 2 for non-int ranges")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// int range, should be finite
|
|
||||||
val r0 = start?.toInt() ?: context.raiseArgumentError("start is not integer")
|
|
||||||
var r1 = end?.toInt() ?: context.raiseArgumentError("end is not integer")
|
|
||||||
if( isEndInclusive ) r1++
|
|
||||||
val i = index + r0
|
|
||||||
if( i >= r1 ) context.raiseIndexOutOfBounds("index $index is not in range (${r1-r0})")
|
|
||||||
return ObjInt(i.toLong())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val isIntRange: Boolean by lazy {
|
|
||||||
start is ObjInt && end is ObjInt
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
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("isEndInclusive") {
|
|
||||||
thisAs<ObjRange>().isEndInclusive.toObj()
|
|
||||||
}
|
|
||||||
addFn("size") {
|
|
||||||
val self = thisAs<ObjRange>()
|
|
||||||
if (self.start == null || self.end == null)
|
|
||||||
raiseError("size is only available for finite ranges")
|
|
||||||
if (self.isIntRange) {
|
|
||||||
if (self.isEndInclusive)
|
|
||||||
ObjInt(self.end.toLong() - self.start.toLong() + 1)
|
|
||||||
else
|
|
||||||
ObjInt(self.end.toLong() - self.start.toLong())
|
|
||||||
} else {
|
|
||||||
ObjInt(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ObjNamespace(val name: String) : Obj() {
|
data class ObjNamespace(val name: String) : Obj() {
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "namespace ${name}"
|
return "namespace ${name}"
|
||||||
@ -413,5 +304,5 @@ class ObjNullPointerError(context: Context) : ObjError(context, "object is null"
|
|||||||
|
|
||||||
class ObjAssertionError(context: Context, message: String) : ObjError(context, message)
|
class ObjAssertionError(context: Context, message: String) : ObjError(context, message)
|
||||||
class ObjClassCastError(context: Context, message: String) : ObjError(context, message)
|
class ObjClassCastError(context: Context, message: String) : ObjError(context, message)
|
||||||
class ObjIndexOutOfBoundsError(context: Context,message: String="index out of bounds") : ObjError(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 ObjIllegalArgumentError(context: Context, message: String = "illegal argument") : ObjError(context, message)
|
120
library/src/commonMain/kotlin/net/sergeych/ling/ObjRange.kt
Normal file
120
library/src/commonMain/kotlin/net/sergeych/ling/ObjRange.kt
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package net.sergeych.ling
|
||||||
|
|
||||||
|
class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Obj() {
|
||||||
|
|
||||||
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val result = StringBuilder()
|
||||||
|
result.append("${start?.inspect() ?: '∞'} ..")
|
||||||
|
if (!isEndInclusive) result.append('<')
|
||||||
|
result.append(" ${end?.inspect() ?: '∞'}")
|
||||||
|
return result.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
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.isEndInclusive && isEndInclusive ->
|
||||||
|
end.compareTo(context, other.end) >= 0
|
||||||
|
|
||||||
|
!other.isEndInclusive && !isEndInclusive ->
|
||||||
|
end.compareTo(context, other.end) >= 0
|
||||||
|
|
||||||
|
other.isEndInclusive && !isEndInclusive ->
|
||||||
|
end.compareTo(context, other.end) > 0
|
||||||
|
|
||||||
|
!other.isEndInclusive && isEndInclusive ->
|
||||||
|
end.compareTo(context, other.end) >= 0
|
||||||
|
|
||||||
|
else -> throw IllegalStateException("unknown comparison")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (isEndInclusive && cmp < 0 || !isEndInclusive && cmp <= 0) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAt(context: Context, index: Int): Obj {
|
||||||
|
if (!isIntRange && !isCharRange) {
|
||||||
|
return when (index) {
|
||||||
|
0 -> start ?: ObjNull
|
||||||
|
1 -> end ?: ObjNull
|
||||||
|
else -> context.raiseIndexOutOfBounds("index out of range: $index for max of 2 for non-int ranges")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// int range, should be finite
|
||||||
|
val r0 = start?.toInt() ?: context.raiseArgumentError("start is not integer")
|
||||||
|
var r1 = end?.toInt() ?: context.raiseArgumentError("end is not integer")
|
||||||
|
if (isEndInclusive) r1++
|
||||||
|
val i = index + r0
|
||||||
|
if (i >= r1) context.raiseIndexOutOfBounds("index $index is not in range (${r1 - r0})")
|
||||||
|
return if( isIntRange ) ObjInt(i.toLong()) else ObjChar(i.toChar())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val isIntRange: Boolean by lazy {
|
||||||
|
start is ObjInt && end is ObjInt
|
||||||
|
}
|
||||||
|
|
||||||
|
val isCharRange: Boolean by lazy {
|
||||||
|
start is ObjChar && end is ObjChar
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
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("isCharRange") {
|
||||||
|
thisAs<ObjRange>().isCharRange.toObj()
|
||||||
|
}
|
||||||
|
addFn("isEndInclusive") {
|
||||||
|
thisAs<ObjRange>().isEndInclusive.toObj()
|
||||||
|
}
|
||||||
|
addFn("size") {
|
||||||
|
val self = thisAs<ObjRange>()
|
||||||
|
if (self.start == null || self.end == null)
|
||||||
|
raiseError("size is only available for finite ranges")
|
||||||
|
if (self.isIntRange || self.isCharRange) {
|
||||||
|
if (self.isEndInclusive)
|
||||||
|
ObjInt(self.end.toLong() - self.start.toLong() + 1)
|
||||||
|
else
|
||||||
|
ObjInt(self.end.toLong() - self.start.toLong())
|
||||||
|
} else {
|
||||||
|
ObjInt(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -975,6 +975,18 @@ class ScriptTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCharacterRange() = runTest {
|
||||||
|
eval("""
|
||||||
|
val x = '0'..'9'
|
||||||
|
println(x)
|
||||||
|
assert( '5' in x)
|
||||||
|
assert( 'z' !in x)
|
||||||
|
for( ch in x )
|
||||||
|
println(ch)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
// @Test
|
// @Test
|
||||||
// fun testLambda1() = runTest {
|
// fun testLambda1() = runTest {
|
||||||
// val l = eval("""
|
// val l = eval("""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user