From 6eabcc315f47eafdcacf04d9053f78c70046ce16 Mon Sep 17 00:00:00 2001 From: sergeych Date: Mon, 17 Nov 2025 14:08:32 +0100 Subject: [PATCH] fixed errors --- lynglib/build.gradle.kts | 2 +- .../kotlin/net/sergeych/lyng/ClosureScope.kt | 9 ++-- .../kotlin/net/sergeych/lyng/Compiler.kt | 13 ++++- .../kotlin/net/sergeych/lyng/obj/ObjBuffer.kt | 5 +- .../net/sergeych/lyng/obj/ObjInstance.kt | 51 ++++++++++++------- .../kotlin/net/sergeych/lyng/obj/ObjRef.kt | 18 +++++++ 6 files changed, 72 insertions(+), 26 deletions(-) diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index 0ce7518..fe9763e 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.10.3-SNAPSHOT" +version = "0.11.1-SNAPSHOT" buildscript { repositories { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt index 46d466c..a6e7447 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt @@ -30,10 +30,11 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) : Scope(callScope, callScope.args, thisObj = closureScope.thisObj) { init { - // Preserve the lexical class context from where the lambda was defined (closure), - // so that visibility checks (private/protected) inside lambdas executed within other - // methods (e.g., Mutex.withLock) still see the original declaring class context. - this.currentClassCtx = closureScope.currentClassCtx + // Preserve the lexical class context of the closure by default. This ensures that lambdas + // created inside a class method keep access to that class's private/protected members even + // when executed from within another object's method (e.g., Mutex.withLock), which may set + // its own currentClassCtx temporarily. If the closure has no class context, inherit caller's. + this.currentClassCtx = closureScope.currentClassCtx ?: callScope.currentClassCtx } override fun get(name: String): ObjRecord? { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 6fc1a92..59fa4d0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -224,8 +224,19 @@ class Compiler( while (true) { val opToken = cc.next() - val op = byLevel[level][opToken.type] + val op = byLevel[level][opToken.type] if (op == null) { + // handle ternary conditional at the top precedence level only: a ? b : c + if (opToken.type == Token.Type.QUESTION && level == 0) { + val thenRef = parseExpressionLevel(level + 1) + ?: throw ScriptError(opToken.pos, "Expecting expression after '?'") + val colon = cc.next() + if (colon.type != Token.Type.COLON) colon.raiseSyntax("missing ':'") + val elseRef = parseExpressionLevel(level + 1) + ?: throw ScriptError(colon.pos, "Expecting expression after ':'") + lvalue = ConditionalRef(lvalue!!, thenRef, elseRef) + continue + } cc.previous() break } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt index 9dfb67e..9426f73 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt @@ -62,7 +62,10 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() { val size by byteArray::size override fun hashCode(): Int { - return byteArray.hashCode() + // On some platforms (notably JS), UByteArray.hashCode() is not content-based. + // For map/set keys we must ensure hash is consistent with equals(contentEquals). + // Convert to ByteArray and use contentHashCode() which is value-based and stable. + return byteArray.asByteArray().contentHashCode() } override suspend fun compareTo(scope: Scope, other: Obj): Int { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt index 8bfe0d9..79afb6b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt @@ -32,9 +32,15 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { // Direct (unmangled) lookup first instanceScope[name]?.let { val decl = it.declaringClass ?: objClass.findDeclaringClassOf(name) - val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx - val allowed = if (it.visibility == net.sergeych.lyng.Visibility.Private) (decl === objClass) else canAccessMember(it.visibility, decl, caller) - if (!allowed) + // When execution passes through suspension points (e.g., withLock), + // currentClassCtx could be lost. Fall back to the instance scope class ctx + // to preserve correct visibility semantics for private/protected members + // accessed from within the class methods. + // Allow unconditional access when accessing through `this` of the same instance + if (scope.thisObj === this) return it + val caller0 = scope.currentClassCtx ?: instanceScope.currentClassCtx + val caller = caller0 // do not default to objClass for outsiders + if (!canAccessMember(it.visibility, decl, caller)) scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${decl?.className ?: "?"})")) return it } @@ -51,15 +57,15 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { return null } findMangled()?.let { rec -> - val declName = rec.importedFrom?.packageName // unused; use mangled key instead // derive declaring class by mangled prefix: try self then parents val declaring = when { instanceScope.objects.containsKey("${cls.className}::$name") -> cls else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") } } - val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx - val allowed = if (rec.visibility == net.sergeych.lyng.Visibility.Private) (declaring === objClass) else canAccessMember(rec.visibility, declaring, caller) - if (!allowed) + if (scope.thisObj === this) return rec + val caller0 = scope.currentClassCtx ?: instanceScope.currentClassCtx + val caller = caller0 // do not default to objClass for outsiders + if (!canAccessMember(rec.visibility, declaring, caller)) scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${declaring?.className ?: "?"})")) return rec } @@ -71,10 +77,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { // Direct (unmangled) first instanceScope[name]?.let { f -> val decl = f.declaringClass ?: objClass.findDeclaringClassOf(name) - val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx - val allowed = if (f.visibility == net.sergeych.lyng.Visibility.Private) (decl === objClass) else canAccessMember(f.visibility, decl, caller) - if (!allowed) - ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl?.className ?: "?"})").raise() + if (scope.thisObj === this) { + // direct self-assignment allowed; enforce mutability below + } else { + val caller0 = scope.currentClassCtx ?: instanceScope.currentClassCtx + val caller = caller0 // do not default to objClass for outsiders + if (!canAccessMember(f.visibility, decl, caller)) + ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl?.className ?: "?"})").raise() + } if (!f.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise() if (f.value.assign(scope, newValue) == null) f.value = newValue @@ -95,10 +105,12 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { instanceScope.objects.containsKey("${cls.className}::$name") -> cls else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") } } - val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx - val allowed = if (rec.visibility == net.sergeych.lyng.Visibility.Private) (declaring === objClass) else canAccessMember(rec.visibility, declaring, caller) - if (!allowed) - ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${declaring?.className ?: "?"})").raise() + if (scope.thisObj !== this) { + val caller0 = scope.currentClassCtx ?: instanceScope.currentClassCtx + val caller = caller0 // do not default to objClass for outsiders + if (!canAccessMember(rec.visibility, declaring, caller)) + ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${declaring?.className ?: "?"})").raise() + } if (!rec.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise() if (rec.value.assign(scope, newValue) == null) rec.value = newValue @@ -111,9 +123,9 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { onNotFoundResult: (()->Obj?)?): Obj = instanceScope[name]?.let { rec -> val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name) - val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx - val allowed = if (rec.visibility == net.sergeych.lyng.Visibility.Private) (decl === objClass) else canAccessMember(rec.visibility, decl, caller) - if (!allowed) + val caller0 = scope.currentClassCtx ?: instanceScope.currentClassCtx + val caller = caller0 ?: if (scope.thisObj === this) objClass else null + if (!canAccessMember(rec.visibility, decl, caller)) scope.raiseError(ObjAccessException(scope, "can't invoke method $name (declared in ${decl?.className ?: "?"})")) // execute with lexical class context propagated to declaring class val saved = instanceScope.currentClassCtx @@ -131,7 +143,8 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { // fallback: class-scope function (registered during class body execution) objClass.classScope?.objects?.get(name)?.let { rec -> val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name) - val caller = scope.currentClassCtx + val caller0 = scope.currentClassCtx ?: instanceScope.currentClassCtx + val caller = caller0 ?: if (scope.thisObj === this) objClass else null if (!canAccessMember(rec.visibility, decl, caller)) scope.raiseError(ObjAccessException(scope, "can't invoke method $name (declared in ${decl?.className ?: "?"})")) val saved = instanceScope.currentClassCtx diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt index 491236f..b024564 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt @@ -253,6 +253,24 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r } } +/** Conditional (ternary) operator reference: cond ? a : b */ +class ConditionalRef( + private val condition: ObjRef, + private val ifTrue: ObjRef, + private val ifFalse: ObjRef +) : ObjRef { + override suspend fun get(scope: Scope): ObjRecord { + val condVal = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) condition.evalValue(scope) else condition.get(scope).value + val condTrue = when (condVal) { + is ObjBool -> condVal.value + is ObjInt -> condVal.value != 0L + else -> condVal.toBool() + } + val branch = if (condTrue) ifTrue else ifFalse + return branch.get(scope) + } +} + /** Cast operator reference: left `as` rightType or `as?` (nullable). */ class CastRef( private val valueRef: ObjRef,