fix #11 and private visibility for constructor params, fields and methods.
This commit is contained in:
		
							parent
							
								
									fffa3d31bb
								
							
						
					
					
						commit
						20c81dbf2e
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -13,3 +13,4 @@ xcuserdata
 | 
			
		||||
*.gpg
 | 
			
		||||
.gigaide
 | 
			
		||||
/kotlin-js-store/yarn.lock
 | 
			
		||||
/test.lyng
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										117
									
								
								docs/OOP.md
									
									
									
									
									
								
							
							
						
						
									
										117
									
								
								docs/OOP.md
									
									
									
									
									
								
							@ -1,12 +1,19 @@
 | 
			
		||||
# OO implementation in Lyng
 | 
			
		||||
 | 
			
		||||
Short introduction
 | 
			
		||||
## Declaration 
 | 
			
		||||
 | 
			
		||||
The class clause looks like
 | 
			
		||||
 | 
			
		||||
    class Point(x,y)
 | 
			
		||||
    assert( Point is Class ) 
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
It creates new `Class` with two fields. Here is the more practical sample:
 | 
			
		||||
 | 
			
		||||
    class Point(x,y) {
 | 
			
		||||
        fun length() { sqrt(x*x + y*y) } 
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    assert( Point is Class )
 | 
			
		||||
    val p = Point(3,4)
 | 
			
		||||
    assert(p is Point)
 | 
			
		||||
    assertEquals(5, p.length())
 | 
			
		||||
@ -32,7 +39,111 @@ Form now on `Point` is a class, it's type is `Class`, and we can create instance
 | 
			
		||||
example above.
 | 
			
		||||
 | 
			
		||||
Class point has a _method_, or a _member function_ `length()` that uses its _fields_ `x` and `y` to
 | 
			
		||||
calculate the magnitude.
 | 
			
		||||
calculate the magnitude. Length is called
 | 
			
		||||
 | 
			
		||||
### default values in constructor
 | 
			
		||||
 | 
			
		||||
Constructor arguments are the same as function arguments except visibility
 | 
			
		||||
statements discussed later, there could be default values, ellipsis, etc.
 | 
			
		||||
 | 
			
		||||
    class Point(x=0,y=0) 
 | 
			
		||||
    val p = Point()
 | 
			
		||||
    assert( p.x == 0 && p.y == 0 )
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
## Methods
 | 
			
		||||
 | 
			
		||||
Functions defined inside a class body are methods, and unless declared
 | 
			
		||||
`private` are available to be called from outside the class:
 | 
			
		||||
 | 
			
		||||
    class Point(x,y) {
 | 
			
		||||
        // public method declaration:
 | 
			
		||||
        fun length() { sqrt(d2()) }
 | 
			
		||||
 | 
			
		||||
        // private method:
 | 
			
		||||
        private fun d2() {x*x + y*y}
 | 
			
		||||
    }
 | 
			
		||||
    val p = Point(3,4)
 | 
			
		||||
    // private called from inside public: OK
 | 
			
		||||
    assertEquals( 5, p.length() )
 | 
			
		||||
    // but us not available directly
 | 
			
		||||
    assertThrows() { p.d2() }
 | 
			
		||||
    void
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
## fields and visibility
 | 
			
		||||
 | 
			
		||||
It is possible to add non-constructor fields:
 | 
			
		||||
 | 
			
		||||
    class Point(x,y) {
 | 
			
		||||
        fun length() { sqrt(x*x + y*y) } 
 | 
			
		||||
    
 | 
			
		||||
        // set at construction time:   
 | 
			
		||||
        val initialLength = length()
 | 
			
		||||
    }
 | 
			
		||||
    val p = Point(3,4)
 | 
			
		||||
    p.x = 3
 | 
			
		||||
    p.y = 0
 | 
			
		||||
    assertEquals( 3, p.length() )
 | 
			
		||||
    // but initial length could not be changed after as declard val:
 | 
			
		||||
    assert( p.initialLength == 5 )
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
### Mutable fields
 | 
			
		||||
 | 
			
		||||
Are declared with var
 | 
			
		||||
 | 
			
		||||
    class Point(x,y) {
 | 
			
		||||
        var isSpecial = false
 | 
			
		||||
    }
 | 
			
		||||
    val p = Point(0,0)
 | 
			
		||||
    assert( p.isSpecial == false )
 | 
			
		||||
 | 
			
		||||
    p.isSpecial = true
 | 
			
		||||
    assert( p.isSpecial == true )
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
### Private fields
 | 
			
		||||
 | 
			
		||||
Private fields are visible only _inside the class instance_:
 | 
			
		||||
 | 
			
		||||
    class SecretCounter {
 | 
			
		||||
        private var count = 0
 | 
			
		||||
        
 | 
			
		||||
        fun increment() {
 | 
			
		||||
            count++
 | 
			
		||||
            void // hide counter
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        fun isEnough() {
 | 
			
		||||
            count > 10
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    val c = SecretCounter()
 | 
			
		||||
    assert( c.isEnough() == false )
 | 
			
		||||
    assert( c.increment() == void )
 | 
			
		||||
    for( i in 0..10 ) c.increment()
 | 
			
		||||
    assert( c.isEnough() )
 | 
			
		||||
 | 
			
		||||
    // but the count is not available outside:
 | 
			
		||||
    assertThrows() { c.count }
 | 
			
		||||
    void
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
It is possible to provide private constructor parameters so they can be
 | 
			
		||||
set at construction but not available outside the class:
 | 
			
		||||
 | 
			
		||||
    class SecretCounter(private var count = 0) {
 | 
			
		||||
        // ...
 | 
			
		||||
    }
 | 
			
		||||
    val c = SecretCounter(10)
 | 
			
		||||
    assertThrows() { c.count }
 | 
			
		||||
    void
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Theory
 | 
			
		||||
 | 
			
		||||
## Basic principles:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -23,10 +23,12 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
 | 
			
		||||
    suspend fun assignToContext(
 | 
			
		||||
        context: Context,
 | 
			
		||||
        fromArgs: Arguments = context.args,
 | 
			
		||||
        defaultAccessType: Compiler.AccessType = Compiler.AccessType.Var
 | 
			
		||||
        defaultAccessType: Compiler.AccessType = Compiler.AccessType.Var,
 | 
			
		||||
        defaultVisibility: Compiler.Visibility = Compiler.Visibility.Public
 | 
			
		||||
    ) {
 | 
			
		||||
        fun assign(a: Item, value: Obj) {
 | 
			
		||||
            context.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value)
 | 
			
		||||
            context.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value,
 | 
			
		||||
                a.visibility ?: defaultVisibility)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        suspend fun processHead(index: Int): Int {
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,13 @@ class Compiler(
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Token.Type.PRIVATE, Token.Type.PROTECTED -> {
 | 
			
		||||
                    if(cc.nextIdValue() in setOf("var", "val", "class", "fun", "fn")) {
 | 
			
		||||
                        continue
 | 
			
		||||
                    } else
 | 
			
		||||
                        throw ScriptError(t.pos, "unexpected keyword ${t.value}")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Token.Type.PLUS2, Token.Type.MINUS2 -> {
 | 
			
		||||
                    cc.previous()
 | 
			
		||||
                    parseExpression(cc)
 | 
			
		||||
@ -408,7 +415,7 @@ class Compiler(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enum class Visibility {
 | 
			
		||||
        Public, Private, Protected, Internal
 | 
			
		||||
        Public, Private, Protected//, Internal
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -429,43 +436,16 @@ class Compiler(
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Token.Type.NEWLINE -> {}
 | 
			
		||||
                Token.Type.PROTECTED, Token.Type.PRIVATE -> {
 | 
			
		||||
                    if (!isClassDeclaration) {
 | 
			
		||||
                        cc.restorePos(startPos); return null
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Token.Type.ID -> {
 | 
			
		||||
                    // visibility
 | 
			
		||||
                    val visibility = when (t.value) {
 | 
			
		||||
                        "private" -> {
 | 
			
		||||
                            if (!isClassDeclaration) {
 | 
			
		||||
                                cc.restorePos(startPos); return null
 | 
			
		||||
                            }
 | 
			
		||||
                            t = cc.next()
 | 
			
		||||
                            Visibility.Private
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        "protected" -> {
 | 
			
		||||
                            if (!isClassDeclaration) {
 | 
			
		||||
                                cc.restorePos(startPos); return null
 | 
			
		||||
                            }
 | 
			
		||||
                            t = cc.next()
 | 
			
		||||
                            Visibility.Protected
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        "internal" -> {
 | 
			
		||||
                            if (!isClassDeclaration) {
 | 
			
		||||
                                cc.restorePos(startPos); return null
 | 
			
		||||
                            }
 | 
			
		||||
                            t = cc.next()
 | 
			
		||||
                            Visibility.Internal
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        "public" -> {
 | 
			
		||||
                            if (!isClassDeclaration) {
 | 
			
		||||
                                cc.restorePos(startPos); return null
 | 
			
		||||
                            }
 | 
			
		||||
                            t = cc.next()
 | 
			
		||||
                            Visibility.Public
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        else -> null
 | 
			
		||||
                    }
 | 
			
		||||
                    val visibility = if( isClassDeclaration )
 | 
			
		||||
                        cc.getVisibility(Visibility.Public)
 | 
			
		||||
                    else Visibility.Public
 | 
			
		||||
                    // val/var?
 | 
			
		||||
                    val access = when (t.value) {
 | 
			
		||||
                        "val" -> {
 | 
			
		||||
@ -703,7 +683,7 @@ class Compiler(
 | 
			
		||||
        val nameToken = cc.requireToken(Token.Type.ID)
 | 
			
		||||
        val constructorArgsDeclaration =
 | 
			
		||||
            if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
 | 
			
		||||
                parseArgsDeclaration(cc)
 | 
			
		||||
                parseArgsDeclaration(cc, isClassDeclaration = true)
 | 
			
		||||
            else null
 | 
			
		||||
 | 
			
		||||
        if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
 | 
			
		||||
@ -752,7 +732,9 @@ class Compiler(
 | 
			
		||||
                    }
 | 
			
		||||
                    Visibility.Protected ->
 | 
			
		||||
                        thisObj.protectedFields += name
 | 
			
		||||
                    else -> {
 | 
			
		||||
 | 
			
		||||
                    Visibility.Private -> {
 | 
			
		||||
                        //println("private field: $name")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -762,20 +744,6 @@ class Compiler(
 | 
			
		||||
        // inheritance must alter this code:
 | 
			
		||||
        val newClass = ObjClass(className).apply {
 | 
			
		||||
            instanceConstructor = constructorCode
 | 
			
		||||
            constructorArgsDeclaration?.let { cad ->
 | 
			
		||||
                // we need accessors for all fields:
 | 
			
		||||
                for (f in cad.params) {
 | 
			
		||||
                    createField(
 | 
			
		||||
                        f.name,
 | 
			
		||||
                        statement {
 | 
			
		||||
                            val context = (thisObj as ObjInstance).instanceContext
 | 
			
		||||
                            context[f.name]?.value ?: raiseError("field is not initialized: ${f.name}")
 | 
			
		||||
                        },
 | 
			
		||||
                        true,
 | 
			
		||||
//                        (f.accessType ?: defaultAccess).isMutable,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return statement {
 | 
			
		||||
@ -1100,6 +1068,8 @@ class Compiler(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun parseFunctionDeclaration(tokens: CompilerContext): Statement {
 | 
			
		||||
        val visibility = tokens.getVisibility()
 | 
			
		||||
 | 
			
		||||
        var t = tokens.next()
 | 
			
		||||
        val start = t.pos
 | 
			
		||||
        val name = if (t.type != Token.Type.ID)
 | 
			
		||||
@ -1136,7 +1106,7 @@ class Compiler(
 | 
			
		||||
            // we added fn in the context. now we must save closure
 | 
			
		||||
            // for the function
 | 
			
		||||
            closure = context
 | 
			
		||||
            context.addItem(name, false, fnBody)
 | 
			
		||||
            context.addItem(name, false, fnBody, visibility)
 | 
			
		||||
            // as the function can be called from anywhere, we have
 | 
			
		||||
            // saved the proper context in the closure
 | 
			
		||||
            fnBody
 | 
			
		||||
@ -1162,6 +1132,14 @@ class Compiler(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement {
 | 
			
		||||
        // we are just after var/val, visibility if exists is 2 steps behind
 | 
			
		||||
        val visibility = when( tokens.atOffset(-2)?.type ) {
 | 
			
		||||
            Token.Type.PRIVATE ->
 | 
			
		||||
                Visibility.Private
 | 
			
		||||
            Token.Type.PROTECTED -> Visibility.Protected
 | 
			
		||||
            else -> Visibility.Public
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val nameToken = tokens.next()
 | 
			
		||||
        val start = nameToken.pos
 | 
			
		||||
        if (nameToken.type != Token.Type.ID)
 | 
			
		||||
@ -1190,7 +1168,7 @@ class Compiler(
 | 
			
		||||
            // create a separate copy:
 | 
			
		||||
            val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
 | 
			
		||||
 | 
			
		||||
            context.addItem(name, mutable, initValue)
 | 
			
		||||
            context.addItem(name, mutable, initValue, visibility)
 | 
			
		||||
            ObjVoid
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ internal class CompilerContext(val tokens: List<Token>) {
 | 
			
		||||
    var loopLevel = 0
 | 
			
		||||
        private set
 | 
			
		||||
 | 
			
		||||
    inline fun <T> parseLoop(f: () -> T): Pair<Boolean,T> {
 | 
			
		||||
    inline fun <T> parseLoop(f: () -> T): Pair<Boolean, T> {
 | 
			
		||||
        if (++loopLevel == 0) breakFound = false
 | 
			
		||||
        val result = f()
 | 
			
		||||
        return Pair(breakFound, result).also {
 | 
			
		||||
@ -98,4 +98,40 @@ internal class CompilerContext(val tokens: List<Token>) {
 | 
			
		||||
        breakFound = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return value of the next token if it is an identifier, null otherwise.
 | 
			
		||||
     * Does not change position.
 | 
			
		||||
     */
 | 
			
		||||
    fun nextIdValue(): String? {
 | 
			
		||||
        return if (hasNext()) {
 | 
			
		||||
            val nt = tokens[currentIndex]
 | 
			
		||||
            if (nt.type == Token.Type.ID)
 | 
			
		||||
                nt.value
 | 
			
		||||
            else null
 | 
			
		||||
        } else null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    fun current(): Token = tokens[currentIndex]
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If the token at current position plus offset (could be negative) exists, returns it, otherwise returns null.
 | 
			
		||||
     */
 | 
			
		||||
    fun atOffset(offset: Int): Token? =
 | 
			
		||||
        if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Scan backwards as deep as specified looking for visibility token. Does not change position.
 | 
			
		||||
     */
 | 
			
		||||
    fun getVisibility(default: Compiler.Visibility = Compiler.Visibility.Public, depths: Int = 2): Compiler.Visibility {
 | 
			
		||||
        for( i in -depths .. -1) {
 | 
			
		||||
            when( atOffset(i)?.type) {
 | 
			
		||||
                Token.Type.PROTECTED -> return Compiler.Visibility.Protected
 | 
			
		||||
                Token.Type.PRIVATE -> return Compiler.Visibility.Private
 | 
			
		||||
                else -> {}
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return default
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -28,7 +28,8 @@ class Context(
 | 
			
		||||
    fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastError(this, msg))
 | 
			
		||||
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    fun raiseSymbolNotFound(name: String): Nothing = raiseError(ObjSymbolNotDefinedError(this, "symbol is not defined: $name"))
 | 
			
		||||
    fun raiseSymbolNotFound(name: String): Nothing =
 | 
			
		||||
        raiseError(ObjSymbolNotDefinedError(this, "symbol is not defined: $name"))
 | 
			
		||||
 | 
			
		||||
    fun raiseError(message: String): Nothing {
 | 
			
		||||
        throw ExecutionError(ObjError(this, message))
 | 
			
		||||
@ -73,8 +74,13 @@ class Context(
 | 
			
		||||
 | 
			
		||||
    fun copy() = Context(this, args, pos, thisObj)
 | 
			
		||||
 | 
			
		||||
    fun addItem(name: String, isMutable: Boolean, value: Obj): ObjRecord {
 | 
			
		||||
        return ObjRecord(value, isMutable).also { objects.put(name, it) }
 | 
			
		||||
    fun addItem(
 | 
			
		||||
        name: String,
 | 
			
		||||
        isMutable: Boolean,
 | 
			
		||||
        value: Obj,
 | 
			
		||||
        visibility: Compiler.Visibility = Compiler.Visibility.Public
 | 
			
		||||
    ): ObjRecord {
 | 
			
		||||
        return ObjRecord(value, isMutable, visibility).also { objects.put(name, it) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getOrCreateNamespace(name: String): ObjClass {
 | 
			
		||||
 | 
			
		||||
@ -280,6 +280,8 @@ private class Parser(fromPos: Pos) {
 | 
			
		||||
                        when (text) {
 | 
			
		||||
                            "in" -> Token("in", from, Token.Type.IN)
 | 
			
		||||
                            "is" -> Token("is", from, Token.Type.IS)
 | 
			
		||||
                            "protected" -> Token("protected", from, Token.Type.PROTECTED)
 | 
			
		||||
                            "private" -> Token("private", from, Token.Type.PRIVATE)
 | 
			
		||||
                            else -> Token(text, from, Token.Type.ID)
 | 
			
		||||
                        }
 | 
			
		||||
                } else
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,8 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
 | 
			
		||||
        AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
 | 
			
		||||
        SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
 | 
			
		||||
        LABEL, ATLABEL, // label@ at@label
 | 
			
		||||
        PRIVATE, PROTECTED,
 | 
			
		||||
        //PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS,
 | 
			
		||||
        ELLIPSIS, DOTDOT, DOTDOTLT,
 | 
			
		||||
        NEWLINE,
 | 
			
		||||
        EOF,
 | 
			
		||||
 | 
			
		||||
@ -1453,4 +1453,15 @@ class ScriptTest {
 | 
			
		||||
            assertEquals(sqrt(109), p.length())
 | 
			
		||||
            """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testPrivateConstructorParams() = runTest {
 | 
			
		||||
        val c = Context()
 | 
			
		||||
        c.eval("""
 | 
			
		||||
            class Point(private var x,y)
 | 
			
		||||
            val p = Point(1,2)
 | 
			
		||||
            p.y = 101
 | 
			
		||||
            assertThrows() { p.x = 10 }
 | 
			
		||||
            """)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user