OOP: structs with field access!
This commit is contained in:
		
							parent
							
								
									397dcaae92
								
							
						
					
					
						commit
						d3f23be7fe
					
				
							
								
								
									
										36
									
								
								docs/OOP.md
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								docs/OOP.md
									
									
									
									
									
								
							@ -76,12 +76,36 @@ Regular methods are called on instances as usual `instance.method()`. The method
 | 
			
		||||
The class is a some data record with named fields and fixed order, in fact. To define a class,
 | 
			
		||||
just Provide a name and a record like this:
 | 
			
		||||
 | 
			
		||||
    class Vec2(x,y)
 | 
			
		||||
    // creating new class with main constructor
 | 
			
		||||
    // with all fields public and mutable:
 | 
			
		||||
 | 
			
		||||
This way, you have created a _constructor_, so calling `Vec2( 10, 20 )` would create an _instane_ of `Vec2` class:
 | 
			
		||||
    struct Point(x,y)
 | 
			
		||||
    assert( Point is Class )
 | 
			
		||||
    
 | 
			
		||||
    // now we can create instance
 | 
			
		||||
    val p1 = Point(3,4)
 | 
			
		||||
 | 
			
		||||
    class Vec2(x,y)
 | 
			
		||||
    Vec2(10,20)
 | 
			
		||||
    >> eee
 | 
			
		||||
    // is is of the newly created type:
 | 
			
		||||
    assert( p1 is Point )
 | 
			
		||||
 | 
			
		||||
TBD
 | 
			
		||||
    // we can read and write its fields:
 | 
			
		||||
    assert( p1.x == 3 )
 | 
			
		||||
    assert( p1.y == 4 )
 | 
			
		||||
 | 
			
		||||
    p1.y++ 
 | 
			
		||||
    assert( p1.y == 5 )
 | 
			
		||||
 | 
			
		||||
    >>> void
 | 
			
		||||
    
 | 
			
		||||
Let's see in details. The statement `struct Point(x,y)` creates a struct, or public class,
 | 
			
		||||
with two field, which are mutable and publicly visible, because it is _struct_. `(x,y)` here 
 | 
			
		||||
is the [argument list], same as when defining a function. All together creates a class with
 | 
			
		||||
a _constructor_ that requires two parameters for fields. So when creating it with
 | 
			
		||||
`Point(10, 20)` we say _calling Point constructor_ with these parameters.
 | 
			
		||||
 | 
			
		||||
Such declaration is identical to `class Point(var x,var y)` which does exactly the same.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
TBD
 | 
			
		||||
 | 
			
		||||
[argument list](declaring_arguments.md)
 | 
			
		||||
@ -4,15 +4,15 @@ package net.sergeych.lyng
 | 
			
		||||
 * List of argument declarations in the __definition__ of the lambda, class constructor,
 | 
			
		||||
 * function, etc. It is created by [Compiler.parseArgsDeclaration]
 | 
			
		||||
 */
 | 
			
		||||
data class ArgsDeclaration(val args: List<Item>, val endTokenType: Token.Type) {
 | 
			
