associateBy and Lyng:Iterable .toFlow() for kotlin integration simplicity
This commit is contained in:
parent
ea2cf1f373
commit
950e8301c3
@ -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
|
||||
|
@ -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 |
|
||||
| | |
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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.
|
||||
@ -36,4 +39,19 @@ 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))
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user