fix #35 serialization alsso user classes. Fixed user classes comparison
This commit is contained in:
		
							parent
							
								
									f805e1ee82
								
							
						
					
					
						commit
						6df06a6911
					
				@ -2,6 +2,7 @@ package net.sergeych.lyng
 | 
			
		||||
 | 
			
		||||
import net.sergeych.lyng.obj.Obj
 | 
			
		||||
import net.sergeych.lyng.obj.ObjList
 | 
			
		||||
import net.sergeych.lyng.obj.ObjRecord
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * List of argument declarations in the __definition__ of the lambda, class constructor,
 | 
			
		||||
@ -22,17 +23,20 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * parse args and create local vars in a given context
 | 
			
		||||
     * parse args and create local vars in a given context properly interpreting
 | 
			
		||||
     * ellipsis args and default values
 | 
			
		||||
     */
 | 
			
		||||
    suspend fun assignToContext(
 | 
			
		||||
        scope: Scope,
 | 
			
		||||
        arguments: Arguments = scope.args,
 | 
			
		||||
        defaultAccessType: AccessType = AccessType.Var,
 | 
			
		||||
        defaultVisibility: Visibility = Visibility.Public
 | 
			
		||||
        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,
 | 
			
		||||
                a.visibility ?: defaultVisibility)
 | 
			
		||||
                a.visibility ?: defaultVisibility,
 | 
			
		||||
                recordType = defaultRecordType)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // will be used with last lambda arg fix
 | 
			
		||||
 | 
			
		||||
