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