parent
f7f020f4d6
commit
1a90b25b1e
@ -128,3 +128,55 @@ Usage example:
|
||||
yield()
|
||||
} while(true)
|
||||
}
|
||||
|
||||
# Data exchange for coroutines
|
||||
|
||||
## Flow
|
||||
|
||||
Flow is an async cold sequence; it is named after kotlin's Flow as it resembles it closely. The cold means the flow is only evaluated when iterated (collected, in Kotlin terms), before it is inactive. Sequence means that it is potentially unlimited, as in our example of glorious Fibonacci number generator:
|
||||
|
||||
// Fibonacch numbers flow!
|
||||
val f = flow {
|
||||
println("Starting generator")
|
||||
var n1 = 0
|
||||
var n2 = 1
|
||||
emit(n1)
|
||||
emit(n2)
|
||||
while(true) {
|
||||
val n = n1 + n2
|
||||
emit(n)
|
||||
n1 = n2
|
||||
n2 = n
|
||||
}
|
||||
}
|
||||
val correctFibs = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
|
||||
println("Generation starts")
|
||||
assertEquals( correctFibs, f.take(correctFibs.size))
|
||||
>>> Generation starts
|
||||
>>> Starting generator
|
||||
>>> void
|
||||
|
||||
Great: the generator is not executed until collected bu the `f.take()` call, which picks specified number of elements from the flow, can cancel it.
|
||||
|
||||
Important difference from the channels or like, every time you collect the flow, you collect it anew:
|
||||
|
||||
val f = flow {
|
||||
emit("start")
|
||||
(1..4).forEach { emit(it) }
|
||||
}
|
||||
// let's collect flow:
|
||||
val result = []
|
||||
for( x in f ) result += x
|
||||
println(result)
|
||||
|
||||
// let's collect it once again:
|
||||
println(f.toList())
|
||||
|
||||
// and again:
|
||||
//assertEquals( result, f.toList() )
|
||||
|
||||
>>> ["start", 1, 2, 3, 4]
|
||||
>>> ["start", 1, 2, 3, 4]
|
||||
>>> void
|
||||
|
||||
1
|
||||
|
@ -65,6 +65,7 @@ kotlin {
|
||||
languageSettings.optIn("kotlin.contracts.ExperimentalContracts")
|
||||
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
|
||||
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
|
||||
languageSettings.optIn("kotlinx.coroutines.flow.DelicateCoroutinesApi")
|
||||
}
|
||||
|
||||
val commonMain by getting {
|
||||
|
@ -1,19 +0,0 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.lyng.obj.ObjRecord
|
||||
|
||||
/**
|
||||
* Special version of the [Scope] used to `apply` new this object to
|
||||
* _parent context property.
|
||||
*
|
||||
* @param _parent context to apply to
|
||||
* @param args arguments for the new context
|
||||
* @param appliedScope the new context to apply, it will have lower priority except for `this` which
|
||||
* will be reset by appliedContext's `this`.
|
||||
*/
|
||||
class AppliedScope(_parent: Scope, args: Arguments, val appliedScope: Scope)
|
||||
: Scope(_parent, args, appliedScope.pos, appliedScope.thisObj) {
|
||||
override fun get(name: String): ObjRecord? =
|
||||
if (name == "this") thisObj.asReadonly
|
||||
else super.get(name) ?: appliedScope[name]
|
||||
}
|
@ -34,7 +34,8 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
defaultRecordType: ObjRecord.Type = ObjRecord.Type.ConstructorField
|
||||
) {
|
||||
fun assign(a: Item, value: Obj) {
|
||||
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value,
|
||||
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable,
|
||||
value.byValueCopy(),
|
||||
a.visibility ?: defaultVisibility,
|
||||
recordType = defaultRecordType)
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ data class Arguments(val list: List<Obj>, val tailBlockMode: Boolean = false) :
|
||||
|
||||
fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
|
||||
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
|
||||
return list.first()
|
||||
return list.first().byValueCopy()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,16 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.lyng.obj.ObjRecord
|
||||
|
||||
/**
|
||||
* Scope that adds a "closure" to caller; most often it is used to apply class instance to caller scope.
|
||||
* Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols
|
||||
* from [closureScope] with proper precedence
|
||||
*/
|
||||
class ClosureScope(val callScope: Scope,val closureScope: Scope) : Scope(callScope, callScope.args, thisObj = callScope.thisObj) {
|
||||
|
||||
override fun get(name: String): ObjRecord? {
|
||||
// closure should be treated below callScope
|
||||
return super.get(name) ?: closureScope.get(name)
|
||||
}
|
||||
}
|
@ -470,7 +470,7 @@ class Compiler(
|
||||
|
||||
val callStatement = statement {
|
||||
// and the source closure of the lambda which might have other thisObj.
|
||||
val context = AppliedScope(closure!!, args, this)
|
||||
val context = ClosureScope(this, closure!!) //AppliedScope(closure!!, args, this)
|
||||
if (argsDeclaration == null) {
|
||||
// no args: automatic var 'it'
|
||||
val l = args.list
|
||||
@ -1540,7 +1540,8 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun parseFunctionDeclaration(
|
||||
private suspend fun
|
||||
parseFunctionDeclaration(
|
||||
visibility: Visibility = Visibility.Public,
|
||||
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
|
||||
isExtern: Boolean = false,
|
||||
@ -1586,9 +1587,11 @@ class Compiler(
|
||||
|
||||
val fnBody = statement(t.pos) { callerContext ->
|
||||
callerContext.pos = start
|
||||
|
||||
// restore closure where the function was defined, and making a copy of it
|
||||
// for local space (otherwise it will write local stuff to closure!)
|
||||
val context = closure?.copy() ?: callerContext.raiseError("bug: closure not set")
|
||||
val context = closure?.let { ClosureScope(callerContext, it) }
|
||||
?: callerContext.raiseError("bug: closure not set")
|
||||
|
||||
// load params from caller context
|
||||
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
|
||||
@ -1597,7 +1600,7 @@ class Compiler(
|
||||
}
|
||||
fnStatements.execute(context)
|
||||
}
|
||||
val fnCreatestatement = statement(start) { context ->
|
||||
val fnCreateStatement = statement(start) { context ->
|
||||
// we added fn in the context. now we must save closure
|
||||
// for the function
|
||||
closure = context
|
||||
@ -1606,7 +1609,12 @@ class Compiler(
|
||||
val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found")
|
||||
if (type !is ObjClass) context.raiseClassCastError("$typeName is not the class instance")
|
||||
type.addFn(name, isOpen = true) {
|
||||
fnBody.execute(this)
|
||||
// ObjInstance has a fixed instance scope, so we need to build a closure
|
||||
(thisObj as? ObjInstance)?.let { i ->
|
||||
fnBody.execute(ClosureScope(this, i.instanceScope))
|
||||
}
|
||||
// other classes can create one-time scope for this rare case:
|
||||
?: fnBody.execute(thisObj.autoInstanceScope(this))
|
||||
}
|
||||
}
|
||||
// regular function/method
|
||||
@ -1616,10 +1624,10 @@ class Compiler(
|
||||
fnBody
|
||||
}
|
||||
return if (isStatic) {
|
||||
currentInitScope += fnCreatestatement
|
||||
currentInitScope += fnCreateStatement
|
||||
NopStatement
|
||||
} else
|
||||
fnCreatestatement
|
||||
fnCreateStatement
|
||||
}
|
||||
|
||||
private suspend fun parseBlock(skipLeadingBrace: Boolean = false): Statement {
|
||||
|
@ -16,7 +16,7 @@ import net.sergeych.lyng.pacman.ImportProvider
|
||||
*
|
||||
* There are special types of scopes:
|
||||
*
|
||||
* - [AppliedScope] - scope used to apply a closure to some thisObj scope
|
||||
* - [ClosureScope] - scope used to apply a closure to some thisObj scope
|
||||
*/
|
||||
open class Scope(
|
||||
val parent: Scope?,
|
||||
@ -73,7 +73,7 @@ open class Scope(
|
||||
|
||||
inline fun <reified T : Obj> requiredArg(index: Int): T {
|
||||
if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}")
|
||||
return (args.list[index] as? T)
|
||||
return (args.list[index].byValueCopy() as? T)
|
||||
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index]::class.simpleName}")
|
||||
}
|
||||
|
||||
@ -94,8 +94,15 @@ open class Scope(
|
||||
raiseError("This function does not accept any arguments")
|
||||
}
|
||||
|
||||
inline fun <reified T : Obj> thisAs(): T = (thisObj as? T)
|
||||
?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
||||
inline fun <reified T : Obj> thisAs(): T {
|
||||
var s: Scope? = this
|
||||
do {
|
||||
val t = s!!.thisObj
|
||||
if (t is T) return t
|
||||
s = s.parent
|
||||
} while(s != null)
|
||||
raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
||||
}
|
||||
|
||||
internal val objects = mutableMapOf<String, ObjRecord>()
|
||||
|
||||
@ -193,6 +200,11 @@ open class Scope(
|
||||
val importManager by lazy { (currentImportProvider as? ImportManager)
|
||||
?: throw IllegalStateException("this scope has no manager in the chain (provided $currentImportProvider") }
|
||||
|
||||
override fun toString(): String {
|
||||
val contents = objects.entries.joinToString { "${if( it.value.isMutable ) "var" else "val" } ${it.key}=${it.value.value}" }
|
||||
return "S[this=$thisObj $contents]"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun new(): Scope =
|
||||
|
@ -153,6 +153,16 @@ 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
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
|
||||
addVoidFn("delay") {
|
||||
delay((this.args.firstAndOnly().toDouble()/1000.0).roundToLong())
|
||||
@ -182,7 +192,7 @@ class Script(
|
||||
addConst("Mutex", ObjMutex.type)
|
||||
|
||||
addFn("launch") {
|
||||
val callable = args.firstAndOnly() as Statement
|
||||
val callable = requireOnlyArg<Statement>()
|
||||
ObjDeferred(globalDefer {
|
||||
callable.execute(this@addFn)
|
||||
})
|
||||
@ -193,6 +203,9 @@ class Script(
|
||||
ObjVoid
|
||||
}
|
||||
|
||||
addFn("flow") {
|
||||
ObjFlow(requireOnlyArg<Statement>())
|
||||
}
|
||||
|
||||
val pi = ObjReal(PI)
|
||||
addConst("π", pi)
|
||||
|
@ -14,6 +14,8 @@ open class ScriptError(val pos: Pos, val errorMessage: String, cause: Throwable?
|
||||
cause
|
||||
)
|
||||
|
||||
class ScriptFlowIsNoMoreCollected: Exception()
|
||||
|
||||
class ExecutionError(val errorObject: ObjException) : ScriptError(errorObject.scope.pos, errorObject.message)
|
||||
|
||||
class ImportException(pos: Pos, message: String) : ScriptError(pos, message)
|
@ -55,7 +55,8 @@ open class Obj {
|
||||
scope: Scope,
|
||||
name: String,
|
||||
args: Arguments = Arguments.EMPTY
|
||||
): T = invokeInstanceMethod(scope, name, args) as T
|
||||
): T =
|
||||
invokeInstanceMethod(scope, name, args) as T
|
||||
|
||||
/**
|
||||
* Invoke a method of the object if exists
|
||||
@ -68,7 +69,10 @@ open class Obj {
|
||||
args: Arguments = Arguments.EMPTY,
|
||||
onNotFoundResult: Obj?=null
|
||||
): Obj =
|
||||
objClass.getInstanceMemberOrNull(name)?.value?.invoke(scope, this, args)
|
||||
objClass.getInstanceMemberOrNull(name)?.value?.invoke(
|
||||
scope,
|
||||
this,
|
||||
args)
|
||||
?: onNotFoundResult
|
||||
?: scope.raiseSymbolNotFound(name)
|
||||
|
||||
@ -251,6 +255,14 @@ open class Obj {
|
||||
scope.raiseNotImplemented()
|
||||
}
|
||||
|
||||
fun autoInstanceScope(parent: Scope): Scope {
|
||||
val scope = parent.copy(newThisObj = this, args = parent.args)
|
||||
for( m in objClass.members) {
|
||||
scope.objects[m.key] = m.value
|
||||
}
|
||||
return scope
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val rootObjectType = ObjClass("Obj").apply {
|
||||
|
@ -36,7 +36,7 @@ open class ObjClass(
|
||||
/**
|
||||
* members: fields most often. These are called with [ObjInstance] withs ths [ObjInstance.objClass]
|
||||
*/
|
||||
private val members = mutableMapOf<String, ObjRecord>()
|
||||
internal val members = mutableMapOf<String, ObjRecord>()
|
||||
|
||||
override fun toString(): String = className
|
||||
|
||||
|
141
lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt
Normal file
141
lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt
Normal file
@ -0,0 +1,141 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.ChannelResult
|
||||
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.mp_tools.globalLaunch
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
|
||||
class ObjFlowBuilder(val output: SendChannel<Obj>) : Obj() {
|
||||
|
||||
override val objClass = type
|
||||
|
||||
companion object {
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
val type = object : ObjClass("FlowBuilder") {}.apply {
|
||||
addFn("emit") {
|
||||
val data = requireOnlyArg<Obj>()
|
||||
println("well well $data")
|
||||
try {
|
||||
println("builder ${thisAs<ObjFlowBuilder>().hashCode()}")
|
||||
println("channel ${thisAs<ObjFlowBuilder>().output.hashCode()}")
|
||||
val channel = thisAs<ObjFlowBuilder>().output
|
||||
if( !channel.isClosedForSend )
|
||||
channel.send(data)
|
||||
else
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
} catch (x: Exception) {
|
||||
if( x !is CancellationException )
|
||||
x.printStackTrace()
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChannel<Obj> {
|
||||
val channel = Channel<Obj>(Channel.RENDEZVOUS)
|
||||
val builder = ObjFlowBuilder(channel)
|
||||
val builderScope = scope.copy(newThisObj = builder)
|
||||
globalLaunch {
|
||||
try {
|
||||
producer.execute(builderScope)
|
||||
}
|
||||
catch(x: ScriptFlowIsNoMoreCollected) {
|
||||
x.printStackTrace()
|
||||
// premature flow closing, OK
|
||||
}
|
||||
catch(x: Exception) {
|
||||
x.printStackTrace()
|
||||
}
|
||||
channel.close()
|
||||
}
|
||||
return channel
|
||||
}
|
||||
|
||||
class ObjFlow(val producer: Statement) : Obj() {
|
||||
|
||||
override val objClass = type
|
||||
|
||||
companion object {
|
||||
val type = object : ObjClass("Flow", ObjIterable) {
|
||||
override suspend fun callOn(scope: Scope): Obj {
|
||||
scope.raiseError("Flow constructor is not available")
|
||||
}
|
||||
}.apply {
|
||||
addFn("iterator") {
|
||||
println("called iterator!")
|
||||
ObjFlowIterator(thisAs<ObjFlow>().producer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ObjFlowIterator(val producer: Statement) : Obj() {
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
private var channel: ReceiveChannel<Obj>? = null
|
||||
|
||||
private var nextItem: ChannelResult<Obj>? = null
|
||||
|
||||
private var isCancelled = false
|
||||
|
||||
private fun checkNotCancelled(scope: Scope) {
|
||||
if( isCancelled )
|
||||
scope.raiseIllegalState("iteration is cancelled")
|
||||
}
|
||||
suspend fun hasNext(scope: Scope): ObjBool {
|
||||
checkNotCancelled(scope)
|
||||
// cold start:
|
||||
if (channel == null) channel = createLyngFlowInput(scope, producer)
|
||||
if (nextItem == null) nextItem = channel!!.receiveCatching()
|
||||
return ObjBool(nextItem!!.isSuccess)
|
||||
}
|
||||
|
||||
suspend fun next(scope: Scope): Obj {
|
||||
checkNotCancelled(scope)
|
||||
if (hasNext(scope).value == false) scope.raiseIllegalState("iteration is done")
|
||||
return nextItem!!.getOrThrow().also { nextItem = null }
|
||||
}
|
||||
|
||||
private val access = Mutex()
|
||||
suspend fun cancel() {
|
||||
access.withLock {
|
||||
if (!isCancelled) {
|
||||
isCancelled = true
|
||||
channel?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = object : ObjClass("FlowIterator", ObjIterator) {
|
||||
|
||||
}.apply {
|
||||
addFn("hasNext") {
|
||||
thisAs<ObjFlowIterator>().hasNext(this).toObj()
|
||||
}
|
||||
addFn("next") {
|
||||
val x = thisAs<ObjFlowIterator>()
|
||||
x.next(this)
|
||||
}
|
||||
addFn("cancelIteration") {
|
||||
val x = thisAs<ObjFlowIterator>()
|
||||
x.cancel()
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -34,7 +34,10 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
onNotFoundResult: Obj?): Obj =
|
||||
instanceScope[name]?.let {
|
||||
if (it.visibility.isPublic)
|
||||
it.value.invoke(scope, this, args)
|
||||
it.value.invoke(
|
||||
instanceScope,
|
||||
this,
|
||||
args)
|
||||
else
|
||||
scope.raiseError(ObjAccessException(scope, "can't invoke non-public method $name"))
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ val ObjIterable by lazy {
|
||||
addFn("toMap") {
|
||||
val result = ObjMap()
|
||||
thisObj.toFlow(this).collect { pair ->
|
||||
result.map[pair.getAt(this,0)] = pair.getAt(this, 1)
|
||||
result.map[pair.getAt(this, 0)] = pair.getAt(this, 1)
|
||||
}
|
||||
result
|
||||
}
|
||||
@ -86,6 +86,24 @@ val ObjIterable by lazy {
|
||||
ObjList(result)
|
||||
}
|
||||
|
||||
addFn("take") {
|
||||
var n = requireOnlyArg<ObjInt>().value.toInt()
|
||||
val result = mutableListOf<Obj>()
|
||||
if (n > 0) {
|
||||
thisObj.enumerate(this) {
|
||||
result += it
|
||||
--n > 0
|
||||
}
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
|
||||
// addFn("drop" ) {
|
||||
// var n = requireOnlyArg<ObjInt>().value.toInt()
|
||||
// if( n < 0 ) raiseIllegalArgument("drop($n): should be positive")
|
||||
// val it = callMethod<>()
|
||||
// }
|
||||
|
||||
addFn("isEmpty") {
|
||||
ObjBool(
|
||||
thisObj.invokeInstanceMethod(this, "iterator")
|
||||
|
@ -1,3 +1,4 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
val ObjIterator by lazy { ObjClass("Iterator") }
|
||||
val ObjIterator by lazy { ObjClass("Iterator") }
|
||||
|
||||
|
@ -55,4 +55,26 @@ fun Obj.toFlow(scope: Scope): Flow<Obj> = flow {
|
||||
while (hasNext.invoke(scope, iterator).toBool()) {
|
||||
emit(next.invoke(scope, iterator))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
while (hasNext.invoke(scope, iterator).toBool()) {
|
||||
val nextValue = next.invoke(scope, iterator)
|
||||
if( !callback(nextValue) ) {
|
||||
closeIt = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if( closeIt )
|
||||
iterator.invokeInstanceMethod(scope, "cancelIteration", onNotFoundResult = ObjVoid)
|
||||
}
|
@ -93,7 +93,9 @@ data class ObjString(val value: String) : Obj() {
|
||||
ObjString(decoder.unpackBinaryData().decodeToString())
|
||||
}.apply {
|
||||
addFn("toInt") {
|
||||
ObjInt(thisAs<ObjString>().value.toLong())
|
||||
ObjInt(thisAs<ObjString>().value.toLongOrNull()
|
||||
?: raiseIllegalArgument("can't convert to int: $thisObj")
|
||||
)
|
||||
}
|
||||
addFn("startsWith") {
|
||||
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
|
||||
@ -137,7 +139,9 @@ data class ObjString(val value: String) : Obj() {
|
||||
}
|
||||
addFn("encodeUtf8") { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) }
|
||||
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||
addFn("toReal") { ObjReal(thisAs<ObjString>().value.toDouble()) }
|
||||
addFn("toReal") {
|
||||
ObjReal(thisAs<ObjString>().value.toDouble())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -63,4 +63,55 @@ class TestCoroutines {
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlows() = runTest {
|
||||
eval("""
|
||||
val f = flow {
|
||||
println("Starting generator")
|
||||
var n1 = 0
|
||||
var n2 = 1
|
||||
emit(n1)
|
||||
emit(n2)
|
||||
while(true) {
|
||||
val n = n1 + n2
|
||||
emit(n)
|
||||
n1 = n2
|
||||
n2 = n
|
||||
}
|
||||
}
|
||||
val correctFibs = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
|
||||
assertEquals( correctFibs, f.take(correctFibs.size))
|
||||
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlow2() = runTest {
|
||||
eval("""
|
||||
val f = flow {
|
||||
println("Starting generator")
|
||||
emit("start")
|
||||
emit("start2")
|
||||
println("Emitting")
|
||||
(1..4).forEach {
|
||||
// println("you hoo "+it)
|
||||
emit(it)
|
||||
}
|
||||
println("Done emitting")
|
||||
}
|
||||
// let's collect flow:
|
||||
val result = []
|
||||
// for( x in f ) result += x
|
||||
println(result)
|
||||
|
||||
// let's collect it once again:
|
||||
println(f.toList())
|
||||
println(f.toList())
|
||||
// for( x in f ) println(x)
|
||||
// for( x in f ) println(x)
|
||||
|
||||
//assertEquals( result, f.toList() )
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
@ -1274,6 +1274,106 @@ class ScriptTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCaptureLocals() = runTest {
|
||||
eval(
|
||||
"""
|
||||
|
||||
fun outer(prefix) {
|
||||
val p1 = "0" + prefix
|
||||
{
|
||||
p1 + "2" + it
|
||||
}
|
||||
}
|
||||
fun outer2(prefix) {
|
||||
val p1 = "*" + prefix
|
||||
{
|
||||
p1 + "2" + it
|
||||
}
|
||||
}
|
||||
val x = outer("1")
|
||||
val y = outer2("1")
|
||||
println(x("!"))
|
||||
assertEquals( "0123", x("3") )
|
||||
assertEquals( "*123", y("3") )
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInstanceCallScopeIsCorrect() = runTest {
|
||||
eval(
|
||||
"""
|
||||
|
||||
val prefix = ":"
|
||||
|
||||
class T(text) {
|
||||
fun getText() {
|
||||
println(text)
|
||||
prefix + text + "!"
|
||||
}
|
||||
}
|
||||
|
||||
val text = "invalid"
|
||||
|
||||
val t1 = T("foo")
|
||||
val t2 = T("bar")
|
||||
|
||||
// get inside the block
|
||||
for( i in 1..3 ) {
|
||||
assertEquals( "foo", t1.text )
|
||||
assertEquals( ":foo!", t1.getText() )
|
||||
assertEquals( "bar", t2.text )
|
||||
assertEquals( ":bar!", t2.getText() )
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAppliedScopes() = runTest {
|
||||
eval(
|
||||
"""
|
||||
class T(text) {
|
||||
fun getText() {
|
||||
println(text)
|
||||
text + "!"
|
||||
}
|
||||
}
|
||||
|
||||
val prefix = ":"
|
||||
val lambda = {
|
||||
prefix + getText() + "!"
|
||||
}
|
||||
|
||||
val text = "invalid"
|
||||
val t1 = T("foo")
|
||||
val t2 = T("bar")
|
||||
|
||||
t1.apply {
|
||||
// it must take "text" from class t1:
|
||||
assertEquals("foo", text)
|
||||
assertEquals( "foo!", getText() )
|
||||
assertEquals( ":foo!!", lambda() )
|
||||
}
|
||||
t2.apply {
|
||||
assertEquals("bar", text)
|
||||
assertEquals( "bar!", getText() )
|
||||
assertEquals( ":bar!!", lambda() )
|
||||
}
|
||||
// worst case: names clash
|
||||
fun badOne() {
|
||||
val prefix = "&"
|
||||
t1.apply {
|
||||
assertEquals( ":foo!!", lambda() )
|
||||
}
|
||||
}
|
||||
badOne()
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLambdaWithArgsEllipsis() = runTest {
|
||||
eval(
|
||||
@ -2244,10 +2344,12 @@ class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun testSet2() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
assertEquals( Set( ...[1,2,3]), Set(1,2,3) )
|
||||
assertEquals( Set( ...[1,false,"ok"]), Set("ok", 1, false) )
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -2284,9 +2386,11 @@ class ScriptTest {
|
||||
eval(
|
||||
"""
|
||||
class Point(x,y)
|
||||
|
||||
// see the difference: apply changes this to newly created Point:
|
||||
val p = Point(1,2).apply {
|
||||
this.x++; this.y++
|
||||
this.x++
|
||||
y++
|
||||
}
|
||||
assertEquals(p, Point(2,3))
|
||||
>>> void
|
||||
@ -2305,6 +2409,10 @@ class ScriptTest {
|
||||
}
|
||||
|
||||
fun Object.isInteger() {
|
||||
println(this)
|
||||
println(this is Int)
|
||||
println(this is Real)
|
||||
println(this is String)
|
||||
when(this) {
|
||||
is Int -> true
|
||||
is Real -> toInt() == this
|
||||
@ -2319,7 +2427,7 @@ class ScriptTest {
|
||||
assert( 12.isInteger() == true )
|
||||
assert( 12.1.isInteger() == false )
|
||||
assert( "5".isInteger() )
|
||||
assert( ! "5.2".isInteger() )
|
||||
assert( !"5.2".isInteger() )
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
@ -2446,20 +2554,26 @@ class ScriptTest {
|
||||
fun testDefaultImportManager() = runTest {
|
||||
val scope = Scope.new()
|
||||
assertFails {
|
||||
scope.eval("""
|
||||
scope.eval(
|
||||
"""
|
||||
import foo
|
||||
foo()
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
scope.importManager.addTextPackages("""
|
||||
scope.importManager.addTextPackages(
|
||||
"""
|
||||
package foo
|
||||
|
||||
fun foo() { "bar" }
|
||||
""".trimIndent())
|
||||
scope.eval("""
|
||||
""".trimIndent()
|
||||
)
|
||||
scope.eval(
|
||||
"""
|
||||
import foo
|
||||
assertEquals( "bar", foo())
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -2478,7 +2592,8 @@ class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun testBuffer() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
import lyng.buffer
|
||||
|
||||
assertEquals( 0, Buffer().size )
|
||||
@ -2497,12 +2612,14 @@ class ScriptTest {
|
||||
assertEquals(101, buffer[2])
|
||||
assertEquals("Heelo", buffer.decodeUtf8())
|
||||
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBufferCompare() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
import lyng.buffer
|
||||
|
||||
println("Hello".characters())
|
||||
@ -2520,7 +2637,8 @@ class ScriptTest {
|
||||
assertEquals("foo", map[b2])
|
||||
assertEquals(null, map[b3])
|
||||
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -2548,6 +2666,7 @@ class ScriptTest {
|
||||
)
|
||||
delay(1000)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTimeStatics() = runTest {
|
||||
eval(
|
||||
@ -2589,7 +2708,8 @@ class ScriptTest {
|
||||
println(Script.defaultImportManager.packageNames)
|
||||
println(s.importManager.packageNames)
|
||||
|
||||
s.importManager.addTextPackages("""
|
||||
s.importManager.addTextPackages(
|
||||
"""
|
||||
package foo
|
||||
|
||||
import lyng.time
|
||||
@ -2597,8 +2717,10 @@ class ScriptTest {
|
||||
fun foo() {
|
||||
println("foo: %s"(Instant()))
|
||||
}
|
||||
""".trimIndent())
|
||||
s.importManager.addTextPackages("""
|
||||
""".trimIndent()
|
||||
)
|
||||
s.importManager.addTextPackages(
|
||||
"""
|
||||
package bar
|
||||
|
||||
import lyng.time
|
||||
@ -2606,24 +2728,28 @@ class ScriptTest {
|
||||
fun bar() {
|
||||
println("bar: %s"(Instant()))
|
||||
}
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
println(s.importManager.packageNames)
|
||||
|
||||
s.eval("""
|
||||
s.eval(
|
||||
"""
|
||||
import foo
|
||||
import bar
|
||||
|
||||
foo()
|
||||
bar()
|
||||
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIndexIntIncrements() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
val x = [1,2,3]
|
||||
x[1]++
|
||||
++x[0]
|
||||
@ -2636,12 +2762,14 @@ class ScriptTest {
|
||||
assert( b == Buffer(1,3,3) )
|
||||
++b[0]
|
||||
assertEquals( b, Buffer(2,3,3) )
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIndexIntDecrements() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
val x = [1,2,3]
|
||||
x[1]--
|
||||
--x[0]
|
||||
@ -2654,13 +2782,14 @@ class ScriptTest {
|
||||
assert( b == Buffer(1,1,3) )
|
||||
--b[0]
|
||||
assertEquals( b, Buffer(0,1,3) )
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRangeToList() = runTest {
|
||||
val x = eval("""(1..10).toList()""") as ObjList
|
||||
assertEquals(listOf(1,2,3,4,5,6,7,8,9,10), x.list.map { it.toInt() })
|
||||
assertEquals(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), x.list.map { it.toInt() })
|
||||
val y = eval("""(-2..3).toList()""") as ObjList
|
||||
println(y.list)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user