@ -1122,7 +1122,7 @@ class Compiler(
 | 
			
		||||
            thisObj
 | 
			
		||||
        }
 | 
			
		||||
        // inheritance must alter this code:
 | 
			
		||||
        val newClass = ObjClass(className).apply {
 | 
			
		||||
        val newClass = ObjInstanceClass(className).apply {
 | 
			
		||||
            instanceConstructor = constructorCode
 | 
			
		||||
            constructorMeta = constructorArgsDeclaration
 | 
			
		||||
        }
 | 
			
		||||
@ -1630,7 +1630,7 @@ class Compiler(
 | 
			
		||||
            // create a separate copy:
 | 
			
		||||
            val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
 | 
			
		||||
 | 
			
		||||
            context.addItem(name, isMutable, initValue, visibility)
 | 
			
		||||
            context.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Field)
 | 
			
		||||
            initValue
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -116,9 +116,10 @@ open class Scope(
 | 
			
		||||
        name: String,
 | 
			
		||||
        isMutable: Boolean,
 | 
			
		||||
        value: Obj,
 | 
			
		||||
        visibility: Visibility = Visibility.Public
 | 
			
		||||
        visibility: Visibility = Visibility.Public,
 | 
			
		||||
        recordType: ObjRecord.Type = ObjRecord.Type.Other
 | 
			
		||||
    ): ObjRecord {
 | 
			
		||||
        return ObjRecord(value, isMutable, visibility).also { objects[name] = it }
 | 
			
		||||
        return ObjRecord(value, isMutable, visibility,type = recordType).also { objects[name] = it }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getOrCreateNamespace(name: String): ObjClass {
 | 
			
		||||
 | 
			
		||||
@ -131,6 +131,12 @@ class Script(
 | 
			
		||||
                if( a.compareTo(this, b) != 0 )
 | 
			
		||||
                    raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect()} == ${b.inspect()}"))
 | 
			
		||||
            }
 | 
			
		||||
            addVoidFn("assertNotEquals") {
 | 
			
		||||
                val a = requiredArg<Obj>(0)
 | 
			
		||||
                val b = requiredArg<Obj>(1)
 | 
			
		||||
                if( a.compareTo(this, b) == 0 )
 | 
			
		||||
                    raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect()} != ${b.inspect()}"))
 | 
			
		||||
            }
 | 
			
		||||
            addFn("assertThrows") {
 | 
			
		||||
                val code = requireOnlyArg<Statement>()
 | 
			
		||||
                val result =try {
 | 
			
		||||
 | 
			
		||||
@ -57,13 +57,20 @@ open class Obj {
 | 
			
		||||
        args: Arguments = Arguments.EMPTY
 | 
			
		||||
    ): T = invokeInstanceMethod(scope, name, args) as T
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invoke a method of the object if exists
 | 
			
		||||
     * it [onNotFoundResult] is not null, it returns it when symbol is not found
 | 
			
		||||
     * otherwise throws [ObjSymbolNotDefinedException] object exception
 | 
			
		||||
     */
 | 
			
		||||
    open suspend fun invokeInstanceMethod(
 | 
			
		||||
        scope: Scope,
 | 
			
		||||
        name: String,
 | 
			
		||||
        args: Arguments = Arguments.EMPTY
 | 
			
		||||
        args: Arguments = Arguments.EMPTY,
 | 
			
		||||
        onNotFoundResult: Obj?=null
 | 
			
		||||
    ): Obj =
 | 
			
		||||
        // note that getInstanceMember traverses the hierarchy
 | 
			
		||||
        objClass.getInstanceMember(scope.pos, name).value.invoke(scope, this, args)
 | 
			
		||||
        objClass.getInstanceMemberOrNull(name)?.value?.invoke(scope, this, args)
 | 
			
		||||
            ?: onNotFoundResult
 | 
			
		||||
            ?: scope.raiseSymbolNotFound(name)
 | 
			
		||||
 | 
			
		||||
    open suspend fun getInstanceMethod(
 | 
			
		||||
        scope: Scope,
 | 
			
		||||
@ -341,7 +348,7 @@ object ObjNull : Obj() {
 | 
			
		||||
        scope.raiseNPE()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments): Obj {
 | 
			
		||||
    override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments, onNotFoundResult: Obj?): Obj {
 | 
			
		||||
        scope.raiseNPE()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -97,8 +97,10 @@ open class ObjClass(
 | 
			
		||||
        return super.readField(scope, name)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments): Obj {
 | 
			
		||||
        return classMembers[name]?.value?.invoke(scope, this, args) ?: super.invokeInstanceMethod(scope, name, args)
 | 
			
		||||
    override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments,
 | 
			
		||||
                                              onNotFoundResult: Obj?): Obj {
 | 
			
		||||
        return classMembers[name]?.value?.invoke(scope, this, args)
 | 
			
		||||
            ?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj = scope.raiseNotImplemented()
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ package net.sergeych.lyng.obj
 | 
			
		||||
 | 
			
		||||
import net.sergeych.lyng.Arguments
 | 
			
		||||
import net.sergeych.lyng.Scope
 | 
			
		||||
import net.sergeych.lynon.LynonDecoder
 | 
			
		||||
import net.sergeych.lynon.LynonEncoder
 | 
			
		||||
import net.sergeych.lynon.LynonType
 | 
			
		||||
 | 
			
		||||
@ -29,14 +30,15 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
 | 
			
		||||
        } ?: super.writeField(scope, name, newValue)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments): Obj =
 | 
			
		||||
    override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments,
 | 
			
		||||
                                              onNotFoundResult: Obj?): Obj =
 | 
			
		||||
        instanceScope[name]?.let {
 | 
			
		||||
            if (it.visibility.isPublic)
 | 
			
		||||
                it.value.invoke(scope, this, args)
 | 
			
		||||
            else
 | 
			
		||||
                scope.raiseError(ObjAccessException(scope, "can't invoke non-public method $name"))
 | 
			
		||||
        }
 | 
			
		||||
            ?: super.invokeInstanceMethod(scope, name, args)
 | 
			
		||||
            ?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
 | 
			
		||||
 | 
			
		||||
    private val publicFields: Map<String, ObjRecord>
 | 
			
		||||
        get() = instanceScope.objects.filter { it.value.visibility.isPublic }
 | 
			
		||||
@ -49,19 +51,51 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
 | 
			
		||||
    override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
 | 
			
		||||
        val meta = objClass.constructorMeta
 | 
			
		||||
            ?: scope.raiseError("can't serialize non-serializable object (no constructor meta)")
 | 
			
		||||
        for( p in meta.params) {
 | 
			
		||||
            val r = readField(scope, p.name)
 | 
			
		||||
            println("serialize ${p.name}=${r.value}")
 | 
			
		||||
            TODO()
 | 
			
		||||
//            encoder.encodeObj(scope, r.value)
 | 
			
		||||
        // actual constructor can vary, for example, adding new fields with default
 | 
			
		||||
        // values, so we save size of the construction:
 | 
			
		||||
 | 
			
		||||
        // using objlist allow for some optimizations:
 | 
			
		||||
        val params = meta.params.map { readField(scope, it.name).value }
 | 
			
		||||
        encoder.encodeAnyList(scope, params)
 | 
			
		||||
        serializeStateVars(scope, encoder)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected val instanceVars: Map<String, ObjRecord> by lazy {
 | 
			
		||||
        instanceScope.objects.filter { it.value.type.serializable }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected suspend fun serializeStateVars(scope: Scope,encoder: LynonEncoder) {
 | 
			
		||||
        val vars = instanceVars.values.map { it.value }
 | 
			
		||||
        if( vars.isNotEmpty()) {
 | 
			
		||||
            encoder.encodeAnyList(scope, vars)
 | 
			
		||||
            println("serialized state vars $vars")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal suspend fun deserializeStateVars(scope: Scope, decoder: LynonDecoder) {
 | 
			
		||||
        val localVars = instanceVars.values.toList()
 | 
			
		||||
        if( localVars.isNotEmpty() ) {
 | 
			
		||||
            println("gonna read vars")
 | 
			
		||||
            val vars = decoder.decodeAnyList(scope)
 | 
			
		||||
            if (vars.size > instanceVars.size)
 | 
			
		||||
                scope.raiseIllegalArgument("serialized vars has bigger size than instance vars")
 | 
			
		||||
            println("deser state vars $vars")
 | 
			
		||||
            for ((i, v) in vars.withIndex()) {
 | 
			
		||||
                localVars[i].value = vars[i]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected val comparableVars: Map<String, ObjRecord> by lazy {
 | 
			
		||||
        instanceScope.objects.filter {
 | 
			
		||||
            it.value.type.comparable
 | 
			
		||||
        }
 | 
			
		||||
        // todo: possible vars?
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun compareTo(scope: Scope, other: Obj): Int {
 | 
			
		||||
        if (other !is ObjInstance) return -1
 | 
			
		||||
        if (other.objClass != objClass) return -1
 | 
			
		||||
        for (f in publicFields) {
 | 
			
		||||
        for (f in comparableVars) {
 | 
			
		||||
            val a = f.value.value
 | 
			
		||||
            val b = other.instanceScope[f.key]!!.value
 | 
			
		||||
            val d = a.compareTo(scope, b)
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,28 @@
 | 
			
		||||
package net.sergeych.lyng.obj
 | 
			
		||||
 | 
			
		||||
import net.sergeych.lyng.Arguments
 | 
			
		||||
import net.sergeych.lyng.Scope
 | 
			
		||||
import net.sergeych.lynon.LynonDecoder
 | 
			
		||||
import net.sergeych.lynon.LynonType
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Special variant of [ObjClass] to be used in [ObjInstance], e.g. for Lyng compiled classes
 | 
			
		||||
 */
 | 
			
		||||
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")
 | 
			
		||||
        val actualSize = constructorMeta?.params?.size ?: 0
 | 
			
		||||
        if( args.size > actualSize )
 | 
			
		||||
            scope.raiseIllegalArgument("constructor $name has only $actualSize but serialized version has ${args.size}")
 | 
			
		||||
        val newScope = scope.copy(args = Arguments(args))
 | 
			
		||||
        return (callOn(newScope) as ObjInstance).apply {
 | 
			
		||||
            deserializeStateVars(scope,decoder)
 | 
			
		||||
            invokeInstanceMethod(scope, "onDeserialized", onNotFoundResult = ObjVoid)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -10,8 +10,17 @@ data class ObjRecord(
 | 
			
		||||
    var value: Obj,
 | 
			
		||||
    val isMutable: Boolean,
 | 
			
		||||
    val visibility: Visibility = Visibility.Public,
 | 
			
		||||
    var importedFrom: Scope? = null
 | 
			
		||||
    var importedFrom: Scope? = null,
 | 
			
		||||
    val isTransient: Boolean = false,
 | 
			
		||||
    val type: Type = Type.Other
 | 
			
		||||
) {
 | 
			
		||||
    enum class Type(val comparable: Boolean = false,val serializable: Boolean = false) {
 | 
			
		||||
        Field(true, true),
 | 
			
		||||
        @Suppress("unused")
 | 
			
		||||
        Fun,
 | 
			
		||||
        ConstructorField(true, true),
 | 
			
		||||
        Other
 | 
			
		||||
    }
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    fun qualifiedName(name: String): String =
 | 
			
		||||
        "${importedFrom?.packageName ?: "anonymous"}.$name"
 | 
			
		||||
 | 
			
		||||
@ -2242,6 +2242,14 @@ class ScriptTest {
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testSet2() = runTest {
 | 
			
		||||
        eval("""
 | 
			
		||||
            assertEquals( Set( ...[1,2,3]), Set(1,2,3) )
 | 
			
		||||
            assertEquals( Set( ...[1,false,"ok"]), Set("ok", 1, false) )
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testLet() = runTest {
 | 
			
		||||
        eval(
 | 
			
		||||
 | 
			
		||||
@ -50,4 +50,27 @@ class TypesTest {
 | 
			
		||||
           
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testUserClassCompareTo() = runTest {
 | 
			
		||||
        eval("""
 | 
			
		||||
            class Point(val a,b)
 | 
			
		||||
            
 | 
			
		||||
            assertEquals(Point(0,1), Point(0,1) )
 | 
			
		||||
            assertNotEquals(Point(0,1), Point(1,1) )
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testUserClassCompareTo2() = runTest {
 | 
			
		||||
        eval("""
 | 
			
		||||
            class Point(val a,b) {
 | 
			
		||||
                var c = 0
 | 
			
		||||
            }
 | 
			
		||||
            assertEquals(Point(0,1), Point(0,1) )
 | 
			
		||||
            assertEquals(Point(0,1).apply { c = 2 }, Point(0,1).apply { c = 2 } )
 | 
			
		||||
            assertNotEquals(Point(0,1), Point(1,1) )
 | 
			
		||||
            assertNotEquals(Point(0,1), Point(0,1).apply { c = 1 } )
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -566,6 +566,72 @@ class LynonTests {
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testClassSerializationNoInstanceVars() = runTest {
 | 
			
		||||
        testScope().eval("""
 | 
			
		||||
            import lyng.serialization
 | 
			
		||||
            
 | 
			
		||||
            class Point(x,y)
 | 
			
		||||
            
 | 
			
		||||
//            println( Lynon.encode(Point(0,0)).toDump() )
 | 
			
		||||
            testEncode(Point(0,0))
 | 
			
		||||
            testEncode(Point(10,11))
 | 
			
		||||
            testEncode(Point(-1,2))
 | 
			
		||||
            testEncode(Point(-1,-2))
 | 
			
		||||
            testEncode(Point("point!",-2))
 | 
			
		||||
            
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testClassSerializationWithInstanceVars() = runTest {
 | 
			
		||||
        testScope().eval("""
 | 
			
		||||
            import lyng.serialization
 | 
			
		||||
            
 | 
			
		||||
            class Point(x=0) {
 | 
			
		||||
                var y = 0
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            testEncode(Point())
 | 
			
		||||
            testEncode(Point(1))
 | 
			
		||||
            testEncode(Point(1).apply { y = 2 })
 | 
			
		||||
            testEncode(Point(10).also { it.y = 11 })
 | 
			
		||||
            
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testClassSerializationWithInstanceVars2() = runTest {
 | 
			
		||||
        testScope().eval("""
 | 
			
		||||
            import lyng.serialization
 | 
			
		||||
            
 | 
			
		||||
            var onInitComment = null
 | 
			
		||||
            
 | 
			
		||||
            class Point(x=0) {
 | 
			
		||||
                var y = 0
 | 
			
		||||
                var comment = null
 | 
			
		||||
 | 
			
		||||
                fun onDeserialized() {
 | 
			
		||||
                    onInitComment = comment
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            testEncode(Point())
 | 
			
		||||
            testEncode(Point(1))
 | 
			
		||||
            testEncode(Point(1).apply { y = 2 })
 | 
			
		||||
            testEncode(Point(10).also { it.y = 11 })
 | 
			
		||||
 | 
			
		||||
            // important: class init is called before setting non-constructor fields
 | 
			
		||||
            // this is decessary, so deserialized fields are only available
 | 
			
		||||
            // after onDeserialized() call (if exists):
 | 
			
		||||
            // deserialized:
 | 
			
		||||
            testEncode(Point(10).also { it.y = 11; it.comment = "comment" })
 | 
			
		||||
            println("-- on init comment "+onInitComment)
 | 
			
		||||
            assertEquals("comment", onInitComment)
 | 
			
		||||
            
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user