		||||
data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type) {
 | 
			
		||||
    init {
 | 
			
		||||
        val i = args.count { it.isEllipsis }
 | 
			
		||||
        if (i > 1) throw ScriptError(args[i].pos, "there can be only one argument")
 | 
			
		||||
        val start = args.indexOfFirst { it.defaultValue != null }
 | 
			
		||||
        val i = params.count { it.isEllipsis }
 | 
			
		||||
        if (i > 1) throw ScriptError(params[i].pos, "there can be only one argument")
 | 
			
		||||
        val start = params.indexOfFirst { it.defaultValue != null }
 | 
			
		||||
        if (start >= 0)
 | 
			
		||||
            for (j in start + 1 until args.size)
 | 
			
		||||
                if (args[j].defaultValue == null) throw ScriptError(
 | 
			
		||||
                    args[j].pos,
 | 
			
		||||
            for (j in start + 1 until params.size)
 | 
			
		||||
                if (params[j].defaultValue == null) throw ScriptError(
 | 
			
		||||
                    params[j].pos,
 | 
			
		||||
                    "required argument can't follow default one"
 | 
			
		||||
                )
 | 
			
		||||
    }
 | 
			
		||||
@ -31,8 +31,8 @@ data class ArgsDeclaration(val args: List<Item>, val endTokenType: Token.Type) {
 | 
			
		||||
 | 
			
		||||
        suspend fun processHead(index: Int): Int {
 | 
			
		||||
            var i = index
 | 
			
		||||
            while (i != args.size) {
 | 
			
		||||
                val a = args[i]
 | 
			
		||||
            while (i != params.size) {
 | 
			
		||||
                val a = params[i]
 | 
			
		||||
                if (a.isEllipsis) break
 | 
			
		||||
                val value = when {
 | 
			
		||||
                    i < fromArgs.size -> fromArgs[i]
 | 
			
		||||
@ -46,10 +46,10 @@ data class ArgsDeclaration(val args: List<Item>, val endTokenType: Token.Type) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        suspend fun processTail(index: Int): Int {
 | 
			
		||||
            var i = args.size - 1
 | 
			
		||||
            var i = params.size - 1
 | 
			
		||||
            var j = fromArgs.size - 1
 | 
			
		||||
            while (i > index) {
 | 
			
		||||
                val a = args[i]
 | 
			
		||||
                val a = params[i]
 | 
			
		||||
                if (a.isEllipsis) break
 | 
			
		||||
                val value = when {
 | 
			
		||||
                    j >= index -> {
 | 
			
		||||
@ -66,14 +66,14 @@ data class ArgsDeclaration(val args: List<Item>, val endTokenType: Token.Type) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun processEllipsis(index: Int, toFromIndex: Int) {
 | 
			
		||||
            val a = args[index]
 | 
			
		||||
            val a = params[index]
 | 
			
		||||
            val l = if (index > toFromIndex) ObjList()
 | 
			
		||||
            else ObjList(fromArgs.values.subList(index, toFromIndex + 1).toMutableList())
 | 
			
		||||
            assign(a, l)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val leftIndex = processHead(0)
 | 
			
		||||
        if (leftIndex < args.size) {
 | 
			
		||||
        if (leftIndex < params.size) {
 | 
			
		||||
            val end = processTail(leftIndex)
 | 
			
		||||
            processEllipsis(leftIndex, end)
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
@ -16,14 +16,14 @@ class Compiler(
 | 
			
		||||
 | 
			
		||||
    private fun parseScript(start: Pos, tokens: CompilerContext): Script {
 | 
			
		||||
        val statements = mutableListOf<Statement>()
 | 
			
		||||
        while (parseStatement(tokens,braceMeansLambda = true)?.also {
 | 
			
		||||
        while (parseStatement(tokens, braceMeansLambda = true)?.also {
 | 
			
		||||
                statements += it
 | 
			
		||||
            } != null) {/**/
 | 
			
		||||
        }
 | 
			
		||||
        return Script(start, statements)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun parseStatement(cc: CompilerContext,braceMeansLambda: Boolean = false): Statement? {
 | 
			
		||||
    private fun parseStatement(cc: CompilerContext, braceMeansLambda: Boolean = false): Statement? {
 | 
			
		||||
        while (true) {
 | 
			
		||||
            val t = cc.next()
 | 
			
		||||
            return when (t.type) {
 | 
			
		||||
@ -49,7 +49,7 @@ class Compiler(
 | 
			
		||||
 | 
			
		||||
                Token.Type.LBRACE -> {
 | 
			
		||||
                    cc.previous()
 | 
			
		||||
                    if( braceMeansLambda )
 | 
			
		||||
                    if (braceMeansLambda)
 | 
			
		||||
                        parseExpression(cc)
 | 
			
		||||
                    else
 | 
			
		||||
                        parseBlock(cc)
 | 
			
		||||
@ -141,8 +141,10 @@ class Compiler(
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        if (!isCall) {
 | 
			
		||||
                            operand = Accessor { context ->
 | 
			
		||||
                            operand = Accessor({ context ->
 | 
			
		||||
                                left.getter(context).value.readField(context, next.value)
 | 
			
		||||
                            }) { cc, newValue ->
 | 
			
		||||
                                left.getter(cc).value.writeField(cc, next.value, newValue)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    } ?: throw ScriptError(t.pos, "Expecting expression before dot")
 | 
			
		||||
@ -354,7 +356,7 @@ class Compiler(
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Accessor { x ->
 | 
			
		||||
            if( closure == null ) closure = x
 | 
			
		||||
            if (closure == null) closure = x
 | 
			
		||||
            callStatement.asReadonly
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -695,10 +697,16 @@ class Compiler(
 | 
			
		||||
 | 
			
		||||
    private fun parseClassDeclaration(cc: CompilerContext, isStruct: Boolean): Statement {
 | 
			
		||||
        val nameToken = cc.requireToken(Token.Type.ID)
 | 
			
		||||
        val parsedArgs = parseArgsDeclaration(cc)
 | 
			
		||||
        val constructorArgsDeclaration =
 | 
			
		||||
            if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
 | 
			
		||||
                parseArgsDeclaration(cc)
 | 
			
		||||
            else null
 | 
			
		||||
 | 
			
		||||
        if( parsedArgs != null && parsedArgs.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()
 | 
			
		||||
@ -707,7 +715,10 @@ class Compiler(
 | 
			
		||||
        val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) {
 | 
			
		||||
            // parse body
 | 
			
		||||
            TODO("parse body")
 | 
			
		||||
        } else null
 | 
			
		||||
        } else {
 | 
			
		||||
            cc.previous()
 | 
			
		||||
            null
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // create class
 | 
			
		||||
        val className = nameToken.value
 | 
			
		||||
@ -721,21 +732,45 @@ class Compiler(
 | 
			
		||||
 | 
			
		||||
        val constructorCode = statement {
 | 
			
		||||
            // constructor code is registered with class instance and is called over
 | 
			
		||||
            // new `thisObj` already set by class to ObjInstance
 | 
			
		||||
            // 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
 | 
			
		||||
//            parsedArgs?.let { pa ->
 | 
			
		||||
//                pa.extractArgs { (def, value) ->
 | 
			
		||||
//                val access = def.accessType ?: defaultAccess
 | 
			
		||||
//                val visibility = def.visibility ?: defaultVisibility
 | 
			
		||||
//                addItem(def.name, access.isMutable, value)
 | 
			
		||||
//            }
 | 
			
		||||
//
 | 
			
		||||
            thisObj
 | 
			
		||||
            constructorArgsDeclaration?.let { cad ->
 | 
			
		||||
                cad.assignToContext(this)
 | 
			
		||||
                // note that accessors are created in ObjClass instance, not during instance
 | 
			
		||||
                // initialization (not here)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            thisObj
 | 
			
		||||
        }
 | 
			
		||||
        // 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
 | 
			
		||||
                            println("called on $thisObj")
 | 
			
		||||
                            context[f.name]?.value ?: raiseError("field is not initialized: ${f.name}")
 | 
			
		||||
                        },
 | 
			
		||||
                        true,
 | 
			
		||||
//                        (f.accessType ?: defaultAccess).isMutable,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return statement {
 | 
			
		||||
            // the main statement should create custom ObjClass instance with field
 | 
			
		||||
            // accessors, constructor registration, etc.
 | 
			
		||||
            addItem(className, false, newClass)
 | 
			
		||||
            newClass
 | 
			
		||||
        }
 | 
			
		||||
        TODO()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1063,8 +1098,11 @@ class Compiler(
 | 
			
		||||
            throw ScriptError(t.pos, "Bad function definition: expected '(' after 'fn ${name}'")
 | 
			
		||||
 | 
			
		||||
        val argsDeclaration = parseArgsDeclaration(tokens)
 | 
			
		||||
        if( argsDeclaration == null || argsDeclaration.endTokenType != Token.Type.RPAREN)
 | 
			
		||||
            throw ScriptError(t.pos, "Bad function definition: expected valid argument declaration or () after 'fn ${name}'")
 | 
			
		||||
        if (argsDeclaration == null || argsDeclaration.endTokenType != Token.Type.RPAREN)
 | 
			
		||||
            throw ScriptError(
 | 
			
		||||
                t.pos,
 | 
			
		||||
                "Bad function definition: expected valid argument declaration or () after 'fn ${name}'"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        // Here we should be at open body
 | 
			
		||||
        val fnStatements = parseBlock(tokens)
 | 
			
		||||
 | 
			
		||||
@ -170,8 +170,7 @@ open class Obj {
 | 
			
		||||
        val value = obj?.value
 | 
			
		||||
        return when (value) {
 | 
			
		||||
            is Statement -> {
 | 
			
		||||
                // readonly property, important: call it on this
 | 
			
		||||
                value.execute(context.copy(context.pos, newThisObj = this)).asReadonly
 | 
			
		||||
                WithAccess(value.execute(context.copy(context.pos, newThisObj = this)), obj.isMutable)
 | 
			
		||||
            }
 | 
			
		||||
            // could be writable property naturally
 | 
			
		||||
            null -> ObjNull.asReadonly
 | 
			
		||||
 | 
			
		||||
@ -4,14 +4,10 @@ val ObjClassType by lazy { ObjClass("Class") }
 | 
			
		||||
 | 
			
		||||
class ObjClass(
 | 
			
		||||
    val className: String,
 | 
			
		||||
    val constructorArgs: List<ArgsDeclaration.Item> = emptyList(),
 | 
			
		||||
    vararg val parents: ObjClass,
 | 
			
		||||
) : Obj() {
 | 
			
		||||
    constructor(
 | 
			
		||||
        className: String,
 | 
			
		||||
        vararg parents: ObjClass,
 | 
			
		||||
    ) : this(className, emptyList(), *parents)
 | 
			
		||||
 | 
			
		||||
    var instanceConstructor: Statement? = null
 | 
			
		||||
 | 
			
		||||
    val allParentsSet: Set<ObjClass> = parents.flatMap {
 | 
			
		||||
        listOf(it) + it.allParentsSet
 | 
			
		||||
@ -26,27 +22,28 @@ class ObjClass(
 | 
			
		||||
 | 
			
		||||
    override suspend fun compareTo(context: Context, other: Obj): Int = if (other === this) 0 else -1
 | 
			
		||||
 | 
			
		||||
//    private var initInstanceHandler: (suspend (Context, List<Obj>) -> Obj)? = null
 | 
			
		||||
    override suspend fun callOn(context: Context): Obj {
 | 
			
		||||
        println("callOn $this constructing....")
 | 
			
		||||
        println("on context: $context")
 | 
			
		||||
        val instance = ObjInstance(this)
 | 
			
		||||
        instance.instanceContext = context.copy(newThisObj = instance,args = context.args)
 | 
			
		||||
        if (instanceConstructor != null) {
 | 
			
		||||
            instanceConstructor!!.execute(instance.instanceContext)
 | 
			
		||||
        }
 | 
			
		||||
        return instance
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //    suspend fun newInstance(context: Context, vararg args: Obj): Obj =
 | 
			
		||||
//        initInstanceHandler?.invoke(context, args.toList())
 | 
			
		||||
//            ?: context.raiseError("No initInstance handler for $this")
 | 
			
		||||
//
 | 
			
		||||
//    fun buildInstance(f: suspend Context.(List<Obj>) -> Obj) {
 | 
			
		||||
//        if (initInstanceHandler != null) throw IllegalStateException("initInstance already set")
 | 
			
		||||
//        initInstanceHandler = f
 | 
			
		||||
//    }
 | 
			
		||||
//
 | 
			
		||||
//    fun addParent(context: Context, parent: Obj) {
 | 
			
		||||
//        val self = context.thisObj
 | 
			
		||||
//        self.parentInstances.add(parent)
 | 
			
		||||
//    }
 | 
			
		||||
//
 | 
			
		||||
    fun defaultInstance(): Obj = object : Obj() {
 | 
			
		||||
        override val objClass: ObjClass = this@ObjClass
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun createField(name: String, initialValue: Obj, isMutable: Boolean = false, pos: Pos = Pos.builtIn) {
 | 
			
		||||
    fun createField(
 | 
			
		||||
        name: String,
 | 
			
		||||
        initialValue: Obj,
 | 
			
		||||
        isMutable: Boolean = false,
 | 
			
		||||
        visibility: Compiler.Visibility = Compiler.Visibility.Public,
 | 
			
		||||
        pos: Pos = Pos.builtIn
 | 
			
		||||
    ) {
 | 
			
		||||
        if (name in members || allParentsSet.any { name in it.members } == true)
 | 
			
		||||
            throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
 | 
			
		||||
        members[name] = WithAccess(initialValue, isMutable)
 | 
			
		||||
@ -154,6 +151,7 @@ val ObjArray by lazy {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ObjInstance(override val objClass: ObjClass): Obj() {
 | 
			
		||||
class ObjInstance(override val objClass: ObjClass) : Obj() {
 | 
			
		||||
 | 
			
		||||
    internal lateinit var instanceContext: Context
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -156,6 +156,8 @@ class Script(
 | 
			
		||||
            // interfaces
 | 
			
		||||
            addConst("Iterable", ObjIterable)
 | 
			
		||||
            addConst("Array", ObjArray)
 | 
			
		||||
            addConst("Class", ObjClassType)
 | 
			
		||||
            addConst("Object", Obj().objClass)
 | 
			
		||||
 | 
			
		||||
            val pi = ObjReal(PI)
 | 
			
		||||
            addConst("π", pi)
 | 
			
		||||
 | 
			
		||||
@ -1383,14 +1383,36 @@ class ScriptTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun simpleClassDelaration() = runTest {
 | 
			
		||||
        eval( """
 | 
			
		||||
//            class Vec2(x,y)
 | 
			
		||||
//             println(Vec2(1,2)::class)
 | 
			
		||||
            println("---------------------")
 | 
			
		||||
            println(Int::class)
 | 
			
		||||
        """.trimIndent()
 | 
			
		||||
        )
 | 
			
		||||
    fun testSimpleStruct() = runTest {
 | 
			
		||||
        val c = Context()
 | 
			
		||||
        c.eval("""
 | 
			
		||||
            struct Point(x,y)
 | 
			
		||||
            assert( Point::class is Class )
 | 
			
		||||
            val p = Point(2,3)
 | 
			
		||||
            assert(p is Point)
 | 
			
		||||
            println(p)
 | 
			
		||||
            println(p.x)
 | 
			
		||||
            assert( p.x == 2 )
 | 
			
		||||
            assert( p.y == 3 )
 | 
			
		||||
            
 | 
			
		||||
            val p2 = Point(p.x+1,p.y+1)
 | 
			
		||||
            p.x = 0
 | 
			
		||||
            assertEquals( 0, p.x )
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testNonAssignalbeFieldInStruct() = runTest {
 | 
			
		||||
        val c = Context()
 | 
			
		||||
        c.eval("""
 | 
			
		||||
            struct Point(x,y)
 | 
			
		||||
            val p = Point("2",3)
 | 
			
		||||
            assert(p is Point)
 | 
			
		||||
            assert( p.x == "2" )
 | 
			
		||||
            assert( p.y == 3 )
 | 
			
		||||
            
 | 
			
		||||
            p.x = 0
 | 
			
		||||
            assertEquals( 0, p.x )
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user