From 7e289382ed197192dadc52ce92adc41e219b4042 Mon Sep 17 00:00:00 2001 From: sergeych Date: Fri, 31 Oct 2025 14:55:23 +0400 Subject: [PATCH] Scope.copy renamed to createChild for clarity; fixed error with Exception self serializing. --- lynglib/build.gradle.kts | 2 +- .../kotlin/net/sergeych/lyng/Compiler.kt | 14 ++-- .../kotlin/net/sergeych/lyng/Scope.kt | 22 +++++-- .../kotlin/net/sergeych/lyng/obj/Obj.kt | 29 ++++---- .../kotlin/net/sergeych/lyng/obj/ObjClass.kt | 4 +- .../net/sergeych/lyng/obj/ObjDynamic.kt | 10 +-- .../net/sergeych/lyng/obj/ObjException.kt | 8 +-- .../kotlin/net/sergeych/lyng/obj/ObjFlow.kt | 2 +- .../net/sergeych/lyng/obj/ObjInstanceClass.kt | 2 +- .../net/sergeych/lyng/obj/ObjIterable.kt | 2 +- .../sergeych/lyng/pacman/ImportProvider.kt | 2 +- .../kotlin/net/sergeych/lyng/statements.kt | 2 +- lynglib/src/commonTest/kotlin/ScriptTest.kt | 66 +++++++++++++------ 13 files changed, 101 insertions(+), 64 deletions(-) diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index b242058..1609bb8 100644 --- a/lynglib/build.gradle.kts +++ b/lynglib/build.gradle.kts @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget group = "net.sergeych" -version = "0.9.2-SNAPSHOT" +version = "0.9.3-SNAPSHOT" buildscript { repositories { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 01e1165..e98f426 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -796,7 +796,7 @@ class Compiler( val v = left.getter(context) if (v.value == ObjNull && isOptional) return@Accessor v.value.asReadonly v.value.callOn( - context.copy( + context.createChildScope( context.pos, args.toArguments(context, detectedBlockArgument) // Arguments( @@ -902,7 +902,7 @@ class Compiler( val args = extras?.let { required + it } ?: required val fn = scope.get(t.value)?.value ?: scope.raiseSymbolNotFound("annotation not found: ${t.value}") if (fn !is Statement) scope.raiseIllegalArgument("annotation must be callable, got ${fn.objClass}") - (fn.execute(scope.copy(Arguments(args))) as? Statement) + (fn.execute(scope.createChildScope(Arguments(args))) as? Statement) ?: scope.raiseClassCastError("function annotation must return callable") } } @@ -1197,7 +1197,7 @@ class Compiler( } } if (exceptionObject != null) { - val catchContext = this.copy(pos = cdata.catchVar.pos) + val catchContext = this.createChildScope(pos = cdata.catchVar.pos) catchContext.addItem(cdata.catchVar.value, false, objException) result = cdata.block.execute(catchContext) isCaught = true @@ -1313,7 +1313,7 @@ class Compiler( // accessors, constructor registration, etc. addItem(className, false, newClass) if (initScope.isNotEmpty()) { - val classScope = copy(newThisObj = newClass) + val classScope = createChildScope(newThisObj = newClass) newClass.classScope = classScope for (s in initScope) s.execute(classScope) @@ -1367,7 +1367,7 @@ class Compiler( return statement(body.pos) { cxt -> - val forContext = cxt.copy(start) + val forContext = cxt.createChildScope(start) // loop var: StoredObject val loopSO = forContext.addItem(tVar.value, true, ObjNull) @@ -1536,7 +1536,7 @@ class Compiler( var result: Obj = ObjVoid lateinit var doScope: Scope do { - doScope = it.copy().apply { skipScopeCreation = true } + doScope = it.createChildScope().apply { skipScopeCreation = true } try { result = body.execute(doScope) } catch (e: LoopBreakContinueException) { @@ -1816,7 +1816,7 @@ class Compiler( val block = parseScript() return statement(startPos) { // block run on inner context: - block.execute(if (it.skipScopeCreation) it else it.copy(startPos)) + block.execute(if (it.skipScopeCreation) it else it.createChildScope(startPos)) }.also { val t1 = cc.next() if (t1.type != Token.Type.RBRACE) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index 9d53fd6..3429432 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -138,13 +138,27 @@ open class Scope( ) } - fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope = + /** + * Creates a new child scope using the provided arguments and optional `thisObj`. + */ + fun createChildScope(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope = Scope(this, args, pos, newThisObj ?: thisObj) - fun copy(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope = + /** + * Creates a new child scope using the provided arguments and optional `thisObj`. + * The child scope inherits the current scope's properties such as position and the existing `thisObj` if no new `thisObj` is provided. + * + * @param args The arguments to associate with the child scope. Defaults to [Arguments.EMPTY]. + * @param newThisObj The new `thisObj` to associate with the child scope. Defaults to the current scope's `thisObj` if not provided. + * @return A new instance of [Scope] initialized with the specified arguments and `thisObj`. + */ + fun createChildScope(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope = Scope(this, args, pos, newThisObj ?: thisObj) - fun copy() = Scope(this, args, pos, thisObj) + /** + * @return A child scope with the same arguments, position and [thisObj] + */ + fun createChildScope() = Scope(this, args, pos, thisObj) fun addItem( name: String, @@ -240,8 +254,6 @@ open class Scope( p = p.parent } println("--------------------") - ObjVoid - } open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt index b559a3a..640ffa9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt @@ -17,15 +17,12 @@ package net.sergeych.lyng.obj -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.sergeych.lyng.* import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonType -import net.sergeych.synctools.ProtectedOp open class Obj { @@ -39,12 +36,12 @@ open class Obj { var isFrozen: Boolean = false - private val monitor = Mutex() +// private val monitor = Mutex() // private val memberMutex = Mutex() - internal var parentInstances: MutableList = mutableListOf() +// internal var parentInstances: MutableList = mutableListOf() - private val opInstances = ProtectedOp() +// private val opInstances = ProtectedOp() open suspend fun inspect(scope: Scope): String = toString(scope).value @@ -100,7 +97,7 @@ open class Obj { // note that getInstanceMember traverses the hierarchy objClass.getInstanceMember(scope.pos, name).value - fun getMemberOrNull(name: String): Obj? = objClass.getInstanceMemberOrNull(name)?.value +// fun getMemberOrNull(name: String): Obj? = objClass.getInstanceMemberOrNull(name)?.value // methods that to override @@ -232,14 +229,14 @@ open class Obj { if (isFrozen) scope.raiseError("attempt to mutate frozen object") } - suspend fun sync(block: () -> T): T = monitor.withLock { block() } +// suspend fun sync(block: () -> T): T = monitor.withLock { block() } open suspend fun readField(scope: Scope, name: String): ObjRecord { // could be property or class field: val obj = objClass.getInstanceMemberOrNull(name) ?: scope.raiseError("no such field: $name") return when (val value = obj.value) { is Statement -> { - ObjRecord(value.execute(scope.copy(scope.pos, newThisObj = this)), obj.isMutable) + ObjRecord(value.execute(scope.createChildScope(scope.pos, newThisObj = this)), obj.isMutable) } // could be writable property naturally // null -> ObjNull.asReadonly @@ -268,11 +265,11 @@ open class Obj { } suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments): Obj = - callOn(scope.copy(scope.pos, args = args, newThisObj = thisObj)) + callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj)) suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj = callOn( - scope.copy( + scope.createChildScope( scope.pos, args = Arguments(args.toList()), newThisObj = thisObj @@ -281,7 +278,7 @@ open class Obj { suspend fun invoke(scope: Scope, thisObj: Obj): Obj = callOn( - scope.copy( + scope.createChildScope( scope.pos, args = Arguments.EMPTY, newThisObj = thisObj @@ -289,7 +286,7 @@ open class Obj { ) suspend fun invoke(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj = - callOn(scope.copy(atPos, args = args, newThisObj = thisObj)) + callOn(scope.createChildScope(atPos, args = args, newThisObj = thisObj)) val asReadonly: ObjRecord by lazy { ObjRecord(this, false) } @@ -302,7 +299,7 @@ open class Obj { } fun autoInstanceScope(parent: Scope): Scope { - val scope = parent.copy(newThisObj = this, args = parent.args) + val scope = parent.createChildScope(newThisObj = this, args = parent.args) for (m in objClass.members) { scope.objects[m.key] = m.value } @@ -335,7 +332,7 @@ open class Obj { } // utilities addFn("let") { - args.firstAndOnly().callOn(copy(Arguments(thisObj))) + args.firstAndOnly().callOn(createChildScope(Arguments(thisObj))) } addFn("apply") { val body = args.firstAndOnly() @@ -347,7 +344,7 @@ open class Obj { thisObj } addFn("also") { - args.firstAndOnly().callOn(copy(Arguments(thisObj))) + args.firstAndOnly().callOn(createChildScope(Arguments(thisObj))) thisObj } addFn("run") { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt index f2b2e50..9fdf781 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt @@ -61,7 +61,7 @@ open class ObjClass( override suspend fun callOn(scope: Scope): Obj { val instance = ObjInstance(this) - instance.instanceScope = scope.copy(newThisObj = instance, args = scope.args) + instance.instanceScope = scope.createChildScope(newThisObj = instance, args = scope.args) if (instanceConstructor != null) { instanceConstructor!!.execute(instance.instanceScope) } @@ -69,7 +69,7 @@ open class ObjClass( } suspend fun callWithArgs(scope: Scope, vararg plainArgs: Obj): Obj { - return callOn(scope.copy(Arguments(*plainArgs))) + return callOn(scope.createChildScope(Arguments(*plainArgs))) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt index c5cfe68..63f88b8 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt @@ -53,7 +53,7 @@ class ObjDynamic : Obj() { internal var writeCallback: Statement? = null override suspend fun readField(scope: Scope, name: String): ObjRecord { - return readCallback?.execute(scope.copy(Arguments(ObjString(name))))?.let { + return readCallback?.execute(scope.createChildScope(Arguments(ObjString(name))))?.let { if (writeCallback != null) it.asMutable else @@ -63,17 +63,17 @@ class ObjDynamic : Obj() { } override suspend fun writeField(scope: Scope, name: String, newValue: Obj) { - writeCallback?.execute(scope.copy(Arguments(ObjString(name), newValue))) + writeCallback?.execute(scope.createChildScope(Arguments(ObjString(name), newValue))) ?: super.writeField(scope, name, newValue) } override suspend fun getAt(scope: Scope, index: Obj): Obj { - return readCallback?.execute( scope.copy(Arguments(index))) + return readCallback?.execute( scope.createChildScope(Arguments(index))) ?: super.getAt(scope, index) } override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) { - writeCallback?.execute(scope.copy(Arguments(index, newValue))) + writeCallback?.execute(scope.createChildScope(Arguments(index, newValue))) ?: super.putAt(scope, index, newValue) } @@ -82,7 +82,7 @@ class ObjDynamic : Obj() { suspend fun create(scope: Scope, builder: Statement): ObjDynamic { val delegate = ObjDynamic() val context = ObjDynamicContext(delegate) - builder.execute(scope.copy(newThisObj = context)) + builder.execute(scope.createChildScope(newThisObj = context)) return delegate } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt index f5efcb5..b623c42 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt @@ -28,9 +28,9 @@ import net.sergeych.synctools.withLock import kotlin.contracts.ExperimentalContracts /** - * note on [getStackTrace]. If [useStackTrace] is not null, it is used instead. Otherwise, it is calculated - * from the current scope which is treated as exception scope. It is used to restore serialized - * exception with stack trace; the scope of the de-serialized exception is not valid + * Note on [getStackTrace]. If [useStackTrace] is not null, it is used instead. Otherwise, it is calculated + * from the current scope, which is treated as an exception scope. It is used to restore a serialized + * exception with stack trace; the scope of the deserialized exception is not valid * for stack unwinding. */ open class ObjException( @@ -127,7 +127,7 @@ open class ObjException( } } - val Root = ExceptionClass("Throwable").apply { + val Root = ExceptionClass("Exception").apply { addConst("message", statement { (thisObj as ObjException).message.toObj() }) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt index fb91d83..4787a0d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt @@ -58,7 +58,7 @@ class ObjFlowBuilder(val output: SendChannel) : Obj() { private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChannel { val channel = Channel(Channel.RENDEZVOUS) val builder = ObjFlowBuilder(channel) - val builderScope = scope.copy(newThisObj = builder) + val builderScope = scope.createChildScope(newThisObj = builder) globalLaunch { try { producer.execute(builderScope) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstanceClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstanceClass.kt index 098d73e..35a2db9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstanceClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstanceClass.kt @@ -32,7 +32,7 @@ class ObjInstanceClass(val name: String) : ObjClass(name) { 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)) + val newScope = scope.createChildScope(args = Arguments(args)) return (callOn(newScope) as ObjInstance).apply { deserializeStateVars(scope, decoder) invokeInstanceMethod(scope, "onDeserialized") { ObjVoid } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt index c2c7962..9a84340 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt @@ -93,7 +93,7 @@ val ObjIterable by lazy { val fn = requiredArg(0) while (it.invokeInstanceMethod(this, "hasNext").toBool()) { val x = it.invokeInstanceMethod(this, "next") - fn.execute(this.copy(Arguments(listOf(x)))) + fn.execute(this.createChildScope(Arguments(listOf(x)))) } ObjVoid } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportProvider.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportProvider.kt index 3ee2699..77073e1 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportProvider.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportProvider.kt @@ -71,7 +71,7 @@ abstract class ImportProvider( newModuleAt(pos).also { it.eval("import lyng.stdlib\n") } - }.copy() + }.createChildScope() } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt index 81ad9a3..4b8ced0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt @@ -60,7 +60,7 @@ abstract class Statement( val type = ObjClass("Callable") } - suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.copy(args = Arguments(*args))) + suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.createChildScope(args = Arguments(*args))) } diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 8752538..25d0a91 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -3170,6 +3170,34 @@ class ScriptTest { ) } + @Test + fun testExceptionSerializationPlain() = runTest { + eval( + """ + import lyng.serialization + val x = [1,2,3] + assertEquals( "[1,2,3]", x.toString() ) + try { + throw "test" + } + catch (e) { + println(e.stackTrace) + e.printStackTrace() + val coded = Lynon.encode(e) + val decoded = Lynon.decode(coded) + assertEquals( e::class, decoded::class ) + assertEquals( e.stackTrace, decoded.stackTrace ) + assertEquals( e.message, decoded.message ) + println("-------------------- e") + println(e.toString()) + println("-------------------- dee") + println(decoded.toString()) + assertEquals( e.toString(), decoded.toString() ) + } + """.trimIndent() + ) + } + @Test fun testThisInClosure() = runTest { eval( @@ -3287,25 +3315,25 @@ class ScriptTest { // @Test - fun testMinimumOptimization() = runTest { - val x = Scope().eval( - """ - fun naiveCountHappyNumbers() { - var count = 0 - for( n1 in 0..9 ) - for( n2 in 0..9 ) - for( n3 in 0..9 ) - for( n4 in 0..9 ) - for( n5 in 0..9 ) - for( n6 in 0..9 ) - if( n1 + n2 + n3 == n4 + n5 + n6 ) count++ - count - } - naiveCountHappyNumbers() - """.trimIndent() - ).toInt() - assertEquals(55252, x) - } +// fun testMinimumOptimization() = runTest { +// val x = Scope().eval( +// """ +// fun naiveCountHappyNumbers() { +// var count = 0 +// for( n1 in 0..9 ) +// for( n2 in 0..9 ) +// for( n3 in 0..9 ) +// for( n4 in 0..9 ) +// for( n5 in 0..9 ) +// for( n6 in 0..9 ) +// if( n1 + n2 + n3 == n4 + n5 + n6 ) count++ +// count +// } +// naiveCountHappyNumbers() +// """.trimIndent() +// ).toInt() +// assertEquals(55252, x) +// } @Test fun testRegex1() = runTest {