diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index 140a00b..ca53530 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -205,6 +205,20 @@ open class Scope( return "S[this=$thisObj $contents]" } + fun trace(text: String="") { + println("trace Scope: $text ------------------") + var p = this.parent + var level = 0 + while (p != null) { + println(" parent#${++level}: $p") + println(" ( ${p.args.list} )") + p = p.parent + } + println("--------------------") + ObjVoid + + } + companion object { fun new(): Scope = diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index 760938c..3efa9d0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -26,8 +26,15 @@ class Script( companion object { - private val rootScope: Scope = Scope(null).apply { + internal val rootScope: Scope = Scope(null).apply { ObjException.addExceptionsToContext(this) + addFn("print") { + for ((i, a) in args.withIndex()) { + if (i > 0) print(' ' + a.asStr.value) + else print(a.asStr.value) + } + ObjVoid + } addFn("println") { for ((i, a) in args.withIndex()) { if (i > 0) print(' ' + a.asStr.value) @@ -153,16 +160,26 @@ class Script( } result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown")) } - addFn("traceScope") { - println("trace Scope: $this") - var p = this.parent - var level = 0 - while (p != null) { - println(" parent#${++level}: $p") - p = p.parent + addFn("require") { + val condition = requiredArg(0) + if( !condition.value ) { + val message = args.list.getOrNull(1)?.toString() ?: "requirement not met" + raiseIllegalArgument(message) } ObjVoid } + addFn("check") { + val condition = requiredArg(0) + if( !condition.value ) { + val message = args.list.getOrNull(1)?.toString() ?: "check failed" + raiseIllegalState(message) + } + ObjVoid + } + addFn("traceScope") { + this.trace(args.get(0)?.toString() ?: "") + ObjVoid + } addVoidFn("delay") { delay((this.args.firstAndOnly().toDouble()/1000.0).roundToLong()) @@ -204,7 +221,9 @@ class Script( } addFn("flow") { - ObjFlow(requireOnlyArg()) + // important is: current context contains closure often used in call; + // we'll need it for the producer + ObjFlow(requireOnlyArg(), this) } val pi = ObjReal(PI) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt index c05d062..a37585c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt @@ -7,9 +7,7 @@ import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import net.sergeych.lyng.Scope -import net.sergeych.lyng.ScriptFlowIsNoMoreCollected -import net.sergeych.lyng.Statement +import net.sergeych.lyng.* import net.sergeych.mp_tools.globalLaunch import kotlin.coroutines.cancellation.CancellationException @@ -60,7 +58,7 @@ private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChann return channel } -class ObjFlow(val producer: Statement) : Obj() { +class ObjFlow(val producer: Statement, val scope: Scope) : Obj() { override val objClass = type @@ -71,7 +69,8 @@ class ObjFlow(val producer: Statement) : Obj() { } }.apply { addFn("iterator") { - ObjFlowIterator(thisAs().producer) + val objFlow = thisAs() + ObjFlowIterator( statement { objFlow.producer.execute(ClosureScope(this,objFlow.scope)) } ) } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt index 653d5a6..ff97e37 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt @@ -111,6 +111,5 @@ val ObjIterable by lazy { .not() ) } - } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt new file mode 100644 index 0000000..e1e4f1a --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt @@ -0,0 +1,5 @@ +package net.sergeych.lyng.stdlib_included + +internal val rootLyng = """ + +""".trimIndent() \ No newline at end of file diff --git a/lynglib/src/commonMain/lyng/stdlib/Iterable.lyng b/lynglib/src/commonMain/lyng/stdlib/Iterable.lyng new file mode 100644 index 0000000..5d53d4a --- /dev/null +++ b/lynglib/src/commonMain/lyng/stdlib/Iterable.lyng @@ -0,0 +1,16 @@ + +fun Iterable.filter( predicate ) { + flow { + for( item in this ) + if( predicate(item) ) + emit(item) + } +} + +fun Iterable.drop(n) { + require( n >= 0, "drop amount must be non-negative") + var count = 0 + filter { + count++ < N + } +} \ No newline at end of file diff --git a/lynglib/src/commonTest/kotlin/CoroutinesTest.kt b/lynglib/src/commonTest/kotlin/CoroutinesTest.kt index d1c951a..fe3bf40 100644 --- a/lynglib/src/commonTest/kotlin/CoroutinesTest.kt +++ b/lynglib/src/commonTest/kotlin/CoroutinesTest.kt @@ -106,4 +106,67 @@ class TestCoroutines { assertEquals( result, f.toList()) """.trimIndent()) } + + @Test + fun testFlowClosures() = runTest { + eval(""" + fun filter( a, b ) { + println("filter: %s, %s"(a,b)) + flow { + emit(a) + emit(b) + } + } + + assertEquals( [5, 1], filter(5,1).toList() ) + assertEquals( [2, 3], filter(2,3).toList() ) + + """.trimIndent()) + } + + + @Test + fun testFilterFlow() = runTest { + eval(""" + fun filter( list, predicate ) { + val p = predicate + println("predicate "+predicate+" / "+p) + flow { + // here p is captured only once and does not change! + for( item in list ) { + print("filter "+p+" "+item+": ") + if( p(item) ) { + println("OK") + emit(item) + } + else println("NO") + } + } + } + +// fun drop(i, n) { +// require( n >= 0, "drop amount must be non-negative") +// var count = 0 +// println("drop %d"(n)) +// filter(i) { +// count++ >= n +// } +// } + + val src = (1..1).toList() + assertEquals( 1, filter(src) { true }.toList().size ) + println("----------------------------------------------------------") + println("----------------------------------------------------------") + println("----------------------------------------------------------") + println("----------------------------------------------------------") + assertEquals( 0, filter(src) { false }.toList().size ) +// assertEquals( 3, filter(src) { true }.size() ) + +// assertEquals( [7,8], drop((1..8).toList(),6).toList()) +// assertEquals( [1,3,5,7], filter((1..8).toList()) { +// println("call2") +// it % 2 == 1 +// }.toList()) + """.trimIndent()) + } } \ No newline at end of file