for in range, range size and indexing
This commit is contained in:
parent
2b3b0b298c
commit
dcab60b7bb
@ -45,17 +45,57 @@ are equal or within another, taking into account the end-inclusiveness:
|
||||
assert( (1..<3) in (1..3) )
|
||||
>>> void
|
||||
|
||||
## Range size and indexed access
|
||||
|
||||
This might be confusing, but the range size and limits are used with for loops
|
||||
so their meaning is special.
|
||||
|
||||
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:
|
||||
|
||||
val r = 1..3
|
||||
assert( r.size == 3 )
|
||||
assert( r[0] == 1 )
|
||||
assert( r[1] == 2 )
|
||||
assert( r[2] == 3 )
|
||||
>>> void
|
||||
|
||||
And for end-exclusive range:
|
||||
|
||||
val r = 1..<3
|
||||
assert(r.size == 2)
|
||||
assert( r[0] == 1 )
|
||||
assert( r[1] == 2 )
|
||||
>>> void
|
||||
|
||||
In spite of this you can use ranges in for loops:
|
||||
|
||||
for( i in 1..3 )
|
||||
println(i)
|
||||
>>> 1
|
||||
>>> 2
|
||||
>>> 3
|
||||
>>> void
|
||||
|
||||
but
|
||||
|
||||
for( i in 1..<3 )
|
||||
println(i)
|
||||
>>> 1
|
||||
>>> 2
|
||||
>>> void
|
||||
|
||||
# Instance members
|
||||
|
||||
| member | description | args |
|
||||
|-----------------|----------------------------|---------------|
|
||||
|-----------------|------------------------------|---------------|
|
||||
| contains(other) | used in `in` | Range, or Any |
|
||||
| inclusiveEnd | true for '..' | Bool |
|
||||
| isEndInclusive | true for '..' | Bool |
|
||||
| isOpen | at any end | Bool |
|
||||
| isIntRange | both start and end are Int | Bool |
|
||||
| start | | Bool |
|
||||
| end | | Bool |
|
||||
| | | |
|
||||
| | | |
|
||||
| size | for finite ranges, see above | Long |
|
||||
| [] | see above | |
|
||||
| | | |
|
||||
|
@ -303,14 +303,14 @@ class Compiler(
|
||||
|
||||
Token.Type.DOTDOT, Token.Type.DOTDOTLT -> {
|
||||
// closed-range operator
|
||||
val inclusiveEnd = t.type == Token.Type.DOTDOT
|
||||
val isEndInclusive = 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
|
||||
isEndInclusive = isEndInclusive
|
||||
).asReadonly
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,10 @@ class Context(
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseNPE(): Nothing = raiseError(ObjNullPointerError(this))
|
||||
|
||||
fun raiseIndexOutOfBounds(message: String = "Index out of bounds"): Nothing =
|
||||
raiseError(ObjIndexOutOfBoundsError(this, message))
|
||||
fun raiseArgumentError(message: String = "Illegal argument error"): Nothing =
|
||||
raiseError(ObjIllegalArgumentError(this, message))
|
||||
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastError(this, msg))
|
||||
|
||||
fun raiseError(message: String): Nothing {
|
||||
|
@ -52,7 +52,8 @@ sealed class Obj {
|
||||
getInstanceMemberOrNull(name)
|
||||
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
||||
|
||||
suspend fun callInstanceMethod(context: Context,
|
||||
suspend fun callInstanceMethod(
|
||||
context: Context,
|
||||
name: String,
|
||||
args: Arguments = Arguments.EMPTY
|
||||
): Obj =
|
||||
@ -193,7 +194,7 @@ sealed class Obj {
|
||||
members[name] = WithAccess(initialValue, isMutable)
|
||||
}
|
||||
|
||||
fun addFn(name: String, isOpen: Boolean = false, code: suspend Context.()->Obj) {
|
||||
fun addFn(name: String, isOpen: Boolean = false, code: suspend Context.() -> Obj) {
|
||||
createField(name, statement { code() }, isOpen)
|
||||
}
|
||||
|
||||
@ -210,7 +211,6 @@ sealed class 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) }
|
||||
|
||||
@ -287,36 +287,40 @@ fun Obj.toBool(): Boolean =
|
||||
(this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean $this")
|
||||
|
||||
|
||||
class ObjRange(val start: Obj?, val end: Obj?,val inclusiveEnd: Boolean) : Obj() {
|
||||
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( !inclusiveEnd) result.append('<')
|
||||
if (!isEndInclusive) result.append('<')
|
||||
result.append(" ${end ?: '∞'}")
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
suspend fun containsRange(context: Context, other: ObjRange): Boolean {
|
||||
if( start != null ) {
|
||||
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 (other.start != null && start.compareTo(context, other.start) > 0) return false
|
||||
}
|
||||
if( end != null ) {
|
||||
if (end != null) {
|
||||
// same with the end: if it is open, it can't be contained in ours:
|
||||
if( other.end == null ) return false
|
||||
if (other.end == null) return false
|
||||
// both exists, now there could be 4 cases:
|
||||
return when {
|
||||
other.inclusiveEnd && inclusiveEnd ->
|
||||
other.isEndInclusive && isEndInclusive ->
|
||||
end.compareTo(context, other.end) >= 0
|
||||
!other.inclusiveEnd && !inclusiveEnd ->
|
||||
|
||||
!other.isEndInclusive && !isEndInclusive ->
|
||||
end.compareTo(context, other.end) >= 0
|
||||
other.inclusiveEnd && !inclusiveEnd ->
|
||||
|
||||
other.isEndInclusive && !isEndInclusive ->
|
||||
end.compareTo(context, other.end) > 0
|
||||
!other.inclusiveEnd && inclusiveEnd ->
|
||||
|
||||
!other.isEndInclusive && isEndInclusive ->
|
||||
end.compareTo(context, other.end) >= 0
|
||||
|
||||
else -> throw IllegalStateException("unknown comparison")
|
||||
}
|
||||
}
|
||||
@ -325,7 +329,7 @@ class ObjRange(val start: Obj?, val end: Obj?,val inclusiveEnd: Boolean) : Obj()
|
||||
|
||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
|
||||
if( other is ObjRange)
|
||||
if (other is ObjRange)
|
||||
return containsRange(context, other)
|
||||
|
||||
if (start == null && end == null) return true
|
||||
@ -334,18 +338,36 @@ class ObjRange(val start: Obj?, val end: Obj?,val inclusiveEnd: Boolean) : Obj()
|
||||
}
|
||||
if (end != null) {
|
||||
val cmp = end.compareTo(context, other)
|
||||
if (inclusiveEnd && cmp < 0 || !inclusiveEnd && cmp <= 0) return false
|
||||
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" ) {
|
||||
addFn("start") {
|
||||
thisAs<ObjRange>().start ?: ObjNull
|
||||
}
|
||||
addFn("end") {
|
||||
@ -357,8 +379,21 @@ class ObjRange(val start: Obj?, val end: Obj?,val inclusiveEnd: Boolean) : Obj()
|
||||
addFn("isIntRange") {
|
||||
thisAs<ObjRange>().isIntRange.toObj()
|
||||
}
|
||||
addFn("inclusiveEnd") {
|
||||
thisAs<ObjRange>().inclusiveEnd.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -378,3 +413,5 @@ class ObjNullPointerError(context: Context) : ObjError(context, "object is null"
|
||||
|
||||
class ObjAssertionError(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 ObjIllegalArgumentError(context: Context,message: String="illegal argument") : ObjError(context,message)
|
@ -6,9 +6,9 @@ class ObjList(val list: MutableList<Obj>) : Obj() {
|
||||
list.joinToString(separator = ", ") { it.inspect() }
|
||||
}]"
|
||||
|
||||
fun normalize(context: Context, index: Int, allowInclusiveEnd: Boolean = false): Int {
|
||||
fun normalize(context: Context, index: Int, allowisEndInclusive: Boolean = false): Int {
|
||||
val i = if (index < 0) list.size + index else index
|
||||
if (allowInclusiveEnd && i == list.size) return i
|
||||
if (allowisEndInclusive && i == list.size) return i
|
||||
if (i !in list.indices) context.raiseError("index $index out of bounds for size ${list.size}")
|
||||
return i
|
||||
}
|
||||
@ -75,7 +75,7 @@ class ObjList(val list: MutableList<Obj>) : Obj() {
|
||||
val l = thisAs<ObjList>()
|
||||
var index = l.normalize(
|
||||
this, requiredArg<ObjInt>(0).value.toInt(),
|
||||
allowInclusiveEnd = true
|
||||
allowisEndInclusive = true
|
||||
)
|
||||
for (i in 1..<args.size) l.list.add(index++, args[i])
|
||||
ObjVoid
|
||||
|
@ -904,7 +904,7 @@ class ScriptTest {
|
||||
assert(r.isOpen == false)
|
||||
assert(r.start == 10)
|
||||
assert(r.end == 20)
|
||||
assert(r.inclusiveEnd == true)
|
||||
assert(r.isEndInclusive == true)
|
||||
assert(r.isIntRange)
|
||||
|
||||
assert(12 in r)
|
||||
@ -938,7 +938,7 @@ class ScriptTest {
|
||||
assert(r.isOpen == false)
|
||||
assert(r.start == 10)
|
||||
assert(r.end == 20)
|
||||
assert(r.inclusiveEnd == false)
|
||||
assert(r.isEndInclusive == false)
|
||||
assert(r.isIntRange)
|
||||
|
||||
assert(12 in r)
|
||||
|
Loading…
x
Reference in New Issue
Block a user