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