Add descending ranges and for-loop support
This commit is contained in:
parent
d8c53c500e
commit
f1003f5b95
@ -25,6 +25,23 @@ Exclusive end ranges are adopted from kotlin either:
|
|||||||
assert(4 in r)
|
assert(4 in r)
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
Descending finite ranges are explicit too:
|
||||||
|
|
||||||
|
val r = 5 downTo 1
|
||||||
|
assert(r.isDescending)
|
||||||
|
assert(r.toList() == [5,4,3,2,1])
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Use `downUntil` when the lower bound should be excluded:
|
||||||
|
|
||||||
|
val r = 5 downUntil 1
|
||||||
|
assert(r.toList() == [5,4,3,2])
|
||||||
|
assert(1 !in r)
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
This is explicit by design: `5..1` is not treated as a reverse range. It is an
|
||||||
|
ordinary ascending range with no values in it when iterated.
|
||||||
|
|
||||||
In any case, we can test an object to belong to using `in` and `!in` and
|
In any case, we can test an object to belong to using `in` and `!in` and
|
||||||
access limits:
|
access limits:
|
||||||
|
|
||||||
@ -73,6 +90,23 @@ but
|
|||||||
>>> 2
|
>>> 2
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
Descending ranges work in `for` loops exactly the same way:
|
||||||
|
|
||||||
|
for( i in 3 downTo 1 )
|
||||||
|
println(i)
|
||||||
|
>>> 3
|
||||||
|
>>> 2
|
||||||
|
>>> 1
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
And with an exclusive lower bound:
|
||||||
|
|
||||||
|
for( i in 3 downUntil 1 )
|
||||||
|
println(i)
|
||||||
|
>>> 3
|
||||||
|
>>> 2
|
||||||
|
>>> void
|
||||||
|
|
||||||
### Stepped ranges
|
### Stepped ranges
|
||||||
|
|
||||||
Use `step` to change the iteration increment. The range bounds still define membership,
|
Use `step` to change the iteration increment. The range bounds still define membership,
|
||||||
@ -80,9 +114,18 @@ so iteration ends when the next value is no longer in the range.
|
|||||||
|
|
||||||
assert( [1,3,5] == (1..5 step 2).toList() )
|
assert( [1,3,5] == (1..5 step 2).toList() )
|
||||||
assert( [1,3] == (1..<5 step 2).toList() )
|
assert( [1,3] == (1..<5 step 2).toList() )
|
||||||
|
assert( [5,3,1] == (5 downTo 1 step 2).toList() )
|
||||||
assert( ['a','c','e'] == ('a'..'e' step 2).toList() )
|
assert( ['a','c','e'] == ('a'..'e' step 2).toList() )
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
Descending ranges still use a positive `step`; the direction comes from
|
||||||
|
`downTo` / `downUntil`:
|
||||||
|
|
||||||
|
assert( ['e','c','a'] == ('e' downTo 'a' step 2).toList() )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
A negative step with `downTo` / `downUntil` is invalid.
|
||||||
|
|
||||||
Real ranges require an explicit step:
|
Real ranges require an explicit step:
|
||||||
|
|
||||||
assert( [0,0.25,0.5,0.75,1.0] == (0.0..1.0 step 0.25).toList() )
|
assert( [0,0.25,0.5,0.75,1.0] == (0.0..1.0 step 0.25).toList() )
|
||||||
@ -119,6 +162,7 @@ Exclusive end char ranges are supported too:
|
|||||||
|-----------------|------------------------------|---------------|
|
|-----------------|------------------------------|---------------|
|
||||||
| contains(other) | used in `in` | Range, or Any |
|
| contains(other) | used in `in` | Range, or Any |
|
||||||
| isEndInclusive | true for '..' | Bool |
|
| isEndInclusive | true for '..' | Bool |
|
||||||
|
| isDescending | true for `downTo`/`downUntil`| 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 |
|
||||||
| step | explicit iteration step | Any? |
|
| step | explicit iteration step | Any? |
|
||||||
|
|||||||
@ -50,8 +50,10 @@ Primary sources used: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/{Parser,T
|
|||||||
- Range literals:
|
- Range literals:
|
||||||
- inclusive: `a..b`
|
- inclusive: `a..b`
|
||||||
- exclusive end: `a..<b`
|
- exclusive end: `a..<b`
|
||||||
|
- descending inclusive: `a downTo b`
|
||||||
|
- descending exclusive end: `a downUntil b`
|
||||||
- open-ended forms are supported (`a..`, `..b`, `..`).
|
- open-ended forms are supported (`a..`, `..b`, `..`).
|
||||||
- optional step: `a..b step 2`
|
- optional step: `a..b step 2`, `a downTo b step 2`
|
||||||
- Lambda literal:
|
- Lambda literal:
|
||||||
- with params: `{ x, y -> x + y }`
|
- with params: `{ x, y -> x + y }`
|
||||||
- implicit `it`: `{ it + 1 }`
|
- implicit `it`: `{ it + 1 }`
|
||||||
|
|||||||
@ -1359,6 +1359,41 @@ size and index access, like lists:
|
|||||||
"total letters: "+letters
|
"total letters: "+letters
|
||||||
>>> "total letters: 10"
|
>>> "total letters: 10"
|
||||||
|
|
||||||
|
When you need a counting loop that goes backwards, use an explicit descending
|
||||||
|
range:
|
||||||
|
|
||||||
|
var sum = 0
|
||||||
|
for( i in 5 downTo 1 ) {
|
||||||
|
sum += i
|
||||||
|
}
|
||||||
|
sum
|
||||||
|
>>> 15
|
||||||
|
|
||||||
|
If the lower bound should be excluded, use `downUntil`:
|
||||||
|
|
||||||
|
val xs = []
|
||||||
|
for( i in 5 downUntil 1 ) {
|
||||||
|
xs.add(i)
|
||||||
|
}
|
||||||
|
xs
|
||||||
|
>>> [5,4,3,2]
|
||||||
|
|
||||||
|
This is intentionally explicit: `5..1` is an empty ascending range, not an
|
||||||
|
implicit reverse loop.
|
||||||
|
|
||||||
|
Descending loops also support `step`:
|
||||||
|
|
||||||
|
val xs = []
|
||||||
|
for( i in 10 downTo 1 step 3 ) {
|
||||||
|
xs.add(i)
|
||||||
|
}
|
||||||
|
xs
|
||||||
|
>>> [10,7,4,1]
|
||||||
|
|
||||||
|
For descending ranges, `step` stays positive. The direction comes from
|
||||||
|
`downTo` / `downUntil`, so `10 downTo 1 step 3` is valid, while
|
||||||
|
`10 downTo 1 step -3` is an error.
|
||||||
|
|
||||||
For loop support breaks the same as while loops above:
|
For loop support breaks the same as while loops above:
|
||||||
|
|
||||||
fun search(haystack, needle) {
|
fun search(haystack, needle) {
|
||||||
@ -1488,6 +1523,14 @@ It could be open and closed:
|
|||||||
assert( 5 !in (1..<5) )
|
assert( 5 !in (1..<5) )
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
Descending ranges are explicit too:
|
||||||
|
|
||||||
|
(5 downTo 1).toList()
|
||||||
|
>>> [5,4,3,2,1]
|
||||||
|
|
||||||
|
(5 downUntil 1).toList()
|
||||||
|
>>> [5,4,3,2]
|
||||||
|
|
||||||
Ranges could be inside other ranges:
|
Ranges could be inside other ranges:
|
||||||
|
|
||||||
assert( (2..3) in (1..10) )
|
assert( (2..3) in (1..10) )
|
||||||
@ -1505,6 +1548,14 @@ and you can use ranges in for-loops:
|
|||||||
>>> b
|
>>> b
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
Descending character ranges work the same way:
|
||||||
|
|
||||||
|
for( ch in 'e' downTo 'a' step 2 ) println(ch)
|
||||||
|
>>> e
|
||||||
|
>>> c
|
||||||
|
>>> a
|
||||||
|
>>> void
|
||||||
|
|
||||||
See [Ranges](Range.md) for detailed documentation on it.
|
See [Ranges](Range.md) for detailed documentation on it.
|
||||||
|
|
||||||
# Time routines
|
# Time routines
|
||||||
|
|||||||
@ -3058,9 +3058,10 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.DOTDOT, Token.Type.DOTDOTLT -> {
|
Token.Type.DOTDOT, Token.Type.DOTDOTLT, Token.Type.DOWNTO, Token.Type.DOWNUNTIL -> {
|
||||||
// range operator
|
// range operator
|
||||||
val isEndInclusive = t.type == Token.Type.DOTDOT
|
val isEndInclusive = t.type == Token.Type.DOTDOT || t.type == Token.Type.DOWNTO
|
||||||
|
val isDescending = t.type == Token.Type.DOWNTO || t.type == Token.Type.DOWNUNTIL
|
||||||
val left = operand
|
val left = operand
|
||||||
// if it is an open end range, then the end of line could be here that we do not want
|
// if it is an open end range, then the end of line could be here that we do not want
|
||||||
// to skip in parseExpression:
|
// to skip in parseExpression:
|
||||||
@ -3078,12 +3079,19 @@ class Compiler(
|
|||||||
val lConst = constIntValueOrNull(left)
|
val lConst = constIntValueOrNull(left)
|
||||||
val rConst = constIntValueOrNull(rightRef)
|
val rConst = constIntValueOrNull(rightRef)
|
||||||
if (lConst != null && rConst != null) {
|
if (lConst != null && rConst != null) {
|
||||||
operand = ConstRef(ObjRange(ObjInt.of(lConst), ObjInt.of(rConst), isEndInclusive).asReadonly)
|
operand = ConstRef(
|
||||||
|
ObjRange(
|
||||||
|
ObjInt.of(lConst),
|
||||||
|
ObjInt.of(rConst),
|
||||||
|
isEndInclusive,
|
||||||
|
isDescending = isDescending
|
||||||
|
).asReadonly
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
operand = RangeRef(left, rightRef, isEndInclusive)
|
operand = RangeRef(left, rightRef, isEndInclusive, isDescending = isDescending)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
operand = RangeRef(left, rightRef, isEndInclusive)
|
operand = RangeRef(left, rightRef, isEndInclusive, isDescending = isDescending)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3098,7 +3106,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
val leftRef = range.start?.takeUnless { it.isNull }?.let { ConstRef(it.asReadonly) }
|
val leftRef = range.start?.takeUnless { it.isNull }?.let { ConstRef(it.asReadonly) }
|
||||||
val rightRef = range.end?.takeUnless { it.isNull }?.let { ConstRef(it.asReadonly) }
|
val rightRef = range.end?.takeUnless { it.isNull }?.let { ConstRef(it.asReadonly) }
|
||||||
RangeRef(leftRef, rightRef, range.isEndInclusive)
|
RangeRef(leftRef, rightRef, range.isEndInclusive, isDescending = range.isDescending)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
@ -3108,7 +3116,13 @@ class Compiler(
|
|||||||
if (rangeRef.step != null) throw ScriptError(t.pos, "step is already specified for this range")
|
if (rangeRef.step != null) throw ScriptError(t.pos, "step is already specified for this range")
|
||||||
val stepExpr = parseExpression() ?: throw ScriptError(t.pos, "Expected step expression")
|
val stepExpr = parseExpression() ?: throw ScriptError(t.pos, "Expected step expression")
|
||||||
val stepRef = StatementRef(stepExpr)
|
val stepRef = StatementRef(stepExpr)
|
||||||
operand = RangeRef(rangeRef.left, rangeRef.right, rangeRef.isEndInclusive, stepRef)
|
operand = RangeRef(
|
||||||
|
rangeRef.left,
|
||||||
|
rangeRef.right,
|
||||||
|
rangeRef.isEndInclusive,
|
||||||
|
isDescending = rangeRef.isDescending,
|
||||||
|
step = stepRef
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.LBRACE, Token.Type.NULL_COALESCE_BLOCKINVOKE -> {
|
Token.Type.LBRACE, Token.Type.NULL_COALESCE_BLOCKINVOKE -> {
|
||||||
@ -7828,15 +7842,23 @@ class Compiler(
|
|||||||
if (range.step != null && !range.step.isNull) return null
|
if (range.step != null && !range.step.isNull) return null
|
||||||
val start = range.start?.toLong() ?: return null
|
val start = range.start?.toLong() ?: return null
|
||||||
val end = range.end?.toLong() ?: return null
|
val end = range.end?.toLong() ?: return null
|
||||||
val endExclusive = if (range.isEndInclusive) end + 1 else end
|
val stopBoundary = if (range.isDescending) {
|
||||||
return ConstIntRange(start, endExclusive)
|
if (range.isEndInclusive) end - 1 else end
|
||||||
|
} else {
|
||||||
|
if (range.isEndInclusive) end + 1 else end
|
||||||
|
}
|
||||||
|
return ConstIntRange(start, stopBoundary, range.isDescending)
|
||||||
}
|
}
|
||||||
is RangeRef -> {
|
is RangeRef -> {
|
||||||
if (ref.step != null) return null
|
if (ref.step != null) return null
|
||||||
val start = constIntValueOrNull(ref.left) ?: return null
|
val start = constIntValueOrNull(ref.left) ?: return null
|
||||||
val end = constIntValueOrNull(ref.right) ?: return null
|
val end = constIntValueOrNull(ref.right) ?: return null
|
||||||
val endExclusive = if (ref.isEndInclusive) end + 1 else end
|
val stopBoundary = if (ref.isDescending) {
|
||||||
return ConstIntRange(start, endExclusive)
|
if (ref.isEndInclusive) end - 1 else end
|
||||||
|
} else {
|
||||||
|
if (ref.isEndInclusive) end + 1 else end
|
||||||
|
}
|
||||||
|
return ConstIntRange(start, stopBoundary, ref.isDescending)
|
||||||
}
|
}
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
|
|||||||
@ -448,6 +448,8 @@ private class Parser(fromPos: Pos, private val interpolationEnabled: Boolean = t
|
|||||||
"is" -> Token("is", from, Token.Type.IS)
|
"is" -> Token("is", from, Token.Type.IS)
|
||||||
"by" -> Token("by", from, Token.Type.BY)
|
"by" -> Token("by", from, Token.Type.BY)
|
||||||
"step" -> Token("step", from, Token.Type.STEP)
|
"step" -> Token("step", from, Token.Type.STEP)
|
||||||
|
"downTo" -> Token("downTo", from, Token.Type.DOWNTO)
|
||||||
|
"downUntil" -> Token("downUntil", from, Token.Type.DOWNUNTIL)
|
||||||
"object" -> Token("object", from, Token.Type.OBJECT)
|
"object" -> Token("object", from, Token.Type.OBJECT)
|
||||||
"as" -> {
|
"as" -> {
|
||||||
// support both `as` and tight `as?` without spaces
|
// support both `as` and tight `as?` without spaces
|
||||||
|
|||||||
@ -36,7 +36,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
|||||||
PLUS, MINUS, STAR, SLASH, PERCENT,
|
PLUS, MINUS, STAR, SLASH, PERCENT,
|
||||||
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN, IFNULLASSIGN,
|
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN, IFNULLASSIGN,
|
||||||
PLUS2, MINUS2,
|
PLUS2, MINUS2,
|
||||||
IN, NOTIN, IS, NOTIS, BY, STEP,
|
IN, NOTIN, IS, NOTIS, BY, STEP, DOWNTO, DOWNUNTIL,
|
||||||
EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ, MATCH, NOTMATCH,
|
EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ, MATCH, NOTMATCH,
|
||||||
SHUTTLE,
|
SHUTTLE,
|
||||||
AND, BITAND, OR, BITOR, BITXOR, NOT, BITNOT, DOT, ARROW, EQARROW, QUESTION, COLONCOLON,
|
AND, BITAND, OR, BITOR, BITXOR, NOT, BITNOT, DOT, ARROW, EQARROW, QUESTION, COLONCOLON,
|
||||||
|
|||||||
@ -3617,6 +3617,9 @@ class BytecodeCompiler(
|
|||||||
val inclusiveSlot = allocSlot()
|
val inclusiveSlot = allocSlot()
|
||||||
val inclusiveId = builder.addConst(BytecodeConst.Bool(ref.isEndInclusive))
|
val inclusiveId = builder.addConst(BytecodeConst.Bool(ref.isEndInclusive))
|
||||||
builder.emit(Opcode.CONST_BOOL, inclusiveId, inclusiveSlot)
|
builder.emit(Opcode.CONST_BOOL, inclusiveId, inclusiveSlot)
|
||||||
|
val descendingSlot = allocSlot()
|
||||||
|
val descendingId = builder.addConst(BytecodeConst.Bool(ref.isDescending))
|
||||||
|
builder.emit(Opcode.CONST_BOOL, descendingId, descendingSlot)
|
||||||
val stepSlot = if (ref.step != null) {
|
val stepSlot = if (ref.step != null) {
|
||||||
val step = compileRefWithFallback(ref.step, null, Pos.builtIn) ?: return null
|
val step = compileRefWithFallback(ref.step, null, Pos.builtIn) ?: return null
|
||||||
ensureObjSlot(step).slot
|
ensureObjSlot(step).slot
|
||||||
@ -3627,7 +3630,7 @@ class BytecodeCompiler(
|
|||||||
slot
|
slot
|
||||||
}
|
}
|
||||||
val dst = allocSlot()
|
val dst = allocSlot()
|
||||||
builder.emit(Opcode.MAKE_RANGE, startSlot, endSlot, inclusiveSlot, stepSlot, dst)
|
builder.emit(Opcode.MAKE_RANGE, startSlot, endSlot, inclusiveSlot, descendingSlot, stepSlot, dst)
|
||||||
updateSlotType(dst, SlotType.OBJ)
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
slotObjClass[dst] = ObjRange.type
|
slotObjClass[dst] = ObjRange.type
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
@ -6244,11 +6247,14 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
val iSlot = loopSlotId
|
val iSlot = loopSlotId
|
||||||
val endSlot = allocSlot()
|
val endSlot = allocSlot()
|
||||||
|
val descendingSlot = allocSlot()
|
||||||
if (range != null) {
|
if (range != null) {
|
||||||
val startId = builder.addConst(BytecodeConst.IntVal(range.start))
|
val startId = builder.addConst(BytecodeConst.IntVal(range.start))
|
||||||
val endId = builder.addConst(BytecodeConst.IntVal(range.endExclusive))
|
val endId = builder.addConst(BytecodeConst.IntVal(range.stopBoundary))
|
||||||
|
val descendingId = builder.addConst(BytecodeConst.Bool(range.isDescending))
|
||||||
builder.emit(Opcode.CONST_INT, startId, iSlot)
|
builder.emit(Opcode.CONST_INT, startId, iSlot)
|
||||||
builder.emit(Opcode.CONST_INT, endId, endSlot)
|
builder.emit(Opcode.CONST_INT, endId, endSlot)
|
||||||
|
builder.emit(Opcode.CONST_BOOL, descendingId, descendingSlot)
|
||||||
updateSlotType(iSlot, SlotType.INT)
|
updateSlotType(iSlot, SlotType.INT)
|
||||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||||
} else {
|
} else {
|
||||||
@ -6258,9 +6264,15 @@ class BytecodeCompiler(
|
|||||||
val startValue = compileRef(left) ?: return null
|
val startValue = compileRef(left) ?: return null
|
||||||
val endValue = compileRef(right) ?: return null
|
val endValue = compileRef(right) ?: return null
|
||||||
if (startValue.type != SlotType.INT || endValue.type != SlotType.INT) return null
|
if (startValue.type != SlotType.INT || endValue.type != SlotType.INT) return null
|
||||||
|
val descendingId = builder.addConst(BytecodeConst.Bool(rangeRef.isDescending))
|
||||||
emitMove(startValue, iSlot)
|
emitMove(startValue, iSlot)
|
||||||
emitMove(endValue, endSlot)
|
emitMove(endValue, endSlot)
|
||||||
|
builder.emit(Opcode.CONST_BOOL, descendingId, descendingSlot)
|
||||||
|
if (rangeRef.isDescending) {
|
||||||
if (rangeRef.isEndInclusive) {
|
if (rangeRef.isEndInclusive) {
|
||||||
|
builder.emit(Opcode.DEC_INT, endSlot)
|
||||||
|
}
|
||||||
|
} else if (rangeRef.isEndInclusive) {
|
||||||
builder.emit(Opcode.INC_INT, endSlot)
|
builder.emit(Opcode.INC_INT, endSlot)
|
||||||
}
|
}
|
||||||
updateSlotType(iSlot, SlotType.INT)
|
updateSlotType(iSlot, SlotType.INT)
|
||||||
@ -6270,7 +6282,7 @@ class BytecodeCompiler(
|
|||||||
val rangeValue = compileRef(rangeLocal) ?: return null
|
val rangeValue = compileRef(rangeLocal) ?: return null
|
||||||
val rangeObj = ensureObjSlot(rangeValue)
|
val rangeObj = ensureObjSlot(rangeValue)
|
||||||
val okSlot = allocSlot()
|
val okSlot = allocSlot()
|
||||||
builder.emit(Opcode.RANGE_INT_BOUNDS, rangeObj.slot, iSlot, endSlot, okSlot)
|
builder.emit(Opcode.RANGE_INT_BOUNDS, rangeObj.slot, iSlot, endSlot, descendingSlot, okSlot)
|
||||||
val badRangeLabel = builder.label()
|
val badRangeLabel = builder.label()
|
||||||
builder.emit(
|
builder.emit(
|
||||||
Opcode.JMP_IF_FALSE,
|
Opcode.JMP_IF_FALSE,
|
||||||
@ -6294,14 +6306,7 @@ class BytecodeCompiler(
|
|||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
val doneLabel = builder.label()
|
val doneLabel = builder.label()
|
||||||
builder.mark(loopLabel)
|
builder.mark(loopLabel)
|
||||||
builder.emit(
|
emitIntForLoopCheck(iSlot, endSlot, descendingSlot, endLabel)
|
||||||
Opcode.JMP_IF_GTE_INT,
|
|
||||||
listOf(
|
|
||||||
CmdBuilder.Operand.IntVal(iSlot),
|
|
||||||
CmdBuilder.Operand.IntVal(endSlot),
|
|
||||||
CmdBuilder.Operand.LabelRef(endLabel)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
updateSlotType(iSlot, SlotType.INT)
|
updateSlotType(iSlot, SlotType.INT)
|
||||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||||
loopStack.addLast(
|
loopStack.addLast(
|
||||||
@ -6324,7 +6329,7 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot!!)
|
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot!!)
|
||||||
}
|
}
|
||||||
builder.mark(continueLabel)
|
builder.mark(continueLabel)
|
||||||
builder.emit(Opcode.INC_INT, iSlot)
|
emitIntForLoopStep(iSlot, descendingSlot)
|
||||||
if (hasRealWiden) {
|
if (hasRealWiden) {
|
||||||
emitLoopRealCoercions(realWidenSlots)
|
emitLoopRealCoercions(realWidenSlots)
|
||||||
}
|
}
|
||||||
@ -6377,14 +6382,7 @@ class BytecodeCompiler(
|
|||||||
val continueLabel = builder.label()
|
val continueLabel = builder.label()
|
||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
builder.mark(loopLabel)
|
builder.mark(loopLabel)
|
||||||
builder.emit(
|
emitIntForLoopCheck(iSlot, endSlot, descendingSlot, endLabel)
|
||||||
Opcode.JMP_IF_GTE_INT,
|
|
||||||
listOf(
|
|
||||||
CmdBuilder.Operand.IntVal(iSlot),
|
|
||||||
CmdBuilder.Operand.IntVal(endSlot),
|
|
||||||
CmdBuilder.Operand.LabelRef(endLabel)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
updateSlotType(iSlot, SlotType.INT)
|
updateSlotType(iSlot, SlotType.INT)
|
||||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||||
loopStack.addLast(
|
loopStack.addLast(
|
||||||
@ -6407,7 +6405,7 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot!!)
|
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot!!)
|
||||||
}
|
}
|
||||||
builder.mark(continueLabel)
|
builder.mark(continueLabel)
|
||||||
builder.emit(Opcode.INC_INT, iSlot)
|
emitIntForLoopStep(iSlot, descendingSlot)
|
||||||
if (hasRealWiden) {
|
if (hasRealWiden) {
|
||||||
emitLoopRealCoercions(realWidenSlots)
|
emitLoopRealCoercions(realWidenSlots)
|
||||||
}
|
}
|
||||||
@ -8719,11 +8717,59 @@ class BytecodeCompiler(
|
|||||||
val end = range.end as? ObjInt ?: return null
|
val end = range.end as? ObjInt ?: return null
|
||||||
val left = ConstRef(start.asReadonly)
|
val left = ConstRef(start.asReadonly)
|
||||||
val right = ConstRef(end.asReadonly)
|
val right = ConstRef(end.asReadonly)
|
||||||
return RangeRef(left, right, range.isEndInclusive)
|
return RangeRef(left, right, range.isEndInclusive, isDescending = range.isDescending)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun emitIntForLoopCheck(iSlot: Int, stopSlot: Int, descendingSlot: Int, endLabel: CmdBuilder.Label) {
|
||||||
|
val descendingLabel = builder.label()
|
||||||
|
val afterCheckLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(
|
||||||
|
CmdBuilder.Operand.IntVal(descendingSlot),
|
||||||
|
CmdBuilder.Operand.LabelRef(descendingLabel)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_GTE_INT,
|
||||||
|
listOf(
|
||||||
|
CmdBuilder.Operand.IntVal(iSlot),
|
||||||
|
CmdBuilder.Operand.IntVal(stopSlot),
|
||||||
|
CmdBuilder.Operand.LabelRef(endLabel)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(afterCheckLabel)))
|
||||||
|
builder.mark(descendingLabel)
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_LTE_INT,
|
||||||
|
listOf(
|
||||||
|
CmdBuilder.Operand.IntVal(iSlot),
|
||||||
|
CmdBuilder.Operand.IntVal(stopSlot),
|
||||||
|
CmdBuilder.Operand.LabelRef(endLabel)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
builder.mark(afterCheckLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emitIntForLoopStep(iSlot: Int, descendingSlot: Int) {
|
||||||
|
val descendingLabel = builder.label()
|
||||||
|
val afterStepLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(
|
||||||
|
CmdBuilder.Operand.IntVal(descendingSlot),
|
||||||
|
CmdBuilder.Operand.LabelRef(descendingLabel)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.INC_INT, iSlot)
|
||||||
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(afterStepLabel)))
|
||||||
|
builder.mark(descendingLabel)
|
||||||
|
builder.emit(Opcode.DEC_INT, iSlot)
|
||||||
|
builder.mark(afterStepLabel)
|
||||||
|
}
|
||||||
|
|
||||||
private fun extractRangeFromLocal(source: Statement): RangeRef? {
|
private fun extractRangeFromLocal(source: Statement): RangeRef? {
|
||||||
val target = if (source is BytecodeStatement) source.original else source
|
val target = if (source is BytecodeStatement) source.original else source
|
||||||
val expr = target as? ExpressionStatement ?: return null
|
val expr = target as? ExpressionStatement ?: return null
|
||||||
|
|||||||
@ -147,7 +147,7 @@ class CmdBuilder {
|
|||||||
Opcode.CHECK_IS, Opcode.MAKE_QUALIFIED_VIEW ->
|
Opcode.CHECK_IS, Opcode.MAKE_QUALIFIED_VIEW ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.RANGE_INT_BOUNDS ->
|
Opcode.RANGE_INT_BOUNDS ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.RET_LABEL, Opcode.THROW ->
|
Opcode.RET_LABEL, Opcode.THROW ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.RESOLVE_SCOPE_SLOT ->
|
Opcode.RESOLVE_SCOPE_SLOT ->
|
||||||
@ -228,7 +228,7 @@ class CmdBuilder {
|
|||||||
Opcode.SET_INDEX ->
|
Opcode.SET_INDEX ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.MAKE_RANGE ->
|
Opcode.MAKE_RANGE ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.LIST_LITERAL ->
|
Opcode.LIST_LITERAL ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.GET_MEMBER_SLOT ->
|
Opcode.GET_MEMBER_SLOT ->
|
||||||
@ -311,10 +311,10 @@ class CmdBuilder {
|
|||||||
}
|
}
|
||||||
Opcode.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
|
Opcode.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
|
||||||
Opcode.GET_OBJ_CLASS -> CmdGetObjClass(operands[0], operands[1])
|
Opcode.GET_OBJ_CLASS -> CmdGetObjClass(operands[0], operands[1])
|
||||||
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3])
|
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||||
Opcode.LOAD_THIS -> CmdLoadThis(operands[0])
|
Opcode.LOAD_THIS -> CmdLoadThis(operands[0])
|
||||||
Opcode.LOAD_THIS_VARIANT -> CmdLoadThisVariant(operands[0], operands[1])
|
Opcode.LOAD_THIS_VARIANT -> CmdLoadThisVariant(operands[0], operands[1])
|
||||||
Opcode.MAKE_RANGE -> CmdMakeRange(operands[0], operands[1], operands[2], operands[3], operands[4])
|
Opcode.MAKE_RANGE -> CmdMakeRange(operands[0], operands[1], operands[2], operands[3], operands[4], operands[5])
|
||||||
Opcode.CHECK_IS -> CmdCheckIs(operands[0], operands[1], operands[2])
|
Opcode.CHECK_IS -> CmdCheckIs(operands[0], operands[1], operands[2])
|
||||||
Opcode.ASSERT_IS -> CmdAssertIs(operands[0], operands[1])
|
Opcode.ASSERT_IS -> CmdAssertIs(operands[0], operands[1])
|
||||||
Opcode.MAKE_QUALIFIED_VIEW -> CmdMakeQualifiedView(operands[0], operands[1], operands[2])
|
Opcode.MAKE_QUALIFIED_VIEW -> CmdMakeQualifiedView(operands[0], operands[1], operands[2])
|
||||||
|
|||||||
@ -96,11 +96,12 @@ object CmdDisassembler {
|
|||||||
is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
||||||
is CmdAssertIs -> Opcode.ASSERT_IS to intArrayOf(cmd.objSlot, cmd.typeSlot)
|
is CmdAssertIs -> Opcode.ASSERT_IS to intArrayOf(cmd.objSlot, cmd.typeSlot)
|
||||||
is CmdMakeQualifiedView -> Opcode.MAKE_QUALIFIED_VIEW to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
is CmdMakeQualifiedView -> Opcode.MAKE_QUALIFIED_VIEW to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
||||||
is CmdRangeIntBounds -> Opcode.RANGE_INT_BOUNDS to intArrayOf(cmd.src, cmd.startSlot, cmd.endSlot, cmd.okSlot)
|
is CmdRangeIntBounds -> Opcode.RANGE_INT_BOUNDS to intArrayOf(cmd.src, cmd.startSlot, cmd.endSlot, cmd.descendingSlot, cmd.okSlot)
|
||||||
is CmdMakeRange -> Opcode.MAKE_RANGE to intArrayOf(
|
is CmdMakeRange -> Opcode.MAKE_RANGE to intArrayOf(
|
||||||
cmd.startSlot,
|
cmd.startSlot,
|
||||||
cmd.endSlot,
|
cmd.endSlot,
|
||||||
cmd.inclusiveSlot,
|
cmd.inclusiveSlot,
|
||||||
|
cmd.descendingSlot,
|
||||||
cmd.stepSlot,
|
cmd.stepSlot,
|
||||||
cmd.dst
|
cmd.dst
|
||||||
)
|
)
|
||||||
|
|||||||
@ -273,6 +273,7 @@ class CmdMakeRange(
|
|||||||
internal val startSlot: Int,
|
internal val startSlot: Int,
|
||||||
internal val endSlot: Int,
|
internal val endSlot: Int,
|
||||||
internal val inclusiveSlot: Int,
|
internal val inclusiveSlot: Int,
|
||||||
|
internal val descendingSlot: Int,
|
||||||
internal val stepSlot: Int,
|
internal val stepSlot: Int,
|
||||||
internal val dst: Int,
|
internal val dst: Int,
|
||||||
) : Cmd() {
|
) : Cmd() {
|
||||||
@ -280,9 +281,10 @@ class CmdMakeRange(
|
|||||||
val start = frame.slotToObj(startSlot)
|
val start = frame.slotToObj(startSlot)
|
||||||
val end = frame.slotToObj(endSlot)
|
val end = frame.slotToObj(endSlot)
|
||||||
val inclusive = frame.slotToObj(inclusiveSlot).toBool()
|
val inclusive = frame.slotToObj(inclusiveSlot).toBool()
|
||||||
|
val descending = frame.slotToObj(descendingSlot).toBool()
|
||||||
val stepObj = frame.slotToObj(stepSlot)
|
val stepObj = frame.slotToObj(stepSlot)
|
||||||
val step = if (stepObj.isNull) null else stepObj
|
val step = if (stepObj.isNull) null else stepObj
|
||||||
frame.storeObjResult(dst, ObjRange(start, end, isEndInclusive = inclusive, step = step))
|
frame.storeObjResult(dst, ObjRange(start, end, isEndInclusive = inclusive, isDescending = descending, step = step))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -430,6 +432,7 @@ class CmdRangeIntBounds(
|
|||||||
internal val src: Int,
|
internal val src: Int,
|
||||||
internal val startSlot: Int,
|
internal val startSlot: Int,
|
||||||
internal val endSlot: Int,
|
internal val endSlot: Int,
|
||||||
|
internal val descendingSlot: Int,
|
||||||
internal val okSlot: Int,
|
internal val okSlot: Int,
|
||||||
) : Cmd() {
|
) : Cmd() {
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
@ -439,10 +442,19 @@ class CmdRangeIntBounds(
|
|||||||
frame.setBool(okSlot, false)
|
frame.setBool(okSlot, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (range.isDescending) {
|
||||||
|
frame.setBool(okSlot, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
val start = (range.start as ObjInt).value
|
val start = (range.start as ObjInt).value
|
||||||
val end = (range.end as ObjInt).value
|
val end = (range.end as ObjInt).value
|
||||||
frame.setInt(startSlot, start)
|
frame.setInt(startSlot, start)
|
||||||
frame.setInt(endSlot, if (range.isEndInclusive) end + 1 else end)
|
frame.setInt(endSlot, if (range.isDescending) {
|
||||||
|
if (range.isEndInclusive) end - 1 else end
|
||||||
|
} else {
|
||||||
|
if (range.isEndInclusive) end + 1 else end
|
||||||
|
})
|
||||||
|
frame.setBool(descendingSlot, range.isDescending)
|
||||||
frame.setBool(okSlot, true)
|
frame.setBool(okSlot, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,7 +43,7 @@ private val fallbackKeywordIds = setOf(
|
|||||||
// declarations & modifiers
|
// declarations & modifiers
|
||||||
"fun", "fn", "class", "interface", "enum", "val", "var", "type", "import", "package",
|
"fun", "fn", "class", "interface", "enum", "val", "var", "type", "import", "package",
|
||||||
"abstract", "closed", "override", "public", "lazy", "dynamic",
|
"abstract", "closed", "override", "public", "lazy", "dynamic",
|
||||||
"private", "protected", "static", "open", "extern", "init", "get", "set", "by", "step",
|
"private", "protected", "static", "open", "extern", "init", "get", "set", "by", "step", "downTo", "downUntil",
|
||||||
// control flow and misc
|
// control flow and misc
|
||||||
"if", "else", "when", "while", "do", "for", "try", "catch", "finally",
|
"if", "else", "when", "while", "do", "for", "try", "catch", "finally",
|
||||||
"throw", "return", "break", "continue", "this", "null", "true", "false", "unset", "void"
|
"throw", "return", "break", "continue", "this", "null", "true", "false", "unset", "void"
|
||||||
@ -74,7 +74,7 @@ private fun kindOf(type: Type, value: String): HighlightKind? = when (type) {
|
|||||||
Type.COMMA, Type.SEMICOLON, Type.COLON -> HighlightKind.Punctuation
|
Type.COMMA, Type.SEMICOLON, Type.COLON -> HighlightKind.Punctuation
|
||||||
|
|
||||||
// textual control keywords
|
// textual control keywords
|
||||||
Type.IN, Type.NOTIN, Type.IS, Type.NOTIS, Type.AS, Type.ASNULL, Type.BY, Type.STEP, Type.OBJECT,
|
Type.IN, Type.NOTIN, Type.IS, Type.NOTIS, Type.AS, Type.ASNULL, Type.BY, Type.STEP, Type.DOWNTO, Type.DOWNUNTIL, Type.OBJECT,
|
||||||
Type.AND, Type.OR, Type.NOT -> HighlightKind.Keyword
|
Type.AND, Type.OR, Type.NOT -> HighlightKind.Keyword
|
||||||
|
|
||||||
// labels / annotations
|
// labels / annotations
|
||||||
|
|||||||
@ -28,6 +28,7 @@ class ObjRange(
|
|||||||
val start: Obj?,
|
val start: Obj?,
|
||||||
val end: Obj?,
|
val end: Obj?,
|
||||||
val isEndInclusive: Boolean,
|
val isEndInclusive: Boolean,
|
||||||
|
val isDescending: Boolean = false,
|
||||||
val step: Obj? = null
|
val step: Obj? = null
|
||||||
) : Obj() {
|
) : Obj() {
|
||||||
|
|
||||||
@ -39,15 +40,38 @@ class ObjRange(
|
|||||||
|
|
||||||
override suspend fun defaultToString(scope: Scope): ObjString {
|
override suspend fun defaultToString(scope: Scope): ObjString {
|
||||||
val result = StringBuilder()
|
val result = StringBuilder()
|
||||||
result.append("${start?.inspect(scope) ?: '∞'} ..")
|
result.append(start?.inspect(scope) ?: "∞")
|
||||||
|
when {
|
||||||
|
isDescending && isEndInclusive -> result.append(" downTo ")
|
||||||
|
isDescending && !isEndInclusive -> result.append(" downUntil ")
|
||||||
|
else -> {
|
||||||
|
result.append(" ..")
|
||||||
if (!isEndInclusive) result.append('<')
|
if (!isEndInclusive) result.append('<')
|
||||||
result.append(" ${end?.inspect(scope) ?: '∞'}")
|
result.append(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.append(end?.inspect(scope) ?: "∞")
|
||||||
if (hasExplicitStep) {
|
if (hasExplicitStep) {
|
||||||
result.append(" step ${step?.inspect(scope)}")
|
result.append(" step ${step?.inspect(scope)}")
|
||||||
}
|
}
|
||||||
return ObjString(result.toString())
|
return ObjString(result.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class NormalizedLowerBound(val value: Obj, val inclusive: Boolean)
|
||||||
|
private data class NormalizedUpperBound(val value: Obj, val inclusive: Boolean)
|
||||||
|
|
||||||
|
private fun normalizedLowerBound(): NormalizedLowerBound? =
|
||||||
|
when {
|
||||||
|
isDescending -> end?.takeUnless { it.isNull }?.let { NormalizedLowerBound(it, isEndInclusive) }
|
||||||
|
else -> start?.takeUnless { it.isNull }?.let { NormalizedLowerBound(it, true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun normalizedUpperBound(): NormalizedUpperBound? =
|
||||||
|
when {
|
||||||
|
isDescending -> start?.takeUnless { it.isNull }?.let { NormalizedUpperBound(it, true) }
|
||||||
|
else -> end?.takeUnless { it.isNull }?.let { NormalizedUpperBound(it, isEndInclusive) }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IF end is open (null/ObjNull), returns null
|
* IF end is open (null/ObjNull), returns null
|
||||||
* Otherwise, return correct value for the exclusive end
|
* Otherwise, return correct value for the exclusive end
|
||||||
@ -74,29 +98,21 @@ class ObjRange(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun containsRange(scope: Scope, other: ObjRange): Boolean {
|
suspend fun containsRange(scope: Scope, other: ObjRange): Boolean {
|
||||||
if (!isOpenStart) {
|
val ourLower = normalizedLowerBound()
|
||||||
// our start is not -∞ so other start should be GTE or is not contained:
|
val otherLower = other.normalizedLowerBound()
|
||||||
if (!other.isOpenStart && start!!.compareTo(scope, other.start!!) > 0) return false
|
if (ourLower != null) {
|
||||||
}
|
if (otherLower == null) return false
|
||||||
if (!isOpenEnd) {
|
val cmp = ourLower.value.compareTo(scope, otherLower.value)
|
||||||
// same with the end: if it is open, it can't be contained in ours:
|
if (cmp == -2 || cmp > 0) return false
|
||||||
if (other.isOpenEnd) return false
|
if (cmp == 0 && otherLower.inclusive && !ourLower.inclusive) return false
|
||||||
// both exists, now there could be 4 cases:
|
|
||||||
return when {
|
|
||||||
other.isEndInclusive && isEndInclusive ->
|
|
||||||
end!!.compareTo(scope, other.end!!) >= 0
|
|
||||||
|
|
||||||
!other.isEndInclusive && !isEndInclusive ->
|
|
||||||
end!!.compareTo(scope, other.end!!) >= 0
|
|
||||||
|
|
||||||
other.isEndInclusive && !isEndInclusive ->
|
|
||||||
end!!.compareTo(scope, other.end!!) > 0
|
|
||||||
|
|
||||||
!other.isEndInclusive && isEndInclusive ->
|
|
||||||
end!!.compareTo(scope, other.end!!) >= 0
|
|
||||||
|
|
||||||
else -> throw IllegalStateException("unknown comparison")
|
|
||||||
}
|
}
|
||||||
|
val ourUpper = normalizedUpperBound()
|
||||||
|
val otherUpper = other.normalizedUpperBound()
|
||||||
|
if (ourUpper != null) {
|
||||||
|
if (otherUpper == null) return false
|
||||||
|
val cmp = ourUpper.value.compareTo(scope, otherUpper.value)
|
||||||
|
if (cmp == -2 || cmp < 0) return false
|
||||||
|
if (cmp == 0 && otherUpper.inclusive && !ourUpper.inclusive) return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -108,35 +124,38 @@ class ObjRange(
|
|||||||
|
|
||||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||||
if (start is ObjInt && end is ObjInt && other is ObjInt) {
|
if (start is ObjInt && end is ObjInt && other is ObjInt) {
|
||||||
val s = start.value
|
val lower = if (isDescending) end.value else start.value
|
||||||
val e = end.value
|
val upper = if (isDescending) start.value else end.value
|
||||||
val v = other.value
|
val v = other.value
|
||||||
if (v < s) return false
|
if (v < lower || v > upper) return false
|
||||||
return if (isEndInclusive) v <= e else v < e
|
return if (isDescending) v != lower || isEndInclusive else v != upper || isEndInclusive
|
||||||
}
|
}
|
||||||
if (start is ObjChar && end is ObjChar && other is ObjChar) {
|
if (start is ObjChar && end is ObjChar && other is ObjChar) {
|
||||||
val s = start.value
|
val lower = if (isDescending) end.value else start.value
|
||||||
val e = end.value
|
val upper = if (isDescending) start.value else end.value
|
||||||
val v = other.value
|
val v = other.value
|
||||||
if (v < s) return false
|
if (v < lower || v > upper) return false
|
||||||
return if (isEndInclusive) v <= e else v < e
|
return if (isDescending) v != lower || isEndInclusive else v != upper || isEndInclusive
|
||||||
}
|
}
|
||||||
if (start is ObjString && end is ObjString && other is ObjString) {
|
if (start is ObjString && end is ObjString && other is ObjString) {
|
||||||
val s = start.value
|
val lower = if (isDescending) end.value else start.value
|
||||||
val e = end.value
|
val upper = if (isDescending) start.value else end.value
|
||||||
val v = other.value
|
val v = other.value
|
||||||
if (v < s) return false
|
if (v < lower || v > upper) return false
|
||||||
return if (isEndInclusive) v <= e else v < e
|
return if (isDescending) v != lower || isEndInclusive else v != upper || isEndInclusive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOpenStart && isOpenEnd) return true
|
val lower = normalizedLowerBound()
|
||||||
if (!isOpenStart) {
|
val upper = normalizedUpperBound()
|
||||||
if (start!!.compareTo(scope, other) > 0) return false
|
if (lower == null && upper == null) return true
|
||||||
|
if (lower != null) {
|
||||||
|
val cmp = lower.value.compareTo(scope, other)
|
||||||
|
if (cmp == -2 || cmp > 0 || (!lower.inclusive && cmp == 0)) return false
|
||||||
}
|
}
|
||||||
if (!isOpenEnd) {
|
if (upper != null) {
|
||||||
val cmp = end!!.compareTo(scope, other)
|
val cmp = upper.value.compareTo(scope, other)
|
||||||
if (isEndInclusive && cmp < 0 || !isEndInclusive && cmp <= 0) return false
|
if (cmp == -2 || cmp < 0 || (!upper.inclusive && cmp == 0)) return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -153,7 +172,12 @@ class ObjRange(
|
|||||||
if (!hasExplicitStep && start is ObjInt && end is ObjInt) {
|
if (!hasExplicitStep && start is ObjInt && end is ObjInt) {
|
||||||
val s = start.value
|
val s = start.value
|
||||||
val e = end.value
|
val e = end.value
|
||||||
if (isEndInclusive) {
|
if (isDescending) {
|
||||||
|
val last = if (isEndInclusive) e else e + 1
|
||||||
|
for (i in s downTo last) {
|
||||||
|
if (!callback(ObjInt.of(i))) break
|
||||||
|
}
|
||||||
|
} else if (isEndInclusive) {
|
||||||
for (i in s..e) {
|
for (i in s..e) {
|
||||||
if (!callback(ObjInt.of(i))) break
|
if (!callback(ObjInt.of(i))) break
|
||||||
}
|
}
|
||||||
@ -165,7 +189,14 @@ class ObjRange(
|
|||||||
} else if (!hasExplicitStep && start is ObjChar && end is ObjChar) {
|
} else if (!hasExplicitStep && start is ObjChar && end is ObjChar) {
|
||||||
val s = start.value
|
val s = start.value
|
||||||
val e = end.value
|
val e = end.value
|
||||||
if (isEndInclusive) {
|
if (isDescending) {
|
||||||
|
var c = s.code
|
||||||
|
val last = if (isEndInclusive) e.code else e.code + 1
|
||||||
|
while (c >= last) {
|
||||||
|
if (!callback(ObjChar(c.toChar()))) break
|
||||||
|
c--
|
||||||
|
}
|
||||||
|
} else if (isEndInclusive) {
|
||||||
for (c in s..e) {
|
for (c in s..e) {
|
||||||
if (!callback(ObjChar(c))) break
|
if (!callback(ObjChar(c))) break
|
||||||
}
|
}
|
||||||
@ -184,6 +215,7 @@ class ObjRange(
|
|||||||
if (start == other.start &&
|
if (start == other.start &&
|
||||||
end == other.end &&
|
end == other.end &&
|
||||||
isEndInclusive == other.isEndInclusive &&
|
isEndInclusive == other.isEndInclusive &&
|
||||||
|
isDescending == other.isDescending &&
|
||||||
step == other.step
|
step == other.step
|
||||||
) 0 else -1
|
) 0 else -1
|
||||||
}
|
}
|
||||||
@ -194,6 +226,7 @@ class ObjRange(
|
|||||||
var result = start?.hashCode() ?: 0
|
var result = start?.hashCode() ?: 0
|
||||||
result = 31 * result + (end?.hashCode() ?: 0)
|
result = 31 * result + (end?.hashCode() ?: 0)
|
||||||
result = 31 * result + isEndInclusive.hashCode()
|
result = 31 * result + isEndInclusive.hashCode()
|
||||||
|
result = 31 * result + isDescending.hashCode()
|
||||||
result = 31 * result + (step?.hashCode() ?: 0)
|
result = 31 * result + (step?.hashCode() ?: 0)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -207,6 +240,7 @@ class ObjRange(
|
|||||||
if (start != other.start) return false
|
if (start != other.start) return false
|
||||||
if (end != other.end) return false
|
if (end != other.end) return false
|
||||||
if (isEndInclusive != other.isEndInclusive) return false
|
if (isEndInclusive != other.isEndInclusive) return false
|
||||||
|
if (isDescending != other.isDescending) return false
|
||||||
if (step != other.step) return false
|
if (step != other.step) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@ -264,6 +298,13 @@ class ObjRange(
|
|||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib",
|
||||||
getter = { thisAs<ObjRange>().isEndInclusive.toObj() }
|
getter = { thisAs<ObjRange>().isEndInclusive.toObj() }
|
||||||
)
|
)
|
||||||
|
addPropertyDoc(
|
||||||
|
name = "isDescending",
|
||||||
|
doc = "Whether the range iterates from the start bound down toward the end bound.",
|
||||||
|
type = type("lyng.Bool"),
|
||||||
|
moduleName = "lyng.stdlib",
|
||||||
|
getter = { thisAs<ObjRange>().isDescending.toObj() }
|
||||||
|
)
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "iterator",
|
name = "iterator",
|
||||||
doc = "Iterator over elements in this range (optimized for Int ranges).",
|
doc = "Iterator over elements in this range (optimized for Int ranges).",
|
||||||
@ -290,18 +331,22 @@ class ObjRange(
|
|||||||
if (startObj is Numeric && explicitStep !is Numeric) {
|
if (startObj is Numeric && explicitStep !is Numeric) {
|
||||||
scope.raiseIllegalState("Numeric range step must be numeric")
|
scope.raiseIllegalState("Numeric range step must be numeric")
|
||||||
}
|
}
|
||||||
|
if (isDescending) {
|
||||||
|
val sign = when (explicitStep) {
|
||||||
|
is ObjInt -> explicitStep.value.compareTo(0)
|
||||||
|
is Numeric -> explicitStep.doubleValue.compareTo(0.0)
|
||||||
|
else -> 1
|
||||||
|
}
|
||||||
|
if (sign < 0) scope.raiseIllegalState("Descending range step must be positive")
|
||||||
|
return explicitStep.negate(scope)
|
||||||
|
}
|
||||||
return explicitStep
|
return explicitStep
|
||||||
}
|
}
|
||||||
if (startObj is ObjInt) {
|
if (startObj is ObjInt) {
|
||||||
val cmp = if (end == null || end.isNull) 0 else startObj.compareTo(scope, end)
|
return ObjInt.of(if (isDescending) -1 else 1)
|
||||||
val dir = if (cmp >= 0) -1 else 1
|
|
||||||
return ObjInt.of(dir.toLong())
|
|
||||||
}
|
}
|
||||||
if (startObj is ObjChar) {
|
if (startObj is ObjChar) {
|
||||||
val endChar = end as? ObjChar
|
return ObjInt.of(if (isDescending) -1 else 1)
|
||||||
?: scope.raiseIllegalState("Char range requires Char end to infer step")
|
|
||||||
val dir = if (startObj.value >= endChar.value) -1 else 1
|
|
||||||
return ObjInt.of(dir.toLong())
|
|
||||||
}
|
}
|
||||||
if (startObj is ObjReal) {
|
if (startObj is ObjReal) {
|
||||||
scope.raiseIllegalState("Real range requires explicit step")
|
scope.raiseIllegalState("Real range requires explicit step")
|
||||||
|
|||||||
@ -1028,6 +1028,7 @@ class RangeRef(
|
|||||||
internal val left: ObjRef?,
|
internal val left: ObjRef?,
|
||||||
internal val right: ObjRef?,
|
internal val right: ObjRef?,
|
||||||
internal val isEndInclusive: Boolean,
|
internal val isEndInclusive: Boolean,
|
||||||
|
internal val isDescending: Boolean = false,
|
||||||
internal val step: ObjRef? = null
|
internal val step: ObjRef? = null
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled()
|
override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled()
|
||||||
|
|||||||
@ -77,7 +77,11 @@ class IfStatement(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ConstIntRange(val start: Long, val endExclusive: Long)
|
data class ConstIntRange(
|
||||||
|
val start: Long,
|
||||||
|
val stopBoundary: Long,
|
||||||
|
val isDescending: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
class ForInStatement(
|
class ForInStatement(
|
||||||
val loopVarName: String,
|
val loopVarName: String,
|
||||||
|
|||||||
@ -294,6 +294,18 @@ class ScriptTest {
|
|||||||
assertEquals(Token.Type.INT, tt[0].type)
|
assertEquals(Token.Type.INT, tt[0].type)
|
||||||
assertEquals(Token.Type.DOTDOTLT, tt[1].type)
|
assertEquals(Token.Type.DOTDOTLT, tt[1].type)
|
||||||
assertEquals(Token.Type.INT, tt[2].type)
|
assertEquals(Token.Type.INT, tt[2].type)
|
||||||
|
|
||||||
|
tt = parseLyng("5 downTo 4".toSource())
|
||||||
|
|
||||||
|
assertEquals(Token.Type.INT, tt[0].type)
|
||||||
|
assertEquals(Token.Type.DOWNTO, tt[1].type)
|
||||||
|
assertEquals(Token.Type.INT, tt[2].type)
|
||||||
|
|
||||||
|
tt = parseLyng("5 downUntil 4".toSource())
|
||||||
|
|
||||||
|
assertEquals(Token.Type.INT, tt[0].type)
|
||||||
|
assertEquals(Token.Type.DOWNUNTIL, tt[1].type)
|
||||||
|
assertEquals(Token.Type.INT, tt[2].type)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -1280,6 +1292,36 @@ class ScriptTest {
|
|||||||
assertTrue(convIndex > incIndex, "INT_TO_REAL should appear after INC_INT")
|
assertTrue(convIndex > incIndex, "INT_TO_REAL should appear after INC_INT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDescendingForLoopDisasm() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
fun countDown() {
|
||||||
|
var acc = 0
|
||||||
|
for (i in 5 downTo 1) {
|
||||||
|
acc += i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun countDownVar() {
|
||||||
|
var acc = 0
|
||||||
|
val r = 5 downTo 1
|
||||||
|
for (i in r) {
|
||||||
|
acc += i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
val constDisasm = scope.disassembleSymbol("countDown")
|
||||||
|
val varDisasm = scope.disassembleSymbol("countDownVar")
|
||||||
|
assertTrue("DEC_INT" in constDisasm, "expected DEC_INT in descending for-loop disasm")
|
||||||
|
assertTrue("JMP_IF_LTE_INT" in constDisasm, "expected JMP_IF_LTE_INT in descending for-loop disasm")
|
||||||
|
assertTrue("CALL_MEMBER_SLOT" !in constDisasm, "descending literal range should avoid iterator fallback")
|
||||||
|
assertTrue("DEC_INT" in varDisasm, "expected DEC_INT in descending range-variable for-loop disasm")
|
||||||
|
assertTrue("JMP_IF_LTE_INT" in varDisasm, "expected descending comparison in range-variable for-loop disasm")
|
||||||
|
assertTrue("CALL_MEMBER_SLOT" !in varDisasm, "descending range-variable loop should avoid iterator fallback")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIntClosedRangeInclusive() = runTest {
|
fun testIntClosedRangeInclusive() = runTest {
|
||||||
eval(
|
eval(
|
||||||
@ -3485,17 +3527,58 @@ class ScriptTest {
|
|||||||
fun testRangeStepIteration() = runTest {
|
fun testRangeStepIteration() = runTest {
|
||||||
val ints = eval("""(1..5 step 2).toList()""") as ObjList
|
val ints = eval("""(1..5 step 2).toList()""") as ObjList
|
||||||
assertEquals(listOf(1, 3, 5), ints.list.map { it.toInt() })
|
assertEquals(listOf(1, 3, 5), ints.list.map { it.toInt() })
|
||||||
|
val descending = eval("""(5 downTo 1).toList()""") as ObjList
|
||||||
|
assertEquals(listOf(5, 4, 3, 2, 1), descending.list.map { it.toInt() })
|
||||||
|
val descendingExclusive = eval("""(5 downUntil 1).toList()""") as ObjList
|
||||||
|
assertEquals(listOf(5, 4, 3, 2), descendingExclusive.list.map { it.toInt() })
|
||||||
|
val descendingStep = eval("""(10 downTo 1 step 3).toList()""") as ObjList
|
||||||
|
assertEquals(listOf(10, 7, 4, 1), descendingStep.list.map { it.toInt() })
|
||||||
|
val descendingChars = eval("""('e' downTo 'a' step 2).toList()""") as ObjList
|
||||||
|
assertEquals(listOf('e', 'c', 'a'), descendingChars.list.map { it.toString().single() })
|
||||||
val chars = eval("""('a'..'e' step 2).toList()""") as ObjList
|
val chars = eval("""('a'..'e' step 2).toList()""") as ObjList
|
||||||
assertEquals(listOf('a', 'c', 'e'), chars.list.map { it.toString().single() })
|
assertEquals(listOf('a', 'c', 'e'), chars.list.map { it.toString().single() })
|
||||||
val reals = eval("""(0.0..1.0 step 0.25).toList()""") as ObjList
|
val reals = eval("""(0.0..1.0 step 0.25).toList()""") as ObjList
|
||||||
assertEquals(listOf(0.0, 0.25, 0.5, 0.75, 1.0), reals.list.map { it.toDouble() })
|
assertEquals(listOf(0.0, 0.25, 0.5, 0.75, 1.0), reals.list.map { it.toDouble() })
|
||||||
val empty = eval("""(5..1 step 1).toList()""") as ObjList
|
val empty = eval("""(5..1 step 1).toList()""") as ObjList
|
||||||
assertEquals(0, empty.list.size)
|
assertEquals(0, empty.list.size)
|
||||||
|
val plainDescending = eval("""(5..1).toList()""") as ObjList
|
||||||
|
assertEquals(0, plainDescending.list.size)
|
||||||
val openEnd = eval("""(0.. step 1).take(3).toList()""") as ObjList
|
val openEnd = eval("""(0.. step 1).take(3).toList()""") as ObjList
|
||||||
assertEquals(listOf(0, 1, 2), openEnd.list.map { it.toInt() })
|
assertEquals(listOf(0, 1, 2), openEnd.list.map { it.toInt() })
|
||||||
|
assertEquals(
|
||||||
|
true,
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
val r = 10 downTo 1
|
||||||
|
r.isDescending && r.isEndInclusive && (10 in r) && (1 in r) && (0 !in r)
|
||||||
|
""".trimIndent()
|
||||||
|
).toBool()
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
true,
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
val r = 10 downUntil 1
|
||||||
|
r.isDescending && !r.isEndInclusive && (10 in r) && (1 !in r) && ((8 downTo 3) in r)
|
||||||
|
""".trimIndent()
|
||||||
|
).toBool()
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
15,
|
||||||
|
(eval(
|
||||||
|
"""
|
||||||
|
var s = 0
|
||||||
|
for (i in 5 downTo 1) s += i
|
||||||
|
s
|
||||||
|
""".trimIndent()
|
||||||
|
) as ObjInt).toInt()
|
||||||
|
)
|
||||||
assertFailsWith<ExecutionError> {
|
assertFailsWith<ExecutionError> {
|
||||||
eval("""(0.0..1.0).toList()""")
|
eval("""(0.0..1.0).toList()""")
|
||||||
}
|
}
|
||||||
|
assertFailsWith<ExecutionError> {
|
||||||
|
eval("""(5 downTo 1 step -1).toList()""")
|
||||||
|
}
|
||||||
assertFailsWith<ExecutionError> {
|
assertFailsWith<ExecutionError> {
|
||||||
eval("""(0..).toList()""")
|
eval("""(0..).toList()""")
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user