1 parent
f7f020f4d6
commit
1a90b25b1e
20 files changed
+533
-66
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user