well redesigned class vals and vars, classScope introduced for class functions
This commit is contained in:
		
							parent
							
								
									4165da5e81
								
							
						
					
					
						commit
						63b2808109
					
				@ -17,6 +17,15 @@ class Compiler(
 | 
			
		||||
 | 
			
		||||
    class Settings
 | 
			
		||||
 | 
			
		||||
    private val initStack = mutableListOf<MutableList<Statement>>()
 | 
			
		||||
 | 
			
		||||
    val currentInitScope: MutableList<Statement> get() =
 | 
			
		||||
        initStack.lastOrNull() ?: cc.syntaxError("no initialization scope exists here")
 | 
			
		||||
 | 
			
		||||
    private fun pushInitScope(): MutableList<Statement> = mutableListOf<Statement>().also { initStack.add(it)}
 | 
			
		||||
 | 
			
		||||
    private fun popInitScope(): MutableList<Statement> = initStack.removeLast()
 | 
			
		||||
 | 
			
		||||
    private suspend fun parseScript(): Script {
 | 
			
		||||
        val statements = mutableListOf<Statement>()
 | 
			
		||||
        val start = cc.currentPos()
 | 
			
		||||
@ -830,7 +839,11 @@ class Compiler(
 | 
			
		||||
                cc.matchQualifiers("fun") -> parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
 | 
			
		||||
                cc.matchQualifiers("fn") -> parseFunctionDeclaration(isOpen = false, isExtern = isExtern)
 | 
			
		||||
 | 
			
		||||
                cc.matchQualifiers("val", "private", "static") -> parseVarDeclaration(false, Visibility.Private, isStatic = true)
 | 
			
		||||
                cc.matchQualifiers("val", "static") -> parseVarDeclaration(false, Visibility.Public, isStatic = true)
 | 
			
		||||
                cc.matchQualifiers("val", "private") -> parseVarDeclaration(false, Visibility.Private)
 | 
			
		||||
                cc.matchQualifiers("var", "static") -> parseVarDeclaration(true, Visibility.Public, isStatic = true)
 | 
			
		||||
                cc.matchQualifiers("var", "static", "private" ) -> parseVarDeclaration(true, Visibility.Private, isStatic = true)
 | 
			
		||||
                cc.matchQualifiers("var", "private") -> parseVarDeclaration(true, Visibility.Private)
 | 
			
		||||
                cc.matchQualifiers("val", "open") -> parseVarDeclaration(false, Visibility.Private, true)
 | 
			
		||||
                cc.matchQualifiers("var", "open") -> parseVarDeclaration(true, Visibility.Private, true)
 | 
			
		||||
@ -1090,6 +1103,8 @@ class Compiler(
 | 
			
		||||
        cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
 | 
			
		||||
        val t = cc.next()
 | 
			
		||||
 | 
			
		||||
        pushInitScope()
 | 
			
		||||
 | 
			
		||||
        val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) {
 | 
			
		||||
            // parse body
 | 
			
		||||
            parseScript().also {
 | 
			
		||||
@ -1100,6 +1115,8 @@ class Compiler(
 | 
			
		||||
            null
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val initScope = popInitScope()
 | 
			
		||||
 | 
			
		||||
        // create class
 | 
			
		||||
        val className = nameToken.value
 | 
			
		||||
 | 
			
		||||
@ -1131,6 +1148,13 @@ class Compiler(
 | 
			
		||||
            // 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)
 | 
			
		||||
                        .also { println("executed, ${classScope.objects}")}
 | 
			
		||||
            }
 | 
			
		||||
            newClass
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -1600,7 +1624,8 @@ class Compiler(
 | 
			
		||||
    private suspend fun parseVarDeclaration(
 | 
			
		||||
        isMutable: Boolean,
 | 
			
		||||
        visibility: Visibility,
 | 
			
		||||
        @Suppress("UNUSED_PARAMETER") isOpen: Boolean = false
 | 
			
		||||
        @Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
 | 
			
		||||
        isStatic: Boolean = false
 | 
			
		||||
    ): Statement {
 | 
			
		||||
        val nameToken = cc.next()
 | 
			
		||||
        val start = nameToken.pos
 | 
			
		||||
@ -1622,6 +1647,22 @@ class Compiler(
 | 
			
		||||
        val initialExpression = if (setNull) null else parseStatement(true)
 | 
			
		||||
            ?: throw ScriptError(eqToken.pos, "Expected initializer expression")
 | 
			
		||||
 | 
			
		||||
        if( isStatic) {
 | 
			
		||||
            // find objclass instance: this is tricky: this code executes in object initializer,
 | 
			
		||||
            // when creating instance, but we need to execute it in the class initializer which
 | 
			
		||||
            // is missing as for now. Add it to the compiler context?
 | 
			
		||||
            // add there
 | 
			
		||||
            // return
 | 
			
		||||
            currentInitScope += statement {
 | 
			
		||||
                val initValue = initialExpression?.execute(this)?.byValueCopy() ?: ObjNull
 | 
			
		||||
                // todo: get rid of classfield!
 | 
			
		||||
                (thisObj as ObjClass).createClassField(name, initValue, isMutable, visibility, pos)
 | 
			
		||||
                addItem(name, isMutable, initValue, visibility, ObjRecord.Type.Field)
 | 
			
		||||
                ObjVoid
 | 
			
		||||
            }
 | 
			
		||||
            return NopStatement
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return statement(nameToken.pos) { context ->
 | 
			
		||||
            if (context.containsLocal(name))
 | 
			
		||||
                throw ScriptError(nameToken.pos, "Variable $name is already defined")
 | 
			
		||||
 | 
			
		||||
@ -44,11 +44,14 @@ class CompilerContext(val tokens: List<Token>) {
 | 
			
		||||
            if (type != it.type) throw ScriptError(it.pos, message)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    fun syntaxError(at: Pos, message: String = "Syntax error"): Nothing {
 | 
			
		||||
        throw ScriptError(at, message)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun syntaxError(message: String = "Syntax error"): Nothing {
 | 
			
		||||
        throw ScriptError(currentPos(), message)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun currentPos(): Pos = tokens[currentIndex].pos
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,9 @@ open class Scope(
 | 
			
		||||
    fun raiseIllegalState(message: String = "Illegal argument error"): Nothing =
 | 
			
		||||
        raiseError(ObjIllegalStateException(this, message))
 | 
			
		||||
 | 
			
		||||
    fun raiseIllegalAssignment(message: String): Nothing =
 | 
			
		||||
        raiseError(ObjIllegalAssignmentException(this, message))
 | 
			
		||||
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    fun raiseNoSuchElement(message: String = "No such element"): Nothing =
 | 
			
		||||
        raiseError(ObjIllegalArgumentException(this, message))
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,16 @@ open class ObjClass(
 | 
			
		||||
    var constructorMeta: ArgsDeclaration? = null
 | 
			
		||||
    var instanceConstructor: Statement? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the scope for class methods, initialize class vars, etc.
 | 
			
		||||
     *
 | 
			
		||||
     * Important notice. When create a user class, e.g. from Lyng source, it should
 | 
			
		||||
     * be set to a scope by compiler, so it could access local closure, etc. Otherwise,
 | 
			
		||||
     * it will be initialized to default scope on first necessity, e.g. when used in
 | 
			
		||||
     * external, kotlin classes with [addClassConst] and [addClassFn], etc.
 | 
			
		||||
     */
 | 
			
		||||
    var classScope: Scope? = null
 | 
			
		||||
 | 
			
		||||
    val allParentsSet: Set<ObjClass> =
 | 
			
		||||
        parents.flatMap {
 | 
			
		||||
            listOf(it) + it.allParentsSet
 | 
			
		||||
@ -23,9 +33,10 @@ open class ObjClass(
 | 
			
		||||
 | 
			
		||||
    override val objClass: ObjClass by lazy { ObjClassType }
 | 
			
		||||
 | 
			
		||||
    // members: fields most often
 | 
			
		||||
    /**
 | 
			
		||||
     * members: fields most often. These are called with [ObjInstance] withs ths [ObjInstance.objClass]
 | 
			
		||||
     */
 | 
			
		||||
    private val members = mutableMapOf<String, ObjRecord>()
 | 
			
		||||
    private val classMembers = mutableMapOf<String, ObjRecord>()
 | 
			
		||||
 | 
			
		||||
    override fun toString(): String = className
 | 
			
		||||
 | 
			
		||||
@ -40,6 +51,7 @@ open class ObjClass(
 | 
			
		||||
        return instance
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    fun createField(
 | 
			
		||||
        name: String,
 | 
			
		||||
        initialValue: Obj,
 | 
			
		||||
@ -53,6 +65,11 @@ open class ObjClass(
 | 
			
		||||
        members[name] = ObjRecord(initialValue, isMutable, visibility)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun initClassScope(): Scope {
 | 
			
		||||
        if( classScope == null ) classScope = Scope()
 | 
			
		||||
        return classScope!!
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun createClassField(
 | 
			
		||||
        name: String,
 | 
			
		||||
        initialValue: Obj,
 | 
			
		||||
@ -60,10 +77,11 @@ open class ObjClass(
 | 
			
		||||
        visibility: Visibility = Visibility.Public,
 | 
			
		||||
        pos: Pos = Pos.builtIn
 | 
			
		||||
    ) {
 | 
			
		||||
        val existing = classMembers[name]
 | 
			
		||||
        initClassScope()
 | 
			
		||||
        val existing = classScope!!.objects[name]
 | 
			
		||||
        if( existing != null)
 | 
			
		||||
            throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
 | 
			
		||||
        classMembers[name] = ObjRecord(initialValue, isMutable, visibility)
 | 
			
		||||
        classScope!!.addItem(name, isMutable, initialValue, visibility)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun addFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
 | 
			
		||||
@ -91,15 +109,23 @@ open class ObjClass(
 | 
			
		||||
            ?: throw ScriptError(atPos, "symbol doesn't exist: $name")
 | 
			
		||||
 | 
			
		||||
    override suspend fun readField(scope: Scope, name: String): ObjRecord {
 | 
			
		||||
        classMembers[name]?.let {
 | 
			
		||||
        classScope?.objects?.get(name)?.let {
 | 
			
		||||
            return it
 | 
			
		||||
        }
 | 
			
		||||
        return super.readField(scope, name)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun writeField(scope: Scope, name: String, value: Obj) {
 | 
			
		||||
        initClassScope().objects[name]?.let {
 | 
			
		||||
            if( it.isMutable) it.value = value
 | 
			
		||||
            else scope.raiseIllegalAssignment("can't assign $name is not mutable")
 | 
			
		||||
        }
 | 
			
		||||
            ?: super.writeField(scope, name, value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments,
 | 
			
		||||
                                              onNotFoundResult: Obj?): Obj {
 | 
			
		||||
        return classMembers[name]?.value?.invoke(scope, this, args)
 | 
			
		||||
        return classScope?.objects?.get(name)?.value?.invoke(scope, this, args)
 | 
			
		||||
            ?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -10,8 +10,6 @@ import net.sergeych.lynon.LynonType
 | 
			
		||||
 */
 | 
			
		||||
class ObjInstanceClass(val name: String) : ObjClass(name) {
 | 
			
		||||
 | 
			
		||||
//    val onDeserilaized =
 | 
			
		||||
 | 
			
		||||
    override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
 | 
			
		||||
        val args = decoder.decodeAnyList(scope)
 | 
			
		||||
        println("deserializing constructor $name, $args params")
 | 
			
		||||
 | 
			
		||||
@ -2,12 +2,16 @@ package net.sergeych.lyng
 | 
			
		||||
 | 
			
		||||
import net.sergeych.lyng.obj.Obj
 | 
			
		||||
import net.sergeych.lyng.obj.ObjClass
 | 
			
		||||
import net.sergeych.lyng.obj.ObjVoid
 | 
			
		||||
 | 
			
		||||
fun String.toSource(name: String = "eval"): Source = Source(name, this)
 | 
			
		||||
 | 
			
		||||
sealed class ObjType {
 | 
			
		||||
    object Any : ObjType()
 | 
			
		||||
    object Int : ObjType()
 | 
			
		||||
    object Void: ObjType()
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -62,4 +66,8 @@ fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspe
 | 
			
		||||
        override suspend fun execute(scope: Scope): Obj = f(scope)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
object NopStatement: Statement(true, true, ObjType.Void) {
 | 
			
		||||
    override val pos: Pos = Pos.builtIn
 | 
			
		||||
 | 
			
		||||
    override suspend fun execute(scope: Scope): Obj = ObjVoid
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										35
									
								
								lynglib/src/commonTest/kotlin/OOTest.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								lynglib/src/commonTest/kotlin/OOTest.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
import kotlinx.coroutines.test.runTest
 | 
			
		||||
import net.sergeych.lyng.eval
 | 
			
		||||
import kotlin.test.Test
 | 
			
		||||
 | 
			
		||||
class OOTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testClassProps() = runTest {
 | 
			
		||||
        eval("""
 | 
			
		||||
            import lyng.time
 | 
			
		||||
            
 | 
			
		||||
            class Point(x,y) {
 | 
			
		||||
                static val origin = Point(0,0)
 | 
			
		||||
                static var center = origin
 | 
			
		||||
            }
 | 
			
		||||
            assertEquals(Point(0,0), Point.origin)
 | 
			
		||||
            assertEquals(Point(0,0), Point.center)
 | 
			
		||||
            Point.center = Point(1,2)
 | 
			
		||||
            assertEquals(Point(0,0), Point.origin)
 | 
			
		||||
            assertEquals(Point(1,2), Point.center)
 | 
			
		||||
            
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testClassMethods() = runTest {
 | 
			
		||||
        eval("""
 | 
			
		||||
            import lyng.time
 | 
			
		||||
            
 | 
			
		||||
            class Point(x,y) {
 | 
			
		||||
                private static var data = null
 | 
			
		||||
            }
 | 
			
		||||
            assertEquals(Point(0,0), Point(0,0) )
 | 
			
		||||
            
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user