associateBy and Lyng:Iterable .toFlow() for kotlin integration simplicity

This commit is contained in:
Sergey Chernov 2025-07-02 20:45:30 +03:00
parent ea2cf1f373
commit 950e8301c3
10 changed files with 86 additions and 12 deletions

View File

@ -21,6 +21,20 @@ Having `Iterable` in base classes allows to use it in for loop. Also, each `Iter
## Instance methods
| fun/method | description |
|-----------------|---------------------------------------------------------------------------------|
| toList() | create a list from iterable |
| toSet() | create a set from iterable |
| contains(i) | check that iterable contains `i` |
| `i in iterator` | same as `contains(i)` |
| isEmpty() | check iterable is empty |
| forEach(f) | call f for each element |
| toMap() | create a map from list of key-value pairs (arrays of 2 items or like) |
| map(f) | create a list of values returned by `f` called for each element of the iterable |
| indexOf(i) | return index if the first encounter of i or a negative value if not found |
| associateBy(kf) | create a map where keys are returned by kf that will be called for each element |
fun Iterable.toList(): List
fun Iterable.toSet(): Set
fun Iterable.indexOf(element): Int
@ -28,6 +42,7 @@ Having `Iterable` in base classes allows to use it in for loop. Also, each `Iter
fun Iterable.isEmpty(element): Bool
fun Iterable.forEach(block: (Any?)->Void ): Void
fun Iterable.map(block: (Any?)->Void ): List
fun Iterable.associateBy( keyMaker: (Any?)->Any): Map
## Abstract methods

View File

@ -1215,7 +1215,7 @@ Typical set of String functions includes:
| s1 + s2 | concatenation |
| s1 += s2 | self-modifying concatenation |
| toReal() | attempts to parse string as a Real value |
| | |
| toInt() | parse string to Int value |
| | |

View File

@ -158,7 +158,6 @@ class Compiler(
isCall = true
val lambda =
parseLambdaExpression(cc)
println(cc.current())
operand = Accessor { context ->
context.pos = next.pos
val v = left.getter(context).value

View File

@ -87,6 +87,14 @@ open class Obj {
// note that getInstanceMember traverses the hierarchy
objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args)
open suspend fun getInstanceMethod(
context: Context,
name: String,
args: Arguments = Arguments.EMPTY
): Obj =
// note that getInstanceMember traverses the hierarchy
objClass.getInstanceMember(context.pos, name).value
fun getMemberOrNull(name: String): Obj? = objClass.getInstanceMemberOrNull(name)?.value
// methods that to override
@ -210,6 +218,8 @@ open class Obj {
context.raiseNotImplemented("indexing")
}
suspend fun getAt(context: Context, index: Int): Obj = getAt(context, ObjInt(index.toLong()))
open suspend fun putAt(context: Context, index: Int, newValue: Obj) {
context.raiseNotImplemented("indexing")
}

View File

@ -48,12 +48,20 @@ val ObjIterable by lazy {
}
addFn("toMap") {
val result = mutableListOf<Obj>()
val it = thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
result += it.invokeInstanceMethod(this, "next")
val result = ObjMap()
thisObj.toFlow(this).collect { pair ->
result.map[pair.getAt(this,0)] = pair.getAt(this, 1)
}
ObjMap(ObjMap.listToMap(this, result))
result
}
addFn("associateBy") {
val association = requireOnlyArg<Statement>()
val result = ObjMap()
thisObj.toFlow(this).collect {
result.map[association.call(this, it)] = it
}
result
}
addFn("forEach", isOpen = true) {
@ -67,12 +75,10 @@ val ObjIterable by lazy {
}
addFn("map", isOpen = true) {
val it = thisObj.invokeInstanceMethod(this, "iterator")
val fn = requiredArg<Statement>(0)
val result = mutableListOf<Obj>()
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
val x = it.invokeInstanceMethod(this, "next")
result += fn.execute(this.copy(Arguments(listOf(x))))
thisObj.toFlow(this).collect {
result += fn.call(this, it)
}
ObjList(result)
}

View File

@ -2,6 +2,9 @@
package net.sergeych.lyng
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
/**
* Iterator wrapper to allow Kotlin collections to be returned from Lyng objects;
* each object is converted to a Lyng object.
@ -37,3 +40,18 @@ class ObjKotlinObjIterator(val iterator: Iterator<Obj>) : Obj() {
}
}
/**
* Convert Lyng's Iterable to Kotlin's Flow.
*
* As Lyng is totally asynchronous, its iterator can't be trivially converted to Kotlin's synchronous iterator.
* It is, though, trivially convertible to Kotlin's Flow.
*/
fun Obj.toFlow(context: Context): Flow<Obj> = flow {
val iterator = invokeInstanceMethod(context, "iterator")
val hasNext = iterator.getInstanceMethod(context, "hasNext")
val next = iterator.getInstanceMethod(context, "next")
while (hasNext.invoke(context, iterator).toBool()) {
emit(next.invoke(context, iterator))
}
}

View File

@ -34,7 +34,7 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
}
}
class ObjMap(val map: MutableMap<Obj, Obj>) : Obj() {
class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
override val objClass = type

View File

@ -76,6 +76,9 @@ data class ObjString(val value: String) : Obj() {
companion object {
val type = ObjClass("String").apply {
addFn("toInt") {
ObjInt(thisAs<ObjString>().value.toLong())
}
addFn("startsWith") {
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
}

View File

@ -34,6 +34,8 @@ abstract class Statement(
val type = ObjClass("Callable")
}
suspend fun call(context: Context,vararg args: Obj) = execute(context.copy(args = Arguments(*args)))
}
fun Statement.raise(text: String): Nothing {

View File

@ -1,4 +1,6 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.*
import kotlin.test.*
@ -2311,4 +2313,23 @@ class ScriptTest {
assert( ! "5.2".isInteger() )
""".trimIndent())
}
@Test
fun testToFlow() = runTest() {
val c = Context()
val arr = c.eval("[1,2,3]")
// array is iterable so we can:
assertEquals(listOf(1,2,3), arr.toFlow(c).map { it.toInt() }.toList())
}
@Test
fun testAssociateBy() = runTest() {
eval("""
val m = [123, 456].associateBy { "k:%s"(it) }
println(m)
assertEquals(123, m["k:123"])
assertEquals(456, m["k:456"])
""")
listOf(1,2,3).associateBy { it * 10 }
}
}