Add EfficientIterables.md documentation for Kotlin interop. Optimize ObjInt operations by integrating value caching and updating number operations. Introduce high-performance enumerate method for ObjList, ObjSet, ObjRange, and custom iterables. Update compiler and loop handling for improved enumeration logic.
This commit is contained in:
parent
5f3a54d08f
commit
3f235878c0
@ -44,6 +44,7 @@ and it is multithreaded on platforms supporting it (automatically, no code chang
|
||||
- [Language home](https://lynglang.com)
|
||||
- [introduction and tutorial](docs/tutorial.md) - start here please
|
||||
- [Testing and Assertions](docs/Testing.md)
|
||||
- [Efficient Iterables in Kotlin Interop](docs/EfficientIterables.md)
|
||||
- [Samples directory](docs/samples)
|
||||
- [Formatter (core + CLI + IDE)](docs/formatter.md)
|
||||
- [Books directory](docs)
|
||||
|
||||
@ -12,7 +12,7 @@ Is a [Iterable] with known `size`, a finite [Iterable]:
|
||||
(1)
|
||||
: `comparator(a,b)` should return -1 if `a < b`, +1 if `a > b` or zero.
|
||||
|
||||
See [List], [Set] and [Iterable]
|
||||
See [List], [Set], [Iterable] and [Efficient Iterables in Kotlin Interop](EfficientIterables.md)
|
||||
|
||||
[Iterable]: Iterable.md
|
||||
[List]: List.md
|
||||
|
||||
92
docs/EfficientIterables.md
Normal file
92
docs/EfficientIterables.md
Normal file
@ -0,0 +1,92 @@
|
||||
# Efficient Iterables in Kotlin Interop
|
||||
|
||||
Lyng provides high-performance iteration mechanisms that allow Kotlin-side code to interact with Lyng iterables efficiently and vice versa.
|
||||
|
||||
## 1. Enumerating Lyng Objects from Kotlin
|
||||
|
||||
To iterate over a Lyng object (like a `List`, `Set`, or `Range`) from Kotlin code, use the virtual `enumerate` method:
|
||||
|
||||
```kotlin
|
||||
val lyngList: Obj = ...
|
||||
lyngList.enumerate(scope) { item ->
|
||||
println("Processing $item")
|
||||
true // return true to continue, false to break
|
||||
}
|
||||
```
|
||||
|
||||
### Why it's efficient:
|
||||
- **Zero allocation**: Unlike traditional iterators, it doesn't create a `LyngIterator` object or any intermediate wrappers.
|
||||
- **Direct access**: Subclasses like `ObjList` override `enumerate` to iterate directly over their internal Kotlin collections.
|
||||
- **Reduced overhead**: It avoids multiple `invokeInstanceMethod` calls for `hasNext()` and `next()` on every step, which would normally involve dynamic dispatch and scope overhead.
|
||||
|
||||
## 2. Reactive Enumeration with Flow
|
||||
|
||||
If you prefer a reactive approach or need to integrate with Kotlin Coroutines flows, use `toFlow()`:
|
||||
|
||||
```kotlin
|
||||
lyngList.toFlow(scope).collect { item ->
|
||||
// ...
|
||||
}
|
||||
```
|
||||
*Note: `toFlow()` internally uses the Lyng iterator protocol (`iterator()`, `hasNext()`, `next()`), so it's slightly less efficient than `enumerate()` for performance-critical loops, but more idiomatic for flow-based processing.*
|
||||
|
||||
## 3. Creating Efficient Iterables for Lyng in Kotlin
|
||||
|
||||
When implementing a custom object in Kotlin that should be iterable in Lyng (e.g., usable in `for (x in myObj) { ... }`), follow these steps to ensure maximum performance.
|
||||
|
||||
### A. Inherit from `Obj` and use `ObjIterable`
|
||||
Ensure your object's class has `ObjIterable` as a parent so the Lyng compiler recognizes it as an iterable.
|
||||
|
||||
```kotlin
|
||||
class MyCollection(val items: List<Obj>) : Obj() {
|
||||
override val objClass = MyCollection.type
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("MyCollection", ObjIterable).apply {
|
||||
// Provide a Lyng-side iterator for compatibility with
|
||||
// manual iterator usage in Lyng scripts.
|
||||
// Using ObjKotlinObjIterator if items are already Obj instances:
|
||||
addFn("iterator") {
|
||||
ObjKotlinObjIterator(thisAs<MyCollection>().items.iterator())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### B. Override `enumerate` for Maximum Performance
|
||||
The Lyng compiler's `for` loops use the `enumerate` method. By overriding it in your Kotlin class, you provide a "fast path" for iteration.
|
||||
|
||||
```kotlin
|
||||
class MyCollection(val items: List<Obj>) : Obj() {
|
||||
// ...
|
||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
for (item in items) {
|
||||
// If callback returns false, it means 'break' was called in Lyng
|
||||
if (!callback(item)) break
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### C. Use `ObjInt.of()` for Numeric Data
|
||||
If your iterable contains integers, always use `ObjInt.of(Long)` instead of the `ObjInt(Long)` constructor. Lyng maintains a cache for small integers (-128 to 127), which significantly reduces object allocations and GC pressure during tight loops.
|
||||
|
||||
```kotlin
|
||||
// Efficiently creating an integer object
|
||||
val obj = ObjInt.of(42L)
|
||||
|
||||
// Or using extension methods which also use the cache:
|
||||
val obj2 = 42.toObj()
|
||||
val obj3 = 42L.toObj()
|
||||
```
|
||||
|
||||
#### Note on `toObj()` extensions:
|
||||
While `<reified T> T.toObj()` is convenient, using specific extensions like `Int.toObj()` or `Long.toObj()` is slightly more efficient as they use the `ObjInt` cache.
|
||||
|
||||
## 4. Summary of Best Practices
|
||||
|
||||
- **To Consume**: Use `enumerate(scope) { item -> ... true }`.
|
||||
- **To Implement**: Override `enumerate` in your `Obj` subclass.
|
||||
- **To Register**: Use `ObjIterable` (or `ObjCollection`) as a parent class in your `ObjClass` definition.
|
||||
- **To Optimize**: Use `ObjInt.of()` (or `.toObj()`) for all integer object allocations.
|
||||
@ -142,6 +142,8 @@ optional function applied to each item that must return result string for an ite
|
||||
|
||||
fun iterator(): Iterator
|
||||
|
||||
For high-performance Kotlin-side interop and custom iterable implementation details, see [Efficient Iterables in Kotlin Interop](EfficientIterables.md).
|
||||
|
||||
## Included in interfaces:
|
||||
|
||||
- [Collection], Array, [List]
|
||||
|
||||
@ -23,6 +23,8 @@ must throw `ObjIterationFinishedError`.
|
||||
|
||||
Iterators are returned when implementing [Iterable] interface.
|
||||
|
||||
For high-performance Kotlin-side interop and custom iterable implementation details, see [Efficient Iterables in Kotlin Interop](EfficientIterables.md).
|
||||
|
||||
## Implemented for classes:
|
||||
|
||||
- [List], [Range]
|
||||
|
||||
@ -2040,7 +2040,7 @@ class Compiler(
|
||||
var breakCaught = false
|
||||
|
||||
if (size > 0) {
|
||||
var current = runCatching { sourceObj.getAt(forContext, ObjInt(0)) }
|
||||
var current = runCatching { sourceObj.getAt(forContext, ObjInt.of(0)) }
|
||||
.getOrElse {
|
||||
throw ScriptError(
|
||||
tOp.pos,
|
||||
@ -2065,7 +2065,7 @@ class Compiler(
|
||||
throw lbe
|
||||
}
|
||||
if (++index >= size) break
|
||||
current = sourceObj.getAt(forContext, ObjInt(index.toLong()))
|
||||
current = sourceObj.getAt(forContext, ObjInt.of(index.toLong()))
|
||||
}
|
||||
}
|
||||
if (!breakCaught && elseStatement != null) {
|
||||
@ -2087,7 +2087,7 @@ class Compiler(
|
||||
var result: Obj = ObjVoid
|
||||
if (catchBreak) {
|
||||
for (i in start..<end) {
|
||||
loopVar.value = ObjInt(i)
|
||||
loopVar.value = ObjInt.of(i)
|
||||
try {
|
||||
result = body.execute(forScope)
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
@ -2100,7 +2100,7 @@ class Compiler(
|
||||
}
|
||||
} else {
|
||||
for (i in start..<end) {
|
||||
loopVar.value = ObjInt(i)
|
||||
loopVar.value = ObjInt.of(i)
|
||||
result = body.execute(forScope)
|
||||
}
|
||||
}
|
||||
@ -2112,38 +2112,33 @@ class Compiler(
|
||||
body: Statement, elseStatement: Statement?, label: String?,
|
||||
catchBreak: Boolean
|
||||
): Obj {
|
||||
val iterObj = sourceObj.invokeInstanceMethod(forScope, "iterator")
|
||||
var result: Obj = ObjVoid
|
||||
var completedNaturally = false
|
||||
try {
|
||||
while (iterObj.invokeInstanceMethod(forScope, "hasNext").toBool()) {
|
||||
if (catchBreak)
|
||||
try {
|
||||
loopVar.value = iterObj.invokeInstanceMethod(forScope, "next")
|
||||
result = body.execute(forScope)
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
if (lbe.doContinue) continue
|
||||
// premature finish, will trigger cancel in finally
|
||||
return lbe.result
|
||||
}
|
||||
throw lbe
|
||||
}
|
||||
else {
|
||||
loopVar.value = iterObj.invokeInstanceMethod(forScope, "next")
|
||||
var breakCaught = false
|
||||
sourceObj.enumerate(forScope) { item ->
|
||||
loopVar.value = item
|
||||
if (catchBreak) {
|
||||
try {
|
||||
result = body.execute(forScope)
|
||||
true
|
||||
} catch (lbe: LoopBreakContinueException) {
|
||||
if (lbe.label == label || lbe.label == null) {
|
||||
if (lbe.doContinue) true
|
||||
else {
|
||||
result = lbe.result
|
||||
breakCaught = true
|
||||
false
|
||||
}
|
||||
} else
|
||||
throw lbe
|
||||
}
|
||||
}
|
||||
completedNaturally = true
|
||||
return elseStatement?.execute(forScope) ?: result
|
||||
} finally {
|
||||
if (!completedNaturally) {
|
||||
// Best-effort cancellation on premature termination
|
||||
runCatching {
|
||||
iterObj.invokeInstanceMethod(forScope, "cancelIteration") { ObjVoid }
|
||||
}
|
||||
} else {
|
||||
result = body.execute(forScope)
|
||||
true
|
||||
}
|
||||
}
|
||||
return if (!breakCaught && elseStatement != null) {
|
||||
elseStatement.execute(forScope)
|
||||
} else result
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
|
||||
@ -127,6 +127,40 @@ open class Obj {
|
||||
return invokeInstanceMethod(scope, "contains", other).toBool()
|
||||
}
|
||||
|
||||
/**
|
||||
* Call [callback] for each element of this obj considering it provides [Iterator]
|
||||
* methods `hasNext` and `next`.
|
||||
*
|
||||
* IF callback returns false, iteration is stopped.
|
||||
*/
|
||||
open suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
val iterator = invokeInstanceMethod(scope, "iterator")
|
||||
val hasNext = iterator.getInstanceMethod(scope, "hasNext")
|
||||
val next = iterator.getInstanceMethod(scope, "next")
|
||||
var closeIt = false
|
||||
try {
|
||||
while (hasNext.invoke(scope, iterator).toBool()) {
|
||||
val nextValue = next.invoke(scope, iterator)
|
||||
val shouldContinue = try {
|
||||
callback(nextValue)
|
||||
} catch (e: Exception) {
|
||||
// iteration aborted due to exception in callback
|
||||
closeIt = true
|
||||
throw e
|
||||
}
|
||||
if (!shouldContinue) {
|
||||
closeIt = true
|
||||
break
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (closeIt) {
|
||||
// Best-effort cancel on premature termination
|
||||
iterator.invokeInstanceMethod(scope, "cancelIteration") { ObjVoid }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default toString implementation:
|
||||
*
|
||||
@ -444,8 +478,8 @@ open class Obj {
|
||||
is Obj -> obj
|
||||
is Double -> ObjReal(obj)
|
||||
is Float -> ObjReal(obj.toDouble())
|
||||
is Int -> ObjInt(obj.toLong())
|
||||
is Long -> ObjInt(obj)
|
||||
is Int -> ObjInt.of(obj.toLong())
|
||||
is Long -> ObjInt.of(obj)
|
||||
is String -> ObjString(obj)
|
||||
is CharSequence -> ObjString(obj.toString())
|
||||
is Boolean -> ObjBool(obj)
|
||||
|
||||
@ -45,11 +45,11 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
|
||||
}
|
||||
|
||||
override suspend fun incrementAndGet(scope: Scope): Obj {
|
||||
return ObjInt(value + 1)
|
||||
return of(value + 1)
|
||||
}
|
||||
|
||||
override suspend fun decrementAndGet(scope: Scope): Obj {
|
||||
return ObjInt(value - 1)
|
||||
return of(value - 1)
|
||||
}
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
@ -63,29 +63,29 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
|
||||
|
||||
override suspend fun plus(scope: Scope, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value + other.value)
|
||||
of(this.value + other.value)
|
||||
else
|
||||
ObjReal(this.doubleValue + other.toDouble())
|
||||
|
||||
override suspend fun minus(scope: Scope, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value - other.value)
|
||||
of(this.value - other.value)
|
||||
else
|
||||
ObjReal(this.doubleValue - other.toDouble())
|
||||
|
||||
override suspend fun mul(scope: Scope, other: Obj): Obj =
|
||||
if (other is ObjInt) {
|
||||
ObjInt(this.value * other.value)
|
||||
of(this.value * other.value)
|
||||
} else ObjReal(this.value * other.toDouble())
|
||||
|
||||
override suspend fun div(scope: Scope, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value / other.value)
|
||||
of(this.value / other.value)
|
||||
else ObjReal(this.value / other.toDouble())
|
||||
|
||||
override suspend fun mod(scope: Scope, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value % other.value)
|
||||
of(this.value % other.value)
|
||||
else ObjReal(this.value.toDouble() % other.toDouble())
|
||||
|
||||
/**
|
||||
@ -158,8 +158,15 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Zero = ObjInt(0, true)
|
||||
val One = ObjInt(1, true)
|
||||
private val cache = Array(256) { ObjInt((it - 128).toLong(), true) }
|
||||
|
||||
fun of(value: Long): ObjInt {
|
||||
return if (value in -128L..127L) cache[(value + 128).toInt()]
|
||||
else ObjInt(value)
|
||||
}
|
||||
|
||||
val Zero = of(0)
|
||||
val One = of(1)
|
||||
val type = object : ObjClass("Int") {
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
||||
when (lynonType) {
|
||||
@ -178,4 +185,5 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.toObj() = ObjInt(this.toLong())
|
||||
fun Int.toObj() = ObjInt.of(this.toLong())
|
||||
fun Long.toObj() = ObjInt.of(this)
|
||||
@ -76,36 +76,3 @@ fun Obj.toFlow(scope: Scope): Flow<Obj> = flow {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call [callback] for each element of this obj considering it provides [Iterator]
|
||||
* methods `hasNext` and `next`.
|
||||
*
|
||||
* IF callback returns false, iteration is stopped.
|
||||
*/
|
||||
suspend fun Obj.enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
val iterator = invokeInstanceMethod(scope, "iterator")
|
||||
val hasNext = iterator.getInstanceMethod(scope, "hasNext")
|
||||
val next = iterator.getInstanceMethod(scope, "next")
|
||||
var closeIt = false
|
||||
try {
|
||||
while (hasNext.invoke(scope, iterator).toBool()) {
|
||||
val nextValue = next.invoke(scope, iterator)
|
||||
val shouldContinue = try {
|
||||
callback(nextValue)
|
||||
} catch (e: Exception) {
|
||||
// iteration aborted due to exception in callback
|
||||
closeIt = true
|
||||
throw e
|
||||
}
|
||||
if (!shouldContinue) {
|
||||
closeIt = true
|
||||
break
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (closeIt) {
|
||||
// Best-effort cancel on premature termination
|
||||
iterator.invokeInstanceMethod(scope, "cancelIteration") { ObjVoid }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,6 +140,12 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
return list.contains(other)
|
||||
}
|
||||
|
||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
for (item in list) {
|
||||
if (!callback(item)) break
|
||||
}
|
||||
}
|
||||
|
||||
override val objClass: ObjClass
|
||||
get() = type
|
||||
|
||||
|
||||
@ -114,6 +114,36 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
||||
start is ObjChar && end is ObjChar
|
||||
}
|
||||
|
||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
if (isIntRange) {
|
||||
val s = (start as ObjInt).value
|
||||
val e = (end as ObjInt).value
|
||||
if (isEndInclusive) {
|
||||
for (i in s..e) {
|
||||
if (!callback(ObjInt.of(i))) break
|
||||
}
|
||||
} else {
|
||||
for (i in s..<e) {
|
||||
if (!callback(ObjInt.of(i))) break
|
||||
}
|
||||
}
|
||||
} else if (isCharRange) {
|
||||
val s = (start as ObjChar).value
|
||||
val e = (end as ObjChar).value
|
||||
if (isEndInclusive) {
|
||||
for (c in s..e) {
|
||||
if (!callback(ObjChar(c))) break
|
||||
}
|
||||
} else {
|
||||
for (c in s..<e) {
|
||||
if (!callback(ObjChar(c))) break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
super.enumerate(scope, callback)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
return (other as? ObjRange)?.let {
|
||||
if( start == other.start && end == other.end ) 0 else -1
|
||||
|
||||
@ -34,6 +34,12 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||
return set.contains(other)
|
||||
}
|
||||
|
||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
for (item in set) {
|
||||
if (!callback(item)) break
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||
return ObjSet(
|
||||
if (other is ObjSet)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user