diff --git a/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/fs/LyngFsModule.kt b/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/fs/LyngFsModule.kt index 5c30fc2..ecff150 100644 --- a/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/fs/LyngFsModule.kt +++ b/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/fs/LyngFsModule.kt @@ -23,6 +23,8 @@ package net.sergeych.lyng.io.fs import net.sergeych.lyng.ModuleScope import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeFacade +import net.sergeych.lyng.requireScope import net.sergeych.lyng.miniast.* import net.sergeych.lyng.obj.* import net.sergeych.lyng.pacman.ImportManager @@ -437,7 +439,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) { moduleName = module.packageName ) { fsGuard { - val chunkIt = thisObj.invokeInstanceMethod(this, "readUtf8Chunks") + val chunkIt = thisObj.invokeInstanceMethod(requireScope(), "readUtf8Chunks") ObjFsLinesIterator(chunkIt) } } @@ -463,7 +465,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) { // --- Helper classes and utilities --- -private fun parsePathArg(scope: Scope, self: ObjPath, arg: Obj): LyngPath { +private fun parsePathArg(scope: ScopeFacade, self: ObjPath, arg: Obj): LyngPath { return when (arg) { is ObjString -> arg.value.toPath() is ObjPath -> arg.path @@ -472,11 +474,11 @@ private fun parsePathArg(scope: Scope, self: ObjPath, arg: Obj): LyngPath { } // Map Fs access denials to Lyng runtime exceptions for script-friendly errors -private suspend inline fun Scope.fsGuard(crossinline block: suspend () -> Obj): Obj { +private suspend inline fun ScopeFacade.fsGuard(crossinline block: suspend () -> Obj): Obj { return try { block() } catch (e: AccessDeniedException) { - raiseError(ObjIllegalOperationException(this, e.reasonDetail ?: "access denied")) + raiseError(ObjIllegalOperationException(requireScope(), e.reasonDetail ?: "access denied")) } } @@ -668,16 +670,17 @@ class ObjFsLinesIterator( } } - private suspend fun ensureBufferFilled(scope: Scope) { + private suspend fun ensureBufferFilled(scope: ScopeFacade) { if (buffer.contains('\n') || exhausted) return + val actualScope = scope.requireScope() // Pull next chunk from the underlying iterator - val it = chunksIterator.invokeInstanceMethod(scope, "iterator") - val hasNext = it.invokeInstanceMethod(scope, "hasNext").toBool() + val it = chunksIterator.invokeInstanceMethod(actualScope, "iterator") + val hasNext = it.invokeInstanceMethod(actualScope, "hasNext").toBool() if (!hasNext) { exhausted = true return } - val next = it.invokeInstanceMethod(scope, "next") + val next = it.invokeInstanceMethod(actualScope, "next") buffer += next.toString() } } diff --git a/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/process/LyngProcessModule.kt b/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/process/LyngProcessModule.kt index a832fbc..3c41a16 100644 --- a/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/process/LyngProcessModule.kt +++ b/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/process/LyngProcessModule.kt @@ -20,6 +20,8 @@ package net.sergeych.lyng.io.process import kotlinx.coroutines.flow.Flow import net.sergeych.lyng.ModuleScope import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeFacade +import net.sergeych.lyng.requireScope import net.sergeych.lyng.miniast.* import net.sergeych.lyng.obj.* import net.sergeych.lyng.pacman.ImportManager @@ -204,20 +206,21 @@ class ObjRunningProcess( override fun toString(): String = "RunningProcess($process)" } -private suspend inline fun Scope.processGuard(crossinline block: suspend () -> Obj): Obj { +private suspend inline fun ScopeFacade.processGuard(crossinline block: suspend () -> Obj): Obj { return try { block() } catch (e: ProcessAccessDeniedException) { - raiseError(ObjIllegalOperationException(this, e.reasonDetail ?: "process access denied")) + raiseError(ObjIllegalOperationException(requireScope(), e.reasonDetail ?: "process access denied")) } catch (e: Exception) { - raiseError(ObjIllegalOperationException(this, e.message ?: "process error")) + raiseError(ObjIllegalOperationException(requireScope(), e.message ?: "process error")) } } -private fun Flow.toLyngFlow(flowScope: Scope): ObjFlow { - val producer = ObjNativeCallable { - val builder = (this as? net.sergeych.lyng.BytecodeClosureScope)?.callScope?.thisObj as? ObjFlowBuilder - ?: this.thisObj as? ObjFlowBuilder +private fun Flow.toLyngFlow(flowScope: ScopeFacade): ObjFlow { + val producer = net.sergeych.lyng.obj.ObjExternCallable.fromBridge { + val scope = requireScope() + val builder = (scope as? net.sergeych.lyng.BytecodeClosureScope)?.callScope?.thisObj as? ObjFlowBuilder + ?: scope.thisObj as? ObjFlowBuilder this@toLyngFlow.collect { try { @@ -229,5 +232,5 @@ private fun Flow.toLyngFlow(flowScope: Scope): ObjFlow { } ObjVoid } - return ObjFlow(producer, flowScope) + return ObjFlow(producer, flowScope.requireScope()) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt index 7f9bd22..1fdb716 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt @@ -28,7 +28,7 @@ class BlockStatement( override val pos: Pos = startPos override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "block statement") + return bytecodeOnly(scope, "block statement") } fun statements(): List = block.debugStatements() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassInstanceDeclStatements.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassInstanceDeclStatements.kt index 5740f2c..fac98d0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassInstanceDeclStatements.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassInstanceDeclStatements.kt @@ -24,7 +24,7 @@ class ClassInstanceInitDeclStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "class instance init declaration") + return bytecodeOnly(scope, "class instance init declaration") } } @@ -42,7 +42,7 @@ class ClassInstanceFieldDeclStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "class instance field declaration") + return bytecodeOnly(scope, "class instance field declaration") } } @@ -61,7 +61,7 @@ class ClassInstancePropertyDeclStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "class instance property declaration") + return bytecodeOnly(scope, "class instance property declaration") } } @@ -79,6 +79,6 @@ class ClassInstanceDelegatedDeclStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "class instance delegated declaration") + return bytecodeOnly(scope, "class instance delegated declaration") } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt index 0027d98..b9d80a5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt @@ -23,7 +23,7 @@ import net.sergeych.lyng.obj.ObjRecord /** * Bytecode-oriented closure scope that keeps the call scope parent chain for stack traces * while carrying the lexical closure for `this` variants and module resolution. - * Unlike interpreter closure scopes, it does not override name lookup. + * Unlike legacy closure scopes, it does not override name lookup. */ class BytecodeClosureScope( val callScope: Scope, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DelegatedVarDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DelegatedVarDeclStatement.kt index 1ac1bce..a4bef18 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DelegatedVarDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DelegatedVarDeclStatement.kt @@ -32,6 +32,6 @@ class DelegatedVarDeclStatement( override val pos: Pos = startPos override suspend fun execute(context: Scope): Obj { - return interpreterDisabled(context, "delegated var declaration") + return bytecodeOnly(context, "delegated var declaration") } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DestructuringVarDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DestructuringVarDeclStatement.kt index 45066e3..5ebc111 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DestructuringVarDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DestructuringVarDeclStatement.kt @@ -29,6 +29,6 @@ class DestructuringVarDeclStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(context: Scope): Obj { - return interpreterDisabled(context, "destructuring declaration") + return bytecodeOnly(context, "destructuring declaration") } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/EnumDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/EnumDeclStatement.kt index 54b536f..1ad8b66 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/EnumDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/EnumDeclStatement.kt @@ -28,7 +28,7 @@ class EnumDeclStatement( override val pos: Pos = startPos override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "enum declaration") + return bytecodeOnly(scope, "enum declaration") } override suspend fun callOn(scope: Scope): Obj { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ExtensionPropertyDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ExtensionPropertyDeclStatement.kt index 477fbfd..18e4d9c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ExtensionPropertyDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ExtensionPropertyDeclStatement.kt @@ -29,6 +29,6 @@ class ExtensionPropertyDeclStatement( override val pos: Pos = startPos override suspend fun execute(context: Scope): Obj { - return interpreterDisabled(context, "extension property declaration") + return bytecodeOnly(context, "extension property declaration") } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/RegexCache.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/RegexCache.kt index 3ab51ab..7483e72 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/RegexCache.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/RegexCache.kt @@ -20,7 +20,7 @@ package net.sergeych.lyng /** * Tiny, size-bounded cache for compiled Regex patterns. Activated only when [PerfFlags.REGEX_CACHE] is true. * This is a very simple FIFO-ish cache sufficient for micro-benchmarks and common repeated patterns. - * Not thread-safe by design; the interpreter typically runs scripts on confined executors. + * Not thread-safe by design; the runtime typically runs scripts on confined executors. */ object RegexCache { private const val MAX = 64 @@ -48,4 +48,4 @@ object RegexCache { } fun clear() = map.clear() -} \ No newline at end of file +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index 471610c..828e32e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -725,7 +725,7 @@ open class Scope( return ns.objClass } - inline fun addVoidFn(vararg names: String, crossinline fn: suspend Scope.() -> Unit) { + inline fun addVoidFn(vararg names: String, crossinline fn: suspend ScopeFacade.() -> Unit) { addFn(*names) { fn(this) ObjVoid @@ -741,8 +741,8 @@ open class Scope( return CmdDisassembler.disassemble(bytecode) } - fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend Scope.() -> Obj) { - val newFn = net.sergeych.lyng.obj.ObjNativeCallable { fn() } + fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend ScopeFacade.() -> Obj) { + val newFn = net.sergeych.lyng.obj.ObjExternCallable.fromBridge { fn() } for (name in names) { addItem( name, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ScopeFacade.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ScopeFacade.kt index fec0256..7364109 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ScopeFacade.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ScopeFacade.kt @@ -18,6 +18,7 @@ package net.sergeych.lyng import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.ObjRecord +import net.sergeych.lyng.obj.ObjString /** * Limited facade for Kotlin bridge callables. @@ -31,11 +32,20 @@ interface ScopeFacade { suspend fun resolve(rec: ObjRecord, name: String): Obj suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) fun raiseError(message: String): Nothing + fun raiseError(obj: net.sergeych.lyng.obj.ObjException): Nothing + fun raiseClassCastError(message: String): Nothing + fun raiseIllegalArgument(message: String): Nothing + fun raiseNoSuchElement(message: String = "No such element"): Nothing fun raiseSymbolNotFound(name: String): Nothing fun raiseIllegalState(message: String = "Illegal argument error"): Nothing + fun raiseNotImplemented(what: String = "operation"): Nothing + suspend fun call(callee: Obj, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Obj + suspend fun toStringOf(obj: Obj, forInspect: Boolean = false): ObjString + suspend fun inspect(obj: Obj): String + fun trace(text: String = "") } -internal class ScopeBridge(private val scope: Scope) : ScopeFacade { +internal class ScopeBridge(internal val scope: Scope) : ScopeFacade { override val args: Arguments get() = scope.args override var pos: Pos @@ -48,6 +58,50 @@ internal class ScopeBridge(private val scope: Scope) : ScopeFacade { override suspend fun resolve(rec: ObjRecord, name: String): Obj = scope.resolve(rec, name) override suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) = scope.assign(rec, name, newValue) override fun raiseError(message: String): Nothing = scope.raiseError(message) + override fun raiseError(obj: net.sergeych.lyng.obj.ObjException): Nothing = scope.raiseError(obj) + override fun raiseClassCastError(message: String): Nothing = scope.raiseClassCastError(message) + override fun raiseIllegalArgument(message: String): Nothing = scope.raiseIllegalArgument(message) + override fun raiseNoSuchElement(message: String): Nothing = scope.raiseNoSuchElement(message) override fun raiseSymbolNotFound(name: String): Nothing = scope.raiseSymbolNotFound(name) override fun raiseIllegalState(message: String): Nothing = scope.raiseIllegalState(message) + override fun raiseNotImplemented(what: String): Nothing = scope.raiseNotImplemented(what) + override suspend fun call(callee: Obj, args: Arguments, newThisObj: Obj?): Obj { + return callee.callOn(scope.createChildScope(scope.pos, args = args, newThisObj = newThisObj)) + } + override suspend fun toStringOf(obj: Obj, forInspect: Boolean): ObjString = obj.toString(scope, forInspect) + override suspend fun inspect(obj: Obj): String = obj.inspect(scope) + override fun trace(text: String) = scope.trace(text) } + +inline fun ScopeFacade.requiredArg(index: Int): T { + if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}") + return (args.list[index].byValueCopy() as? T) + ?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index]::class.simpleName}") +} + +inline fun ScopeFacade.requireOnlyArg(): T { + if (args.list.size != 1) raiseError("Expected exactly 1 argument, got ${args.list.size}") + return requiredArg(0) +} + +fun ScopeFacade.requireExactCount(count: Int) { + if (args.list.size != count) { + raiseError("Expected exactly $count arguments, got ${args.list.size}") + } +} + +fun ScopeFacade.requireNoArgs() { + if (args.list.isNotEmpty()) { + raiseError("This function does not accept any arguments") + } +} + +inline fun ScopeFacade.thisAs(): T { + val obj = thisObj + return (obj as? T) ?: raiseClassCastError( + "Cannot cast ${obj.objClass.className} to ${T::class.simpleName}" + ) +} + +fun ScopeFacade.requireScope(): Scope = + (this as? ScopeBridge)?.scope ?: raiseIllegalState("ScopeFacade requires ScopeBridge") diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index 186ea6d..e240ca8 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -90,7 +90,7 @@ class Script( } } if (statements.isNotEmpty()) { - scope.raiseIllegalState("interpreter execution is not supported; missing module bytecode") + scope.raiseIllegalState("bytecode-only execution is required; missing module bytecode") } return ObjVoid } @@ -212,15 +212,15 @@ class Script( addConst("Unset", ObjUnset) addFn("print") { for ((i, a) in args.withIndex()) { - if (i > 0) print(' ' + a.toString(this).value) - else print(a.toString(this).value) + if (i > 0) print(' ' + toStringOf(a).value) + else print(toStringOf(a).value) } ObjVoid } addFn("println") { for ((i, a) in args.withIndex()) { - if (i > 0) print(' ' + a.toString(this).value) - else print(a.toString(this).value) + if (i > 0) print(' ' + toStringOf(a).value) + else print(toStringOf(a).value) } println() ObjVoid @@ -233,7 +233,7 @@ class Script( } else { Arguments.EMPTY } - callee.callOn(createChildScope(pos, args = rest)) + call(callee, rest) } addFn("floor") { val x = args.firstAndOnly() @@ -331,12 +331,12 @@ class Script( var result = value if (range.start != null && !range.start.isNull) { - if (result.compareTo(this, range.start) < 0) { + if (result.compareTo(requireScope(), range.start) < 0) { result = range.start } } if (range.end != null && !range.end.isNull) { - val cmp = range.end.compareTo(this, result) + val cmp = range.end.compareTo(requireScope(), result) if (range.isEndInclusive) { if (cmp < 0) result = range.end } else { @@ -359,20 +359,20 @@ class Script( addVoidFn("assert") { val cond = requiredArg(0) val message = if (args.size > 1) - ": " + (args[1] as Obj).callOn(this).toString(this).value + ": " + toStringOf(call(args[1] as Obj)).value else "" if (!cond.value == true) - raiseError(ObjAssertionFailedException(this, "Assertion failed$message")) + raiseError(ObjAssertionFailedException(requireScope(), "Assertion failed$message")) } addVoidFn("assertEquals") { val a = requiredArg(0) val b = requiredArg(1) - if (a.compareTo(this, b) != 0) + if (a.compareTo(requireScope(), b) != 0) raiseError( ObjAssertionFailedException( - this, - "Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}" + requireScope(), + "Assertion failed: ${inspect(a)} == ${inspect(b)}" ) ) } @@ -380,22 +380,22 @@ class Script( addVoidFn("assertEqual") { val a = requiredArg(0) val b = requiredArg(1) - if (a.compareTo(this, b) != 0) + if (a.compareTo(requireScope(), b) != 0) raiseError( ObjAssertionFailedException( - this, - "Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}" + requireScope(), + "Assertion failed: ${inspect(a)} == ${inspect(b)}" ) ) } addVoidFn("assertNotEquals") { val a = requiredArg(0) val b = requiredArg(1) - if (a.compareTo(this, b) == 0) + if (a.compareTo(requireScope(), b) == 0) raiseError( ObjAssertionFailedException( - this, - "Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}" + requireScope(), + "Assertion failed: ${inspect(a)} != ${inspect(b)}" ) ) } @@ -428,7 +428,7 @@ class Script( else -> raiseIllegalArgument("Expected 1 or 2 arguments, got ${args.size}") } val result = try { - code.callOn(this) + call(code) null } catch (e: ExecutionError) { e.errorObject @@ -437,7 +437,7 @@ class Script( } if (result == null) raiseError( ObjAssertionFailedException( - this, + requireScope(), "Expected exception but nothing was thrown" ) ) @@ -451,7 +451,7 @@ class Script( } addFn("dynamic", callSignature = CallSignature(tailBlockReceiverType = "DelegateContext")) { - ObjDynamic.create(this, requireOnlyArg()) + ObjDynamic.create(requireScope(), requireOnlyArg()) } val root = this @@ -468,7 +468,7 @@ class Script( val condition = requiredArg(0) if (!condition.value) { var message = args.list.getOrNull(1) - if (message is Obj && message.objClass == Statement.type) message = message.callOn(this) + if (message is Obj && message.objClass == Statement.type) message = call(message) raiseIllegalArgument(message?.toString() ?: "requirement not met") } ObjVoid @@ -477,26 +477,26 @@ class Script( val condition = requiredArg(0) if (!condition.value) { var message = args.list.getOrNull(1) - if (message is Obj && message.objClass == Statement.type) message = message.callOn(this) + if (message is Obj && message.objClass == Statement.type) message = call(message) raiseIllegalState(message?.toString() ?: "check failed") } ObjVoid } addFn("traceScope") { - this.trace(args.getOrNull(0)?.toString() ?: "") + trace(args.getOrNull(0)?.toString() ?: "") ObjVoid } addFn("run") { - requireOnlyArg().callOn(this) + call(requireOnlyArg()) } addFn("cached") { val builder = requireOnlyArg() val capturedScope = this var calculated = false var cachedValue: Obj = ObjVoid - ObjNativeCallable { + net.sergeych.lyng.obj.ObjExternCallable.fromBridge { if (!calculated) { - cachedValue = builder.callOn(capturedScope) + cachedValue = capturedScope.call(builder) calculated = true } cachedValue @@ -504,7 +504,7 @@ class Script( } addFn("lazy") { val builder = requireOnlyArg() - ObjLazyDelegate(builder, this) + ObjLazyDelegate(builder, requireScope()) } addVoidFn("delay") { val a = args.firstAndOnly() @@ -512,7 +512,7 @@ class Script( is ObjInt -> delay(a.value) is ObjReal -> delay((a.value * 1000).roundToLong()) is ObjDuration -> delay(a.duration) - else -> raiseIllegalArgument("Expected Int, Real or Duration, got ${a.inspect(this)}") + else -> raiseIllegalArgument("Expected Int, Real or Duration, got ${inspect(a)}") } } @@ -551,8 +551,9 @@ class Script( addFn("launch") { val callable = requireOnlyArg() + val captured = this ObjDeferred(globalDefer { - callable.callOn(this@addFn) + captured.call(callable) }) } @@ -564,7 +565,7 @@ class Script( addFn("flow", callSignature = CallSignature(tailBlockReceiverType = "FlowBuilder")) { // important is: current context contains closure often used in call; // we'll need it for the producer - ObjFlow(requireOnlyArg(), this) + ObjFlow(requireOnlyArg(), requireScope()) } val pi = ObjReal(PI) @@ -639,7 +640,7 @@ class Script( is ObjInt -> delay(a.value * 1000) is ObjReal -> delay((a.value * 1000).roundToLong()) is ObjDuration -> delay(a.duration) - else -> raiseIllegalArgument("Expected Duration, Int or Real, got ${a.inspect(this)}") + else -> raiseIllegalArgument("Expected Duration, Int or Real, got ${inspect(a)}") } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/TryStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/TryStatement.kt index e067428..7fd414d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/TryStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/TryStatement.kt @@ -36,7 +36,7 @@ class TryStatement( ) override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "try statement") + return bytecodeOnly(scope, "try statement") } private fun resolveExceptionClass(scope: Scope, name: String): ObjClass { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/VarDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/VarDeclStatement.kt index 03d312f..c58744a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/VarDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/VarDeclStatement.kt @@ -33,6 +33,6 @@ class VarDeclStatement( override val pos: Pos = startPos override suspend fun execute(context: Scope): Obj { - return interpreterDisabled(context, "var declaration") + return bytecodeOnly(context, "var declaration") } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/WhenStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/WhenStatement.kt index 6195730..ed3c4a2 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/WhenStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/WhenStatement.kt @@ -19,8 +19,8 @@ package net.sergeych.lyng import net.sergeych.lyng.obj.Obj sealed class WhenCondition(open val expr: Statement, open val pos: Pos) { - protected fun interpreterDisabled(scope: Scope): Nothing { - return scope.raiseIllegalState("interpreter execution is not supported; when condition requires bytecode") + protected fun bytecodeOnly(scope: Scope): Nothing { + return scope.raiseIllegalState("bytecode-only execution is required; when condition needs compiled bytecode") } abstract suspend fun matches(scope: Scope, value: Obj): Boolean @@ -31,7 +31,7 @@ class WhenEqualsCondition( override val pos: Pos, ) : WhenCondition(expr, pos) { override suspend fun matches(scope: Scope, value: Obj): Boolean { - return interpreterDisabled(scope) + return bytecodeOnly(scope) } } @@ -41,7 +41,7 @@ class WhenInCondition( override val pos: Pos, ) : WhenCondition(expr, pos) { override suspend fun matches(scope: Scope, value: Obj): Boolean { - return interpreterDisabled(scope) + return bytecodeOnly(scope) } } @@ -51,7 +51,7 @@ class WhenIsCondition( override val pos: Pos, ) : WhenCondition(expr, pos) { override suspend fun matches(scope: Scope, value: Obj): Boolean { - return interpreterDisabled(scope) + return bytecodeOnly(scope) } } @@ -64,6 +64,6 @@ class WhenStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "when statement") + return bytecodeOnly(scope, "when statement") } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt index c83752c..1b9b05d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -1880,13 +1880,23 @@ private suspend fun assignDestructurePattern(frame: CmdFrame, pattern: ListLiter private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value: Obj, pos: Pos) { when (ref) { - is ListLiteralRef -> assignDestructurePattern(frame, ref, value, pos) + is ListLiteralRef -> { + assignDestructurePattern(frame, ref, value, pos) + return + } is LocalSlotRef -> { val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = ref.captureOwnerScopeId != null) if (index != null) { frame.frame.setObj(index, value) return } + val scopeSlot = frame.fn.scopeSlotNames.indexOfFirst { it == ref.name } + if (scopeSlot >= 0) { + val target = frame.scopeTarget(scopeSlot) + val slotIndex = frame.ensureScopeSlot(target, scopeSlot) + target.setSlotValue(slotIndex, value) + return + } } is LocalVarRef -> { val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = false) @@ -1894,6 +1904,13 @@ private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value: frame.frame.setObj(index, value) return } + val scopeSlot = frame.fn.scopeSlotNames.indexOfFirst { it == ref.name } + if (scopeSlot >= 0) { + val target = frame.scopeTarget(scopeSlot) + val slotIndex = frame.ensureScopeSlot(target, scopeSlot) + target.setSlotValue(slotIndex, value) + return + } } is FastLocalVarRef -> { val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = false) @@ -1901,6 +1918,13 @@ private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value: frame.frame.setObj(index, value) return } + val scopeSlot = frame.fn.scopeSlotNames.indexOfFirst { it == ref.name } + if (scopeSlot >= 0) { + val target = frame.scopeTarget(scopeSlot) + val slotIndex = frame.ensureScopeSlot(target, scopeSlot) + target.setSlotValue(slotIndex, value) + return + } } else -> {} } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/DocRegistrationHelpers.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/DocRegistrationHelpers.kt index 6f9175d..19f1207 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/DocRegistrationHelpers.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/DocRegistrationHelpers.kt @@ -19,6 +19,7 @@ package net.sergeych.lyng.miniast import net.sergeych.lyng.ModuleScope import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeFacade import net.sergeych.lyng.Visibility import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.ObjClass @@ -40,7 +41,7 @@ inline fun Scope.addFnDoc( tags: Map> = emptyMap(), moduleName: String? = null, callSignature: net.sergeych.lyng.CallSignature? = null, - crossinline fn: suspend Scope.() -> T + crossinline fn: suspend ScopeFacade.() -> T ) { // Register runtime function(s) addFn(*names, callSignature = callSignature) { fn() } @@ -57,7 +58,7 @@ inline fun Scope.addVoidFnDoc( doc: String, tags: Map> = emptyMap(), moduleName: String? = null, - crossinline fn: suspend Scope.() -> Unit + crossinline fn: suspend ScopeFacade.() -> Unit ) { addFnDoc( *names, @@ -98,7 +99,7 @@ fun ObjClass.addFnDoc( visibility: Visibility = Visibility.Public, tags: Map> = emptyMap(), moduleName: String? = null, - code: suspend Scope.() -> Obj + code: suspend ScopeFacade.() -> Obj ) { // Register runtime method addFn(name, isOpen, visibility, code = code) @@ -136,7 +137,7 @@ fun ObjClass.addClassFnDoc( isOpen: Boolean = false, tags: Map> = emptyMap(), moduleName: String? = null, - code: suspend Scope.() -> Obj + code: suspend ScopeFacade.() -> Obj ) { addClassFn(name, isOpen, code) BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) { @@ -152,8 +153,8 @@ fun ObjClass.addPropertyDoc( type: TypeDoc? = null, visibility: Visibility = Visibility.Public, moduleName: String? = null, - getter: (suspend Scope.() -> Obj)? = null, - setter: (suspend Scope.(Obj) -> Unit)? = null + getter: (suspend ScopeFacade.() -> Obj)? = null, + setter: (suspend ScopeFacade.(Obj) -> Unit)? = null ) { addProperty(name, getter, setter, visibility) BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Accessor.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Accessor.kt index a6d339c..b13aeda 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Accessor.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Accessor.kt @@ -20,7 +20,6 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.Compiler import net.sergeych.lyng.Pos import net.sergeych.lyng.Scope -import net.sergeych.lyng.ScriptError // avoid KDOC bug: keep it @Suppress("unused") @@ -37,10 +36,11 @@ private class LambdaRef( private val getterFn: suspend (Scope) -> ObjRecord, private val setterFn: (suspend (Pos, Scope, Obj) -> Unit)? = null ) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord = getterFn(scope) + override suspend fun get(scope: Scope): ObjRecord { + return scope.raiseIllegalState("bytecode-only execution is required; Accessor evaluation is disabled") + } override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { - val s = setterFn ?: throw ScriptError(pos, "can't assign value") - s(pos, scope, newValue) + scope.raiseIllegalState("bytecode-only execution is required; Accessor assignment is disabled") } } @@ -57,4 +57,4 @@ val Accessor.getter: suspend (Scope) -> ObjRecord fun Accessor.setter(pos: Pos): suspend (Scope, Obj) -> Unit = { scope, newValue -> this.setAt(pos, scope, newValue) -} \ No newline at end of file +} 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 bd16f30..c143d64 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt @@ -562,10 +562,10 @@ open class Obj { else -> del.objClass.getInstanceMemberOrNull("getValue") } if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") { - val wrapper = ObjNativeCallable { + val wrapper = ObjExternCallable.fromBridge { val th2 = if (thisObj === ObjVoid) ObjNull else thisObj val allArgs = (listOf(th2, ObjString(name)) + args.list).toTypedArray() - del.invokeInstanceMethod(this, "invoke", Arguments(*allArgs)) + del.invokeInstanceMethod(requireScope(), "invoke", Arguments(*allArgs)) } return obj.copy( value = wrapper, @@ -740,7 +740,7 @@ open class Obj { returns = type("lyng.String"), moduleName = "lyng.stdlib" ) { - thisObj.toString(this, true) + toStringOf(thisObj, true) } addFnDoc( name = "inspect", @@ -748,7 +748,7 @@ open class Obj { returns = type("lyng.String"), moduleName = "lyng.stdlib" ) { - thisObj.inspect(this).toObj() + inspect(thisObj).toObj() } addFnDoc( name = "contains", @@ -757,7 +757,7 @@ open class Obj { returns = type("lyng.Bool"), moduleName = "lyng.stdlib" ) { - ObjBool(thisObj.contains(this, args.firstAndOnly())) + ObjBool(thisObj.contains(requireScope(), args.firstAndOnly())) } // utilities addFnDoc( @@ -766,7 +766,7 @@ open class Obj { params = listOf(ParamDoc("block")), moduleName = "lyng.stdlib" ) { - args.firstAndOnly().callOn(createChildScope(Arguments(thisObj))) + call(args.firstAndOnly(), Arguments(thisObj)) } addFnDoc( name = "apply", @@ -775,11 +775,12 @@ open class Obj { moduleName = "lyng.stdlib" ) { val body = args.firstAndOnly() + val scope = requireScope() (thisObj as? ObjInstance)?.let { - body.callOn(ApplyScope(this, it.instanceScope)) + body.callOn(ApplyScope(scope, it.instanceScope)) } ?: run { - val appliedScope = createChildScope(newThisObj = thisObj) - body.callOn(ApplyScope(this, appliedScope)) + val appliedScope = scope.createChildScope(newThisObj = thisObj) + body.callOn(ApplyScope(scope, appliedScope)) } thisObj } @@ -789,7 +790,7 @@ open class Obj { params = listOf(ParamDoc("block")), moduleName = "lyng.stdlib" ) { - args.firstAndOnly().callOn(createChildScope(Arguments(thisObj))) + call(args.firstAndOnly(), Arguments(thisObj)) thisObj } addFnDoc( @@ -798,16 +799,16 @@ open class Obj { params = listOf(ParamDoc("block")), moduleName = "lyng.stdlib" ) { - args.firstAndOnly().callOn(this) + call(args.firstAndOnly()) } addFn("getAt") { requireExactCount(1) - thisObj.getAt(this, requiredArg(0)) + thisObj.getAt(requireScope(), requiredArg(0)) } addFn("putAt") { requireExactCount(2) val newValue = args[1] - thisObj.putAt(this, requiredArg(0), newValue) + thisObj.putAt(requireScope(), requiredArg(0), newValue) newValue } addFnDoc( @@ -816,7 +817,7 @@ open class Obj { returns = type("lyng.String"), moduleName = "lyng.stdlib" ) { - thisObj.toJson(this).toString().toObj() + thisObj.toJson(requireScope()).toString().toObj() } addFnDoc( name = "toJsonString", @@ -824,7 +825,7 @@ open class Obj { returns = type("lyng.String"), moduleName = "lyng.stdlib" ) { - thisObj.toJson(this).toString().toObj() + thisObj.toJson(requireScope()).toString().toObj() } addFnDoc( name = "clamp", @@ -836,12 +837,12 @@ open class Obj { var result = thisObj if (range.start != null && !range.start.isNull) { - if (result.compareTo(this, range.start) < 0) { + if (result.compareTo(requireScope(), range.start) < 0) { result = range.start } } if (range.end != null && !range.end.isNull) { - val cmp = range.end.compareTo(this, result) + val cmp = range.end.compareTo(requireScope(), result) if (range.isEndInclusive) { if (cmp < 0) result = range.end } else { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt index 4209d06..febc726 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt @@ -32,7 +32,7 @@ val ObjArray by lazy { doc = "Iterator over elements of this array using its indexer.", returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))), moduleName = "lyng.stdlib" - ) { ObjArrayIterator(thisObj).also { it.init(this) } } + ) { ObjArrayIterator(thisObj).also { it.init(requireScope()) } } addFnDoc( name = "contains", @@ -42,9 +42,10 @@ val ObjArray by lazy { isOpen = true, moduleName = "lyng.stdlib" ) { + val scope = requireScope() val obj = args.firstAndOnly() - for (i in 0.. return@addFnDoc (mid).toObj() cmp > 0 -> high = mid - 1 @@ -105,4 +114,4 @@ val ObjArray by lazy { (-low - 1).toObj() } } -} \ No newline at end of file +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArrayIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArrayIterator.kt index 6a332a2..89af06b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArrayIterator.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArrayIterator.kt @@ -38,8 +38,8 @@ class ObjArrayIterator(val array: Obj) : Obj() { addFn("next") { val self = thisAs() if (self.nextIndex < self.lastIndex) { - self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj()) - } else raiseError(ObjIterationFinishedException(this)) + self.array.invokeInstanceMethod(requireScope(), "getAt", (self.nextIndex++).toObj()) + } else raiseError(ObjIterationFinishedException(requireScope())) } addFn("hasNext") { val self = thisAs() @@ -48,4 +48,4 @@ class ObjArrayIterator(val array: Obj) : Obj() { } } } -} \ No newline at end of file +} 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 0c67917..90a9bd2 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt @@ -930,9 +930,9 @@ open class ObjClass( isOverride: Boolean = false, pos: Pos = Pos.builtIn, methodId: Int? = null, - code: (suspend Scope.() -> Obj)? = null + code: (suspend net.sergeych.lyng.ScopeFacade.() -> Obj)? = null ) { - val stmt = code?.let { ObjNativeCallable { it() } } ?: ObjNull + val stmt = code?.let { ObjExternCallable.fromBridge { it() } } ?: ObjNull createField( name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, @@ -945,8 +945,8 @@ open class ObjClass( fun addProperty( name: String, - getter: (suspend Scope.() -> Obj)? = null, - setter: (suspend Scope.(Obj) -> Unit)? = null, + getter: (suspend net.sergeych.lyng.ScopeFacade.() -> Obj)? = null, + setter: (suspend net.sergeych.lyng.ScopeFacade.(Obj) -> Unit)? = null, visibility: Visibility = Visibility.Public, writeVisibility: Visibility? = null, declaringClass: ObjClass? = this, @@ -957,8 +957,8 @@ open class ObjClass( prop: ObjProperty? = null, methodId: Int? = null ) { - val g = getter?.let { ObjNativeCallable { it() } } - val s = setter?.let { ObjNativeCallable { it(requiredArg(0)); ObjVoid } } + val g = getter?.let { ObjExternCallable.fromBridge { it() } } + val s = setter?.let { ObjExternCallable.fromBridge { it(requiredArg(0)); ObjVoid } } val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s) createField( name, finalProp, false, visibility, writeVisibility, pos, declaringClass, @@ -969,8 +969,8 @@ open class ObjClass( } fun addClassConst(name: String, value: Obj) = createClassField(name, value) - fun addClassFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) { - createClassField(name, ObjNativeCallable { code() }, isOpen, type = ObjRecord.Type.Fun) + fun addClassFn(name: String, isOpen: Boolean = false, code: suspend net.sergeych.lyng.ScopeFacade.() -> Obj) { + createClassField(name, ObjExternCallable.fromBridge { code() }, isOpen, type = ObjRecord.Type.Fun) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDateTime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDateTime.kt index 60d24ac..dbce33b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDateTime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDateTime.kt @@ -53,9 +53,8 @@ class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() { if (rec.type == ObjRecord.Type.Fun) { val target = rec.value return ObjRecord( - ObjNativeCallable { - val callScope = createChildScope(args = args, newThisObj = this@ObjDateTime) - target.callOn(callScope) + ObjExternCallable.fromBridge { + call(target, args, newThisObj = this@ObjDateTime) }, rec.isMutable ) 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 b9d81ad..51596e4 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt @@ -139,7 +139,7 @@ open class ObjException( class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) { init { constructorMeta = ArgsDeclaration( - listOf(ArgsDeclaration.Item("message", defaultValue = ObjNativeCallable { ObjString(name) })), + listOf(ArgsDeclaration.Item("message", defaultValue = ObjExternCallable.fromBridge { ObjString(name) })), Token.Type.RPAREN ) } @@ -177,17 +177,17 @@ open class ObjException( } val Root = ExceptionClass("Exception").apply { - instanceInitializers.add(ObjNativeCallable { + instanceInitializers.add(ObjExternCallable.fromBridge { if (thisObj is ObjInstance) { val msg = get("message")?.value ?: ObjString("Exception") (thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg) - val stack = captureStackTrace(this) + val stack = captureStackTrace(requireScope()) (thisObj as ObjInstance).instanceScope.addItem("Exception::stackTrace", false, stack) } ObjVoid }) - instanceConstructor = ObjNativeCallable { ObjVoid } + instanceConstructor = ObjExternCallable.fromBridge { ObjVoid } addPropertyDoc( name = "message", doc = "Human‑readable error message.", @@ -244,7 +244,7 @@ open class ObjException( is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList() else -> ObjList() } - val at = stack.list.firstOrNull()?.toString(this) ?: ObjString("(unknown)") + val at = stack.list.firstOrNull()?.let { toStringOf(it) } ?: ObjString("(unknown)") ObjString("${thisObj.objClass.className}: $msg at $at") } } 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 d3c6d40..f22e6b9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt @@ -106,8 +106,8 @@ class ObjFlow(val producer: Obj, val scope: Scope) : Obj() { moduleName = "lyng.stdlib" ) { val objFlow = thisAs() - ObjFlowIterator(ObjNativeCallable { - objFlow.producer.callOn(this) + ObjFlowIterator(ObjExternCallable.fromBridge { + call(objFlow.producer) }) } } @@ -164,7 +164,7 @@ class ObjFlowIterator(val producer: Obj) : Obj() { doc = "Whether another element is available from the flow.", returns = type("lyng.Bool"), moduleName = "lyng.stdlib" - ) { thisAs().hasNext(this).toObj() } + ) { thisAs().hasNext(requireScope()).toObj() } addFnDoc( name = "next", doc = "Receive the next element from the flow or throw if completed.", @@ -172,7 +172,7 @@ class ObjFlowIterator(val producer: Obj) : Obj() { moduleName = "lyng.stdlib" ) { val x = thisAs() - x.next(this) + x.next(requireScope()) } addFnDoc( name = "cancelIteration", 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 f0e6d6b..2ed7f97 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt @@ -177,10 +177,10 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { else -> del.objClass.getInstanceMemberOrNull("getValue") } if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") { - val wrapper = ObjNativeCallable { + val wrapper = ObjExternCallable.fromBridge { val th2 = if (thisObj === ObjVoid) ObjNull else thisObj val allArgs = (listOf(th2, ObjString(name)) + args.list).toTypedArray() - del.invokeInstanceMethod(this, "invoke", Arguments(*allArgs)) + del.invokeInstanceMethod(requireScope(), "invoke", Arguments(*allArgs)) } return obj.copy(value = wrapper, type = ObjRecord.Type.Other) } 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 e717034..db68060 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt @@ -41,10 +41,11 @@ val ObjIterable by lazy { returns = type("lyng.List"), moduleName = "lyng.stdlib" ) { + val scope = requireScope() val result = mutableListOf() - val it = thisObj.invokeInstanceMethod(this, "iterator") - while (it.invokeInstanceMethod(this, "hasNext").toBool()) { - result.add(it.invokeInstanceMethod(this, "next")) + val it = thisObj.invokeInstanceMethod(scope, "iterator") + while (it.invokeInstanceMethod(scope, "hasNext").toBool()) { + result.add(it.invokeInstanceMethod(scope, "next")) } ObjList(result) } @@ -58,10 +59,11 @@ val ObjIterable by lazy { isOpen = true, moduleName = "lyng.stdlib" ) { + val scope = requireScope() val obj = args.firstAndOnly() - val it = thisObj.invokeInstanceMethod(this, "iterator") - while (it.invokeInstanceMethod(this, "hasNext").toBool()) { - if (obj.equals(this, it.invokeInstanceMethod(this, "next"))) + val it = thisObj.invokeInstanceMethod(scope, "iterator") + while (it.invokeInstanceMethod(scope, "hasNext").toBool()) { + if (obj.equals(scope, it.invokeInstanceMethod(scope, "next"))) return@addFnDoc ObjTrue } ObjFalse @@ -75,11 +77,12 @@ val ObjIterable by lazy { isOpen = true, moduleName = "lyng.stdlib" ) { + val scope = requireScope() val obj = args.firstAndOnly() var index = 0 - val it = thisObj.invokeInstanceMethod(this, "iterator") - while (it.invokeInstanceMethod(this, "hasNext").toBool()) { - if (obj.equals(this, it.invokeInstanceMethod(this, "next"))) + val it = thisObj.invokeInstanceMethod(scope, "iterator") + while (it.invokeInstanceMethod(scope, "hasNext").toBool()) { + if (obj.equals(scope, it.invokeInstanceMethod(scope, "next"))) return@addFnDoc ObjInt(index.toLong()) index++ } @@ -96,9 +99,10 @@ val ObjIterable by lazy { this.thisObj else { val result = mutableSetOf() - val it = this.thisObj.invokeInstanceMethod(this, "iterator") - while (it.invokeInstanceMethod(this, "hasNext").toBool()) { - result.add(it.invokeInstanceMethod(this, "next")) + val scope = requireScope() + val it = this.thisObj.invokeInstanceMethod(scope, "iterator") + while (it.invokeInstanceMethod(scope, "hasNext").toBool()) { + result.add(it.invokeInstanceMethod(scope, "next")) } ObjSet(result) } @@ -112,10 +116,11 @@ val ObjIterable by lazy { moduleName = "lyng.stdlib", getter = { val result = mutableMapOf() - this.thisObj.enumerate(this) { pair -> + val scope = requireScope() + this.thisObj.enumerate(scope) { pair -> when (pair) { is ObjMapEntry -> result[pair.key] = pair.value - else -> result[pair.getAt(this, 0)] = pair.getAt(this, 1) + else -> result[pair.getAt(scope, 0)] = pair.getAt(scope, 1) } true } @@ -132,9 +137,8 @@ val ObjIterable by lazy { ) { val association = requireOnlyArg() val result = ObjMap() - thisObj.toFlow(this).collect { - val callScope = createChildScope(args = Arguments(it)) - result.map[association.callOn(callScope)] = it + thisObj.toFlow(requireScope()).collect { + result.map[call(association, Arguments(it))] = it } result } @@ -146,11 +150,12 @@ val ObjIterable by lazy { isOpen = true, moduleName = "lyng.stdlib" ) { - val it = thisObj.invokeInstanceMethod(this, "iterator") + val scope = requireScope() + val it = thisObj.invokeInstanceMethod(scope, "iterator") val fn = requiredArg(0) - while (it.invokeInstanceMethod(this, "hasNext").toBool()) { - val x = it.invokeInstanceMethod(this, "next") - fn.callOn(this.createChildScope(Arguments(listOf(x)))) + while (it.invokeInstanceMethod(scope, "hasNext").toBool()) { + val x = it.invokeInstanceMethod(scope, "next") + call(fn, Arguments(listOf(x))) } ObjVoid } @@ -165,9 +170,8 @@ val ObjIterable by lazy { ) { val fn = requiredArg(0) val result = mutableListOf() - thisObj.toFlow(this).collect { - val callScope = createChildScope(args = Arguments(it)) - result.add(fn.callOn(callScope)) + thisObj.toFlow(requireScope()).collect { + result.add(call(fn, Arguments(it))) } ObjList(result) } @@ -182,9 +186,8 @@ val ObjIterable by lazy { ) { val fn = requiredArg(0) val result = mutableListOf() - thisObj.toFlow(this).collect { - val callScope = createChildScope(args = Arguments(it)) - val transformed = fn.callOn(callScope) + thisObj.toFlow(requireScope()).collect { + val transformed = call(fn, Arguments(it)) if( transformed != ObjNull) result.add(transformed) } ObjList(result) @@ -200,7 +203,7 @@ val ObjIterable by lazy { var n = requireOnlyArg().value.toInt() val result = mutableListOf() if (n > 0) { - thisObj.enumerate(this) { + thisObj.enumerate(requireScope()) { result.add(it) --n > 0 } @@ -215,8 +218,8 @@ val ObjIterable by lazy { moduleName = "lyng.stdlib", getter = { ObjBool( - this.thisObj.invokeInstanceMethod(this, "iterator") - .invokeInstanceMethod(this, "hasNext").toBool() + this.thisObj.invokeInstanceMethod(requireScope(), "iterator") + .invokeInstanceMethod(requireScope(), "hasNext").toBool() .not() ) } @@ -229,11 +232,10 @@ val ObjIterable by lazy { returns = type("lyng.List"), moduleName = "lyng.stdlib" ) { - val list = thisObj.callMethod(this, "toList") + val list = thisObj.callMethod(requireScope(), "toList") val comparator = requireOnlyArg() list.quicksort { a, b -> - val callScope = createChildScope(args = Arguments(a, b)) - comparator.callOn(callScope).toInt() + call(comparator, Arguments(a, b)).toInt() } list } @@ -244,7 +246,7 @@ val ObjIterable by lazy { returns = type("lyng.List"), moduleName = "lyng.stdlib" ) { - val list = thisObj.callMethod(this, "toList") + val list = thisObj.callMethod(requireScope(), "toList") list.list.reverse() list } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterator.kt index 319f7f0..36d94e3 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterator.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterator.kt @@ -69,13 +69,12 @@ val ObjIterator by lazy { ) { val out = mutableListOf() while (true) { - val has = thisObj.invokeInstanceMethod(this, "hasNext").toBool() + val has = thisObj.invokeInstanceMethod(requireScope(), "hasNext").toBool() if (!has) break - val v = thisObj.invokeInstanceMethod(this, "next") + val v = thisObj.invokeInstanceMethod(requireScope(), "next") out += v } ObjList(out.toMutableList()) } } } - diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt index 478d696..5db0acd 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt @@ -373,8 +373,7 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { ) { val comparator = requireOnlyArg() thisAs().quicksort { a, b -> - val callScope = createChildScope(args = Arguments(a, b)) - comparator.callOn(callScope).toInt() + call(comparator, Arguments(a, b)).toInt() } ObjVoid } @@ -394,6 +393,7 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { val self = thisAs() val l = self.list if (l.isEmpty()) return@addFnDoc ObjNull + val scope = requireScope() if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { // Fast path: all ints → accumulate as long var i = 0 @@ -407,7 +407,7 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { // Fallback to generic dynamic '+' accumulation starting from current acc var res: Obj = ObjInt(acc) while (i < l.size) { - res = res.plus(this, l[i]) + res = res.plus(scope, l[i]) i++ } return@addFnDoc res @@ -419,7 +419,7 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { var res: Obj = l[0] var k = 1 while (k < l.size) { - res = res.plus(this, l[k]) + res = res.plus(scope, l[k]) k++ } res @@ -431,6 +431,7 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { ) { val l = thisAs().list if (l.isEmpty()) return@addFnDoc ObjNull + val scope = requireScope() if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { var i = 0 var hasOnlyInts = true @@ -451,7 +452,7 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { var i = 1 while (i < l.size) { val v = l[i] - if (v.compareTo(this, res) < 0) res = v + if (v.compareTo(scope, res) < 0) res = v i++ } res @@ -463,6 +464,7 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { ) { val l = thisAs().list if (l.isEmpty()) return@addFnDoc ObjNull + val scope = requireScope() if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { var i = 0 var hasOnlyInts = true @@ -483,7 +485,7 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { var i = 1 while (i < l.size) { val v = l[i] - if (v.compareTo(this, res) > 0) res = v + if (v.compareTo(scope, res) > 0) res = v i++ } res @@ -497,6 +499,7 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { ) { val l = thisAs().list val needle = args.firstAndOnly() + val scope = requireScope() if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && needle is ObjInt) { var i = 0 while (i < l.size) { @@ -508,7 +511,7 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { } var i = 0 while (i < l.size) { - if (l[i].compareTo(this, needle) == 0) return@addFnDoc ObjInt(i.toLong()) + if (l[i].compareTo(scope, needle) == 0) return@addFnDoc ObjInt(i.toLong()) i++ } ObjInt((-1).toLong()) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt index b2eb14e..60aecd4 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt @@ -261,7 +261,7 @@ class ObjMap(val map: MutableMap = mutableMapOf()) : Obj() { val key = requiredArg(0) thisAs().map.getOrPut(key) { val lambda = requiredArg(1) - lambda.callOn(this) + call(lambda) } } addPropertyDoc( diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt index f61138b..a43169d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt @@ -44,7 +44,7 @@ class ObjMutex(val mutex: Mutex): Obj() { // Execute user lambda directly in the current scope to preserve the active scope // ancestry across suspension points. The lambda still constructs a closure scope // on top of this frame, and parseLambdaExpression sets skipScopeCreation for its body. - thisAs().mutex.withLock { f.callOn(this) } + thisAs().mutex.withLock { call(f) } } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjNativeCallable.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjNativeCallable.kt deleted file mode 100644 index 305aa19..0000000 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjNativeCallable.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2026 Sergey S. Chernov - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package net.sergeych.lyng.obj - -import net.sergeych.lyng.Scope -import net.sergeych.lyng.Statement - -class ObjNativeCallable( - private val fn: suspend Scope.() -> Obj -) : Obj() { - - override val objClass: ObjClass - get() = Statement.type - - override suspend fun callOn(scope: Scope): Obj = scope.fn() - - override fun toString(): String = "NativeCallable@${hashCode()}" -} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt index 1ddb898..b4f9869 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt @@ -270,7 +270,7 @@ class ObjRange( moduleName = "lyng.stdlib" ) { val self = thisAs() - self.buildIterator(this) + self.buildIterator(requireScope()) } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRangeIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRangeIterator.kt index d4ef07a..4080c27 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRangeIterator.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRangeIterator.kt @@ -65,10 +65,10 @@ class ObjRangeIterator( companion object { val type = ObjClass("RangeIterator", ObjIterator).apply { addFn("hasNext") { - thisAs().hasNext(this).toObj() + thisAs().hasNext(requireScope()).toObj() } addFn("next") { - thisAs().next(this) + thisAs().next(requireScope()) } } } @@ -98,7 +98,7 @@ class ObjFastIntRangeIterator(private val start: Int, private val endExclusive: companion object { val type = ObjClass("FastIntRangeIterator", ObjIterator).apply { addFn("hasNext") { thisAs().hasNext().toObj() } - addFn("next") { thisAs().next(this) } + addFn("next") { thisAs().next(requireScope()) } } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt index bf4259c..1aff927 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt @@ -125,7 +125,7 @@ data class ObjReal(val value: Double) : Obj(), Numeric { // roundToInt: number rounded to the nearest integer addConstDoc( name = "roundToInt", - value = ObjNativeCallable { + value = ObjExternCallable.fromBridge { (thisObj as ObjReal).value.roundToLong().toObj() }, doc = "This real number rounded to the nearest integer.", 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 602e93d..7c1f863 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt @@ -35,7 +35,7 @@ sealed interface ObjRef { * Default implementation calls [get] and returns its value. Nodes can override to avoid record traffic. */ suspend fun evalValue(scope: Scope): Obj { - scope.raiseIllegalState("interpreter execution is not supported; ObjRef evaluation requires bytecode") + scope.raiseIllegalState("bytecode-only execution is required; ObjRef evaluation is disabled") } suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { throw ScriptError(pos, "can't assign value") @@ -56,18 +56,20 @@ sealed interface ObjRef { } } +private fun Scope.raiseObjRefEvalDisabled(): Nothing { + return raiseIllegalState("bytecode-only execution is required; ObjRef evaluation is disabled") +} + /** Runtime-computed read-only reference backed by a lambda. */ open class ValueFnRef(private val fn: suspend (Scope) -> ObjRecord) : ObjRef { internal fun valueFn(): suspend (Scope) -> ObjRecord = fn - override suspend fun get(scope: Scope): ObjRecord = fn(scope) + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() } /** Compile-time supported ::class operator reference. */ class ClassOperatorRef(val target: ObjRef, val pos: Pos) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord { - return target.evalValue(scope).objClass.asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() } /** Unary operations supported by ObjRef. */ @@ -91,317 +93,16 @@ enum class BinOp { /** R-value reference for unary operations. */ class UnaryOpRef(internal val op: UnaryOp, internal val a: ObjRef) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord { - val v = a.evalValue(scope) - if (PerfFlags.PRIMITIVE_FASTOPS) { - val rFast: Obj? = when (op) { - UnaryOp.NOT -> if (v is ObjBool) if (!v.value) ObjTrue else ObjFalse else null - UnaryOp.NEGATE -> when (v) { - is ObjInt -> ObjInt(-v.value) - is ObjReal -> ObjReal(-v.value) - else -> null - } - UnaryOp.BITNOT -> if (v is ObjInt) ObjInt(v.value.inv()) else null - } - if (rFast != null) { - if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++ - return rFast.asReadonly - } - } - val r = when (op) { - UnaryOp.NOT -> v.logicalNot(scope) - UnaryOp.NEGATE -> v.negate(scope) - UnaryOp.BITNOT -> v.bitNot(scope) - } - return r.asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val v = a.evalValue(scope) - if (PerfFlags.PRIMITIVE_FASTOPS) { - val rFast: Obj? = when (op) { - UnaryOp.NOT -> if (v is ObjBool) if (!v.value) ObjTrue else ObjFalse else null - UnaryOp.NEGATE -> when (v) { - is ObjInt -> ObjInt(-v.value) - is ObjReal -> ObjReal(-v.value) - else -> null - } - UnaryOp.BITNOT -> if (v is ObjInt) ObjInt(v.value.inv()) else null - } - if (rFast != null) { - if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++ - return rFast - } - } - return when (op) { - UnaryOp.NOT -> v.logicalNot(scope) - UnaryOp.NEGATE -> v.negate(scope) - UnaryOp.BITNOT -> v.bitNot(scope) - } - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** R-value reference for binary operations. */ class BinaryOpRef(internal val op: BinOp, internal val left: ObjRef, internal val right: ObjRef) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord { - return evalValue(scope).asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val a = left.evalValue(scope) - val b = right.evalValue(scope) - if (op == BinOp.IS || op == BinOp.NOTIS) { - val result = when { - (a is ObjTypeExpr || a is ObjClass) && (b is ObjTypeExpr || b is ObjClass) -> { - val leftDecl = typeDeclFromObj(scope, a) ?: return ObjBool(false) - val rightDecl = typeDeclFromObj(scope, b) ?: return ObjBool(false) - typeDeclIsSubtype(scope, leftDecl, rightDecl) - } - b is ObjTypeExpr -> matchesTypeDecl(scope, a, b.typeDecl) - else -> a.isInstanceOf(b) - } - return if (op == BinOp.NOTIS) ObjBool(!result) else ObjBool(result) - } - if (op == BinOp.IN || op == BinOp.NOTIN) { - if ((b is ObjTypeExpr || b is ObjClass) && (a is ObjTypeExpr || a is ObjClass)) { - val leftDecl = typeDeclFromObj(scope, a) ?: return ObjBool(op == BinOp.NOTIN) - val rightDecl = typeDeclFromObj(scope, b) ?: return ObjBool(op == BinOp.NOTIN) - val result = typeDeclIsSubtype(scope, leftDecl, rightDecl) - return if (op == BinOp.NOTIN) ObjBool(!result) else ObjBool(result) - } - } - - // Primitive fast paths for common cases (guarded by PerfFlags.PRIMITIVE_FASTOPS) - if (PerfFlags.PRIMITIVE_FASTOPS) { - // Fast membership for common containers - if (op == BinOp.IN || op == BinOp.NOTIN) { - val inResult: Boolean? = when (b) { - is ObjList -> { - if (a is ObjInt) { - var i = 0 - val sz = b.list.size - var found = false - while (i < sz) { - val v = b.list[i] - if (v is ObjInt && v.value == a.value) { - found = true - break - } - i++ - } - found - } else { - b.list.contains(a) - } - } - is ObjSet -> b.set.contains(a) - is ObjMap -> b.map.containsKey(a) - is ObjRange -> { - when (a) { - is ObjInt -> { - val s = b.start as? ObjInt - val e = b.end as? ObjInt - val v = a.value - if (s == null && e == null) null - else { - if (s != null && v < s.value) false - else if (e != null) if (b.isEndInclusive) v <= e.value else v < e.value else true - } - } - is ObjChar -> { - val s = b.start as? ObjChar - val e = b.end as? ObjChar - val v = a.value - if (s == null && e == null) null - else { - if (s != null && v < s.value) false - else if (e != null) if (b.isEndInclusive) v <= e.value else v < e.value else true - } - } - is ObjString -> { - val s = b.start as? ObjString - val e = b.end as? ObjString - val v = a.value - if (s == null && e == null) null - else { - if (s != null && v < s.value) false - else if (e != null) if (b.isEndInclusive) v <= e.value else v < e.value else true - } - } - else -> null - } - } - is ObjString -> when (a) { - is ObjString -> b.value.contains(a.value) - is ObjChar -> b.value.contains(a.value) - else -> null - } - else -> null - } - if (inResult != null) { - if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++ - return if (op == BinOp.IN) { - if (inResult) ObjTrue else ObjFalse - } else { - if (inResult) ObjFalse else ObjTrue - } - } - } - // Fast boolean ops when both operands are ObjBool - if (a is ObjBool && b is ObjBool) { - val r: Obj? = when (op) { - BinOp.OR -> if (a.value || b.value) ObjTrue else ObjFalse - BinOp.AND -> if (a.value && b.value) ObjTrue else ObjFalse - BinOp.EQ -> if (a.value == b.value) ObjTrue else ObjFalse - BinOp.NEQ -> if (a.value != b.value) ObjTrue else ObjFalse - else -> null - } - if (r != null) { - if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++ - return r - } - } - // Fast integer ops when both operands are ObjInt - if (a is ObjInt && b is ObjInt) { - val av = a.value - val bv = b.value - val r: Obj? = when (op) { - BinOp.PLUS -> ObjInt.of(av + bv) - BinOp.MINUS -> ObjInt.of(av - bv) - BinOp.STAR -> ObjInt.of(av * bv) - BinOp.SLASH -> if (bv != 0L) ObjInt.of(av / bv) else null - BinOp.PERCENT -> if (bv != 0L) ObjInt.of(av % bv) else null - BinOp.BAND -> ObjInt.of(av and bv) - BinOp.BXOR -> ObjInt.of(av xor bv) - BinOp.BOR -> ObjInt.of(av or bv) - BinOp.SHL -> ObjInt.of(av shl (bv.toInt() and 63)) - BinOp.SHR -> ObjInt.of(av shr (bv.toInt() and 63)) - BinOp.EQ -> if (av == bv) ObjTrue else ObjFalse - BinOp.NEQ -> if (av != bv) ObjTrue else ObjFalse - BinOp.LT -> if (av < bv) ObjTrue else ObjFalse - BinOp.LTE -> if (av <= bv) ObjTrue else ObjFalse - BinOp.GT -> if (av > bv) ObjTrue else ObjFalse - BinOp.GTE -> if (av >= bv) ObjTrue else ObjFalse - else -> null - } - if (r != null) { - if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++ - return r - } - } - // Fast string operations when both are strings - if (a is ObjString && b is ObjString) { - val r: Obj? = when (op) { - BinOp.EQ -> if (a.value == b.value) ObjTrue else ObjFalse - BinOp.NEQ -> if (a.value != b.value) ObjTrue else ObjFalse - BinOp.LT -> if (a.value < b.value) ObjTrue else ObjFalse - BinOp.LTE -> if (a.value <= b.value) ObjTrue else ObjFalse - BinOp.GT -> if (a.value > b.value) ObjTrue else ObjFalse - BinOp.GTE -> if (a.value >= b.value) ObjTrue else ObjFalse - BinOp.PLUS -> ObjString(a.value + b.value) - else -> null - } - if (r != null) { - if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++ - return r - } - } - // Fast char vs char comparisons - if (a is ObjChar && b is ObjChar) { - val av = a.value - val bv = b.value - val r: Obj? = when (op) { - BinOp.EQ -> if (av == bv) ObjTrue else ObjFalse - BinOp.NEQ -> if (av != bv) ObjTrue else ObjFalse - BinOp.LT -> if (av < bv) ObjTrue else ObjFalse - BinOp.LTE -> if (av <= bv) ObjTrue else ObjFalse - BinOp.GT -> if (av > bv) ObjTrue else ObjFalse - BinOp.GTE -> if (av >= bv) ObjTrue else ObjFalse - else -> null - } - if (r != null) { - if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++ - return r - } - } - // Fast concatenation for String with Int/Char on either side - if (op == BinOp.PLUS) { - when { - a is ObjString && b is ObjInt -> { - if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++ - return ObjString(a.value + b.value.toString()) - } - a is ObjString && b is ObjChar -> { - if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++ - return ObjString(a.value + b.value) - } - b is ObjString && a is ObjInt -> { - if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++ - return ObjString(a.value.toString() + b.value) - } - b is ObjString && a is ObjChar -> { - if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++ - return ObjString(a.value.toString() + b.value) - } - } - } - // Fast numeric mixed ops for Int/Real combinations by promoting to double - if ((a is ObjInt || a is ObjReal) && (b is ObjInt || b is ObjReal)) { - val ad: Double = if (a is ObjInt) a.doubleValue else (a as ObjReal).value - val bd: Double = if (b is ObjInt) b.doubleValue else (b as ObjReal).value - val rNum: Obj? = when (op) { - BinOp.PLUS -> ObjReal.of(ad + bd) - BinOp.MINUS -> ObjReal.of(ad - bd) - BinOp.STAR -> ObjReal.of(ad * bd) - BinOp.SLASH -> ObjReal.of(ad / bd) - BinOp.PERCENT -> ObjReal.of(ad % bd) - BinOp.LT -> if (ad < bd) ObjTrue else ObjFalse - BinOp.LTE -> if (ad <= bd) ObjTrue else ObjFalse - BinOp.GT -> if (ad > bd) ObjTrue else ObjFalse - BinOp.GTE -> if (ad >= bd) ObjTrue else ObjFalse - BinOp.EQ -> if (ad == bd) ObjTrue else ObjFalse - BinOp.NEQ -> if (ad != bd) ObjTrue else ObjFalse - else -> null - } - if (rNum != null) { - if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.primitiveFastOpsHit++ - return rNum - } - } - } - - val r: Obj = when (op) { - BinOp.OR -> a.logicalOr(scope, b) - BinOp.AND -> a.logicalAnd(scope, b) - BinOp.EQARROW -> ObjMapEntry(a, b) - BinOp.EQ -> ObjBool(a.equals(scope, b)) - BinOp.NEQ -> ObjBool(!a.equals(scope, b)) - BinOp.REF_EQ -> ObjBool(a === b) - BinOp.REF_NEQ -> ObjBool(a !== b) - BinOp.MATCH -> a.operatorMatch(scope, b) - BinOp.NOTMATCH -> a.operatorNotMatch(scope, b) - BinOp.LTE -> ObjBool(a.compareTo(scope, b) <= 0) - BinOp.LT -> ObjBool(a.compareTo(scope, b) < 0) - BinOp.GTE -> ObjBool(a.compareTo(scope, b) >= 0) - BinOp.GT -> ObjBool(a.compareTo(scope, b) > 0) - BinOp.IN -> ObjBool(b.contains(scope, a)) - BinOp.NOTIN -> ObjBool(!b.contains(scope, a)) - BinOp.IS -> ObjBool(a.isInstanceOf(b)) - BinOp.NOTIS -> ObjBool(!a.isInstanceOf(b)) - BinOp.SHUTTLE -> ObjInt(a.compareTo(scope, b).toLong()) - BinOp.BAND -> a.bitAnd(scope, b) - BinOp.BXOR -> a.bitXor(scope, b) - BinOp.BOR -> a.bitOr(scope, b) - BinOp.SHL -> a.shl(scope, b) - BinOp.SHR -> a.shr(scope, b) - BinOp.PLUS -> a.plus(scope, b) - BinOp.MINUS -> a.minus(scope, b) - BinOp.STAR -> a.mul(scope, b) - BinOp.SLASH -> a.div(scope, b) - BinOp.PERCENT -> a.mod(scope, b) - } - return r - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** Conditional (ternary) operator reference: cond ? a : b */ @@ -410,13 +111,9 @@ class ConditionalRef( internal val ifTrue: ObjRef, internal val ifFalse: ObjRef ) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord { - return evalCondition(scope).get(scope) - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - return evalCondition(scope).evalValue(scope) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() private suspend fun evalCondition(scope: Scope): ObjRef { val condVal = condition.evalValue(scope) @@ -441,69 +138,9 @@ class CastRef( internal fun castIsNullable(): Boolean = isNullable internal fun castPos(): Pos = atPos - override suspend fun get(scope: Scope): ObjRecord { - val v0 = valueRef.evalValue(scope) - val t = typeRef.evalValue(scope) - // unwrap qualified views - val v = when (v0) { - is ObjQualifiedView -> v0.instance - else -> v0 - } - return when (t) { - is ObjClass -> { - if (v.isInstanceOf(t)) { - // For instances, return a qualified view to enforce ancestor-start dispatch - if (v is ObjInstance) ObjQualifiedView(v, t).asReadonly else v.asReadonly - } else { - if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError( - "Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${t.className}" - ) - } - } - is ObjTypeExpr -> { - if (matchesTypeDecl(scope, v, t.typeDecl)) { - v.asReadonly - } else { - if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError( - "Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${t.typeDecl}" - ) - } - } - else -> scope.raiseClassCastError("${t} is not the class instance") - } - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val v0 = valueRef.evalValue(scope) - val t = typeRef.evalValue(scope) - // unwrap qualified views - val v = when (v0) { - is ObjQualifiedView -> v0.instance - else -> v0 - } - return when (t) { - is ObjClass -> { - if (v.isInstanceOf(t)) { - // For instances, return a qualified view to enforce ancestor-start dispatch - if (v is ObjInstance) ObjQualifiedView(v, t) else v - } else { - if (isNullable) ObjNull else scope.raiseClassCastError( - "Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${t.className}" - ) - } - } - is ObjTypeExpr -> { - if (matchesTypeDecl(scope, v, t.typeDecl)) { - v - } else { - if (isNullable) ObjNull else scope.raiseClassCastError( - "Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${t.typeDecl}" - ) - } - } - else -> scope.raiseClassCastError("${t} is not the class instance") - } - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** Type expression reference used for `is` checks (including unions/intersections). */ @@ -515,37 +152,15 @@ class TypeDeclRef(private val typeDecl: TypeDecl, private val atPos: Pos) : ObjR override fun forEachVariableWithPos(block: (String, Pos) -> Unit) {} - override suspend fun get(scope: Scope): ObjRecord { - return evalValue(scope).asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - return ObjTypeExpr(typeDecl) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** Qualified `this@Type`: resolves to a view of current `this` starting dispatch from the ancestor Type. */ class QualifiedThisRef(val typeName: String, private val atPos: Pos) : ObjRef { internal fun pos(): Pos = atPos - override suspend fun get(scope: Scope): ObjRecord { - val t = scope[typeName]?.value as? ObjClass - ?: scope.raiseError("unknown type $typeName") - - var s: Scope? = scope - while (s != null) { - val inst = s.thisObj as? ObjInstance - if (inst != null) { - if (inst.objClass === t || inst.objClass.allParentsSet.contains(t)) { - return ObjQualifiedView(inst, t).asReadonly - } - } - s = s.parent - } - - scope.raiseClassCastError( - "No instance of type ${t.className} found in the scope chain" - ) - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() } private suspend fun resolveQualifiedThisInstance(scope: Scope, typeName: String): Pair { @@ -579,61 +194,9 @@ class QualifiedThisFieldSlotRef( internal fun receiverTypeName(): String = typeName internal fun optional(): Boolean = isOptional - override suspend fun get(scope: Scope): ObjRecord { - val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance - ?: scope.raiseClassCastError("No instance of type $typeName found in scope") - if (isOptional && inst == ObjNull) return ObjNull.asMutable - fieldId?.let { id -> - val rec = inst.fieldRecordForId(id) - if (rec != null && (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) { - val decl = rec.declaringClass ?: inst.objClass - if (!canAccessMember(rec.visibility, decl, scope.currentClassCtx, rec.memberName ?: name)) { - scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${rec.memberName ?: name} (declared in ${decl.className})")) - } - return rec - } - } - methodId?.let { id -> - val rec = inst.methodRecordForId(id) - if (rec != null && !rec.isAbstract) { - val decl = rec.declaringClass ?: inst.objClass - if (!canAccessMember(rec.visibility, decl, scope.currentClassCtx, rec.memberName ?: name)) { - scope.raiseError(ObjIllegalAccessException(scope, "can't access member ${rec.memberName ?: name} (declared in ${decl.className})")) - } - return inst.resolveRecord(scope, rec, rec.memberName ?: name, decl) - } - } - scope.raiseSymbolNotFound(name) - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { - val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance - ?: scope.raiseClassCastError("No instance of type $typeName found in scope") - if (isOptional && inst == ObjNull) return - fieldId?.let { id -> - val rec = inst.fieldRecordForId(id) - if (rec != null) { - val decl = rec.declaringClass ?: inst.objClass - if (!canAccessMember(rec.effectiveWriteVisibility, decl, scope.currentClassCtx, rec.memberName ?: name)) { - scope.raiseError(ObjIllegalAccessException(scope, "can't assign to field ${rec.memberName ?: name} (declared in ${decl.className})")) - } - assignToRecord(scope, rec, newValue) - return - } - } - methodId?.let { id -> - val rec = inst.methodRecordForId(id) - if (rec != null) { - val decl = rec.declaringClass ?: inst.objClass - if (!canAccessMember(rec.effectiveWriteVisibility, decl, scope.currentClassCtx, rec.memberName ?: name)) { - scope.raiseError(ObjIllegalAccessException(scope, "can't assign to member ${rec.memberName ?: name} (declared in ${decl.className})")) - } - scope.assign(rec, rec.memberName ?: name, newValue) - return - } - } - scope.raiseSymbolNotFound(name) - } + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) = scope.raiseObjRefEvalDisabled() private suspend fun assignToRecord(scope: Scope, rec: ObjRecord, newValue: Obj) { if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) { @@ -666,28 +229,9 @@ class QualifiedThisMethodSlotCallRef( internal fun hasTailBlock(): Boolean = tailBlock internal fun optionalInvoke(): Boolean = isOptional - override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance - ?: scope.raiseClassCastError("No instance of type $typeName found in scope") - if (isOptional && inst == ObjNull) return ObjNull - val callArgs = args.toArguments(scope, tailBlock) - val id = methodId ?: scope.raiseSymbolNotFound(name) - val rec = inst.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name) - val decl = rec.declaringClass ?: inst.objClass - if (!canAccessMember(rec.visibility, decl, scope.currentClassCtx, rec.memberName ?: name)) { - scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method ${rec.memberName ?: name} (declared in ${decl.className})")) - } - return when (rec.type) { - ObjRecord.Type.Property -> { - if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, inst, decl) - else scope.raiseError("property $name cannot be called with arguments") - } - ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(inst.instanceScope, inst, callArgs, decl) - else -> scope.raiseError("member $name is not callable") - } - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** Assignment compound op: target op= value */ @@ -697,59 +241,7 @@ class AssignOpRef( internal val value: ObjRef, private val atPos: Pos, ) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord { - val x = target.evalValue(scope) - val y = value.evalValue(scope) - val inPlace: Obj? = when (op) { - BinOp.PLUS -> x.plusAssign(scope, y) - BinOp.MINUS -> x.minusAssign(scope, y) - BinOp.STAR -> x.mulAssign(scope, y) - BinOp.SLASH -> x.divAssign(scope, y) - BinOp.PERCENT -> x.modAssign(scope, y) - else -> null - } - if (inPlace != null) return inPlace.asReadonly - val fast: Obj? = if (PerfFlags.PRIMITIVE_FASTOPS) { - when { - x is ObjInt && y is ObjInt -> { - val xv = x.value - val yv = y.value - when (op) { - BinOp.PLUS -> ObjInt.of(xv + yv) - BinOp.MINUS -> ObjInt.of(xv - yv) - BinOp.STAR -> ObjInt.of(xv * yv) - BinOp.SLASH -> if (yv != 0L) ObjInt.of(xv / yv) else null - BinOp.PERCENT -> if (yv != 0L) ObjInt.of(xv % yv) else null - else -> null - } - } - (x is ObjInt || x is ObjReal) && (y is ObjInt || y is ObjReal) -> { - val xv = if (x is ObjInt) x.doubleValue else (x as ObjReal).value - val yv = if (y is ObjInt) y.doubleValue else (y as ObjReal).value - when (op) { - BinOp.PLUS -> ObjReal.of(xv + yv) - BinOp.MINUS -> ObjReal.of(xv - yv) - BinOp.STAR -> ObjReal.of(xv * yv) - BinOp.SLASH -> ObjReal.of(xv / yv) - BinOp.PERCENT -> ObjReal.of(xv % yv) - else -> null - } - } - x is ObjString && op == BinOp.PLUS -> ObjString(x.value + y.toString()) - else -> null - } - } else null - val result: Obj = fast ?: when (op) { - BinOp.PLUS -> x.plus(scope, y) - BinOp.MINUS -> x.minus(scope, y) - BinOp.STAR -> x.mul(scope, y) - BinOp.SLASH -> x.div(scope, y) - BinOp.PERCENT -> x.mod(scope, y) - else -> scope.raiseError("unsupported assignment op: $op") - } - target.setAt(atPos, scope, result) - return result.asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() } /** Pre/post ++/-- on l-values */ @@ -759,35 +251,12 @@ class IncDecRef( internal val isPost: Boolean, private val atPos: Pos, ) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord { - val rec = target.get(scope) - if (!rec.isMutable) scope.raiseError("Cannot ${if (isIncrement) "increment" else "decrement"} immutable value") - val v = target.evalValue(scope) - val one = ObjInt.One - // We now treat numbers as immutable and always perform write-back via setAt. - // This avoids issues where literals are shared and mutated in-place. - // For post-inc: return ORIGINAL value; for pre-inc: return NEW value. - val result = if (PerfFlags.PRIMITIVE_FASTOPS) { - when (v) { - is ObjInt -> if (isIncrement) ObjInt.of(v.value + 1L) else ObjInt.of(v.value - 1L) - is ObjReal -> if (isIncrement) ObjReal.of(v.value + 1.0) else ObjReal.of(v.value - 1.0) - else -> if (isIncrement) v.plus(scope, one) else v.minus(scope, one) - } - } else { - if (isIncrement) v.plus(scope, one) else v.minus(scope, one) - } - target.setAt(atPos, scope, result) - return (if (isPost) v else result).asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() } /** Elvis operator reference: a ?: b */ class ElvisRef(internal val left: ObjRef, internal val right: ObjRef) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord { - val a = left.evalValue(scope) - val r = if (a != ObjNull) a else right.evalValue(scope) - return r.asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() } /** Logical OR with short-circuit: a || b */ @@ -795,21 +264,9 @@ class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef internal fun left(): ObjRef = left internal fun right(): ObjRef = right - override suspend fun get(scope: Scope): ObjRecord { - return evalValue(scope).asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val a = left.evalValue(scope) - if ((a as? ObjBool)?.value == true) return ObjTrue - val b = right.evalValue(scope) - if (PerfFlags.PRIMITIVE_FASTOPS) { - if (a is ObjBool && b is ObjBool) { - return if (a.value || b.value) ObjTrue else ObjFalse - } - } - return a.logicalOr(scope, b) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** Logical AND with short-circuit: a && b */ @@ -817,29 +274,17 @@ class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRe internal fun left(): ObjRef = left internal fun right(): ObjRef = right - override suspend fun get(scope: Scope): ObjRecord { - return evalValue(scope).asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val a = left.evalValue(scope) - if ((a as? ObjBool)?.value == false) return ObjFalse - val b = right.evalValue(scope) - if (PerfFlags.PRIMITIVE_FASTOPS) { - if (a is ObjBool && b is ObjBool) { - return if (a.value && b.value) ObjTrue else ObjFalse - } - } - return a.logicalAnd(scope, b) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** * Read-only reference that always returns the same cached record. */ class ConstRef(private val record: ObjRecord) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord = record - override suspend fun evalValue(scope: Scope): Obj = record.value + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() // Expose constant value for compiler constant folding (pure, read-only) val constValue: Obj get() = record.value } @@ -912,279 +357,9 @@ class FieldRef( } } - override suspend fun get(scope: Scope): ObjRecord { - val fieldPic = PerfFlags.FIELD_PIC - val picCounters = PerfFlags.PIC_DEBUG_COUNTERS - val base = target.evalValue(scope) - if (base == ObjNull && isOptional) return ObjNull.asMutable - if (fieldPic) { - val key: Long - val ver: Int - when (base) { - is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion } - is ObjClass -> { key = base.classId; ver = base.layoutVersion } - else -> { key = 0L; ver = -1 } - } - if (key != 0L) { - rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) { - if (picCounters) PerfStats.fieldPicHit++ - noteReadHit() - val rec0 = g(base, scope) - if (base is ObjClass) { - val idx0 = base.classScope?.getSlotIndexOf(name) - if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null } - } else { tRecord = null } - return rec0 - } } - rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) { - if (picCounters) PerfStats.fieldPicHit++ - noteReadHit() - // move-to-front: promote 2→1 - val tK = rKey2; val tV = rVer2; val tG = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = tK; rVer1 = tV; rGetter1 = tG - val rec0 = g(base, scope) - if (base is ObjClass) { - val idx0 = base.classScope?.getSlotIndexOf(name) - if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null } - } else { tRecord = null } - return rec0 - } } - if (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) { - if (picCounters) PerfStats.fieldPicHit++ - noteReadHit() - // move-to-front: promote 3→1 - val tK = rKey3; val tV = rVer3; val tG = rGetter3 - rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = tK; rVer1 = tV; rGetter1 = tG - val rec0 = g(base, scope) - if (base is ObjClass) { - val idx0 = base.classScope?.getSlotIndexOf(name) - if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null } - } else { tRecord = null } - return rec0 - } } - if (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) { - if (picCounters) PerfStats.fieldPicHit++ - noteReadHit() - // move-to-front: promote 4→1 - val tK = rKey4; val tV = rVer4; val tG = rGetter4 - rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3 - rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = tK; rVer1 = tV; rGetter1 = tG - val rec0 = g(base, scope) - if (base is ObjClass) { - val idx0 = base.classScope?.getSlotIndexOf(name) - if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null } - } else { tRecord = null } - return rec0 - } } - // Slow path - if (picCounters) PerfStats.fieldPicMiss++ - noteReadMiss() - val rec = try { - base.readField(scope, name) - } catch (e: ExecutionError) { - // Cache-after-miss negative entry: rethrow the same error quickly for this shape - rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3 - rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = key; rVer1 = ver; rGetter1 = { _, sc -> sc.raiseError(e.message ?: "no such field: $name") } - throw e - } - // Install move-to-front with a handle-aware getter; honor PIC size flag - if (size4ReadsEnabled()) { - rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3 - rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 - } - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - when (base) { - is ObjClass -> { - val clsScope = base.classScope - val capturedIdx = clsScope?.getSlotIndexOf(name) - if (clsScope != null && capturedIdx != null) { - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> - val scope0 = (obj as ObjClass).classScope!! - val r0 = scope0.getSlotRecord(capturedIdx) - if (!r0.visibility.isPublic) - sc.raiseError(ObjIllegalAccessException(sc, "can't access non-public field $name")) - r0 - } - } else { - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) } - } - } - is ObjInstance -> { - val cls = base.objClass - val effectiveKey = cls.publicMemberResolution[name] - if (effectiveKey != null) { - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> - if (obj is ObjInstance && obj.objClass === cls) { - val slot = cls.fieldSlotForKey(effectiveKey) - if (slot != null) { - val idx = slot.slot - val rec = if (idx >= 0 && idx < obj.fieldSlots.size) obj.fieldSlots[idx] else null - if (rec != null && - (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && - !rec.isAbstract) { - rec - } else obj.readField(sc, name) - } else { - val rec = obj.fieldRecordForKey(effectiveKey) ?: obj.instanceScope.objects[effectiveKey] - if (rec != null && rec.type != ObjRecord.Type.Delegated) rec - else obj.readField(sc, name) - } - } else obj.readField(sc, name) - } - } else { - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) } - } - } - else -> { - // For instances and other types, fall back to name-based lookup per access (slot index may differ per instance) - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) } - } - } - return rec - } - } - return base.readField(scope, name) - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { - val fieldPic = PerfFlags.FIELD_PIC - val picCounters = PerfFlags.PIC_DEBUG_COUNTERS - val base = target.evalValue(scope) - if (base == ObjNull && isOptional) { - // no-op on null receiver for optional chaining assignment - return - } - // Read→write micro fast-path: reuse transient record captured by get() - if (fieldPic) { - val key: Long - val ver: Int - when (base) { - is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion } - is ObjClass -> { key = base.classId; ver = base.layoutVersion } - else -> { key = 0L; ver = -1 } - } - val rec = tRecord - if (rec != null && tKey == key && tVer == ver && tFrameId == scope.frameId) { - // If it is a property, we must go through writeField (slow path for now) - // or handle it here. - if (rec.type != ObjRecord.Type.Property) { - // visibility/mutability checks - if (!rec.isMutable) scope.raiseError(ObjIllegalAssignmentException(scope, "can't reassign val $name")) - if (!rec.visibility.isPublic) - scope.raiseError(ObjIllegalAccessException(scope, "can't access non-public field $name")) - if (rec.value.assign(scope, newValue) == null) rec.value = newValue - return - } - } - if (key != 0L) { - wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { - if (picCounters) PerfStats.fieldPicSetHit++ - noteWriteHit() - return s(base, scope, newValue) - } } - wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) { - if (picCounters) PerfStats.fieldPicSetHit++ - noteWriteHit() - // move-to-front: promote 2→1 - val tK = wKey2; val tV = wVer2; val tS = wSetter2 - wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 - wKey1 = tK; wVer1 = tV; wSetter1 = tS - return s(base, scope, newValue) - } } - if (size4WritesEnabled()) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) { - if (picCounters) PerfStats.fieldPicSetHit++ - noteWriteHit() - // move-to-front: promote 3→1 - val tK = wKey3; val tV = wVer3; val tS = wSetter3 - wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 - wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 - wKey1 = tK; wVer1 = tV; wSetter1 = tS - return s(base, scope, newValue) - } } - if (size4WritesEnabled()) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) { - if (picCounters) PerfStats.fieldPicSetHit++ - noteWriteHit() - // move-to-front: promote 4→1 - val tK = wKey4; val tV = wVer4; val tS = wSetter4 - wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3 - wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 - wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 - wKey1 = tK; wVer1 = tV; wSetter1 = tS - return s(base, scope, newValue) - } } - // Slow path - if (picCounters) PerfStats.fieldPicSetMiss++ - noteWriteMiss() - base.writeField(scope, name, newValue) - // Install move-to-front with a handle-aware setter; honor PIC size flag - if (size4WritesEnabled()) { - wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3 - wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 - } - wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 - when (base) { - is ObjClass -> { - val clsScope = base.classScope - val capturedIdx = clsScope?.getSlotIndexOf(name) - if (clsScope != null && capturedIdx != null) { - wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> - val scope0 = (obj as ObjClass).classScope!! - val r0 = scope0.getSlotRecord(capturedIdx) - if (!r0.isMutable) - sc.raiseError(ObjIllegalAssignmentException(sc, "can't reassign val $name")) - if (r0.value.assign(sc, v) == null) r0.value = v - } - } else { - wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) } - } - } - is ObjInstance -> { - val cls = base.objClass - val effectiveKey = cls.publicMemberResolution[name] - if (effectiveKey != null) { - wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, nv -> - if (obj is ObjInstance && obj.objClass === cls) { - val slot = cls.fieldSlotForKey(effectiveKey) - if (slot != null) { - val idx = slot.slot - val rec = if (idx >= 0 && idx < obj.fieldSlots.size) obj.fieldSlots[idx] else null - if (rec != null && - rec.effectiveWriteVisibility == Visibility.Public && - rec.isMutable && - (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && - !rec.isAbstract) { - if (rec.value.assign(sc, nv) == null) rec.value = nv - } else obj.writeField(sc, name, nv) - } else { - val rec = obj.fieldRecordForKey(effectiveKey) ?: obj.instanceScope.objects[effectiveKey] - if (rec != null && rec.effectiveWriteVisibility == Visibility.Public && rec.isMutable && - (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField)) { - if (rec.value.assign(sc, nv) == null) rec.value = nv - } else obj.writeField(sc, name, nv) - } - } else obj.writeField(sc, name, nv) - } - } else { - wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) } - } - } - else -> { - // For instances and other types, fall back to generic write (instance slot indices may differ per instance) - wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) } - } - } - return - } - } - base.writeField(scope, name, newValue) - } + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) = scope.raiseObjRefEvalDisabled() private fun receiverKeyAndVersion(obj: Obj): Pair = when (obj) { is ObjInstance -> obj.objClass.classId to obj.objClass.layoutVersion @@ -1207,59 +382,7 @@ class FieldRef( return rec.value } - override suspend fun evalValue(scope: Scope): Obj { - // Mirror get(), but return raw Obj to avoid transient ObjRecord on R-value paths - val fieldPic = PerfFlags.FIELD_PIC - val picCounters = PerfFlags.PIC_DEBUG_COUNTERS - val base = target.evalValue(scope) - if (base == ObjNull && isOptional) return ObjNull - if (fieldPic) { - val key: Long - val ver: Int - when (base) { - is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion } - is ObjClass -> { key = base.classId; ver = base.layoutVersion } - else -> { key = 0L; ver = -1 } - } - if (key != 0L) { - rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) { - if (picCounters) PerfStats.fieldPicHit++ - return resolveValue(scope, base, g(base, scope)) - } } - rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) { - if (picCounters) PerfStats.fieldPicHit++ - val tK = rKey2; val tV = rVer2; val tG = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = tK; rVer1 = tV; rGetter1 = tG - return resolveValue(scope, base, g(base, scope)) - } } - if (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) { - if (picCounters) PerfStats.fieldPicHit++ - val tK = rKey3; val tV = rVer3; val tG = rGetter3 - rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = tK; rVer1 = tV; rGetter1 = tG - return resolveValue(scope, base, g(base, scope)) - } } - if (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) { - if (picCounters) PerfStats.fieldPicHit++ - val tK = rKey4; val tV = rVer4; val tG = rGetter4 - rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3 - rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = tK; rVer1 = tV; rGetter1 = tG - return resolveValue(scope, base, g(base, scope)) - } } - if (picCounters) PerfStats.fieldPicMiss++ - val rec = base.readField(scope, name) - // install primary generic getter for this shape - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) } - return resolveValue(scope, base, rec) - } - } - val rec = base.readField(scope, name) - return resolveValue(scope, base, rec) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** @@ -1276,38 +399,9 @@ class ThisFieldSlotRef( internal fun methodId(): Int? = methodId internal fun optional(): Boolean = isOptional - override suspend fun get(scope: Scope): ObjRecord { - val th = scope.thisObj - if (th == ObjNull && isOptional) return ObjNull.asMutable - val inst = th as? ObjInstance ?: scope.raiseClassCastError("member access on non-instance") - val field = fieldId?.let { inst.fieldRecordForId(it) } - if (field != null && (field.type == ObjRecord.Type.Field || field.type == ObjRecord.Type.ConstructorField) && !field.isAbstract) { - return field - } - val method = methodId?.let { inst.methodRecordForId(it) } - if (method != null && !method.isAbstract) { - val decl = method.declaringClass ?: inst.objClass - return inst.resolveRecord(scope, method, method.memberName ?: name, decl) - } - scope.raiseSymbolNotFound(name) - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { - val th = scope.thisObj - if (th == ObjNull && isOptional) return - val inst = th as? ObjInstance ?: scope.raiseClassCastError("member access on non-instance") - val field = fieldId?.let { inst.fieldRecordForId(it) } - if (field != null) { - assignToRecord(scope, field, newValue) - return - } - val method = methodId?.let { inst.methodRecordForId(it) } - if (method != null) { - scope.assign(method, method.memberName ?: name, newValue) - return - } - scope.raiseSymbolNotFound(name) - } + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) = scope.raiseObjRefEvalDisabled() private suspend fun assignToRecord( scope: Scope, @@ -1353,233 +447,18 @@ class IndexRef( is ObjClass -> obj.classId to obj.layoutVersion else -> 0L to -1 } - override suspend fun get(scope: Scope): ObjRecord { - val base = target.evalValue(scope) - if (base == ObjNull && isOptional) return ObjNull.asMutable - val idx = index.evalValue(scope) - val picCounters = PerfFlags.PIC_DEBUG_COUNTERS - if (PerfFlags.RVAL_FASTPATH) { - // Primitive list index fast path: avoid virtual dispatch to getAt when shapes match - if (base is ObjList && idx is ObjInt) { - val i = idx.toInt() - // Bounds checks are enforced by the underlying list access; exceptions propagate as before - return base.list[i].asMutable - } - // String[Int] fast path - if (base is ObjString && idx is ObjInt) { - val i = idx.toInt() - return ObjChar(base.value[i]).asMutable - } - // Map[String] fast path (common case); return ObjNull if absent - if (base is ObjMap && idx is ObjString) { - val v = base.map[idx] ?: ObjNull - return v.asMutable - } - if (PerfFlags.INDEX_PIC) { - // Polymorphic inline cache for other common shapes - val key: Long - val ver: Int - when (base) { - is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion } - is ObjClass -> { key = base.classId; ver = base.layoutVersion } - else -> { key = 0L; ver = -1 } - } - if (key != 0L) { - rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) { - if (picCounters) PerfStats.indexPicHit++ - return g(base, scope, idx).asMutable - } } - rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) { - if (picCounters) PerfStats.indexPicHit++ - val tk = rKey2; val tv = rVer2; val tg = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = tk; rVer1 = tv; rGetter1 = tg - return g(base, scope, idx).asMutable - } } - if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) { - if (picCounters) PerfStats.indexPicHit++ - val tk = rKey3; val tv = rVer3; val tg = rGetter3 - rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = tk; rVer1 = tv; rGetter1 = tg - return g(base, scope, idx).asMutable - } } - if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) { - if (picCounters) PerfStats.indexPicHit++ - val tk = rKey4; val tv = rVer4; val tg = rGetter4 - rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3 - rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = tk; rVer1 = tv; rGetter1 = tg - return g(base, scope, idx).asMutable - } } - // Miss: resolve and install generic handler - if (picCounters) PerfStats.indexPicMiss++ - val v = base.getAt(scope, idx) - if (PerfFlags.INDEX_PIC_SIZE_4) { - rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3 - rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 - } - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc, ix -> obj.getAt(sc, ix) } - return v.asMutable - } - } - } - return base.getAt(scope, idx).asMutable - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val base = target.evalValue(scope) - if (base == ObjNull && isOptional) return ObjNull - val idx = index.evalValue(scope) - val picCounters = PerfFlags.PIC_DEBUG_COUNTERS - if (PerfFlags.RVAL_FASTPATH) { - // Fast list[int] path - if (base is ObjList && idx is ObjInt) { - val i = idx.toInt() - return base.list[i] - } - // String[Int] fast path - if (base is ObjString && idx is ObjInt) { - val i = idx.toInt() - return ObjChar(base.value[i]) - } - // Map[String] fast path - if (base is ObjMap && idx is ObjString) { - return base.map[idx] ?: ObjNull - } - if (PerfFlags.INDEX_PIC) { - // PIC path analogous to get(), but returning raw Obj - val key: Long - val ver: Int - when (base) { - is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion } - is ObjClass -> { key = base.classId; ver = base.layoutVersion } - else -> { key = 0L; ver = -1 } - } - if (key != 0L) { - rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) { - if (picCounters) PerfStats.indexPicHit++ - return g(base, scope, idx) - } } - rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) { - if (picCounters) PerfStats.indexPicHit++ - val tk = rKey2; val tv = rVer2; val tg = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = tk; rVer1 = tv; rGetter1 = tg - return g(base, scope, idx) - } } - if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) { - if (picCounters) PerfStats.indexPicHit++ - val tk = rKey3; val tv = rVer3; val tg = rGetter3 - rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = tk; rVer1 = tv; rGetter1 = tg - return g(base, scope, idx) - } } - if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) { - if (picCounters) PerfStats.indexPicHit++ - val tk = rKey4; val tv = rVer4; val tg = rGetter4 - rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3 - rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = tk; rVer1 = tv; rGetter1 = tg - return g(base, scope, idx) - } } - if (picCounters) PerfStats.indexPicMiss++ - val v = base.getAt(scope, idx) - if (PerfFlags.INDEX_PIC_SIZE_4) { - rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3 - rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 - } - rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc, ix -> obj.getAt(sc, ix) } - return v - } - } - } - return base.getAt(scope, idx) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() - override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { - val base = target.evalValue(scope) - if (base == ObjNull && isOptional) { - // no-op on null receiver for optional chaining assignment - return - } - val idx = index.evalValue(scope) - - // Mirror read fast-path with direct write for ObjList + ObjInt index - if (base is ObjList && idx is ObjInt) { - val i = idx.toInt() - base.list[i] = newValue - return - } - // Direct write fast path for ObjMap + ObjString - if (base is ObjMap && idx is ObjString) { - base.map[idx] = newValue - return - } - if (PerfFlags.RVAL_FASTPATH && PerfFlags.INDEX_PIC) { - // Polymorphic inline cache for index write - val key: Long - val ver: Int - when (base) { - is ObjInstance -> { key = base.objClass.classId; ver = base.objClass.layoutVersion } - is ObjClass -> { key = base.classId; ver = base.layoutVersion } - else -> { key = 0L; ver = -1 } - } - if (key != 0L) { - wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { s(base, scope, idx, newValue); return } } - wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) { - val tk = wKey2; val tv = wVer2; val ts = wSetter2 - wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 - wKey1 = tk; wVer1 = tv; wSetter1 = ts - s(base, scope, idx, newValue); return - } } - if (PerfFlags.INDEX_PIC_SIZE_4) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) { - val tk = wKey3; val tv = wVer3; val ts = wSetter3 - wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 - wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 - wKey1 = tk; wVer1 = tv; wSetter1 = ts - s(base, scope, idx, newValue); return - } } - if (PerfFlags.INDEX_PIC_SIZE_4) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) { - val tk = wKey4; val tv = wVer4; val ts = wSetter4 - wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3 - wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 - wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 - wKey1 = tk; wVer1 = tv; wSetter1 = ts - s(base, scope, idx, newValue); return - } } - // Miss: perform write and install generic handler - base.putAt(scope, idx, newValue) - if (PerfFlags.INDEX_PIC_SIZE_4) { - wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3 - wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 - } - wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 - wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, ix, v -> obj.putAt(sc, ix, v) } - return - } - } - base.putAt(scope, idx, newValue) - } + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) = scope.raiseObjRefEvalDisabled() } /** * R-value reference that wraps a Statement (used during migration for expressions parsed as Statement). */ class StatementRef(internal val statement: Statement) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord { - val bytecode = when (statement) { - is net.sergeych.lyng.bytecode.BytecodeStatement -> statement - is BytecodeBodyProvider -> statement.bytecodeBody() - else -> null - } ?: scope.raiseIllegalState("StatementRef requires bytecode statement") - return bytecode.execute(scope).asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() } /** @@ -1591,21 +470,7 @@ class CallRef( internal val tailBlock: Boolean, internal val isOptionalInvoke: Boolean, ) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord { - val callee = target.evalValue(scope) - if (callee == ObjNull && isOptionalInvoke) return ObjNull.asReadonly - val callArgs = args.toArguments(scope, tailBlock) - val usePool = PerfFlags.SCOPE_POOL && callee !is Statement - val result: Obj = if (usePool) { - scope.withChildFrame(callArgs) { child -> - callee.callOn(child) - } - } else { - // Pooling for Statement callables (lambdas) can still perturb closure semantics; keep safe path for now. - callee.callOn(scope.createChildScope(scope.pos, callArgs)) - } - return result.asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() } /** @@ -1682,23 +547,9 @@ class MethodCallRef( } } - override suspend fun get(scope: Scope): ObjRecord { - val methodPic = PerfFlags.METHOD_PIC - val picCounters = PerfFlags.PIC_DEBUG_COUNTERS - val base = receiver.evalValue(scope) - if (base == ObjNull && isOptional) return ObjNull.asReadonly - val callArgs = args.toArguments(scope, tailBlock) - return performInvoke(scope, base, callArgs, methodPic, picCounters).asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val methodPic = PerfFlags.METHOD_PIC - val picCounters = PerfFlags.PIC_DEBUG_COUNTERS - val base = receiver.evalValue(scope) - if (base == ObjNull && isOptional) return ObjNull - val callArgs = args.toArguments(scope, tailBlock) - return performInvoke(scope, base, callArgs, methodPic, picCounters) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() private suspend fun performInvoke( scope: Scope, @@ -1876,25 +727,9 @@ class ThisMethodSlotCallRef( internal fun hasTailBlock(): Boolean = tailBlock internal fun optionalInvoke(): Boolean = isOptional - override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val base = scope.thisObj - if (base == ObjNull && isOptional) return ObjNull - val callArgs = args.toArguments(scope, tailBlock) - if (base !is ObjInstance) return base.invokeInstanceMethod(scope, name, callArgs) - val id = methodId ?: scope.raiseSymbolNotFound(name) - val rec = base.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name) - val decl = rec.declaringClass ?: base.objClass - return when (rec.type) { - ObjRecord.Type.Property -> { - if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, base, decl) - else scope.raiseError("property $name cannot be called with arguments") - } - ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(base.instanceScope, base, callArgs, decl) - else -> scope.raiseError("member $name is not callable") - } - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** @@ -1923,50 +758,11 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef { return -1 } - override suspend fun get(scope: Scope): ObjRecord { - scope.pos = atPos - if (name == "this") return scope.thisObj.asReadonly - val hit = (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount()) - val slot = if (hit) cachedSlot else resolveSlot(scope) - if (slot >= 0) { - val rec = scope.getSlotRecord(slot) - if (rec.declaringClass != null && - !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name) - ) { - scope.raiseError(ObjIllegalAccessException(scope, "private field access")) - } - if (PerfFlags.PIC_DEBUG_COUNTERS) { - if (hit) PerfStats.localVarPicHit++ else PerfStats.localVarPicMiss++ - } - return rec - } - scope.raiseSymbolNotFound(name) - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - scope.pos = atPos - if (name == "this") return scope.thisObj - scope.getSlotIndexOf(name)?.let { return scope.resolve(scope.getSlotRecord(it), name) } - scope.raiseSymbolNotFound(name) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() - override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { - scope.pos = atPos - if (name == "this") scope.raiseError("can't assign to this") - val slot = - if (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount()) cachedSlot - else resolveSlot(scope) - if (slot >= 0) { - val rec = scope.getSlotRecord(slot) - if (rec.declaringClass == null || - canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name) - ) { - scope.assign(rec, name, newValue) - return - } - } - scope.raiseSymbolNotFound(name) - } + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) = scope.raiseObjRefEvalDisabled() } @@ -1978,32 +774,11 @@ class BoundLocalVarRef( private val atPos: Pos, ) : ObjRef { internal fun slotIndex(): Int = slot - override suspend fun get(scope: Scope): ObjRecord { - scope.pos = atPos - val rec = scope.getSlotRecord(slot) - if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) - scope.raiseError(ObjIllegalAccessException(scope, "private field access")) - return rec - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - scope.pos = atPos - val rec = scope.getSlotRecord(slot) - if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) - scope.raiseError(ObjIllegalAccessException(scope, "private field access")) - // We might not have the name in BoundLocalVarRef, but let's try to find it or use a placeholder - // Actually BoundLocalVarRef is mostly used for parameters which are not delegated yet. - // But for consistency: - return scope.resolve(rec, "local") - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() - override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { - scope.pos = atPos - val rec = scope.getSlotRecord(slot) - if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) - scope.raiseError(ObjIllegalAccessException(scope, "private field access")) - scope.assign(rec, "local", newValue) - } + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) = scope.raiseObjRefEvalDisabled() } /** @@ -2058,51 +833,11 @@ class FastLocalVarRef( return -1 } - override suspend fun get(scope: Scope): ObjRecord { - scope.pos = atPos - val ownerValid = isOwnerValidFor(scope) - val slot = if (ownerValid && cachedSlot >= 0) cachedSlot else resolveSlotInAncestry(scope) - val actualOwner = cachedOwnerScope - if (slot >= 0 && actualOwner != null) { - val rec = actualOwner.getSlotRecord(slot) - if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { - if (PerfFlags.PIC_DEBUG_COUNTERS) { - if (ownerValid) PerfStats.fastLocalHit++ else PerfStats.fastLocalMiss++ - } - return rec - } - } - scope.raiseSymbolNotFound(name) - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - scope.pos = atPos - val ownerValid = isOwnerValidFor(scope) - val slot = if (ownerValid && cachedSlot >= 0) cachedSlot else resolveSlotInAncestry(scope) - val actualOwner = cachedOwnerScope - if (slot >= 0 && actualOwner != null) { - val rec = actualOwner.getSlotRecord(slot) - if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { - return scope.resolve(rec, name) - } - } - scope.raiseSymbolNotFound(name) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() - override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { - scope.pos = atPos - val owner = if (isOwnerValidFor(scope)) cachedOwnerScope else null - val slot = if (owner != null && cachedSlot >= 0) cachedSlot else resolveSlotInAncestry(scope) - val actualOwner = cachedOwnerScope - if (slot >= 0 && actualOwner != null) { - val rec = actualOwner.getSlotRecord(slot) - if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { - scope.assign(rec, name, newValue) - return - } - } - scope.raiseSymbolNotFound(name) - } + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) = scope.raiseObjRefEvalDisabled() } /** @@ -2125,49 +860,11 @@ class ImplicitThisMemberRef( block(name, atPos) } - override suspend fun get(scope: Scope): ObjRecord { - scope.pos = atPos - val th = preferredThisTypeName?.let { typeName -> - scope.thisVariants.firstOrNull { it.objClass.className == typeName } - } ?: scope.thisObj - val inst = th as? ObjInstance - val field = fieldId?.let { inst?.fieldRecordForId(it) ?: th.objClass.fieldRecordForId(it) } - if (field != null && (field.type == ObjRecord.Type.Field || field.type == ObjRecord.Type.ConstructorField) && !field.isAbstract) { - return field - } - val method = methodId?.let { inst?.methodRecordForId(it) ?: th.objClass.methodRecordForId(it) } - if (method != null && !method.isAbstract) { - val decl = method.declaringClass ?: th.objClass - return th.resolveRecord(scope, method, method.memberName ?: name, decl) - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - scope.raiseSymbolNotFound(name) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val rec = get(scope) - return scope.resolve(rec, name) - } - - override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { - scope.pos = atPos - val th = preferredThisTypeName?.let { typeName -> - scope.thisVariants.firstOrNull { it.objClass.className == typeName } - } ?: scope.thisObj - val inst = th as? ObjInstance - val field = fieldId?.let { inst?.fieldRecordForId(it) ?: th.objClass.fieldRecordForId(it) } - if (field != null) { - scope.assign(field, field.memberName ?: name, newValue) - return - } - val method = methodId?.let { inst?.methodRecordForId(it) ?: th.objClass.methodRecordForId(it) } - if (method != null) { - scope.assign(method, method.memberName ?: name, newValue) - return - } - - scope.raiseSymbolNotFound(name) - } + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) = scope.raiseObjRefEvalDisabled() } /** @@ -2196,22 +893,11 @@ class ClassScopeMemberRef( scope.raiseSymbolNotFound(ownerClassName) } - override suspend fun get(scope: Scope): ObjRecord { - scope.pos = atPos - val cls = resolveClass(scope) - return cls.readField(scope, name) - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val rec = get(scope) - return scope.resolve(rec, name) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() - override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { - scope.pos = atPos - val cls = resolveClass(scope) - cls.writeField(scope, name, newValue) - } + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) = scope.raiseObjRefEvalDisabled() } /** @@ -2235,44 +921,9 @@ class ImplicitThisMethodCallRef( internal fun preferredThisTypeName(): String? = preferredThisTypeName internal fun slotId(): Int? = methodId - override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - scope.pos = atPos - val callArgs = args.toArguments(scope, tailBlock) - val localRecord = scope.chainLookupIgnoreClosure(name, followClosure = true, caller = scope.currentClassCtx) - if (localRecord != null) { - val callee = scope.resolve(localRecord, name) - if (callee == ObjNull && isOptional) return ObjNull - val usePool = PerfFlags.SCOPE_POOL - return if (usePool) { - scope.withChildFrame(callArgs) { child -> - callee.callOn(child) - } - } else { - callee.callOn(scope.createChildScope(scope.pos, callArgs)) - } - } - val receiver = preferredThisTypeName?.let { typeName -> - scope.thisVariants.firstOrNull { it.objClass.className == typeName } - } ?: scope.thisObj - if (receiver == ObjNull && isOptional) return ObjNull - val inst = receiver as? ObjInstance ?: return receiver.invokeInstanceMethod(scope, name, callArgs) - if (methodId == null) { - return inst.invokeInstanceMethod(scope, name, callArgs) - } - val id = methodId - val rec = inst.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name) - val decl = rec.declaringClass ?: inst.objClass - return when (rec.type) { - ObjRecord.Type.Property -> { - if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, inst, decl) - else scope.raiseError("property $name cannot be called with arguments") - } - ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(inst.instanceScope, inst, callArgs, decl) - else -> scope.raiseError("member $name is not callable") - } - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** @@ -2319,88 +970,11 @@ class LocalSlotRef( return null } - override suspend fun get(scope: Scope): ObjRecord { - scope.pos = atPos - val resolved = resolveOwnerAndSlot(scope) - if (resolved == null) { - val rec = scope.get(name) ?: scope.raiseError("slot owner not found for $name") - if (rec.declaringClass != null && - !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name) - ) { - scope.raiseError(ObjIllegalAccessException(scope, "private field access")) - } - return rec - } - val owner = resolved.first - val slotIndex = resolved.second - if (slotIndex < 0 || slotIndex >= owner.slotCount()) { - scope.raiseError("slot index out of range for $name") - } - val rec = owner.getSlotRecord(slotIndex) - val direct = owner.getLocalRecordDirect(name) - if (direct != null && direct !== rec) { - owner.updateSlotFor(name, direct) - return direct - } - if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { - scope.raiseError(ObjIllegalAccessException(scope, "private field access")) - } - return rec - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - scope.pos = atPos - val resolved = resolveOwnerAndSlot(scope) - if (resolved == null) { - val rec = scope.get(name) ?: scope.raiseError("slot owner not found for $name") - if (rec.declaringClass != null && - !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name) - ) { - scope.raiseError(ObjIllegalAccessException(scope, "private field access")) - } - return scope.resolve(rec, name) - } - val owner = resolved.first - val slotIndex = resolved.second - if (slotIndex < 0 || slotIndex >= owner.slotCount()) { - scope.raiseError("slot index out of range for $name") - } - val rec = owner.getSlotRecord(slotIndex) - val direct = owner.getLocalRecordDirect(name) - if (direct != null && direct !== rec) { - owner.updateSlotFor(name, direct) - return scope.resolve(direct, name) - } - if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { - scope.raiseError(ObjIllegalAccessException(scope, "private field access")) - } - return scope.resolve(rec, name) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() - override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { - scope.pos = atPos - val resolved = resolveOwnerAndSlot(scope) - if (resolved == null) { - val rec = scope.get(name) ?: scope.raiseError("slot owner not found for $name") - if (rec.declaringClass != null && - !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name) - ) { - scope.raiseError(ObjIllegalAccessException(scope, "private field access")) - } - scope.assign(rec, name, newValue) - return - } - val owner = resolved.first - val slotIndex = resolved.second - if (slotIndex < 0 || slotIndex >= owner.slotCount()) { - scope.raiseError("slot index out of range for $name") - } - val rec = owner.getSlotRecord(slotIndex) - if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { - scope.raiseError(ObjIllegalAccessException(scope, "private field access")) - } - scope.assign(rec, name, newValue) - } + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) = scope.raiseObjRefEvalDisabled() } class ListLiteralRef(private val entries: List) : ObjRef { @@ -2424,81 +998,11 @@ class ListLiteralRef(private val entries: List) : ObjRef { } } - override suspend fun get(scope: Scope): ObjRecord { - return evalValue(scope).asMutable - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - // Heuristic capacity hint: count element entries; spreads handled opportunistically - val elemCount = entries.count { it is ListEntry.Element } - val list = ArrayList(elemCount) - for (e in entries) { - when (e) { - is ListEntry.Element -> { - list += e.ref.evalValue(scope) - } - is ListEntry.Spread -> { - val elements = e.ref.evalValue(scope) - when (elements) { - is ObjList -> { - // Grow underlying array once when possible - list.ensureCapacity(list.size + elements.list.size) - list.addAll(elements.list) - } - else -> scope.raiseError("Spread element must be list") - } - } - } - } - return ObjList(list) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() - override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { - val sourceList = (newValue as? ObjList)?.list - ?: throw ScriptError(pos, "destructuring assignment requires a list on the right side") - - val ellipsisIdx = entries.indexOfFirst { it is ListEntry.Spread } - if (entries.count { it is ListEntry.Spread } > 1) { - throw ScriptError(pos, "destructuring pattern can have only one splat") - } - - if (ellipsisIdx < 0) { - if (sourceList.size < entries.size) - throw ScriptError(pos, "too few elements for destructuring") - for (i in entries.indices) { - val entry = entries[i] - if (entry is ListEntry.Element) { - entry.ref.setAt(pos, scope, sourceList[i]) - } - } - } else { - val headCount = ellipsisIdx - val tailCount = entries.size - ellipsisIdx - 1 - if (sourceList.size < headCount + tailCount) - throw ScriptError(pos, "too few elements for destructuring") - - // head - for (i in 0 until headCount) { - val entry = entries[i] - if (entry is ListEntry.Element) { - entry.ref.setAt(pos, scope, sourceList[i]) - } - } - - // tail - for (i in 0 until tailCount) { - val entry = entries[entries.size - 1 - i] - if (entry is ListEntry.Element) { - entry.ref.setAt(pos, scope, sourceList[sourceList.size - 1 - i]) - } - } - - // ellipsis - val spreadEntry = entries[ellipsisIdx] as ListEntry.Spread - val spreadList = sourceList.subList(headCount, sourceList.size - tailCount) - spreadEntry.ref.setAt(pos, scope, ObjList(spreadList.toMutableList())) - } - } + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) = scope.raiseObjRefEvalDisabled() } // --- Map literal support --- @@ -2511,29 +1015,9 @@ sealed class MapLiteralEntry { class MapLiteralRef(private val entries: List) : ObjRef { internal fun entries(): List = entries - override suspend fun get(scope: Scope): ObjRecord { - return evalValue(scope).asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val result = ObjMap(mutableMapOf()) - for (e in entries) { - when (e) { - is MapLiteralEntry.Named -> { - val v = e.value.evalValue(scope) - result.map[ObjString(e.key)] = v - } - is MapLiteralEntry.Spread -> { - val m = e.ref.evalValue(scope) - if (m !is ObjMap) scope.raiseIllegalArgument("spread element in map literal must be a Map") - for ((k, v) in m.map) { - result.map[k] = v - } - } - } - } - return result - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** @@ -2545,16 +1029,9 @@ class RangeRef( internal val isEndInclusive: Boolean, internal val step: ObjRef? = null ) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord { - return evalValue(scope).asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val l = left?.evalValue(scope) ?: ObjNull - val r = right?.evalValue(scope) ?: ObjNull - val st = step?.evalValue(scope) - return ObjRange(l, r, isEndInclusive = isEndInclusive, step = st) - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** Assignment if null op: target ?= value */ @@ -2563,17 +1040,9 @@ class AssignIfNullRef( internal val value: ObjRef, internal val atPos: Pos, ) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord { - return evalValue(scope).asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val current = target.evalValue(scope) - if (current != ObjNull) return current - val newValue = value.evalValue(scope) - target.setAt(atPos, scope, newValue) - return newValue - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } /** Simple assignment: target = value */ @@ -2582,33 +1051,9 @@ class AssignRef( internal val value: ObjRef, private val atPos: Pos, ) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord { - return evalValue(scope).asReadonly - } + override suspend fun get(scope: Scope): ObjRecord = scope.raiseObjRefEvalDisabled() - override suspend fun evalValue(scope: Scope): Obj { - val v = value.evalValue(scope) - // For properties, we should not call get() on target because it invokes the getter. - // Instead, we call setAt directly. - if (target is FieldRef || - target is IndexRef || - target is LocalVarRef || - target is FastLocalVarRef || - target is BoundLocalVarRef || - target is LocalSlotRef || - target is ThisFieldSlotRef || - target is QualifiedThisFieldSlotRef || - target is ImplicitThisMemberRef) { - target.setAt(atPos, scope, v) - } else { - val rec = target.get(scope) - if (!rec.isMutable) throw ScriptError(atPos, "cannot assign to immutable variable") - if (rec.value.assign(scope, v) == null) { - target.setAt(atPos, scope, v) - } - } - return v - } + override suspend fun evalValue(scope: Scope): Obj = scope.raiseObjRefEvalDisabled() } // (duplicate LocalVarRef removed; the canonical implementation is defined earlier in this file) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt index ddf26d4..0833e6e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt @@ -21,6 +21,7 @@ import net.sergeych.lyng.PerfFlags import net.sergeych.lyng.Pos import net.sergeych.lyng.RegexCache import net.sergeych.lyng.Scope +import net.sergeych.lyng.requireScope import net.sergeych.lyng.miniast.* class ObjRegex(val regex: Regex) : Obj() { @@ -75,9 +76,10 @@ class ObjRegex(val regex: Regex) : Obj() { } createField( name = "operatorMatch", - initialValue = ObjNativeCallable { + initialValue = ObjExternCallable.fromBridge { val other = args.firstAndOnly(Pos.builtIn) - val targetScope = parent ?: this + val scope = requireScope() + val targetScope = scope.parent ?: scope (thisObj as ObjRegex).operatorMatch(targetScope, other) }, type = ObjRecord.Type.Fun diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjSet.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjSet.kt index 11b120c..f487939 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjSet.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjSet.kt @@ -175,7 +175,7 @@ class ObjSet(val set: MutableSet = mutableSetOf()) : Obj() { returns = type("lyng.Set"), moduleName = "lyng.stdlib" ) { - thisAs().mul(this, args.firstAndOnly()) + thisAs().mul(requireScope(), args.firstAndOnly()) } addFnDoc( name = "iterator", @@ -192,7 +192,7 @@ class ObjSet(val set: MutableSet = mutableSetOf()) : Obj() { returns = type("lyng.Set"), moduleName = "lyng.stdlib" ) { - thisAs().plus(this, args.firstAndOnly()) + thisAs().plus(requireScope(), args.firstAndOnly()) } addFnDoc( name = "subtract", @@ -201,7 +201,7 @@ class ObjSet(val set: MutableSet = mutableSetOf()) : Obj() { returns = type("lyng.Set"), moduleName = "lyng.stdlib" ) { - thisAs().minus(this, args.firstAndOnly()) + thisAs().minus(requireScope(), args.firstAndOnly()) } addFnDoc( name = "remove", @@ -216,4 +216,4 @@ class ObjSet(val set: MutableSet = mutableSetOf()) : Obj() { } } } -} \ No newline at end of file +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt index da5b0a7..14648c5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt @@ -25,6 +25,7 @@ import net.sergeych.lyng.PerfFlags import net.sergeych.lyng.Pos import net.sergeych.lyng.RegexCache import net.sergeych.lyng.Scope +import net.sergeych.lyng.requireScope import net.sergeych.lyng.miniast.* import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonEncoder @@ -343,7 +344,7 @@ data class ObjString(val value: String) : Obj() { name = "re", initialValue = ObjProperty( name = "re", - getter = ObjNativeCallable { + getter = ObjExternCallable.fromBridge { val pattern = (thisObj as ObjString).value val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(pattern) else pattern.toRegex() ObjRegex(re) @@ -354,9 +355,10 @@ data class ObjString(val value: String) : Obj() { ) createField( name = "operatorMatch", - initialValue = ObjNativeCallable { + initialValue = ObjExternCallable.fromBridge { val other = args.firstAndOnly(Pos.builtIn) - val targetScope = parent ?: this + val scope = requireScope() + val targetScope = scope.parent ?: scope (thisObj as ObjString).operatorMatch(targetScope, other) }, type = ObjRecord.Type.Fun diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ScopeFacadeObjExtensions.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ScopeFacadeObjExtensions.kt new file mode 100644 index 0000000..f20e060 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ScopeFacadeObjExtensions.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2026 Sergey S. Chernov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sergeych.lyng.obj + +import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeFacade +import net.sergeych.lyng.requireExactCount as coreRequireExactCount +import net.sergeych.lyng.requireNoArgs as coreRequireNoArgs +import net.sergeych.lyng.requireOnlyArg as coreRequireOnlyArg +import net.sergeych.lyng.requiredArg as coreRequiredArg +import net.sergeych.lyng.requireScope as coreRequireScope +import net.sergeych.lyng.thisAs as coreThisAs + +inline fun ScopeFacade.requiredArg(index: Int): T = coreRequiredArg(index) + +inline fun ScopeFacade.requireOnlyArg(): T = coreRequireOnlyArg() + +fun ScopeFacade.requireExactCount(count: Int) = coreRequireExactCount(count) + +fun ScopeFacade.requireNoArgs() = coreRequireNoArgs() + +inline fun ScopeFacade.thisAs(): T = coreThisAs() + +internal fun ScopeFacade.requireScope(): Scope = coreRequireScope() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt index cd17c04..1fa266f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt @@ -60,8 +60,8 @@ abstract class Statement( suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.createChildScope(args = Arguments(*args))) - protected fun interpreterDisabled(scope: Scope, label: String): Nothing { - return scope.raiseIllegalState("interpreter execution is not supported; $label requires bytecode") + protected fun bytecodeOnly(scope: Scope, label: String): Nothing { + return scope.raiseIllegalState("bytecode-only execution is required; $label needs compiled bytecode") } } @@ -73,7 +73,7 @@ class IfStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "if statement") + return bytecodeOnly(scope, "if statement") } } @@ -92,7 +92,7 @@ class ForInStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "for-in statement") + return bytecodeOnly(scope, "for-in statement") } } @@ -106,7 +106,7 @@ class WhileStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "while statement") + return bytecodeOnly(scope, "while statement") } } @@ -119,7 +119,7 @@ class DoWhileStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "do-while statement") + return bytecodeOnly(scope, "do-while statement") } } @@ -129,7 +129,7 @@ class BreakStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "break statement") + return bytecodeOnly(scope, "break statement") } } @@ -138,7 +138,7 @@ class ContinueStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "continue statement") + return bytecodeOnly(scope, "continue statement") } } @@ -148,7 +148,7 @@ class ReturnStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "return statement") + return bytecodeOnly(scope, "return statement") } } @@ -157,7 +157,7 @@ class ThrowStatement( override val pos: Pos, ) : Statement() { override suspend fun execute(scope: Scope): Obj { - return interpreterDisabled(scope, "throw statement") + return bytecodeOnly(scope, "throw statement") } } @@ -165,7 +165,7 @@ class ExpressionStatement( val ref: net.sergeych.lyng.obj.ObjRef, override val pos: Pos ) : Statement() { - override suspend fun execute(scope: Scope): Obj = interpreterDisabled(scope, "expression statement") + override suspend fun execute(scope: Scope): Obj = bytecodeOnly(scope, "expression statement") } fun Statement.raise(text: String): Nothing { @@ -180,5 +180,5 @@ fun Statement.require(cond: Boolean, message: () -> String) { object NopStatement: Statement(true, true, ObjType.Void) { override val pos: Pos = Pos.builtIn - override suspend fun execute(scope: Scope): Obj = interpreterDisabled(scope, "nop statement") + override suspend fun execute(scope: Scope): Obj = bytecodeOnly(scope, "nop statement") } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/packer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/packer.kt index bcf1093..3364b70 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/packer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/packer.kt @@ -18,6 +18,8 @@ package net.sergeych.lynon import net.sergeych.lyng.Scope +import net.sergeych.lyng.requireOnlyArg +import net.sergeych.lyng.requireScope import net.sergeych.lyng.obj.* // Most often used types: @@ -42,10 +44,10 @@ object ObjLynonClass : ObjClass("Lynon") { init { addClassConst("test", ObjString("test_const")) addClassFn("encode") { - encodeAny(this, requireOnlyArg()) + encodeAny(requireScope(), requireOnlyArg()) } addClassFn("decode") { - decodeAny(this, requireOnlyArg()) + decodeAny(requireScope(), requireOnlyArg()) } } } diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index f73bd3f..1358e11 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -30,6 +30,7 @@ import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.encodeToJsonElement import net.sergeych.lyng.* import net.sergeych.lyng.obj.* +import net.sergeych.lyng.thisAs import net.sergeych.lyng.pacman.InlineSourcesImportProvider import net.sergeych.mp_tools.globalDefer import net.sergeych.tools.bm diff --git a/lynglib/src/jvmTest/kotlin/BookTest.kt b/lynglib/src/jvmTest/kotlin/BookTest.kt index 189e5c6..bc68067 100644 --- a/lynglib/src/jvmTest/kotlin/BookTest.kt +++ b/lynglib/src/jvmTest/kotlin/BookTest.kt @@ -178,11 +178,11 @@ suspend fun DocTest.test(_scope: Scope? = null) { scope.apply { addFn("println") { if( bookMode ) { - println("${currentTest.fileNamePart}:${currentTest.line}> ${args.map{it.toString(this).value}.joinToString(" ")}") + println("${currentTest.fileNamePart}:${currentTest.line}> ${args.map{toStringOf(it).value}.joinToString(" ")}") } else { for ((i, a) in args.withIndex()) { - if (i > 0) collectedOutput.append(' '); collectedOutput.append(a.toString(this).value) + if (i > 0) collectedOutput.append(' '); collectedOutput.append(toStringOf(a).value) collectedOutput.append('\n') } }