diff --git a/docs/development/scope_resolution.md b/docs/development/scope_resolution.md new file mode 100644 index 0000000..a5a7a17 --- /dev/null +++ b/docs/development/scope_resolution.md @@ -0,0 +1,14 @@ + + +Provide: + + + fun outer(a1) + // a1 is caller.a1:arg + val a1_local = a1 + 1 + // we return lambda: + { it -> + // a1_local + a1_lcoal + it + } + } \ No newline at end of file diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index f29003d..6d8e8c1 100644 --- a/lynglib/build.gradle.kts +++ b/lynglib/build.gradle.kts @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget group = "net.sergeych" -version = "0.8.10-SNAPSHOT" +version = "0.8.11-SNAPSHOT" buildscript { repositories { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt index c8685b7..97f4ee6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt @@ -48,13 +48,12 @@ data class ArgsDeclaration(val params: List, val endTokenType: Token.Type) arguments: Arguments = scope.args, defaultAccessType: AccessType = AccessType.Var, defaultVisibility: Visibility = Visibility.Public, - defaultRecordType: ObjRecord.Type = ObjRecord.Type.ConstructorField ) { fun assign(a: Item, value: Obj) { scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value.byValueCopy(), a.visibility ?: defaultVisibility, - recordType = defaultRecordType) + recordType = ObjRecord.Type.Argument) } // will be used with last lambda arg fix diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt index 5782a71..92c3ef3 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt @@ -24,10 +24,33 @@ import net.sergeych.lyng.obj.ObjRecord * 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) { +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) + // we take arguments from the callerScope, the rest + // from the closure. + + // note using super, not callScope, as arguments are assigned by the constructor + // and are not assigned yet to vars in callScope self: + super.objects[name]?.let { +// if( name == "predicate" ) { +// println("predicate: ${it.type.isArgument}: ${it.value}") +// } + if( it.type.isArgument ) return it + } + return closureScope.get(name) } +} + +class ApplyScope(_parent: Scope,val applied: Scope) : Scope(_parent, thisObj = applied.thisObj) { + + override fun get(name: String): ObjRecord? { + return applied.get(name) ?: super.get(name) + } + + override fun applyClosure(closure: Scope): Scope { + return this + } + } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CodeContext.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CodeContext.kt new file mode 100644 index 0000000..4f4ea71 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CodeContext.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package net.sergeych.lyng + +sealed class CodeContext { + class Module(@Suppress("unused") val packageName: String?): CodeContext() + class Function(val name: String): CodeContext() + class ClassBody(val name: String): CodeContext() +} \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 48e2f8f..725d972 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -45,6 +45,17 @@ class Compiler( private fun popInitScope(): MutableList = initStack.removeLast() + private val codeContexts = mutableListOf(CodeContext.Module(null)) + + private suspend fun inCodeContext(context: CodeContext,f: suspend ()->T): T { + return try { + codeContexts.add(context) + f() + } finally { + codeContexts.removeLast() + } + } + private suspend fun parseScript(): Script { val statements = mutableListOf() val start = cc.currentPos() @@ -504,7 +515,7 @@ class Compiler( val callStatement = statement { // and the source closure of the lambda which might have other thisObj. - val context = ClosureScope(this, closure!!) //AppliedScope(closure!!, args, this) + val context = this.applyClosure(closure!!) if (argsDeclaration == null) { // no args: automatic var 'it' val l = args.list @@ -516,7 +527,7 @@ class Compiler( // more args: it is a list of args else -> ObjList(l.toMutableList()) } - context.addItem("it", false, itValue) + context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument) } else { // assign vars as declared the standard way argsDeclaration.assignToContext(context, defaultAccessType = AccessType.Val) @@ -525,7 +536,7 @@ class Compiler( } return Accessor { x -> - if (closure == null) closure = x + closure = x callStatement.asReadonly } } @@ -1202,73 +1213,76 @@ class Compiler( private suspend fun parseClassDeclaration(): Statement { val nameToken = cc.requireToken(Token.Type.ID) - val constructorArgsDeclaration = - if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) - parseArgsDeclaration(isClassDeclaration = true) - else null + return inCodeContext(CodeContext.ClassBody(nameToken.value)) { + val constructorArgsDeclaration = + if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) + parseArgsDeclaration(isClassDeclaration = true) + else null - if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN) - throw ScriptError( - nameToken.pos, - "Bad class declaration: expected ')' at the end of the primary constructor" - ) + if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN) + throw ScriptError( + nameToken.pos, + "Bad class declaration: expected ')' at the end of the primary constructor" + ) - cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true) - val t = cc.next() + cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true) + val t = cc.next() - pushInitScope() + pushInitScope() - val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) { - // parse body - parseScript().also { - cc.skipTokens(Token.Type.RBRACE) + val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) { + // parse body + parseScript().also { + cc.skipTokens(Token.Type.RBRACE) + } + } else { + cc.previous() + null } - } else { - cc.previous() - null - } - val initScope = popInitScope() + val initScope = popInitScope() - // create class - val className = nameToken.value + // create class + val className = nameToken.value // @Suppress("UNUSED_VARIABLE") val defaultAccess = if (isStruct) AccessType.Var else AccessType.Initialization // @Suppress("UNUSED_VARIABLE") val defaultVisibility = Visibility.Public - // create instance constructor - // create custom objClass with all fields and instance constructor + // create instance constructor + // create custom objClass with all fields and instance constructor - val constructorCode = statement { - // constructor code is registered with class instance and is called over - // new `thisObj` already set by class to ObjInstance.instanceContext - thisObj as ObjInstance + val constructorCode = statement { + // constructor code is registered with class instance and is called over + // new `thisObj` already set by class to ObjInstance.instanceContext + thisObj as ObjInstance - // the context now is a "class creation context", we must use its args to initialize - // fields. Note that 'this' is already set by class - constructorArgsDeclaration?.assignToContext(this) - bodyInit?.execute(this) + // the context now is a "class creation context", we must use its args to initialize + // fields. Note that 'this' is already set by class + constructorArgsDeclaration?.assignToContext(this) + bodyInit?.execute(this) - thisObj - } - // inheritance must alter this code: - val newClass = ObjInstanceClass(className).apply { - instanceConstructor = constructorCode - constructorMeta = constructorArgsDeclaration - } - - return statement { - // the main statement should create custom ObjClass instance with field - // accessors, constructor registration, etc. - addItem(className, false, newClass) - if (initScope.isNotEmpty()) { - val classScope = copy(newThisObj = newClass) - newClass.classScope = classScope - for (s in initScope) - s.execute(classScope) + thisObj + } + // inheritance must alter this code: + val newClass = ObjInstanceClass(className).apply { + instanceConstructor = constructorCode + constructorMeta = constructorArgsDeclaration + } + + statement { + // the main statement should create custom ObjClass instance with field + // accessors, constructor registration, etc. + addItem(className, false, newClass) + if (initScope.isNotEmpty()) { + val classScope = copy(newThisObj = newClass) + newClass.classScope = classScope + for (s in initScope) + s.execute(classScope) + } + newClass } - newClass } + } @@ -1639,8 +1653,7 @@ class Compiler( } } - private suspend fun - parseFunctionDeclaration( + private suspend fun parseFunctionDeclaration( visibility: Visibility = Visibility.Public, @Suppress("UNUSED_PARAMETER") isOpen: Boolean = false, isExtern: Boolean = false, @@ -1654,7 +1667,7 @@ class Compiler( else t.value val annotation = lastAnnotation - + val parentContext = codeContexts.last() t = cc.next() // Is extension? @@ -1679,61 +1692,66 @@ class Compiler( if (cc.current().type == Token.Type.COLON) parseTypeDeclaration() - // Here we should be at open body - val fnStatements = if (isExtern) - statement { raiseError("extern function not provided: $name") } - else - parseBlock() + return inCodeContext(CodeContext.Function(name)) { - var closure: Scope? = null + // Here we should be at open body + val fnStatements = if (isExtern) + statement { raiseError("extern function not provided: $name") } + else + parseBlock() - val fnBody = statement(t.pos) { callerContext -> - callerContext.pos = start + var closure: Scope? = null - // 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?.let { ClosureScope(callerContext, it) } - ?: callerContext.raiseError("bug: closure not set") + val fnBody = statement(t.pos) { callerContext -> + callerContext.pos = start - // load params from caller context - argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val) - if (extTypeName != null) { - context.thisObj = callerContext.thisObj - } - fnStatements.execute(context) - } - val fnCreateStatement = statement(start) { context -> - // we added fn in the context. now we must save closure - // for the function - closure = context + // restore closure where the function was defined, and making a copy of it + // for local space. If there is no closure, we are in, say, class context where + // the closure is in the class initialization and we needn't more: + val context = closure?.let { ClosureScope(callerContext, it) } + ?: callerContext - val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody) - ?: fnBody - - extTypeName?.let { typeName -> - // class extension method - 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) { - // ObjInstance has a fixed instance scope, so we need to build a closure - (thisObj as? ObjInstance)?.let { i -> - annotatedFnBody.execute(ClosureScope(this, i.instanceScope)) - } - // other classes can create one-time scope for this rare case: - ?: annotatedFnBody.execute(thisObj.autoInstanceScope(this)) + // load params from caller context + argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val) + if (extTypeName != null) { + context.thisObj = callerContext.thisObj } + fnStatements.execute(context) } - // regular function/method - ?: context.addItem(name, false, annotatedFnBody, visibility) - // as the function can be called from anywhere, we have - // saved the proper context in the closure - annotatedFnBody + val fnCreateStatement = statement(start) { context -> + // we added fn in the context. now we must save closure + // for the function, unless we're in the class scope: + if( isStatic || parentContext !is CodeContext.ClassBody) + closure = context + + val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody) + ?: fnBody + + extTypeName?.let { typeName -> + // class extension method + 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) { + // ObjInstance has a fixed instance scope, so we need to build a closure + (thisObj as? ObjInstance)?.let { i -> + annotatedFnBody.execute(ClosureScope(this, i.instanceScope)) + } + // other classes can create one-time scope for this rare case: + ?: annotatedFnBody.execute(thisObj.autoInstanceScope(this)) + } + } + // regular function/method + ?: context.addItem(name, false, annotatedFnBody, visibility) + // as the function can be called from anywhere, we have + // saved the proper context in the closure + annotatedFnBody + } + if (isStatic) { + currentInitScope += fnCreateStatement + NopStatement + } else + fnCreateStatement } - return if (isStatic) { - currentInitScope += fnCreateStatement - NopStatement - } else - fnCreateStatement } private suspend fun parseBlock(skipLeadingBrace: Boolean = false): Statement { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index ce6e4d3..3a9031c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -131,9 +131,16 @@ open class Scope( open operator fun get(name: String): ObjRecord? = if (name == "this") thisObj.asReadonly else { - objects[name] + (objects[name] ?: parent?.get(name) - ?: thisObj.objClass.getInstanceMemberOrNull(name) + ?: thisObj.objClass + .getInstanceMemberOrNull(name) + ) +// ?.also { +// if( name == "predicate") { +// println("got predicate $it") +// } +// } } fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope = @@ -242,6 +249,8 @@ open class Scope( } + open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure) + companion object { fun new(): Scope = diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt index e409cd9..55d146b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt @@ -296,9 +296,14 @@ open class Obj { args.firstAndOnly().callOn(copy(Arguments(thisObj))) } addFn("apply") { - val newContext = (thisObj as? ObjInstance)?.instanceScope ?: this - args.firstAndOnly() - .callOn(newContext) + val body = args.firstAndOnly() + (thisObj as? ObjInstance)?.let { + println("apply in ${thisObj is ObjInstance}, ${it.instanceScope}") + body.callOn(ApplyScope(this, it.instanceScope)) + } ?: run { + println("apply on non-instance $thisObj") + body.callOn(this) + } thisObj } addFn("also") { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDelegate.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDelegate.kt index 572dd38..61996b0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDelegate.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDelegate.kt @@ -17,31 +17,26 @@ package net.sergeych.lyng.obj -import net.sergeych.lyng.Arguments -import net.sergeych.lyng.Scope -import net.sergeych.lyng.Statement -import net.sergeych.lyng.statement - -class ObjDelegateContext() - -class ObjDelegate( - val getter: Statement, - val setter: Statement = statement { raiseNotImplemented("setter is not implemented") } -): Obj() { - - override suspend fun assign(scope: Scope, other: Obj): Obj? { - setter.execute(scope.copy(Arguments(other))) - return other - } - - companion object { - val type = object: ObjClass("Delegate") { - override suspend fun callOn(scope: Scope): Obj { - scope.raiseError("Delegate should not be constructed directly") - } - }.apply { - - } - } - -} \ No newline at end of file +//class ObjDelegateContext() +// +//class ObjDelegate( +// val getter: Statement, +// val setter: Statement = statement { raiseNotImplemented("setter is not implemented") } +//): Obj() { +// +// override suspend fun assign(scope: Scope, other: Obj): Obj? { +// setter.execute(scope.copy(Arguments(other))) +// return other +// } +// +// companion object { +// val type = object: ObjClass("Delegate") { +// override suspend fun callOn(scope: Scope): Obj { +// scope.raiseError("Delegate should not be constructed directly") +// } +// }.apply { +// +// } +// } +// +//} \ No newline at end of file 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 46af989..fb91d83 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt @@ -40,12 +40,12 @@ class ObjFlowBuilder(val output: SendChannel) : Obj() { val data = requireOnlyArg() try { val channel = thisAs().output - if( !channel.isClosedForSend ) + if (!channel.isClosedForSend) channel.send(data) else throw ScriptFlowIsNoMoreCollected() } catch (x: Exception) { - if( x !is CancellationException ) + if (x !is CancellationException) x.printStackTrace() throw ScriptFlowIsNoMoreCollected() } @@ -62,12 +62,10 @@ private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChann globalLaunch { try { producer.execute(builderScope) - } - catch(x: ScriptFlowIsNoMoreCollected) { + } catch (x: ScriptFlowIsNoMoreCollected) { x.printStackTrace() // premature flow closing, OK - } - catch(x: Exception) { + } catch (x: Exception) { x.printStackTrace() } channel.close() @@ -87,7 +85,11 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() { }.apply { addFn("iterator") { val objFlow = thisAs() - ObjFlowIterator( statement { objFlow.producer.execute(ClosureScope(this,objFlow.scope)) } ) + ObjFlowIterator(statement { + objFlow.producer.execute( + ClosureScope(this, objFlow.scope) + ) + }) } } } @@ -105,9 +107,10 @@ class ObjFlowIterator(val producer: Statement) : Obj() { private var isCancelled = false private fun checkNotCancelled(scope: Scope) { - if( isCancelled ) + if (isCancelled) scope.raiseIllegalState("iteration is cancelled") } + suspend fun hasNext(scope: Scope): ObjBool { checkNotCancelled(scope) // cold start: diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRecord.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRecord.kt index 269e490..5d740d4 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRecord.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRecord.kt @@ -35,11 +35,15 @@ data class ObjRecord( Field(true, true), @Suppress("unused") Fun, + @Suppress("unused") ConstructorField(true, true), + Argument(true, true), @Suppress("unused") Class, Enum, - Other + Other; + + val isArgument get() = this == Argument } @Suppress("unused") fun qualifiedName(name: String): String = 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 index 6639357..154fadd 100644 --- 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 @@ -35,7 +35,7 @@ fun Iterable.filter(predicate) { val list = this flow { for( item in list ) { - if( predicate(item) ) { + if( predicate(item) ) {ln emit(item) } } @@ -92,6 +92,24 @@ fun Iterable.joinToString(prefix=" ", transformer=null) { } result ?: "" } + +fun Iterable.any(predicate): Bool { + for( i in this ) { + if( predicate(i) ) { + break true + // todo: add cancelIteration() in for loop! + } + } else false +} + +fun Iterable.all(predicate): Bool { + for( i in this ) { + if( !predicate(i) ) { + break false + } + } + else true +} """.trimIndent() diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 35b33df..4e375e4 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -1359,9 +1359,6 @@ class ScriptTest { } val prefix = ":" - val lambda = { - prefix + getText() + "!" - } val text = "invalid" val t1 = T("foo") @@ -1371,18 +1368,22 @@ class ScriptTest { // it must take "text" from class t1: assertEquals("foo", text) assertEquals( "foo!", getText() ) - assertEquals( ":foo!!", lambda() ) + assertEquals( ":foo!!", { + prefix + getText() + "!" + }()) } t2.apply { assertEquals("bar", text) assertEquals( "bar!", getText() ) - assertEquals( ":bar!!", lambda() ) + assertEquals( ":bar!!", { + prefix + getText() + "!" + }()) } // worst case: names clash fun badOne() { val prefix = "&" t1.apply { - assertEquals( ":foo!!", lambda() ) + assertEquals( ":foo!!", prefix + getText() + "!" ) } } badOne() @@ -2941,5 +2942,4 @@ class ScriptTest { } - } \ No newline at end of file diff --git a/lynglib/src/commonTest/kotlin/StdlibTest.kt b/lynglib/src/commonTest/kotlin/StdlibTest.kt index d170e2a..af9f3bc 100644 --- a/lynglib/src/commonTest/kotlin/StdlibTest.kt +++ b/lynglib/src/commonTest/kotlin/StdlibTest.kt @@ -23,8 +23,9 @@ class StdlibTest { @Test fun testIterableFilter() = runTest { eval(""" - assertEquals([1,3,5,7], (1..8).filter{ it % 2 == 1 }.toList() ) - assertEquals([2,4,6,8], (1..8).filter{ it % 2 == 0 }.toList() ) + assertEquals([2,4,6,8], (1..8).filter{ println("call2"); it % 2 == 0 }.toList() ) + println("-------------------") + assertEquals([1,3,5,7], (1..8).filter{ println("call1"); it % 2 == 1 }.toList() ) """.trimIndent()) } @@ -46,6 +47,16 @@ class StdlibTest { """.trimIndent()) } + @Test + fun testAnyAndAll() = runTest { + eval(""" + assert( [1,2,3].any { it > 2 } ) + assert( ![1,2,3].any { it > 4 } ) + assert( [1,2,3].all { it <= 3 } ) + + """.trimIndent()) + } + @Test fun testRingBuffer() = runTest { eval("""