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