fixed many bugs with variable visibility in mixed scopes

This commit is contained in:
Sergey Chernov 2025-08-17 12:18:51 +03:00
parent eca746b189
commit 464a6dcb99
14 changed files with 285 additions and 162 deletions

View File

@ -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
}
}

View File

@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych" group = "net.sergeych"
version = "0.8.10-SNAPSHOT" version = "0.8.11-SNAPSHOT"
buildscript { buildscript {
repositories { repositories {

View File

@ -48,13 +48,12 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
arguments: Arguments = scope.args, arguments: Arguments = scope.args,
defaultAccessType: AccessType = AccessType.Var, defaultAccessType: AccessType = AccessType.Var,
defaultVisibility: Visibility = Visibility.Public, defaultVisibility: Visibility = Visibility.Public,
defaultRecordType: ObjRecord.Type = ObjRecord.Type.ConstructorField
) { ) {
fun assign(a: Item, value: Obj) { fun assign(a: Item, value: Obj) {
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable,
value.byValueCopy(), value.byValueCopy(),
a.visibility ?: defaultVisibility, a.visibility ?: defaultVisibility,
recordType = defaultRecordType) recordType = ObjRecord.Type.Argument)
} }
// will be used with last lambda arg fix // will be used with last lambda arg fix

View File

@ -24,10 +24,33 @@ import net.sergeych.lyng.obj.ObjRecord
* Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols * Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols
* from [closureScope] with proper precedence * 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? { override fun get(name: String): ObjRecord? {
// closure should be treated below callScope // we take arguments from the callerScope, the rest
return super.get(name) ?: closureScope.get(name) // 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
}
}

View File

@ -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()
}

View File

@ -45,6 +45,17 @@ class Compiler(
private fun popInitScope(): MutableList<Statement> = initStack.removeLast() private fun popInitScope(): MutableList<Statement> = initStack.removeLast()
private val codeContexts = mutableListOf<CodeContext>(CodeContext.Module(null))
private suspend fun <T>inCodeContext(context: CodeContext,f: suspend ()->T): T {
return try {
codeContexts.add(context)
f()
} finally {
codeContexts.removeLast()
}
}
private suspend fun parseScript(): Script { private suspend fun parseScript(): Script {
val statements = mutableListOf<Statement>() val statements = mutableListOf<Statement>()
val start = cc.currentPos() val start = cc.currentPos()
@ -504,7 +515,7 @@ class Compiler(
val callStatement = statement { val callStatement = statement {
// and the source closure of the lambda which might have other thisObj. // 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) { if (argsDeclaration == null) {
// no args: automatic var 'it' // no args: automatic var 'it'
val l = args.list val l = args.list
@ -516,7 +527,7 @@ class Compiler(
// more args: it is a list of args // more args: it is a list of args
else -> ObjList(l.toMutableList()) else -> ObjList(l.toMutableList())
} }
context.addItem("it", false, itValue) context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument)
} else { } else {
// assign vars as declared the standard way // assign vars as declared the standard way
argsDeclaration.assignToContext(context, defaultAccessType = AccessType.Val) argsDeclaration.assignToContext(context, defaultAccessType = AccessType.Val)
@ -525,7 +536,7 @@ class Compiler(
} }
return Accessor { x -> return Accessor { x ->
if (closure == null) closure = x closure = x
callStatement.asReadonly callStatement.asReadonly
} }
} }
@ -1202,6 +1213,7 @@ class Compiler(
private suspend fun parseClassDeclaration(): Statement { private suspend fun parseClassDeclaration(): Statement {
val nameToken = cc.requireToken(Token.Type.ID) val nameToken = cc.requireToken(Token.Type.ID)
return inCodeContext(CodeContext.ClassBody(nameToken.value)) {
val constructorArgsDeclaration = val constructorArgsDeclaration =
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
parseArgsDeclaration(isClassDeclaration = true) parseArgsDeclaration(isClassDeclaration = true)
@ -1257,7 +1269,7 @@ class Compiler(
constructorMeta = constructorArgsDeclaration constructorMeta = constructorArgsDeclaration
} }
return statement { statement {
// the main statement should create custom ObjClass instance with field // the main statement should create custom ObjClass instance with field
// accessors, constructor registration, etc. // accessors, constructor registration, etc.
addItem(className, false, newClass) addItem(className, false, newClass)
@ -1271,6 +1283,8 @@ class Compiler(
} }
} }
}
private fun getLabel(maxDepth: Int = 2): String? { private fun getLabel(maxDepth: Int = 2): String? {
var cnt = 0 var cnt = 0
@ -1639,8 +1653,7 @@ class Compiler(
} }
} }
private suspend fun private suspend fun parseFunctionDeclaration(
parseFunctionDeclaration(
visibility: Visibility = Visibility.Public, visibility: Visibility = Visibility.Public,
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false, @Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
isExtern: Boolean = false, isExtern: Boolean = false,
@ -1654,7 +1667,7 @@ class Compiler(
else t.value else t.value
val annotation = lastAnnotation val annotation = lastAnnotation
val parentContext = codeContexts.last()
t = cc.next() t = cc.next()
// Is extension? // Is extension?
@ -1679,6 +1692,8 @@ class Compiler(
if (cc.current().type == Token.Type.COLON) parseTypeDeclaration() if (cc.current().type == Token.Type.COLON) parseTypeDeclaration()
return inCodeContext(CodeContext.Function(name)) {
// Here we should be at open body // Here we should be at open body
val fnStatements = if (isExtern) val fnStatements = if (isExtern)
statement { raiseError("extern function not provided: $name") } statement { raiseError("extern function not provided: $name") }
@ -1691,9 +1706,10 @@ class Compiler(
callerContext.pos = start callerContext.pos = start
// restore closure where the function was defined, and making a copy of it // restore closure where the function was defined, and making a copy of it
// for local space (otherwise it will write local stuff to closure!) // 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) } val context = closure?.let { ClosureScope(callerContext, it) }
?: callerContext.raiseError("bug: closure not set") ?: callerContext
// load params from caller context // load params from caller context
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val) argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
@ -1704,7 +1720,8 @@ class Compiler(
} }
val fnCreateStatement = statement(start) { context -> val fnCreateStatement = statement(start) { context ->
// we added fn in the context. now we must save closure // we added fn in the context. now we must save closure
// for the function // for the function, unless we're in the class scope:
if( isStatic || parentContext !is CodeContext.ClassBody)
closure = context closure = context
val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody) val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody)
@ -1729,12 +1746,13 @@ class Compiler(
// saved the proper context in the closure // saved the proper context in the closure
annotatedFnBody annotatedFnBody
} }
return if (isStatic) { if (isStatic) {
currentInitScope += fnCreateStatement currentInitScope += fnCreateStatement
NopStatement NopStatement
} else } else
fnCreateStatement fnCreateStatement
} }
}
private suspend fun parseBlock(skipLeadingBrace: Boolean = false): Statement { private suspend fun parseBlock(skipLeadingBrace: Boolean = false): Statement {
val startPos = cc.currentPos() val startPos = cc.currentPos()

View File

@ -131,9 +131,16 @@ open class Scope(
open operator fun get(name: String): ObjRecord? = open operator fun get(name: String): ObjRecord? =
if (name == "this") thisObj.asReadonly if (name == "this") thisObj.asReadonly
else { else {
objects[name] (objects[name]
?: parent?.get(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 = 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 { companion object {
fun new(): Scope = fun new(): Scope =

View File

@ -296,9 +296,14 @@ open class Obj {
args.firstAndOnly().callOn(copy(Arguments(thisObj))) args.firstAndOnly().callOn(copy(Arguments(thisObj)))
} }
addFn("apply") { addFn("apply") {
val newContext = (thisObj as? ObjInstance)?.instanceScope ?: this val body = args.firstAndOnly()
args.firstAndOnly() (thisObj as? ObjInstance)?.let {
.callOn(newContext) 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 thisObj
} }
addFn("also") { addFn("also") {

View File

@ -17,31 +17,26 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Arguments //class ObjDelegateContext()
import net.sergeych.lyng.Scope //
import net.sergeych.lyng.Statement //class ObjDelegate(
import net.sergeych.lyng.statement // val getter: Statement,
// val setter: Statement = statement { raiseNotImplemented("setter is not implemented") }
class ObjDelegateContext() //): Obj() {
//
class ObjDelegate( // override suspend fun assign(scope: Scope, other: Obj): Obj? {
val getter: Statement, // setter.execute(scope.copy(Arguments(other)))
val setter: Statement = statement { raiseNotImplemented("setter is not implemented") } // return other
): Obj() { // }
//
override suspend fun assign(scope: Scope, other: Obj): Obj? { // companion object {
setter.execute(scope.copy(Arguments(other))) // val type = object: ObjClass("Delegate") {
return other // override suspend fun callOn(scope: Scope): Obj {
} // scope.raiseError("Delegate should not be constructed directly")
// }
companion object { // }.apply {
val type = object: ObjClass("Delegate") { //
override suspend fun callOn(scope: Scope): Obj { // }
scope.raiseError("Delegate should not be constructed directly") // }
} //
}.apply { //}
}
}
}

View File

@ -40,12 +40,12 @@ class ObjFlowBuilder(val output: SendChannel<Obj>) : Obj() {
val data = requireOnlyArg<Obj>() val data = requireOnlyArg<Obj>()
try { try {
val channel = thisAs<ObjFlowBuilder>().output val channel = thisAs<ObjFlowBuilder>().output
if( !channel.isClosedForSend ) if (!channel.isClosedForSend)
channel.send(data) channel.send(data)
else else
throw ScriptFlowIsNoMoreCollected() throw ScriptFlowIsNoMoreCollected()
} catch (x: Exception) { } catch (x: Exception) {
if( x !is CancellationException ) if (x !is CancellationException)
x.printStackTrace() x.printStackTrace()
throw ScriptFlowIsNoMoreCollected() throw ScriptFlowIsNoMoreCollected()
} }
@ -62,12 +62,10 @@ private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChann
globalLaunch { globalLaunch {
try { try {
producer.execute(builderScope) producer.execute(builderScope)
} } catch (x: ScriptFlowIsNoMoreCollected) {
catch(x: ScriptFlowIsNoMoreCollected) {
x.printStackTrace() x.printStackTrace()
// premature flow closing, OK // premature flow closing, OK
} } catch (x: Exception) {
catch(x: Exception) {
x.printStackTrace() x.printStackTrace()
} }
channel.close() channel.close()
@ -87,7 +85,11 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() {
}.apply { }.apply {
addFn("iterator") { addFn("iterator") {
val objFlow = thisAs<ObjFlow>() val objFlow = thisAs<ObjFlow>()
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 var isCancelled = false
private fun checkNotCancelled(scope: Scope) { private fun checkNotCancelled(scope: Scope) {
if( isCancelled ) if (isCancelled)
scope.raiseIllegalState("iteration is cancelled") scope.raiseIllegalState("iteration is cancelled")
} }
suspend fun hasNext(scope: Scope): ObjBool { suspend fun hasNext(scope: Scope): ObjBool {
checkNotCancelled(scope) checkNotCancelled(scope)
// cold start: // cold start:

View File

@ -35,11 +35,15 @@ data class ObjRecord(
Field(true, true), Field(true, true),
@Suppress("unused") @Suppress("unused")
Fun, Fun,
@Suppress("unused")
ConstructorField(true, true), ConstructorField(true, true),
Argument(true, true),
@Suppress("unused") @Suppress("unused")
Class, Class,
Enum, Enum,
Other Other;
val isArgument get() = this == Argument
} }
@Suppress("unused") @Suppress("unused")
fun qualifiedName(name: String): String = fun qualifiedName(name: String): String =

View File

@ -35,7 +35,7 @@ fun Iterable.filter(predicate) {
val list = this val list = this
flow { flow {
for( item in list ) { for( item in list ) {
if( predicate(item) ) { if( predicate(item) ) {ln
emit(item) emit(item)
} }
} }
@ -93,5 +93,23 @@ fun Iterable.joinToString(prefix=" ", transformer=null) {
result ?: "" 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() """.trimIndent()

View File

@ -1359,9 +1359,6 @@ class ScriptTest {
} }
val prefix = ":" val prefix = ":"
val lambda = {
prefix + getText() + "!"
}
val text = "invalid" val text = "invalid"
val t1 = T("foo") val t1 = T("foo")
@ -1371,18 +1368,22 @@ class ScriptTest {
// it must take "text" from class t1: // it must take "text" from class t1:
assertEquals("foo", text) assertEquals("foo", text)
assertEquals( "foo!", getText() ) assertEquals( "foo!", getText() )
assertEquals( ":foo!!", lambda() ) assertEquals( ":foo!!", {
prefix + getText() + "!"
}())
} }
t2.apply { t2.apply {
assertEquals("bar", text) assertEquals("bar", text)
assertEquals( "bar!", getText() ) assertEquals( "bar!", getText() )
assertEquals( ":bar!!", lambda() ) assertEquals( ":bar!!", {
prefix + getText() + "!"
}())
} }
// worst case: names clash // worst case: names clash
fun badOne() { fun badOne() {
val prefix = "&" val prefix = "&"
t1.apply { t1.apply {
assertEquals( ":foo!!", lambda() ) assertEquals( ":foo!!", prefix + getText() + "!" )
} }
} }
badOne() badOne()
@ -2941,5 +2942,4 @@ class ScriptTest {
} }
} }

View File

@ -23,8 +23,9 @@ class StdlibTest {
@Test @Test
fun testIterableFilter() = runTest { fun testIterableFilter() = runTest {
eval(""" eval("""
assertEquals([1,3,5,7], (1..8).filter{ it % 2 == 1 }.toList() ) assertEquals([2,4,6,8], (1..8).filter{ println("call2"); it % 2 == 0 }.toList() )
assertEquals([2,4,6,8], (1..8).filter{ it % 2 == 0 }.toList() ) println("-------------------")
assertEquals([1,3,5,7], (1..8).filter{ println("call1"); it % 2 == 1 }.toList() )
""".trimIndent()) """.trimIndent())
} }
@ -46,6 +47,16 @@ class StdlibTest {
""".trimIndent()) """.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 @Test
fun testRingBuffer() = runTest { fun testRingBuffer() = runTest {
eval(""" eval("""