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) )
|
assert( (1..<3) in (1..3) )
|
||||||
>>> void
|
>>> 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
|
# Instance members
|
||||||
|
|
||||||
| member | description | args |
|
| member | description | args |
|
||||||
|-----------------|----------------------------|---------------|
|
|-----------------|------------------------------|---------------|
|
||||||
| contains(other) | used in `in` | Range, or Any |
|
| contains(other) | used in `in` | Range, or Any |
|
||||||
| inclusiveEnd | true for '..' | Bool |
|
| isEndInclusive | true for '..' | Bool |
|
||||||
| isOpen | at any end | Bool |
|
| isOpen | at any end | Bool |
|
||||||
| isIntRange | both start and end are Int | Bool |
|
| isIntRange | both start and end are Int | Bool |
|
||||||
| start | | Bool |
|
| start | | Bool |
|
||||||
| end | | Bool |
|
| end | | Bool |
|
||||||
| | | |
|
| size | for finite ranges, see above | Long |
|
||||||
| | | |
|
| [] | see above | |
|
||||||
| | | |
|
| | | |
|
||||||
|
@ -303,14 +303,14 @@ class Compiler(
|
|||||||
|
|
||||||
Token.Type.DOTDOT, Token.Type.DOTDOTLT -> {
|
Token.Type.DOTDOT, Token.Type.DOTDOTLT -> {
|
||||||
// closed-range operator
|
// closed-range operator
|
||||||
val inclusiveEnd = t.type == Token.Type.DOTDOT
|
val isEndInclusive = t.type == Token.Type.DOTDOT
|
||||||
val left = operand
|
val left = operand
|
||||||
val right = parseStatement(cc)
|
val right = parseStatement(cc)
|
||||||
operand = Accessor {
|
operand = Accessor {
|
||||||
ObjRange(
|
ObjRange(
|
||||||
left?.getter?.invoke(it)?.value ?: ObjNull,
|
left?.getter?.invoke(it)?.value ?: ObjNull,
|
||||||
right?.execute(it) ?: ObjNull,
|
right?.execute(it) ?: ObjNull,
|
||||||
inclusiveEnd = inclusiveEnd
|
isEndInclusive = isEndInclusive
|
||||||
).asReadonly
|
).asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,10 @@ class Context(
|
|||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun raiseNPE(): Nothing = raiseError(ObjNullPointerError(this))
|
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 raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastError(this, msg))
|
||||||
|
|
||||||
fun raiseError(message: String): Nothing {
|
fun raiseError(message: String): Nothing {
|
||||||
|
@ -52,9 +52,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,
|
suspend fun callInstanceMethod(
|
||||||
name: String,
|
context: Context,
|
||||||
args: Arguments = Arguments.EMPTY
|
name: String,
|
||||||
|
args: Arguments = Arguments.EMPTY
|
||||||
): Obj =
|
): 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)
|
||||||
@ -193,7 +194,7 @@ sealed class Obj {
|
|||||||
members[name] = WithAccess(initialValue, isMutable)
|
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)
|
createField(name, statement { code() }, isOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +211,6 @@ sealed class Obj {
|
|||||||
callOn(context.copy(atPos, args = args, newThisObj = thisObj))
|
callOn(context.copy(atPos, args = args, newThisObj = thisObj))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val asReadonly: WithAccess<Obj> by lazy { WithAccess(this, false) }
|
val asReadonly: WithAccess<Obj> by lazy { WithAccess(this, false) }
|
||||||
val asMutable: WithAccess<Obj> by lazy { WithAccess(this, true) }
|
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")
|
(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 val objClass: ObjClass = type
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
val result = StringBuilder()
|
val result = StringBuilder()
|
||||||
result.append("${start ?: '∞'} ..")
|
result.append("${start ?: '∞'} ..")
|
||||||
if( !inclusiveEnd) result.append('<')
|
if (!isEndInclusive) result.append('<')
|
||||||
result.append(" ${end ?: '∞'}")
|
result.append(" ${end ?: '∞'}")
|
||||||
return result.toString()
|
return result.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun containsRange(context: Context, other: ObjRange): Boolean {
|
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:
|
// 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:
|
// 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:
|
// both exists, now there could be 4 cases:
|
||||||
return when {
|
return when {
|
||||||
other.inclusiveEnd && inclusiveEnd ->
|
other.isEndInclusive && isEndInclusive ->
|
||||||
end.compareTo(context, other.end) >= 0
|
end.compareTo(context, other.end) >= 0
|
||||||
!other.inclusiveEnd && !inclusiveEnd ->
|
|
||||||
|
!other.isEndInclusive && !isEndInclusive ->
|
||||||
end.compareTo(context, other.end) >= 0
|
end.compareTo(context, other.end) >= 0
|
||||||
other.inclusiveEnd && !inclusiveEnd ->
|
|
||||||
|
other.isEndInclusive && !isEndInclusive ->
|
||||||
end.compareTo(context, other.end) > 0
|
end.compareTo(context, other.end) > 0
|
||||||
!other.inclusiveEnd && inclusiveEnd ->
|
|
||||||
|
!other.isEndInclusive && isEndInclusive ->
|
||||||
end.compareTo(context, other.end) >= 0
|
end.compareTo(context, other.end) >= 0
|
||||||
|
|
||||||
else -> throw IllegalStateException("unknown comparison")
|
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 {
|
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||||
|
|
||||||
if( other is ObjRange)
|
if (other is ObjRange)
|
||||||
return containsRange(context, other)
|
return containsRange(context, other)
|
||||||
|
|
||||||
if (start == null && end == null) return true
|
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) {
|
if (end != null) {
|
||||||
val cmp = end.compareTo(context, other)
|
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
|
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 {
|
val isIntRange: Boolean by lazy {
|
||||||
start is ObjInt && end is ObjInt
|
start is ObjInt && end is ObjInt
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = ObjClass("Range").apply {
|
val type = ObjClass("Range").apply {
|
||||||
addFn("start" ) {
|
addFn("start") {
|
||||||
thisAs<ObjRange>().start ?: ObjNull
|
thisAs<ObjRange>().start ?: ObjNull
|
||||||
}
|
}
|
||||||
addFn("end") {
|
addFn("end") {
|
||||||
@ -357,8 +379,21 @@ class ObjRange(val start: Obj?, val end: Obj?,val inclusiveEnd: Boolean) : Obj()
|
|||||||
addFn("isIntRange") {
|
addFn("isIntRange") {
|
||||||
thisAs<ObjRange>().isIntRange.toObj()
|
thisAs<ObjRange>().isIntRange.toObj()
|
||||||
}
|
}
|
||||||
addFn("inclusiveEnd") {
|
addFn("isEndInclusive") {
|
||||||
thisAs<ObjRange>().inclusiveEnd.toObj()
|
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 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 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() }
|
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
|
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}")
|
if (i !in list.indices) context.raiseError("index $index out of bounds for size ${list.size}")
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ class ObjList(val list: MutableList<Obj>) : Obj() {
|
|||||||
val l = thisAs<ObjList>()
|
val l = thisAs<ObjList>()
|
||||||
var index = l.normalize(
|
var index = l.normalize(
|
||||||
this, requiredArg<ObjInt>(0).value.toInt(),
|
this, requiredArg<ObjInt>(0).value.toInt(),
|
||||||
allowInclusiveEnd = true
|
allowisEndInclusive = true
|
||||||
)
|
)
|
||||||
for (i in 1..<args.size) l.list.add(index++, args[i])
|
for (i in 1..<args.size) l.list.add(index++, args[i])
|
||||||
ObjVoid
|
ObjVoid
|
||||||
|
@ -904,7 +904,7 @@ class ScriptTest {
|
|||||||
assert(r.isOpen == false)
|
assert(r.isOpen == false)
|
||||||
assert(r.start == 10)
|
assert(r.start == 10)
|
||||||
assert(r.end == 20)
|
assert(r.end == 20)
|
||||||
assert(r.inclusiveEnd == true)
|
assert(r.isEndInclusive == true)
|
||||||
assert(r.isIntRange)
|
assert(r.isIntRange)
|
||||||
|
|
||||||
assert(12 in r)
|
assert(12 in r)
|
||||||
@ -938,7 +938,7 @@ class ScriptTest {
|
|||||||
assert(r.isOpen == false)
|
assert(r.isOpen == false)
|
||||||
assert(r.start == 10)
|
assert(r.start == 10)
|
||||||
assert(r.end == 20)
|
assert(r.end == 20)
|
||||||
assert(r.inclusiveEnd == false)
|
assert(r.isEndInclusive == false)
|
||||||
assert(r.isIntRange)
|
assert(r.isIntRange)
|
||||||
|
|
||||||
assert(12 in r)
|
assert(12 in r)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user