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.Obj
|
||||||
import net.sergeych.lyng.obj.ObjList
|
import net.sergeych.lyng.obj.ObjList
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of argument declarations in the __definition__ of the lambda, class constructor,
|
* 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(
|
suspend fun assignToContext(
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
arguments: Arguments = scope.args,
|
arguments: Arguments = scope.args,
|
||||||
defaultAccessType: AccessType = AccessType.Var,
|
defaultAccessType: AccessType = AccessType.Var,
|
||||||
defaultVisibility: Visibility = Visibility.Public
|
defaultVisibility: Visibility = Visibility.Public,
|
||||||
|
defaultRecordType: ObjRecord.Type = ObjRecord.Type.ConstructorField
|
||||||
) {
|
) {
|
||||||
fun assign(a: Item, value: Obj) {
|
fun assign(a: Item, value: Obj) {
|
||||||
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value,
|
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
|
// will be used with last lambda arg fix
|
||||||
|
@ -1122,7 +1122,7 @@ class Compiler(
|
|||||||
thisObj
|
thisObj
|
||||||
}
|
}
|
||||||
// inheritance must alter this code:
|
// inheritance must alter this code:
|
||||||
val newClass = ObjClass(className).apply {
|
val newClass = ObjInstanceClass(className).apply {
|
||||||
instanceConstructor = constructorCode
|
instanceConstructor = constructorCode
|
||||||
constructorMeta = constructorArgsDeclaration
|
constructorMeta = constructorArgsDeclaration
|
||||||
}
|
}
|
||||||
@ -1630,7 +1630,7 @@ class Compiler(
|
|||||||
// create a separate copy:
|
// create a separate copy:
|
||||||
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
||||||
|
|
||||||
context.addItem(name, isMutable, initValue, visibility)
|
context.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Field)
|
||||||
initValue
|
initValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,9 +116,10 @@ open class Scope(
|
|||||||
name: String,
|
name: String,
|
||||||
isMutable: Boolean,
|
isMutable: Boolean,
|
||||||
value: Obj,
|
value: Obj,
|
||||||
visibility: Visibility = Visibility.Public
|
visibility: Visibility = Visibility.Public,
|
||||||
|
recordType: ObjRecord.Type = ObjRecord.Type.Other
|
||||||
): ObjRecord {
|
): 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 {
|
fun getOrCreateNamespace(name: String): ObjClass {
|
||||||
|
@ -131,6 +131,12 @@ class Script(
|
|||||||
if( a.compareTo(this, b) != 0 )
|
if( a.compareTo(this, b) != 0 )
|
||||||
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect()} == ${b.inspect()}"))
|
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") {
|
addFn("assertThrows") {
|
||||||
val code = requireOnlyArg<Statement>()
|
val code = requireOnlyArg<Statement>()
|
||||||
val result =try {
|
val result =try {
|
||||||
|
@ -57,13 +57,20 @@ open class Obj {
|
|||||||
args: Arguments = Arguments.EMPTY
|
args: Arguments = Arguments.EMPTY
|
||||||
): T = invokeInstanceMethod(scope, name, args) as T
|
): 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(
|
open suspend fun invokeInstanceMethod(
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
name: String,
|
name: String,
|
||||||
args: Arguments = Arguments.EMPTY
|
args: Arguments = Arguments.EMPTY,
|
||||||
|
onNotFoundResult: Obj?=null
|
||||||
): Obj =
|
): Obj =
|
||||||
// note that getInstanceMember traverses the hierarchy
|
objClass.getInstanceMemberOrNull(name)?.value?.invoke(scope, this, args)
|
||||||
objClass.getInstanceMember(scope.pos, name).value.invoke(scope, this, args)
|
?: onNotFoundResult
|
||||||
|
?: scope.raiseSymbolNotFound(name)
|
||||||
|
|
||||||
open suspend fun getInstanceMethod(
|
open suspend fun getInstanceMethod(
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
@ -341,7 +348,7 @@ object ObjNull : Obj() {
|
|||||||
scope.raiseNPE()
|
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()
|
scope.raiseNPE()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,8 +97,10 @@ open class ObjClass(
|
|||||||
return super.readField(scope, name)
|
return super.readField(scope, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments): Obj {
|
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments,
|
||||||
return classMembers[name]?.value?.invoke(scope, this, args) ?: super.invokeInstanceMethod(scope, name, args)
|
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()
|
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.Arguments
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
import net.sergeych.lynon.LynonType
|
import net.sergeych.lynon.LynonType
|
||||||
|
|
||||||
@ -29,14 +30,15 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
} ?: super.writeField(scope, name, newValue)
|
} ?: 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 {
|
instanceScope[name]?.let {
|
||||||
if (it.visibility.isPublic)
|
if (it.visibility.isPublic)
|
||||||
it.value.invoke(scope, this, args)
|
it.value.invoke(scope, this, args)
|
||||||
else
|
else
|
||||||
scope.raiseError(ObjAccessException(scope, "can't invoke non-public method $name"))
|
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>
|
private val publicFields: Map<String, ObjRecord>
|
||||||
get() = instanceScope.objects.filter { it.value.visibility.isPublic }
|
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?) {
|
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
|
||||||
val meta = objClass.constructorMeta
|
val meta = objClass.constructorMeta
|
||||||
?: scope.raiseError("can't serialize non-serializable object (no constructor meta)")
|
?: scope.raiseError("can't serialize non-serializable object (no constructor meta)")
|
||||||
for( p in meta.params) {
|
// actual constructor can vary, for example, adding new fields with default
|
||||||
val r = readField(scope, p.name)
|
// values, so we save size of the construction:
|
||||||
println("serialize ${p.name}=${r.value}")
|
|
||||||
TODO()
|
// using objlist allow for some optimizations:
|
||||||
// encoder.encodeObj(scope, r.value)
|
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 {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other !is ObjInstance) return -1
|
if (other !is ObjInstance) return -1
|
||||||
if (other.objClass != objClass) return -1
|
if (other.objClass != objClass) return -1
|
||||||
for (f in publicFields) {
|
for (f in comparableVars) {
|
||||||
val a = f.value.value
|
val a = f.value.value
|
||||||
val b = other.instanceScope[f.key]!!.value
|
val b = other.instanceScope[f.key]!!.value
|
||||||
val d = a.compareTo(scope, b)
|
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,
|
var value: Obj,
|
||||||
val isMutable: Boolean,
|
val isMutable: Boolean,
|
||||||
val visibility: Visibility = Visibility.Public,
|
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")
|
@Suppress("unused")
|
||||||
fun qualifiedName(name: String): String =
|
fun qualifiedName(name: String): String =
|
||||||
"${importedFrom?.packageName ?: "anonymous"}.$name"
|
"${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
|
@Test
|
||||||
fun testLet() = runTest {
|
fun testLet() = runTest {
|
||||||
eval(
|
eval(
|
||||||
|
@ -50,4 +50,27 @@ class TypesTest {
|
|||||||
|
|
||||||
""".trimIndent())
|
""".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())
|
""".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