fixed many bugs with variable visibility in mixed scopes
This commit is contained in:
parent
eca746b189
commit
464a6dcb99
14
docs/development/scope_resolution.md
Normal file
14
docs/development/scope_resolution.md
Normal 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
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -48,13 +48,12 @@ data class ArgsDeclaration(val params: List<Item>, 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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
@ -45,6 +45,17 @@ class Compiler(
|
||||
|
||||
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 {
|
||||
val statements = mutableListOf<Statement>()
|
||||
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,6 +1213,7 @@ class Compiler(
|
||||
|
||||
private suspend fun parseClassDeclaration(): Statement {
|
||||
val nameToken = cc.requireToken(Token.Type.ID)
|
||||
return inCodeContext(CodeContext.ClassBody(nameToken.value)) {
|
||||
val constructorArgsDeclaration =
|
||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
|
||||
parseArgsDeclaration(isClassDeclaration = true)
|
||||
@ -1257,7 +1269,7 @@ class Compiler(
|
||||
constructorMeta = constructorArgsDeclaration
|
||||
}
|
||||
|
||||
return statement {
|
||||
statement {
|
||||
// the main statement should create custom ObjClass instance with field
|
||||
// accessors, constructor registration, etc.
|
||||
addItem(className, false, newClass)
|
||||
@ -1271,6 +1283,8 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun getLabel(maxDepth: Int = 2): String? {
|
||||
var cnt = 0
|
||||
@ -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,6 +1692,8 @@ class Compiler(
|
||||
|
||||
if (cc.current().type == Token.Type.COLON) parseTypeDeclaration()
|
||||
|
||||
return inCodeContext(CodeContext.Function(name)) {
|
||||
|
||||
// Here we should be at open body
|
||||
val fnStatements = if (isExtern)
|
||||
statement { raiseError("extern function not provided: $name") }
|
||||
@ -1691,9 +1706,10 @@ class Compiler(
|
||||
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!)
|
||||
// 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.raiseError("bug: closure not set")
|
||||
?: callerContext
|
||||
|
||||
// load params from caller context
|
||||
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
|
||||
@ -1704,7 +1720,8 @@ class Compiler(
|
||||
}
|
||||
val fnCreateStatement = statement(start) { context ->
|
||||
// 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
|
||||
|
||||
val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody)
|
||||
@ -1729,12 +1746,13 @@ class Compiler(
|
||||
// saved the proper context in the closure
|
||||
annotatedFnBody
|
||||
}
|
||||
return if (isStatic) {
|
||||
if (isStatic) {
|
||||
currentInitScope += fnCreateStatement
|
||||
NopStatement
|
||||
} else
|
||||
fnCreateStatement
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun parseBlock(skipLeadingBrace: Boolean = false): Statement {
|
||||
val startPos = cc.currentPos()
|
||||
|
@ -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 =
|
||||
|
@ -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") {
|
||||
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//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 {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
@ -40,12 +40,12 @@ class ObjFlowBuilder(val output: SendChannel<Obj>) : Obj() {
|
||||
val data = requireOnlyArg<Obj>()
|
||||
try {
|
||||
val channel = thisAs<ObjFlowBuilder>().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<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 fun checkNotCancelled(scope: Scope) {
|
||||
if( isCancelled )
|
||||
if (isCancelled)
|
||||
scope.raiseIllegalState("iteration is cancelled")
|
||||
}
|
||||
|
||||
suspend fun hasNext(scope: Scope): ObjBool {
|
||||
checkNotCancelled(scope)
|
||||
// cold start:
|
||||
|
@ -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 =
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
@ -93,5 +93,23 @@ 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()
|
||||
|
||||
|
@ -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 {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -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("""
|
||||
|
Loading…
x
Reference in New Issue
Block a user