From 90826b519d4dff7d9fb6de232d7de600c07ad193 Mon Sep 17 00:00:00 2001 From: sergeych Date: Fri, 13 Feb 2026 00:19:47 +0300 Subject: [PATCH] Remove interpreter paths and enforce bytecode-only execution --- bytecode_migration_plan.md | 32 + .../lyng/io/process/LyngProcessModule.kt | 2 +- .../net/sergeych/lyng/ArgsDeclaration.kt | 34 +- .../kotlin/net/sergeych/lyng/Arguments.kt | 162 +-- .../net/sergeych/lyng/BlockStatement.kt | 34 +- .../kotlin/net/sergeych/lyng/CaptureSlot.kt | 2 + .../net/sergeych/lyng/ClassDeclStatement.kt | 15 +- .../lyng/ClassInstanceDeclStatements.kt | 139 ++ .../lyng/ClassStaticFieldInitStatement.kt | 101 ++ .../kotlin/net/sergeych/lyng/ClosureScope.kt | 68 +- .../kotlin/net/sergeych/lyng/Compiler.kt | 1253 ++++++----------- .../sergeych/lyng/FunctionDeclStatement.kt | 78 +- .../sergeych/lyng/InstanceInitStatements.kt | 130 ++ .../lyng/PropertyAccessorStatement.kt | 60 + .../kotlin/net/sergeych/lyng/Scope.kt | 66 +- .../kotlin/net/sergeych/lyng/Script.kt | 48 +- .../lyng/bytecode/BytecodeCompiler.kt | 471 ++++++- .../sergeych/lyng/bytecode/BytecodeConst.kt | 89 ++ .../lyng/bytecode/BytecodeStatement.kt | 124 ++ .../net/sergeych/lyng/bytecode/CmdBuilder.kt | 14 +- .../sergeych/lyng/bytecode/CmdDisassembler.kt | 14 +- .../net/sergeych/lyng/bytecode/CmdRuntime.kt | 689 +++++++-- .../net/sergeych/lyng/bytecode/Opcode.kt | 9 + .../kotlin/net/sergeych/lyng/obj/Obj.kt | 32 +- .../kotlin/net/sergeych/lyng/obj/ObjClass.kt | 35 +- .../net/sergeych/lyng/obj/ObjDateTime.kt | 14 +- .../net/sergeych/lyng/obj/ObjDynamic.kt | 17 +- .../net/sergeych/lyng/obj/ObjException.kt | 6 +- .../kotlin/net/sergeych/lyng/obj/ObjFlow.kt | 12 +- .../net/sergeych/lyng/obj/ObjInstance.kt | 19 +- .../net/sergeych/lyng/obj/ObjIterable.kt | 25 +- .../net/sergeych/lyng/obj/ObjLazyDelegate.kt | 2 +- .../kotlin/net/sergeych/lyng/obj/ObjList.kt | 10 +- .../kotlin/net/sergeych/lyng/obj/ObjMap.kt | 7 +- .../kotlin/net/sergeych/lyng/obj/ObjMutex.kt | 9 +- .../ObjNativeCallable.kt} | 22 +- .../net/sergeych/lyng/obj/ObjProperty.kt | 9 +- .../kotlin/net/sergeych/lyng/obj/ObjReal.kt | 6 +- .../kotlin/net/sergeych/lyng/obj/ObjRegex.kt | 13 +- .../kotlin/net/sergeych/lyng/obj/ObjString.kt | 25 +- .../lyng/resolution/CompileTimeResolution.kt | 2 +- .../sergeych/lyng/tools/LyngLanguageTools.kt | 2 +- .../kotlin/BytecodeRecentOpsTest.kt | 1 - .../CompileTimeResolutionRuntimeTest.kt | 3 - .../lyng/miniast/ParamTypeInferenceTest.kt | 3 +- 45 files changed, 2517 insertions(+), 1391 deletions(-) create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassInstanceDeclStatements.kt create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassStaticFieldInitStatement.kt create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/InstanceInitStatements.kt create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/PropertyAccessorStatement.kt rename lynglib/src/commonMain/kotlin/net/sergeych/lyng/{DeclExecutable.kt => obj/ObjNativeCallable.kt} (61%) diff --git a/bytecode_migration_plan.md b/bytecode_migration_plan.md index 91b5257..2dd5862 100644 --- a/bytecode_migration_plan.md +++ b/bytecode_migration_plan.md @@ -136,6 +136,38 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te - [x] Keep scope sync only for reflection/Kotlin interop, not for execution. - [x] Replace bytecode entry seeding from Scope with frame-only arg/local binding. +## Interpreter Removal v2 (2026-02-12) + +Goal: remove interpreter execution paths entirely without regressing semantics; all runtime execution must be bytecode + frame slots. + +- [ ] Step A1: Interpreter path audit (map all remaining runtime `Statement.execute` and scope-lookup paths). + - [x] Audit results (current hotspots): + - `ClassDeclStatement.executeClassDecl`: executes class body/init via `spec.bodyInit?.execute` and `spec.initScope` (Statement list). + - `FunctionDeclStatement.executeFunctionDecl`: class-body delegated function init uses `Statement.execute` in initializer thunk. + - `EnumDeclStatement.execute`: direct execution path exists (bytecode also emits DECL_ENUM). + - `BlockStatement.execute`: creates child scope, applies slot plan/captures, then executes `Script` (which may interpret). + - `Script.execute`: interpreter loop when `moduleBytecode` is null or disabled. + - `ObjClass.addFn/addProperty` wrappers use `ObjNativeCallable` calling into `Statement.execute` for declared bodies. + - Object expressions: class body executed via `executeClassDecl` path (same as above). + - Extension wrappers: `ObjExtensionMethodCallable` uses callable that executes `Statement`. + +- [ ] Step A2: Bytecode-backed class + init. + - Replace class-body and instance init execution with bytecode functions (per-class + per-instance). + - Remove all class init `Statement.execute` calls. + - [x] Introduce `ClassStaticFieldInitStatement` + bytecode ops `DECL_CLASS_FIELD`/`DECL_CLASS_DELEGATED` for static class field init. + +- [ ] Step A3: Bytecode-safe delegated properties/functions and object expressions. + - Use explicit `object : Statement()` where needed. + - No inline suspend lambdas in hot paths. + - Remove interpreter fallbacks. + +- [ ] Step A4: Bytecode for all blocks/lambdas (including class bodies). + - Compile non-module blocks/lambdas to bytecode; eliminate interpreter gate flags. + +- [ ] Step A5: Delete interpreter execution path and dead code. + - Remove interpreter ops/constants and any runtime name-lookup fallbacks. + - Full test suite green. + ## Notes - Keep imports bound to module frame slots; no scope map writes for imports. 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 e96dfe0..f731c90 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 @@ -217,7 +217,7 @@ private suspend inline fun Scope.processGuard(crossinline block: suspend () -> O private fun Flow.toLyngFlow(flowScope: Scope): ObjFlow { val producer = statement { - val builder = (this as? net.sergeych.lyng.ClosureScope)?.callScope?.thisObj as? ObjFlowBuilder + val builder = (this as? net.sergeych.lyng.BytecodeClosureScope)?.callScope?.thisObj as? ObjFlowBuilder ?: this.thisObj as? ObjFlowBuilder this@toLyngFlow.collect { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt index a4b2cd1..f4d0348 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt @@ -113,7 +113,7 @@ data class ArgsDeclaration(val params: List, val endTokenType: Token.Type) } suspend fun missingValue(a: Item, error: String): Obj { - return a.defaultValue?.execute(scope) + return a.defaultValue?.callOn(scope) ?: if (a.type.isNullable) ObjNull else scope.raiseIllegalArgument(error) } @@ -252,17 +252,14 @@ data class ArgsDeclaration(val params: List, val endTokenType: Token.Type) /** * Assign arguments directly into frame slots using [paramSlotPlan] without creating scope locals. - * Still allows default expressions to evaluate by exposing FrameSlotRef facades in [scope]. + * Default expressions must resolve through frame slots (no scope mirroring). */ suspend fun assignToFrame( scope: Scope, arguments: Arguments = scope.args, paramSlotPlan: Map, frame: FrameAccess, - slotOffset: Int = 0, - defaultAccessType: AccessType = AccessType.Var, - defaultVisibility: Visibility = Visibility.Public, - declaringClass: net.sergeych.lyng.obj.ObjClass? = scope.currentClassCtx + slotOffset: Int = 0 ) { fun slotFor(name: String): Int { val full = paramSlotPlan[name] ?: scope.raiseIllegalState("parameter slot for '$name' is missing") @@ -271,19 +268,6 @@ data class ArgsDeclaration(val params: List, val endTokenType: Token.Type) return slot } - fun ensureScopeRef(a: Item, slot: Int, recordType: ObjRecord.Type) { - if (scope.getLocalRecordDirect(a.name) != null) return - scope.addItem( - a.name, - (a.accessType ?: defaultAccessType).isMutable, - FrameSlotRef(frame, slot), - a.visibility ?: defaultVisibility, - recordType = recordType, - declaringClass = declaringClass, - isTransient = a.isTransient - ) - } - fun setFrameValue(slot: Int, value: Obj) { when (value) { is net.sergeych.lyng.obj.ObjInt -> frame.setInt(slot, value.value) @@ -294,18 +278,12 @@ data class ArgsDeclaration(val params: List, val endTokenType: Token.Type) } fun assign(a: Item, value: Obj) { - val recordType = if (declaringClass != null && a.accessType != null) { - ObjRecord.Type.ConstructorField - } else { - ObjRecord.Type.Argument - } val slot = slotFor(a.name) setFrameValue(slot, value.byValueCopy()) - ensureScopeRef(a, slot, recordType) } suspend fun missingValue(a: Item, error: String): Obj { - return a.defaultValue?.execute(scope) + return a.defaultValue?.callOn(scope) ?: if (a.type.isNullable) ObjNull else scope.raiseIllegalArgument(error) } @@ -459,7 +437,7 @@ data class ArgsDeclaration(val params: List, val endTokenType: Token.Type) /** * Single argument declaration descriptor. * - * @param defaultValue default value, if set, can't be an [Obj] as it can depend on the call site, call args, etc. + * @param defaultValue default value, callable evaluated at call site. * If not null, could be executed on __caller context__ only. */ data class Item( @@ -472,7 +450,7 @@ data class ArgsDeclaration(val params: List, val endTokenType: Token.Type) * Default value, if set, can't be an [Obj] as it can depend on the call site, call args, etc. * So it is a [Statement] that must be executed on __caller context__. */ - val defaultValue: Statement? = null, + val defaultValue: Obj? = null, val accessType: AccessType? = null, val visibility: Visibility? = null, val isTransient: Boolean = false, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt index 2791333..61bb174 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt @@ -20,7 +20,7 @@ package net.sergeych.lyng import net.sergeych.lyng.obj.* data class ParsedArgument( - val value: Statement, + val value: Obj, val pos: Pos, val isSplat: Boolean = false, val name: String? = null, @@ -40,115 +40,115 @@ data class ParsedArgument( if (!hasSplatOrNamed && count == this.size) { val quick = when (count) { 0 -> Arguments.EMPTY - 1 -> Arguments(listOf(this.elementAt(0).value.execute(scope)), tailBlockMode) + 1 -> Arguments(listOf(this.elementAt(0).value.callOn(scope)), tailBlockMode) 2 -> { - val a0 = this.elementAt(0).value.execute(scope) - val a1 = this.elementAt(1).value.execute(scope) + val a0 = this.elementAt(0).value.callOn(scope) + val a1 = this.elementAt(1).value.callOn(scope) Arguments(listOf(a0, a1), tailBlockMode) } 3 -> { - val a0 = this.elementAt(0).value.execute(scope) - val a1 = this.elementAt(1).value.execute(scope) - val a2 = this.elementAt(2).value.execute(scope) + val a0 = this.elementAt(0).value.callOn(scope) + val a1 = this.elementAt(1).value.callOn(scope) + val a2 = this.elementAt(2).value.callOn(scope) Arguments(listOf(a0, a1, a2), tailBlockMode) } 4 -> { - val a0 = this.elementAt(0).value.execute(scope) - val a1 = this.elementAt(1).value.execute(scope) - val a2 = this.elementAt(2).value.execute(scope) - val a3 = this.elementAt(3).value.execute(scope) + val a0 = this.elementAt(0).value.callOn(scope) + val a1 = this.elementAt(1).value.callOn(scope) + val a2 = this.elementAt(2).value.callOn(scope) + val a3 = this.elementAt(3).value.callOn(scope) Arguments(listOf(a0, a1, a2, a3), tailBlockMode) } 5 -> { - val a0 = this.elementAt(0).value.execute(scope) - val a1 = this.elementAt(1).value.execute(scope) - val a2 = this.elementAt(2).value.execute(scope) - val a3 = this.elementAt(3).value.execute(scope) - val a4 = this.elementAt(4).value.execute(scope) + val a0 = this.elementAt(0).value.callOn(scope) + val a1 = this.elementAt(1).value.callOn(scope) + val a2 = this.elementAt(2).value.callOn(scope) + val a3 = this.elementAt(3).value.callOn(scope) + val a4 = this.elementAt(4).value.callOn(scope) Arguments(listOf(a0, a1, a2, a3, a4), tailBlockMode) } 6 -> { - val a0 = this.elementAt(0).value.execute(scope) - val a1 = this.elementAt(1).value.execute(scope) - val a2 = this.elementAt(2).value.execute(scope) - val a3 = this.elementAt(3).value.execute(scope) - val a4 = this.elementAt(4).value.execute(scope) - val a5 = this.elementAt(5).value.execute(scope) + val a0 = this.elementAt(0).value.callOn(scope) + val a1 = this.elementAt(1).value.callOn(scope) + val a2 = this.elementAt(2).value.callOn(scope) + val a3 = this.elementAt(3).value.callOn(scope) + val a4 = this.elementAt(4).value.callOn(scope) + val a5 = this.elementAt(5).value.callOn(scope) Arguments(listOf(a0, a1, a2, a3, a4, a5), tailBlockMode) } 7 -> { - val a0 = this.elementAt(0).value.execute(scope) - val a1 = this.elementAt(1).value.execute(scope) - val a2 = this.elementAt(2).value.execute(scope) - val a3 = this.elementAt(3).value.execute(scope) - val a4 = this.elementAt(4).value.execute(scope) - val a5 = this.elementAt(5).value.execute(scope) - val a6 = this.elementAt(6).value.execute(scope) + val a0 = this.elementAt(0).value.callOn(scope) + val a1 = this.elementAt(1).value.callOn(scope) + val a2 = this.elementAt(2).value.callOn(scope) + val a3 = this.elementAt(3).value.callOn(scope) + val a4 = this.elementAt(4).value.callOn(scope) + val a5 = this.elementAt(5).value.callOn(scope) + val a6 = this.elementAt(6).value.callOn(scope) Arguments(listOf(a0, a1, a2, a3, a4, a5, a6), tailBlockMode) } 8 -> { - val a0 = this.elementAt(0).value.execute(scope) - val a1 = this.elementAt(1).value.execute(scope) - val a2 = this.elementAt(2).value.execute(scope) - val a3 = this.elementAt(3).value.execute(scope) - val a4 = this.elementAt(4).value.execute(scope) - val a5 = this.elementAt(5).value.execute(scope) - val a6 = this.elementAt(6).value.execute(scope) - val a7 = this.elementAt(7).value.execute(scope) + val a0 = this.elementAt(0).value.callOn(scope) + val a1 = this.elementAt(1).value.callOn(scope) + val a2 = this.elementAt(2).value.callOn(scope) + val a3 = this.elementAt(3).value.callOn(scope) + val a4 = this.elementAt(4).value.callOn(scope) + val a5 = this.elementAt(5).value.callOn(scope) + val a6 = this.elementAt(6).value.callOn(scope) + val a7 = this.elementAt(7).value.callOn(scope) Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7), tailBlockMode) } 9 -> if (PerfFlags.ARG_SMALL_ARITY_12) { - val a0 = this.elementAt(0).value.execute(scope) - val a1 = this.elementAt(1).value.execute(scope) - val a2 = this.elementAt(2).value.execute(scope) - val a3 = this.elementAt(3).value.execute(scope) - val a4 = this.elementAt(4).value.execute(scope) - val a5 = this.elementAt(5).value.execute(scope) - val a6 = this.elementAt(6).value.execute(scope) - val a7 = this.elementAt(7).value.execute(scope) - val a8 = this.elementAt(8).value.execute(scope) + val a0 = this.elementAt(0).value.callOn(scope) + val a1 = this.elementAt(1).value.callOn(scope) + val a2 = this.elementAt(2).value.callOn(scope) + val a3 = this.elementAt(3).value.callOn(scope) + val a4 = this.elementAt(4).value.callOn(scope) + val a5 = this.elementAt(5).value.callOn(scope) + val a6 = this.elementAt(6).value.callOn(scope) + val a7 = this.elementAt(7).value.callOn(scope) + val a8 = this.elementAt(8).value.callOn(scope) Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8), tailBlockMode) } else null 10 -> if (PerfFlags.ARG_SMALL_ARITY_12) { - val a0 = this.elementAt(0).value.execute(scope) - val a1 = this.elementAt(1).value.execute(scope) - val a2 = this.elementAt(2).value.execute(scope) - val a3 = this.elementAt(3).value.execute(scope) - val a4 = this.elementAt(4).value.execute(scope) - val a5 = this.elementAt(5).value.execute(scope) - val a6 = this.elementAt(6).value.execute(scope) - val a7 = this.elementAt(7).value.execute(scope) - val a8 = this.elementAt(8).value.execute(scope) - val a9 = this.elementAt(9).value.execute(scope) + val a0 = this.elementAt(0).value.callOn(scope) + val a1 = this.elementAt(1).value.callOn(scope) + val a2 = this.elementAt(2).value.callOn(scope) + val a3 = this.elementAt(3).value.callOn(scope) + val a4 = this.elementAt(4).value.callOn(scope) + val a5 = this.elementAt(5).value.callOn(scope) + val a6 = this.elementAt(6).value.callOn(scope) + val a7 = this.elementAt(7).value.callOn(scope) + val a8 = this.elementAt(8).value.callOn(scope) + val a9 = this.elementAt(9).value.callOn(scope) Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9), tailBlockMode) } else null 11 -> if (PerfFlags.ARG_SMALL_ARITY_12) { - val a0 = this.elementAt(0).value.execute(scope) - val a1 = this.elementAt(1).value.execute(scope) - val a2 = this.elementAt(2).value.execute(scope) - val a3 = this.elementAt(3).value.execute(scope) - val a4 = this.elementAt(4).value.execute(scope) - val a5 = this.elementAt(5).value.execute(scope) - val a6 = this.elementAt(6).value.execute(scope) - val a7 = this.elementAt(7).value.execute(scope) - val a8 = this.elementAt(8).value.execute(scope) - val a9 = this.elementAt(9).value.execute(scope) - val a10 = this.elementAt(10).value.execute(scope) + val a0 = this.elementAt(0).value.callOn(scope) + val a1 = this.elementAt(1).value.callOn(scope) + val a2 = this.elementAt(2).value.callOn(scope) + val a3 = this.elementAt(3).value.callOn(scope) + val a4 = this.elementAt(4).value.callOn(scope) + val a5 = this.elementAt(5).value.callOn(scope) + val a6 = this.elementAt(6).value.callOn(scope) + val a7 = this.elementAt(7).value.callOn(scope) + val a8 = this.elementAt(8).value.callOn(scope) + val a9 = this.elementAt(9).value.callOn(scope) + val a10 = this.elementAt(10).value.callOn(scope) Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10), tailBlockMode) } else null 12 -> if (PerfFlags.ARG_SMALL_ARITY_12) { - val a0 = this.elementAt(0).value.execute(scope) - val a1 = this.elementAt(1).value.execute(scope) - val a2 = this.elementAt(2).value.execute(scope) - val a3 = this.elementAt(3).value.execute(scope) - val a4 = this.elementAt(4).value.execute(scope) - val a5 = this.elementAt(5).value.execute(scope) - val a6 = this.elementAt(6).value.execute(scope) - val a7 = this.elementAt(7).value.execute(scope) - val a8 = this.elementAt(8).value.execute(scope) - val a9 = this.elementAt(9).value.execute(scope) - val a10 = this.elementAt(10).value.execute(scope) - val a11 = this.elementAt(11).value.execute(scope) + val a0 = this.elementAt(0).value.callOn(scope) + val a1 = this.elementAt(1).value.callOn(scope) + val a2 = this.elementAt(2).value.callOn(scope) + val a3 = this.elementAt(3).value.callOn(scope) + val a4 = this.elementAt(4).value.callOn(scope) + val a5 = this.elementAt(5).value.callOn(scope) + val a6 = this.elementAt(6).value.callOn(scope) + val a7 = this.elementAt(7).value.callOn(scope) + val a8 = this.elementAt(8).value.callOn(scope) + val a9 = this.elementAt(9).value.callOn(scope) + val a10 = this.elementAt(10).value.callOn(scope) + val a11 = this.elementAt(11).value.callOn(scope) Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11), tailBlockMode) } else null else -> null @@ -166,12 +166,12 @@ data class ParsedArgument( // Named argument if (named == null) named = linkedMapOf() if (named.containsKey(x.name)) scope.raiseIllegalArgument("argument '${x.name}' is already set") - val v = x.value.execute(scope) + val v = x.value.callOn(scope) named[x.name] = v namedSeen = true continue } - val value = x.value.execute(scope) + val value = x.value.callOn(scope) if (x.isSplat) { when { // IMPORTANT: handle ObjMap BEFORE generic Iterable to ensure map splats diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt index deb3c83..74c2395 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt @@ -32,32 +32,14 @@ class BlockStatement( if (slotPlan.isNotEmpty()) target.applySlotPlan(slotPlan) if (captureSlots.isNotEmpty()) { val captureRecords = scope.captureRecords - if (captureRecords != null) { - for (i in captureSlots.indices) { - val capture = captureSlots[i] - val rec = captureRecords.getOrNull(i) - ?: scope.raiseSymbolNotFound("capture ${capture.name} not found") - target.updateSlotFor(capture.name, rec) - } - } else { - val applyScope = scope as? ApplyScope - for (capture in captureSlots) { - // Interpreter-only capture resolution; bytecode paths must use captureRecords instead. - val rec = if (applyScope != null) { - applyScope.resolveCaptureRecord(capture.name) - ?: applyScope.callScope.resolveCaptureRecord(capture.name) - } else { - scope.resolveCaptureRecord(capture.name) - } - if (rec == null) { - if (scope.getSlotIndexOf(capture.name) == null && scope.getLocalRecordDirect(capture.name) == null) { - continue - } - (applyScope?.callScope ?: scope) - .raiseSymbolNotFound("symbol ${capture.name} not found") - } - target.updateSlotFor(capture.name, rec) - } + if (captureRecords == null) { + scope.raiseIllegalState("missing bytecode capture records") + } + for (i in captureSlots.indices) { + val capture = captureSlots[i] + val rec = captureRecords.getOrNull(i) + ?: scope.raiseSymbolNotFound("capture ${capture.name} not found") + target.updateSlotFor(capture.name, rec) } } return block.execute(target) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CaptureSlot.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CaptureSlot.kt index 263832b..cdc3f38 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CaptureSlot.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CaptureSlot.kt @@ -19,4 +19,6 @@ package net.sergeych.lyng data class CaptureSlot( val name: String, + val ownerScopeId: Int? = null, + val ownerSlot: Int? = null, ) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt index 82d42ca..f9c056a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt @@ -45,7 +45,12 @@ data class ClassDeclSpec( val initScope: List, ) -internal suspend fun executeClassDecl(scope: Scope, spec: ClassDeclSpec): Obj { +internal suspend fun executeClassDecl( + scope: Scope, + spec: ClassDeclSpec, + bodyCaptureRecords: List? = null, + bodyCaptureNames: List? = null +): Obj { if (spec.isObject) { val parentClasses = spec.baseSpecs.map { baseSpec -> val rec = scope[baseSpec.name] ?: throw ScriptError(spec.startPos, "unknown base class: ${baseSpec.name}") @@ -61,6 +66,10 @@ internal suspend fun executeClassDecl(scope: Scope, spec: ClassDeclSpec): Obj { } val classScope = scope.createChildScope(newThisObj = newClass) + if (!bodyCaptureRecords.isNullOrEmpty() && !bodyCaptureNames.isNullOrEmpty()) { + classScope.captureRecords = bodyCaptureRecords + classScope.captureNames = bodyCaptureNames + } classScope.currentClassCtx = newClass newClass.classScope = classScope classScope.addConst("object", newClass) @@ -133,6 +142,10 @@ internal suspend fun executeClassDecl(scope: Scope, spec: ClassDeclSpec): Obj { spec.declaredName?.let { scope.addItem(it, false, newClass) } val classScope = scope.createChildScope(newThisObj = newClass) + if (!bodyCaptureRecords.isNullOrEmpty() && !bodyCaptureNames.isNullOrEmpty()) { + classScope.captureRecords = bodyCaptureRecords + classScope.captureNames = bodyCaptureNames + } classScope.currentClassCtx = newClass newClass.classScope = classScope spec.bodyInit?.execute(classScope) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassInstanceDeclStatements.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassInstanceDeclStatements.kt new file mode 100644 index 0000000..8208c90 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassInstanceDeclStatements.kt @@ -0,0 +1,139 @@ +/* + * 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 + +import net.sergeych.lyng.obj.Obj +import net.sergeych.lyng.obj.ObjClass +import net.sergeych.lyng.obj.ObjProperty +import net.sergeych.lyng.obj.ObjRecord +import net.sergeych.lyng.obj.ObjVoid + +class ClassInstanceInitDeclStatement( + val initStatement: Statement, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + val cls = scope.thisObj as? ObjClass + ?: scope.raiseIllegalState("instance init declaration requires class scope") + cls.instanceInitializers += initStatement + return ObjVoid + } +} + +class ClassInstanceFieldDeclStatement( + val name: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + val isTransient: Boolean, + val fieldId: Int?, + val initStatement: Statement?, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + val cls = scope.thisObj as? ObjClass + ?: scope.raiseIllegalState("instance field declaration requires class scope") + cls.createField( + name, + net.sergeych.lyng.obj.ObjNull, + isMutable = isMutable, + visibility = visibility, + writeVisibility = writeVisibility, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient, + declaringClass = cls, + type = ObjRecord.Type.Field, + fieldId = fieldId + ) + if (!isAbstract) initStatement?.let { cls.instanceInitializers += it } + return ObjVoid + } +} + +class ClassInstancePropertyDeclStatement( + val name: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + val isTransient: Boolean, + val prop: ObjProperty, + val methodId: Int?, + val initStatement: Statement?, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + val cls = scope.thisObj as? ObjClass + ?: scope.raiseIllegalState("instance property declaration requires class scope") + cls.addProperty( + name = name, + visibility = visibility, + writeVisibility = writeVisibility, + declaringClass = cls, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + pos = pos, + prop = prop, + methodId = methodId + ) + if (!isAbstract) initStatement?.let { cls.instanceInitializers += it } + return ObjVoid + } +} + +class ClassInstanceDelegatedDeclStatement( + val name: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + val isTransient: Boolean, + val methodId: Int?, + val initStatement: Statement?, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + val cls = scope.thisObj as? ObjClass + ?: scope.raiseIllegalState("instance delegated declaration requires class scope") + cls.createField( + name, + net.sergeych.lyng.obj.ObjUnset, + isMutable = isMutable, + visibility = visibility, + writeVisibility = writeVisibility, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient, + declaringClass = cls, + type = ObjRecord.Type.Delegated, + methodId = methodId + ) + if (!isAbstract) initStatement?.let { cls.instanceInitializers += it } + return ObjVoid + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassStaticFieldInitStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassStaticFieldInitStatement.kt new file mode 100644 index 0000000..554da63 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassStaticFieldInitStatement.kt @@ -0,0 +1,101 @@ +/* + * 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 + +import net.sergeych.lyng.obj.Obj +import net.sergeych.lyng.obj.ObjClass +import net.sergeych.lyng.obj.ObjNull +import net.sergeych.lyng.obj.ObjRecord +import net.sergeych.lyng.obj.ObjString +import net.sergeych.lyng.obj.ObjUnset +import net.sergeych.lyng.obj.ObjVoid + +class ClassStaticFieldInitStatement( + val name: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val initializer: Statement?, + val isDelegated: Boolean, + val isTransient: Boolean, + private val startPos: Pos, +) : Statement() { + override val pos: Pos = startPos + + override suspend fun execute(scope: Scope): Obj { + val initValue = initializer?.execute(scope)?.byValueCopy() ?: ObjNull + val cls = scope.thisObj as? ObjClass + ?: scope.raiseIllegalState("static field init requires class scope") + return if (isDelegated) { + val accessTypeStr = if (isMutable) "Var" else "Val" + val accessType = ObjString(accessTypeStr) + val finalDelegate = try { + initValue.invokeInstanceMethod( + scope, + "bind", + Arguments(ObjString(name), accessType, scope.thisObj) + ) + } catch (_: Exception) { + initValue + } + cls.createClassField( + name, + ObjUnset, + isMutable, + visibility, + writeVisibility, + startPos, + isTransient = isTransient, + type = ObjRecord.Type.Delegated + ).apply { + delegate = finalDelegate + } + scope.addItem( + name, + isMutable, + ObjUnset, + visibility, + writeVisibility, + recordType = ObjRecord.Type.Delegated, + isTransient = isTransient + ).apply { + delegate = finalDelegate + } + finalDelegate + } else { + cls.createClassField( + name, + initValue, + isMutable, + visibility, + writeVisibility, + startPos, + isTransient = isTransient + ) + scope.addItem( + name, + isMutable, + initValue, + visibility, + writeVisibility, + recordType = ObjRecord.Type.Field, + isTransient = isTransient + ) + initValue + } + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt index 59ae862..0027d98 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt @@ -18,76 +18,12 @@ package net.sergeych.lyng import net.sergeych.lyng.obj.Obj -import net.sergeych.lyng.obj.ObjClass import net.sergeych.lyng.obj.ObjRecord -/** - * Scope that adds a "closure" to caller; most often it is used to apply class instance to caller scope. - * Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols - * from [closureScope] with proper precedence - */ -class ClosureScope( - val callScope: Scope, - val closureScope: Scope, - private val preferredThisType: String? = null -) : - // Important: use closureScope.thisObj so unqualified members (e.g., fields) resolve to the instance - // we captured, not to the caller's `this` (e.g., FlowBuilder). - Scope(callScope, callScope.args, thisObj = closureScope.thisObj) { - - init { - val desired = preferredThisType?.let { typeName -> - callScope.thisVariants.firstOrNull { it.objClass.className == typeName } - } - val primaryThis = closureScope.thisObj - val merged = ArrayList(callScope.thisVariants.size + closureScope.thisVariants.size + 1) - desired?.let { merged.add(it) } - merged.addAll(callScope.thisVariants) - merged.addAll(closureScope.thisVariants) - setThisVariants(primaryThis, merged) - // Preserve the lexical class context of the closure by default. This ensures that lambdas - // created inside a class method keep access to that class's private/protected members even - // when executed from within another object's method (e.g., Mutex.withLock), which may set - // its own currentClassCtx temporarily. If the closure has no class context, inherit caller's. - this.currentClassCtx = closureScope.currentClassCtx ?: callScope.currentClassCtx - } - - override fun get(name: String): ObjRecord? { - if (name == "this") return thisObj.asReadonly - - // 1. Current frame locals (parameters, local variables) - tryGetLocalRecord(this, name, currentClassCtx)?.let { return it } - - // 2. Lexical environment (captured locals from entire ancestry) - closureScope.chainLookupIgnoreClosure(name, followClosure = true, caller = currentClassCtx)?.let { return it } - - // 3. Lexical this members (captured receiver) - val receiver = thisObj - val effectiveClass = receiver as? ObjClass ?: receiver.objClass - for (cls in effectiveClass.mro) { - val rec = cls.members[name] ?: cls.classScope?.objects?.get(name) - if (rec != null && !rec.isAbstract) { - if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx, name)) { - return rec.copy(receiver = receiver) - } - } - } - // Finally, root object fallback - Obj.rootObjectType.members[name]?.let { rec -> - if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) { - return rec.copy(receiver = receiver) - } - } - - // 4. Call environment (caller locals, caller this, and global fallback) - return callScope.get(name) - } -} - /** * 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 [ClosureScope], it does not override name lookup. + * Unlike interpreter closure scopes, it does not override name lookup. */ class BytecodeClosureScope( val callScope: Scope, @@ -118,7 +54,7 @@ class ApplyScope(val callScope: Scope, val applied: Scope) : } override fun applyClosure(closure: Scope, preferredThisType: String?): Scope { - return ClosureScope(this, closure, preferredThisType) + return BytecodeClosureScope(this, closure, preferredThisType) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 2a09c88..ee96d0f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -839,13 +839,15 @@ class Compiler( val slotLoc = lookupSlotLocation(name, includeModule = false) if (slotLoc != null) { val classCtx = codeContexts.lastOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody - if (slotLoc.depth > 0 && - classCtx?.slotPlanId == slotLoc.scopeId && + if (classCtx?.slotPlanId == slotLoc.scopeId && classCtx.declaredMembers.contains(name) ) { - resolutionSink?.referenceMember(name, pos) - val ids = resolveMemberIds(name, pos, null) - return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName()) + val fieldId = classCtx.memberFieldIds[name] + val methodId = classCtx.memberMethodIds[name] + if (fieldId != null || methodId != null) { + resolutionSink?.referenceMember(name, pos) + return ImplicitThisMemberRef(name, pos, fieldId, methodId, currentImplicitThisTypeName()) + } } captureLocalRef(name, slotLoc, pos)?.let { ref -> resolutionSink?.reference(name, pos) @@ -1040,8 +1042,7 @@ class Compiler( class Settings( val miniAstSink: MiniAstSink? = null, val resolutionSink: ResolutionSink? = null, - val bytecodeFallbackReporter: ((Pos, String) -> Unit)? = null, - val useBytecodeStatements: Boolean = true, + val compileBytecode: Boolean = true, val strictSlotRefs: Boolean = true, val allowUnresolvedRefs: Boolean = false, val seedScope: Scope? = null, @@ -1051,7 +1052,7 @@ class Compiler( // Optional sink for mini-AST streaming (null by default, zero overhead when not used) private val miniSink: MiniAstSink? = settings.miniAstSink private val resolutionSink: ResolutionSink? = settings.resolutionSink - private val bytecodeFallbackReporter: ((Pos, String) -> Unit)? = settings.bytecodeFallbackReporter + private val compileBytecode: Boolean = settings.compileBytecode private val seedScope: Scope? = settings.seedScope private val useFastLocalRefs: Boolean = settings.useFastLocalRefs private var resolutionScriptDepth = 0 @@ -1545,11 +1546,8 @@ class Compiler( } while (true) val modulePlan = if (needsSlotPlan) slotPlanIndices(slotPlanStack.last()) else emptyMap() - val wrapScriptBytecode = useBytecodeStatements && - statements.isNotEmpty() && - codeContexts.lastOrNull() is CodeContext.Module && - resolutionScriptDepth == 1 && - statements.none { containsUnsupportedForBytecode(it) } + val isModuleScript = codeContexts.lastOrNull() is CodeContext.Module && resolutionScriptDepth == 1 + val wrapScriptBytecode = compileBytecode && isModuleScript val (finalStatements, moduleBytecode) = if (wrapScriptBytecode) { val unwrapped = statements.map { unwrapBytecodeDeep(it) } val block = InlineBlockStatement(unwrapped, start) @@ -1573,7 +1571,14 @@ class Compiler( statements to null } val moduleRefs = importedModules.map { ImportBindingSource.Module(it.scope.packageName, it.pos) } - Script(start, finalStatements, modulePlan, importBindings.toMap(), moduleRefs, moduleBytecode) + Script( + start, + finalStatements, + modulePlan, + importBindings.toMap(), + moduleRefs, + moduleBytecode + ) }.also { // Best-effort script end notification (use current position) miniSink?.onScriptEnd( @@ -1610,7 +1615,6 @@ class Compiler( private var lastAnnotation: (suspend (Scope, ObjString, Statement) -> Statement)? = null private var isTransientFlag: Boolean = false private var lastLabel: String? = null - private val useBytecodeStatements: Boolean = settings.useBytecodeStatements private val strictSlotRefs: Boolean = settings.strictSlotRefs private val allowUnresolvedRefs: Boolean = settings.allowUnresolvedRefs private val returnLabelStack = ArrayDeque>() @@ -1736,6 +1740,8 @@ class Compiler( if (plan.captureMap.containsKey(name)) return val capture = CaptureSlot( name = name, + ownerScopeId = slotLoc.scopeId, + ownerSlot = slotLoc.slot, ) plan.captureMap[name] = capture plan.captureOwners[name] = slotLoc @@ -1795,41 +1801,18 @@ class Compiler( ) } - private fun containsLoopControl(stmt: Statement, inLoop: Boolean = false): Boolean { - val target = if (stmt is BytecodeStatement) stmt.original else stmt - return when (target) { - is BreakStatement -> target.label != null || !inLoop - is ContinueStatement -> target.label != null || !inLoop - is IfStatement -> { - containsLoopControl(target.ifBody, inLoop) || - (target.elseBody?.let { containsLoopControl(it, inLoop) } ?: false) - } - is ForInStatement -> { - containsLoopControl(target.body, true) || - (target.elseStatement?.let { containsLoopControl(it, inLoop) } ?: false) - } - is WhileStatement -> { - containsLoopControl(target.body, true) || - (target.elseStatement?.let { containsLoopControl(it, inLoop) } ?: false) - } - is DoWhileStatement -> { - containsLoopControl(target.body, true) || - (target.elseStatement?.let { containsLoopControl(it, inLoop) } ?: false) - } - is BlockStatement -> target.statements().any { containsLoopControl(it, inLoop) } - is VarDeclStatement -> target.initializer?.let { containsLoopControl(it, inLoop) } ?: false - is ReturnStatement, is ThrowStatement, is ExpressionStatement -> false - else -> false - } - } - private fun knownClassMapForBytecode(): Map { val result = LinkedHashMap() fun addScope(scope: Scope?) { if (scope == null) return for ((name, rec) in scope.objects) { - val cls = rec.value as? ObjClass ?: continue - if (!result.containsKey(name)) result[name] = cls + val cls = rec.value as? ObjClass + if (cls != null) { + if (!result.containsKey(name)) result[name] = cls + continue + } + val obj = rec.value as? ObjInstance ?: continue + if (!result.containsKey(name)) result[name] = obj.objClass } } addScope(seedScope) @@ -1845,28 +1828,13 @@ class Compiler( } private fun wrapBytecode(stmt: Statement): Statement { - if (!useBytecodeStatements) return stmt - if (codeContexts.lastOrNull() is CodeContext.Module) { - return stmt - } - if (codeContexts.lastOrNull() is CodeContext.ClassBody) { - return stmt - } - if (codeContexts.any { it is CodeContext.Function }) { - return stmt - } - if (containsUnsupportedForBytecode(stmt)) { - return stmt - } + if (codeContexts.lastOrNull() is CodeContext.Module) return stmt + if (codeContexts.lastOrNull() is CodeContext.ClassBody) return stmt + if (codeContexts.any { it is CodeContext.Function }) return stmt if (stmt is FunctionDeclStatement || stmt is ClassDeclStatement || stmt is EnumDeclStatement - ) { - return stmt - } - if (containsLoopControl(stmt)) { - return stmt - } + ) return stmt val allowLocals = codeContexts.lastOrNull() !is CodeContext.ClassBody val returnLabels = returnLabelStack.lastOrNull() ?: emptySet() val allowedScopeNames = moduleSlotPlan()?.slots?.keys @@ -1889,6 +1857,35 @@ class Compiler( ) } + private fun wrapClassBodyBytecode(stmt: Statement, name: String): Statement { + val target = if (stmt is Script) InlineBlockStatement(stmt.statements(), stmt.pos) else stmt + val returnLabels = returnLabelStack.lastOrNull() ?: emptySet() + val allowedScopeNames = moduleSlotPlan()?.slots?.keys + return BytecodeStatement.wrap( + target, + name, + allowLocalSlots = true, + returnLabels = returnLabels, + rangeLocalNames = currentRangeParamNames, + allowedScopeNames = allowedScopeNames, + moduleScopeId = moduleSlotPlan()?.id, + slotTypeByScopeId = slotTypeByScopeId, + knownNameObjClass = knownClassMapForBytecode(), + knownObjectNames = objectDeclNames, + classFieldTypesByName = classFieldTypesByName, + enumEntriesByName = enumEntriesByName, + callableReturnTypeByScopeId = callableReturnTypeByScopeId, + callableReturnTypeByName = callableReturnTypeByName, + lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef + ) + } + + private fun wrapInstanceInitBytecode(stmt: Statement, name: String): BytecodeStatement { + val wrapped = wrapFunctionBytecode(stmt, name) + return (wrapped as? BytecodeStatement) + ?: throw ScriptError(stmt.pos, "non-bytecode init statement: $name") + } + private fun wrapFunctionBytecode( stmt: Statement, name: String, @@ -1896,7 +1893,6 @@ class Compiler( forcedLocalSlots: Map = emptyMap(), forcedLocalScopeId: Int? = null ): Statement { - if (!useBytecodeStatements) return stmt val returnLabels = returnLabelStack.lastOrNull() ?: emptySet() val allowedScopeNames = moduleSlotPlan()?.slots?.keys val knownNames = if (extraKnownNameObjClass.isEmpty()) { @@ -1928,132 +1924,6 @@ class Compiler( ) } - private fun containsUnsupportedForBytecode(stmt: Statement): Boolean { - val target = if (stmt is BytecodeStatement) stmt.original else stmt - return when (target) { - is ExpressionStatement -> containsUnsupportedRef(target.ref) - is IfStatement -> { - containsUnsupportedForBytecode(target.condition) || - containsUnsupportedForBytecode(target.ifBody) || - (target.elseBody?.let { containsUnsupportedForBytecode(it) } ?: false) - } - is ForInStatement -> { - containsUnsupportedForBytecode(target.source) || - containsUnsupportedForBytecode(target.body) || - (target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false) - } - is WhileStatement -> { - containsUnsupportedForBytecode(target.condition) || - containsUnsupportedForBytecode(target.body) || - (target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false) - } - is DoWhileStatement -> { - containsUnsupportedForBytecode(target.body) || - containsUnsupportedForBytecode(target.condition) || - (target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false) - } - is BlockStatement -> target.statements().any { containsUnsupportedForBytecode(it) } - is InlineBlockStatement -> target.statements().any { containsUnsupportedForBytecode(it) } - is VarDeclStatement -> target.initializer?.let { containsUnsupportedForBytecode(it) } ?: false - is DestructuringVarDeclStatement -> containsUnsupportedForBytecode(target.initializer) - is DelegatedVarDeclStatement -> containsUnsupportedForBytecode(target.initializer) - is BreakStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false - is ContinueStatement -> false - is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false - is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr) - is NopStatement -> false - is ExtensionPropertyDeclStatement -> false - is ClassDeclStatement -> false - is FunctionDeclStatement -> false - is EnumDeclStatement -> false - is TryStatement -> { - containsUnsupportedForBytecode(target.body) || - target.catches.any { containsUnsupportedForBytecode(it.block) } || - (target.finallyClause?.let { containsUnsupportedForBytecode(it) } ?: false) - } - is WhenStatement -> { - containsUnsupportedForBytecode(target.value) || - target.cases.any { case -> - case.conditions.any { cond -> - when (cond) { - is WhenEqualsCondition -> containsUnsupportedForBytecode(cond.expr) - is WhenInCondition -> containsUnsupportedForBytecode(cond.expr) - is WhenIsCondition -> false - else -> true - } - } || containsUnsupportedForBytecode(case.block) - } || - (target.elseCase?.let { containsUnsupportedForBytecode(it) } ?: false) - } - else -> true - } - } - - private fun containsUnsupportedRef(ref: ObjRef): Boolean { - return when (ref) { - is net.sergeych.lyng.obj.StatementRef -> containsUnsupportedForBytecode(ref.statement) - is BinaryOpRef -> containsUnsupportedRef(ref.left) || containsUnsupportedRef(ref.right) - is UnaryOpRef -> containsUnsupportedRef(ref.a) - is CastRef -> containsUnsupportedRef(ref.castValueRef()) || containsUnsupportedRef(ref.castTypeRef()) - is net.sergeych.lyng.obj.TypeDeclRef -> false - is AssignRef -> { - val target = ref.target as? LocalSlotRef - if (target != null) { - containsUnsupportedRef(ref.value) - } else { - containsUnsupportedRef(ref.target) || containsUnsupportedRef(ref.value) - } - } - is AssignOpRef -> containsUnsupportedRef(ref.target) || containsUnsupportedRef(ref.value) - is AssignIfNullRef -> containsUnsupportedRef(ref.target) || containsUnsupportedRef(ref.value) - is LogicalAndRef -> containsUnsupportedRef(ref.left()) || containsUnsupportedRef(ref.right()) - is LogicalOrRef -> containsUnsupportedRef(ref.left()) || containsUnsupportedRef(ref.right()) - is ConditionalRef -> - containsUnsupportedRef(ref.condition) || containsUnsupportedRef(ref.ifTrue) || containsUnsupportedRef(ref.ifFalse) - is ElvisRef -> containsUnsupportedRef(ref.left) || containsUnsupportedRef(ref.right) - is FieldRef -> { - val receiverClass = resolveReceiverClassForMember(ref.target) ?: return true - val hasMember = receiverClass.instanceFieldIdMap()[ref.name] != null || - receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] != null - if (!hasMember && !hasExtensionFor(receiverClass.className, ref.name)) return true - containsUnsupportedRef(ref.target) - } - is IndexRef -> containsUnsupportedRef(ref.targetRef) || containsUnsupportedRef(ref.indexRef) - is ListLiteralRef -> ref.entries().any { - when (it) { - is ListEntry.Element -> containsUnsupportedRef(it.ref) - is ListEntry.Spread -> containsUnsupportedRef(it.ref) - } - } - is MapLiteralRef -> ref.entries().any { - when (it) { - is net.sergeych.lyng.obj.MapLiteralEntry.Named -> containsUnsupportedRef(it.value) - is net.sergeych.lyng.obj.MapLiteralEntry.Spread -> containsUnsupportedRef(it.ref) - } - } - is CallRef -> { - val targetName = when (val target = ref.target) { - is LocalVarRef -> target.name - is LocalSlotRef -> target.name - else -> null - } - containsUnsupportedRef(ref.target) || ref.args.any { containsUnsupportedForBytecode(it.value) } - } - is MethodCallRef -> { - val receiverClass = resolveReceiverClassForMember(ref.receiver) ?: return true - val hasMember = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] != null - if (!hasMember && !hasExtensionFor(receiverClass.className, ref.name)) return true - containsUnsupportedRef(ref.receiver) || ref.args.any { containsUnsupportedForBytecode(it.value) } - } - is ImplicitThisMethodCallRef -> false - is QualifiedThisMethodSlotCallRef -> false - is QualifiedThisFieldSlotRef -> false - is ClassScopeMemberRef -> false - is ClassOperatorRef -> containsUnsupportedRef(ref.target) - else -> false - } - } - private fun containsDelegatedRefs(stmt: Statement): Boolean { val target = if (stmt is BytecodeStatement) stmt.original else stmt return when (target) { @@ -2124,18 +1994,39 @@ class Compiler( is IndexRef -> containsDelegatedRefs(ref.targetRef) || containsDelegatedRefs(ref.indexRef) is ListLiteralRef -> ref.entries().any { when (it) { - is ListEntry.Element -> containsDelegatedRefs(it.ref) - is ListEntry.Spread -> containsDelegatedRefs(it.ref) + is ListEntry.Element -> containsDelegatedRefs(it.ref as net.sergeych.lyng.obj.ObjRef) + is ListEntry.Spread -> containsDelegatedRefs(it.ref as net.sergeych.lyng.obj.ObjRef) } } is MapLiteralRef -> ref.entries().any { when (it) { - is net.sergeych.lyng.obj.MapLiteralEntry.Named -> containsDelegatedRefs(it.value) - is net.sergeych.lyng.obj.MapLiteralEntry.Spread -> containsDelegatedRefs(it.ref) + is net.sergeych.lyng.obj.MapLiteralEntry.Named -> { + val v = it.value + when (v) { + is Statement -> containsDelegatedRefs(stmt = v) + is net.sergeych.lyng.obj.ObjRef -> containsDelegatedRefs(ref = v) + else -> false + } + } + is net.sergeych.lyng.obj.MapLiteralEntry.Spread -> containsDelegatedRefs(it.ref as net.sergeych.lyng.obj.ObjRef) + } + } + is CallRef -> containsDelegatedRefs(ref.target) || ref.args.any { + val v = it.value + when (v) { + is Statement -> containsDelegatedRefs(v) + is net.sergeych.lyng.obj.ObjRef -> containsDelegatedRefs(v) + else -> false + } + } + is MethodCallRef -> containsDelegatedRefs(ref.receiver) || ref.args.any { + val v = it.value + when (v) { + is Statement -> containsDelegatedRefs(v) + is net.sergeych.lyng.obj.ObjRef -> containsDelegatedRefs(v) + else -> false } } - is CallRef -> containsDelegatedRefs(ref.target) || ref.args.any { containsDelegatedRefs(it.value) } - is MethodCallRef -> containsDelegatedRefs(ref.receiver) || ref.args.any { containsDelegatedRefs(it.value) } is StatementRef -> containsDelegatedRefs(ref.statement) else -> false } @@ -2354,8 +2245,8 @@ class Compiler( op.generate(opToken.pos, lvalue!!, rvalue) } if (opToken.type == Token.Type.ASSIGN) { - val ctx = codeContexts.lastOrNull() - if (ctx is CodeContext.ClassBody) { + val ctx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody + if (ctx != null) { val target = lvalue val name = when (target) { is LocalVarRef -> target.name @@ -2912,7 +2803,7 @@ class Compiler( paramKnownClasses[param.name] = cls } val returnLabels = label?.let { setOf(it) } ?: emptySet() - val fnStatements = if (useBytecodeStatements) { + val fnStatements = if (compileBytecode) { returnLabelStack.addLast(returnLabels) try { wrapFunctionBytecode( @@ -2922,13 +2813,6 @@ class Compiler( forcedLocalSlots = paramSlotPlanSnapshot, forcedLocalScopeId = paramSlotPlan.id ) - } catch (e: net.sergeych.lyng.bytecode.BytecodeCompileException) { - val pos = e.pos ?: body.pos - bytecodeFallbackReporter?.invoke( - pos, - "lambda bytecode compile failed: ${e.message}" - ) - throw e } finally { returnLabelStack.removeLast() } @@ -2945,123 +2829,61 @@ class Compiler( override fun bytecodeBody(): BytecodeStatement? = fnStatements as? BytecodeStatement override suspend fun execute(scope: Scope): Obj { - // TODO(bytecode): remove this fallback once lambdas are fully bytecode-backed (Step 26). - val usesBytecodeBody = fnStatements is BytecodeStatement && - (captureSlots.isEmpty() || captureRecords != null) - val useBytecodeClosure = captureRecords != null && usesBytecodeBody - val context = if (usesBytecodeBody) { - scope.applyClosureForBytecode(closureScope, preferredThisType = expectedReceiverType).also { - it.args = scope.args - } - } else { - scope.applyClosure(closureScope, preferredThisType = expectedReceiverType) + val context = scope.applyClosureForBytecode(closureScope, preferredThisType = expectedReceiverType).also { + it.args = scope.args } - if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot) if (captureSlots.isNotEmpty()) { if (captureRecords != null) { val records = captureRecords as List - if (useBytecodeClosure) { - context.captureRecords = records - context.captureNames = captureSlots.map { it.name } - } else { - for (i in captureSlots.indices) { - val rec = records.getOrNull(i) - ?: closureScope.raiseSymbolNotFound("capture ${captureSlots[i].name} not found") - context.updateSlotFor(captureSlots[i].name, rec) - } - } + context.captureRecords = records + context.captureNames = captureSlots.map { it.name } } else { - val moduleScope = if (context is ApplyScope) { - var s: Scope? = closureScope - while (s != null && s !is ModuleScope) { - s = s.parent - } - s as? ModuleScope - } else { - null - } - val usesBytecodeBody = fnStatements is BytecodeStatement - val resolvedRecords = if (usesBytecodeBody) ArrayList() else null - val resolvedNames = if (usesBytecodeBody) ArrayList() else null + val resolvedRecords = ArrayList(captureSlots.size) + val resolvedNames = ArrayList(captureSlots.size) for (capture in captureSlots) { - if (moduleScope != null && moduleScope.getLocalRecordDirect(capture.name) != null) { - continue - } - val rec = closureScope.resolveCaptureRecord(capture.name) - ?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found") - if (usesBytecodeBody) { - resolvedRecords?.add(rec) - resolvedNames?.add(capture.name) - } else { - context.updateSlotFor(capture.name, rec) - } - } - if (usesBytecodeBody) { - context.captureRecords = resolvedRecords - context.captureNames = resolvedNames + val rec = closureScope.chainLookupIgnoreClosure( + capture.name, + followClosure = true, + caller = context.currentClassCtx + ) ?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found") + resolvedRecords.add(rec) + resolvedNames.add(capture.name) } + context.captureRecords = resolvedRecords + context.captureNames = resolvedNames } } - if (usesBytecodeBody && fnStatements is BytecodeStatement) { - val bytecodeFn = fnStatements.bytecodeFunction() - val binder: suspend (net.sergeych.lyng.bytecode.CmdFrame, Arguments) -> Unit = { frame, arguments -> - val slotPlan = bytecodeFn.localSlotPlanByName() - if (argsDeclaration == null) { - val l = arguments.list - val itValue: Obj = when (l.size) { - 0 -> ObjVoid - 1 -> l[0] - else -> ObjList(l.toMutableList()) - } - val itSlot = slotPlan["it"] - if (itSlot != null) { - frame.frame.setObj(itSlot, itValue) - if (context.getLocalRecordDirect("it") == null) { - context.addItem( - "it", - false, - FrameSlotRef(frame.frame, itSlot), - recordType = ObjRecord.Type.Argument - ) - } - } else if (context.getLocalRecordDirect("it") == null) { - context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument) - } - } else { - argsDeclaration.assignToFrame( - context, - arguments, - slotPlan, - frame.frame, - defaultAccessType = AccessType.Val - ) - } - } - return try { - net.sergeych.lyng.bytecode.CmdVm().execute(bytecodeFn, context, scope.args, binder) - } catch (e: ReturnException) { - if (e.label == null || returnLabels.contains(e.label)) e.result - else throw e - } - } else { + val bytecodeBody = fnStatements as? BytecodeStatement + ?: scope.raiseIllegalState("non-bytecode lambda body encountered") + val bytecodeFn = bytecodeBody.bytecodeFunction() + val binder: suspend (net.sergeych.lyng.bytecode.CmdFrame, Arguments) -> Unit = { frame, arguments -> + val slotPlan = bytecodeFn.localSlotPlanByName() if (argsDeclaration == null) { - val l = scope.args.list + val l = arguments.list val itValue: Obj = when (l.size) { 0 -> ObjVoid 1 -> l[0] else -> ObjList(l.toMutableList()) } - context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument) + val itSlot = slotPlan["it"] + if (itSlot != null) { + frame.frame.setObj(itSlot, itValue) + } } else { - argsDeclaration.assignToContext(context, scope.args, defaultAccessType = AccessType.Val) - } - return try { - body.execute(context) - } catch (e: ReturnException) { - if (e.label == null || returnLabels.contains(e.label)) e.result - else throw e + argsDeclaration.assignToFrame( + context, + arguments, + slotPlan, + frame.frame + ) } } + return try { + net.sergeych.lyng.bytecode.CmdVm().execute(bytecodeFn, context, scope.args, binder) + } catch (e: ReturnException) { + if (e.label == null || returnLabels.contains(e.label)) e.result + else throw e + } } } val callable: Obj = if (wrapAsExtensionCallable) { @@ -3389,9 +3211,11 @@ class Compiler( miniType = miniType?.let { makeMiniTypeNullable(it) } } - var defaultValue: Statement? = null + var defaultValue: Obj? = null cc.ifNextIs(Token.Type.ASSIGN) { - defaultValue = parseExpression() + val expr = parseExpression() + ?: throw ScriptError(cc.current().pos, "Expected default value expression") + defaultValue = wrapBytecode(expr) } val isEllipsis = cc.skipTokenOfType(Token.Type.ELLIPSIS, isOptional = true) result += ArgsDeclaration.Item( @@ -4762,9 +4586,9 @@ class Compiler( ): Map { if (typeParams.isEmpty()) return emptyMap() val inferred = mutableMapOf() - for (param in argsDeclaration.params) { - val rec = context.getLocalRecordDirect(param.name) ?: continue - val direct = rec.value + val argValues = context.args.list + for ((index, param) in argsDeclaration.params.withIndex()) { + val direct = argValues.getOrNull(index) ?: continue val value = when (direct) { is FrameSlotRef -> direct.read() is RecordSlotRef -> direct.read() @@ -5452,34 +5276,24 @@ class Compiler( "init" -> { if (codeContexts.lastOrNull() is CodeContext.ClassBody && cc.peekNextNonWhitespace().type == Token.Type.LBRACE) { + val classCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody + val implicitThisType = classCtx?.name miniSink?.onEnterFunction(null) - val block = parseBlock() + val block = inCodeContext( + CodeContext.Function( + "", + implicitThisMembers = true, + implicitThisTypeName = implicitThisType + ) + ) { + parseBlock() + } miniSink?.onExitFunction(cc.currentPos()) lastParsedBlockRange?.let { range -> miniSink?.onInitDecl(MiniInitDecl(MiniRange(id.pos, range.end), id.pos)) } - val initPos = id.pos - val initStmt = object : Statement() { - override val pos: Pos = initPos - override suspend fun execute(scope: Scope): Obj { - val cls = scope.thisObj.objClass - val saved = scope.currentClassCtx - scope.currentClassCtx = cls - try { - block.execute(scope) - } finally { - scope.currentClassCtx = saved - } - return ObjVoid - } - } - object : Statement() { - override val pos: Pos = id.pos - override suspend fun execute(scope: Scope): Obj { - scope.currentClassCtx?.instanceInitializers?.add(initStmt) - return ObjVoid - } - } + val initStmt = wrapInstanceInitBytecode(block, "init@${id.pos}") + ClassInstanceInitDeclStatement(initStmt, id.pos) } else null } @@ -5929,14 +5743,22 @@ class Compiler( for ((index, entry) in names.withIndex()) { fieldIds[entry] = index + 1 } - val methodIds = mapOf("valueOf" to 0) + val baseMethodMax = net.sergeych.lyng.obj.EnumBase + .instanceMethodIdMap(includeAbstract = true) + .values + .maxOrNull() ?: -1 + val methodIds = linkedMapOf( + "valueOf" to baseMethodMax + 1, + "name" to baseMethodMax + 2, + "ordinal" to baseMethodMax + 3 + ) compileClassInfos[qualifiedName] = CompileClassInfo( name = qualifiedName, fieldIds = fieldIds, methodIds = methodIds, nextFieldId = fieldIds.size, nextMethodId = methodIds.size, - baseNames = listOf("Object") + baseNames = listOf("Enum") ) enumEntriesByName[qualifiedName] = names.toList() registerClassScopeFieldType(outerClassName, declaredName, qualifiedName) @@ -6109,6 +5931,7 @@ class Compiler( } val initScope = popInitScope() + val wrappedBodyInit = bodyInit?.let { wrapClassBodyBytecode(it, "object@$className") } val spec = ClassDeclSpec( declaredName = declaredName, @@ -6122,7 +5945,7 @@ class Compiler( baseSpecs = baseSpecs.map { ClassDeclBaseSpec(it.name, it.args) }, constructorArgs = null, constructorFieldIds = null, - bodyInit = bodyInit, + bodyInit = wrappedBodyInit, initScope = emptyList() ) return ClassDeclStatement(spec) @@ -6438,6 +6261,8 @@ class Compiler( } val initScope = popInitScope() + val wrappedBodyInit = bodyInit?.let { wrapClassBodyBytecode(it, "class@$qualifiedName") } + val wrappedInitScope = initScope.map { wrapClassBodyBytecode(it, "classInit@$qualifiedName") } // create class val className = qualifiedName @@ -6461,8 +6286,8 @@ class Compiler( baseSpecs = baseSpecs.map { ClassDeclBaseSpec(it.name, it.args) }, constructorArgs = constructorArgsDeclaration, constructorFieldIds = classInfo?.fieldIds, - bodyInit = bodyInit, - initScope = initScope + bodyInit = wrappedBodyInit, + initScope = wrappedInitScope ) ClassDeclStatement(spec) } @@ -6865,6 +6690,9 @@ class Compiler( if (extensionWrapperName != null) { declareLocalName(extensionWrapperName, isMutable = false) } + val declSlotPlan = if (declKind != SymbolKind.MEMBER) slotPlanStack.lastOrNull() else null + val declSlotIndex = declSlotPlan?.slots?.get(name)?.index + val declScopeId = declSlotPlan?.id val typeParamDecls = parseTypeParamList() val explicitTypeParams = typeParamDecls.map { it.name }.toSet() @@ -6909,6 +6737,9 @@ class Compiler( cc.nextNonWhitespace() // consume by isDelegated = true delegateExpression = parseExpression() ?: throw ScriptError(cc.current().pos, "Expected delegate expression") + if (compileBytecode) { + delegateExpression = wrapFunctionBytecode(delegateExpression!!, "delegate@$name") + } } if (!isDelegated && argsDeclaration.endTokenType != Token.Type.RPAREN) @@ -7004,13 +6835,11 @@ class Compiler( } returnLabelStack.addLast(returnLabels) try { - if (actualExtern) - object : Statement() { - override val pos: Pos = start - override suspend fun execute(scope: Scope): Obj { - scope.raiseError("extern function not provided: $name") - } - } + if (actualExtern) { + val msg = ObjString("extern function not provided: $name").asReadonly + val expr = ExpressionStatement(ConstRef(msg), start) + ThrowStatement(expr, start) + } else if (isAbstract || isDelegated) { null } else @@ -7037,9 +6866,7 @@ class Compiler( slotPlanStack.removeLast() resolutionSink?.exitScope(cc.currentPos()) } - val rawFnStatements = parsedFnStatements?.let { - if (containsUnsupportedForBytecode(it)) unwrapBytecodeDeep(it) else it - } + val rawFnStatements = parsedFnStatements?.let { unwrapBytecodeDeep(it) } val inferredReturnClass = returnTypeDecl?.let { resolveTypeDeclObjClass(it) } ?: inferReturnClassFromStatement(rawFnStatements) if (declKind != SymbolKind.MEMBER && inferredReturnClass != null) { @@ -7057,25 +6884,19 @@ class Compiler( forcedLocalSlots[name] = idx } val fnStatements = rawFnStatements?.let { stmt -> - if (useBytecodeStatements && - parentContext !is CodeContext.ClassBody && - !containsUnsupportedForBytecode(stmt) - ) { - val paramKnownClasses = mutableMapOf() - for (param in argsDeclaration.params) { - val cls = resolveTypeDeclObjClass(param.type) ?: continue - paramKnownClasses[param.name] = cls - } - wrapFunctionBytecode( - stmt, - name, - paramKnownClasses, - forcedLocalSlots = forcedLocalSlots, - forcedLocalScopeId = paramSlotPlan.id - ) - } else { - stmt + if (!compileBytecode) return@let stmt + val paramKnownClasses = mutableMapOf() + for (param in argsDeclaration.params) { + val cls = resolveTypeDeclObjClass(param.type) ?: continue + paramKnownClasses[param.name] = cls } + wrapFunctionBytecode( + stmt, + name, + paramKnownClasses, + forcedLocalSlots = forcedLocalSlots, + forcedLocalScopeId = paramSlotPlan.id + ) } // Capture and pop the local declarations count for this function val fnLocalDecls = localDeclCountStack.removeLastOrNull() ?: 0 @@ -7093,82 +6914,100 @@ class Compiler( // for local space. If there is no closure, we are in, say, class context where // the closure is in the class initialization and we needn't more: val context = closureBox.closure?.let { closure -> - if (fnStatements is BytecodeStatement) { - callerContext.applyClosureForBytecode(closure).also { - it.args = callerContext.args - } - } else { - ClosureScope(callerContext, closure) + callerContext.applyClosureForBytecode(closure).also { + it.args = callerContext.args } } ?: callerContext // Capacity hint: parameters + declared locals + small overhead val capacityHint = paramNames.size + fnLocalDecls + 4 context.hintLocalCapacity(capacityHint) - if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot) val captureBase = closureBox.captureContext ?: closureBox.closure - if (captureBase != null && captureSlots.isNotEmpty()) { - if (fnStatements is BytecodeStatement) { - captureBase.raiseIllegalState( - "bytecode function captures require frame slots; scope capture resolution disabled" - ) - } else { - for (capture in captureSlots) { - // Interpreter-only capture resolution; bytecode functions do not use resolveCaptureRecord. - val rec = captureBase.resolveCaptureRecord(capture.name) - ?: captureBase.raiseSymbolNotFound("symbol ${capture.name} not found") - context.updateSlotFor(capture.name, rec) - } - } - } - val bytecodeBody = (fnStatements as? BytecodeStatement) - if (bytecodeBody != null) { - val bytecodeFn = bytecodeBody.bytecodeFunction() - val binder: suspend (net.sergeych.lyng.bytecode.CmdFrame, Arguments) -> Unit = { frame, arguments -> - val slotPlan = bytecodeFn.localSlotPlanByName() - argsDeclaration.assignToFrame( - context, - arguments, - slotPlan, - frame.frame, - defaultAccessType = AccessType.Val - ) - val typeBindings = bindTypeParamsAtRuntime(context, argsDeclaration, mergedTypeParamDecls) - if (typeBindings.isNotEmpty()) { - for ((name, bound) in typeBindings) { - val slot = slotPlan[name] ?: continue - frame.frame.setObj(slot, bound) - } - } - if (extTypeName != null) { - context.thisObj = callerContext.thisObj - } - } - return try { - net.sergeych.lyng.bytecode.CmdVm().execute(bytecodeFn, context, callerContext.args, binder) - } catch (e: ReturnException) { - if (e.label == null || e.label == name || e.label == outerLabel) e.result - else throw e - } + ?: context.raiseIllegalState("non-bytecode function body encountered") + val bytecodeFn = bytecodeBody.bytecodeFunction() + val captureNames = if (captureSlots.isNotEmpty()) { + captureSlots.map { it.name } } else { - // load params from caller context - argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val) - bindTypeParamsAtRuntime(context, argsDeclaration, mergedTypeParamDecls) + val fn = bytecodeFn + val names = fn.localSlotNames + val captures = fn.localSlotCaptures + val ordered = LinkedHashSet() + for (i in names.indices) { + if (captures.getOrNull(i) != true) continue + val name = names[i] ?: continue + ordered.add(name) + } + ordered.toList() + } + val prebuiltCaptures = closureBox.captureRecords + if (prebuiltCaptures != null && captureNames.isNotEmpty()) { + context.captureRecords = prebuiltCaptures + context.captureNames = captureNames + } else if (captureBase != null && captureNames.isNotEmpty()) { + val resolvedRecords = ArrayList(captureNames.size) + for (name in captureNames) { + val rec = captureBase.chainLookupIgnoreClosure( + name, + followClosure = true, + caller = context.currentClassCtx + ) ?: captureBase.raiseSymbolNotFound("symbol $name not found") + resolvedRecords.add(rec) + } + context.captureRecords = resolvedRecords + context.captureNames = captureNames + } + val binder: suspend (net.sergeych.lyng.bytecode.CmdFrame, Arguments) -> Unit = { frame, arguments -> + val slotPlan = bytecodeFn.localSlotPlanByName() + argsDeclaration.assignToFrame( + context, + arguments, + slotPlan, + frame.frame + ) + val typeBindings = bindTypeParamsAtRuntime(context, argsDeclaration, mergedTypeParamDecls) + if (typeBindings.isNotEmpty()) { + for ((name, bound) in typeBindings) { + val slot = slotPlan[name] ?: continue + frame.frame.setObj(slot, bound) + } + } if (extTypeName != null) { context.thisObj = callerContext.thisObj } - return try { - fnStatements?.execute(context) ?: ObjVoid - } catch (e: ReturnException) { - if (e.label == null || e.label == name || e.label == outerLabel) e.result - else throw e - } + } + return try { + net.sergeych.lyng.bytecode.CmdVm().execute(bytecodeFn, context, callerContext.args, binder) + } catch (e: ReturnException) { + if (e.label == null || e.label == name || e.label == outerLabel) e.result + else throw e } } } cc.labels.remove(name) outerLabel?.let { cc.labels.remove(it) } + val parentIsClassBody = parentContext is CodeContext.ClassBody + val delegateInitStatement = if (isDelegated && parentIsClassBody && !isStatic && extTypeName == null) { + val className = currentEnclosingClassName() + ?: throw ScriptError(start, "delegated function outside class body") + val initExpr = delegateExpression ?: throw ScriptError(start, "delegated function missing delegate") + val storageName = "$className::$name" + val initStatement = InstanceDelegatedInitStatement( + storageName = storageName, + memberName = name, + isMutable = false, + visibility = visibility, + writeVisibility = null, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient, + accessTypeLabel = "Callable", + initializer = initExpr, + pos = start + ) + wrapInstanceInitBytecode(initStatement, "fnDelegate@${storageName}") + } else null val spec = FunctionDeclSpec( name = name, visibility = visibility, @@ -7179,16 +7018,19 @@ class Compiler( isTransient = isTransient, isDelegated = isDelegated, delegateExpression = delegateExpression, + delegateInitStatement = delegateInitStatement, extTypeName = extTypeName, extensionWrapperName = extensionWrapperName, memberMethodId = memberMethodId, actualExtern = actualExtern, - parentIsClassBody = parentContext is CodeContext.ClassBody, + parentIsClassBody = parentIsClassBody, externCallSignature = externCallSignature, annotation = annotation, fnBody = fnBody, closureBox = closureBox, captureSlots = captureSlots, + slotIndex = declSlotIndex, + scopeId = declScopeId, startPos = start ) val declaredFn = FunctionDeclStatement(spec) @@ -7864,7 +7706,7 @@ class Compiler( val effectiveEqToken = if (isProperty) null else eqToken // Register the local name at compile time so that subsequent identifiers can be emitted as fast locals - if (!isStatic) declareLocalName(name, isMutable) + if (!isStatic && declaringClassNameCaptured == null) declareLocalName(name, isMutable) val declKind = if (codeContexts.lastOrNull() is CodeContext.ClassBody) { SymbolKind.MEMBER } else { @@ -8071,69 +7913,27 @@ class Compiler( // when creating instance, but we need to execute it in the class initializer which // is missing as for now. Add it to the compiler context? - currentInitScope += object : Statement() { - override val pos: Pos = start - override suspend fun execute(scope: Scope): Obj { - val initValue = initialExpression?.execute(scope)?.byValueCopy() ?: ObjNull - if (isDelegate) { - val accessTypeStr = if (isMutable) "Var" else "Val" - val accessType = ObjString(accessTypeStr) - val finalDelegate = try { - initValue.invokeInstanceMethod( - scope, - "bind", - Arguments(ObjString(name), accessType, scope.thisObj) - ) - } catch (e: Exception) { - initValue - } - (scope.thisObj as ObjClass).createClassField( - name, - ObjUnset, - isMutable, - visibility, - null, - start, - isTransient = isTransient, - type = ObjRecord.Type.Delegated - ).apply { - delegate = finalDelegate - } - // Also expose in current init scope - scope.addItem( - name, - isMutable, - ObjUnset, - visibility, - null, - ObjRecord.Type.Delegated, - isTransient = isTransient - ).apply { - delegate = finalDelegate - } - } else { - (scope.thisObj as ObjClass).createClassField( - name, - initValue, - isMutable, - visibility, - null, - start, - isTransient = isTransient - ) - scope.addItem(name, isMutable, initValue, visibility, null, ObjRecord.Type.Field, isTransient = isTransient) - } - return ObjVoid - } - } + currentInitScope += ClassStaticFieldInitStatement( + name = name, + isMutable = isMutable, + visibility = visibility, + writeVisibility = null, + initializer = initialExpression, + isDelegated = isDelegate, + isTransient = isTransient, + startPos = start + ) return NopStatement } // Check for accessors if it is a class member - var getter: Statement? = null - var setter: Statement? = null + var getter: Obj? = null + var setter: Obj? = null var setterVisibility: Visibility? = null if (declaringClassNameCaptured != null || extTypeName != null) { + val classCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody + val accessorImplicitThisMembers = extTypeName != null || (classCtx != null && !isStatic) + val accessorImplicitThisTypeName = extTypeName ?: if (classCtx != null && !isStatic) classCtx.name else null while (true) { val t = cc.skipWsTokens() if (t.isId("get")) { @@ -8149,11 +7949,11 @@ class Compiler( inCodeContext( CodeContext.Function( "", - implicitThisMembers = extTypeName != null, - implicitThisTypeName = extTypeName + implicitThisMembers = accessorImplicitThisMembers, + implicitThisTypeName = accessorImplicitThisTypeName ) ) { - parseBlock() + wrapFunctionBytecode(parseBlock(), "") } } else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) { cc.skipWsTokens() @@ -8161,13 +7961,13 @@ class Compiler( inCodeContext( CodeContext.Function( "", - implicitThisMembers = extTypeName != null, - implicitThisTypeName = extTypeName + implicitThisMembers = accessorImplicitThisMembers, + implicitThisTypeName = accessorImplicitThisTypeName ) ) { val expr = parseExpression() ?: throw ScriptError(cc.current().pos, "Expected getter expression") - expr + wrapFunctionBytecode(expr, "") } } else { throw ScriptError(cc.current().pos, "Expected { or = after get()") @@ -8188,53 +7988,27 @@ class Compiler( val body = inCodeContext( CodeContext.Function( "", - implicitThisMembers = extTypeName != null, - implicitThisTypeName = extTypeName + implicitThisMembers = accessorImplicitThisMembers, + implicitThisTypeName = accessorImplicitThisTypeName ) ) { - parseBlockWithPredeclared(listOf(setArgName to true)) - } - object : Statement() { - override val pos: Pos = body.pos - override suspend fun execute(scope: Scope): Obj { - val value = scope.args.list.firstOrNull() ?: ObjNull - scope.addItem(setArgName, true, value, recordType = ObjRecord.Type.Argument) - val prev = scope.skipScopeCreation - scope.skipScopeCreation = true - return try { - body.execute(scope) - } finally { - scope.skipScopeCreation = prev - } - } + wrapFunctionBytecode(parseBlockWithPredeclared(listOf(setArgName to true)), "") } + PropertyAccessorStatement(body, setArgName, body.pos) } else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) { cc.skipWsTokens() cc.next() // consume '=' val expr = inCodeContext( CodeContext.Function( "", - implicitThisMembers = extTypeName != null, - implicitThisTypeName = extTypeName + implicitThisMembers = accessorImplicitThisMembers, + implicitThisTypeName = accessorImplicitThisTypeName ) ) { parseExpressionBlockWithPredeclared(listOf(setArgName to true)) } - val st = expr - object : Statement() { - override val pos: Pos = st.pos - override suspend fun execute(scope: Scope): Obj { - val value = scope.args.list.firstOrNull() ?: ObjNull - scope.addItem(setArgName, true, value, recordType = ObjRecord.Type.Argument) - val prev = scope.skipScopeCreation - scope.skipScopeCreation = true - return try { - st.execute(scope) - } finally { - scope.skipScopeCreation = prev - } - } - } + val st = wrapFunctionBytecode(expr, "") + PropertyAccessorStatement(st, setArgName, st.pos) } else { throw ScriptError(cc.current().pos, "Expected { or = after set(...)") } @@ -8256,52 +8030,28 @@ class Compiler( val body = inCodeContext( CodeContext.Function( "", - implicitThisMembers = extTypeName != null, - implicitThisTypeName = extTypeName + implicitThisMembers = accessorImplicitThisMembers, + implicitThisTypeName = accessorImplicitThisTypeName ) ) { parseBlockWithPredeclared(listOf(setArg.value to true)) } - object : Statement() { - override val pos: Pos = body.pos - override suspend fun execute(scope: Scope): Obj { - val value = scope.args.list.firstOrNull() ?: ObjNull - scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument) - val prev = scope.skipScopeCreation - scope.skipScopeCreation = true - return try { - body.execute(scope) - } finally { - scope.skipScopeCreation = prev - } - } - } + val wrapped = wrapFunctionBytecode(body, "") + PropertyAccessorStatement(wrapped, setArg.value, body.pos) } else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) { cc.skipWsTokens() cc.next() // consume '=' val st = inCodeContext( CodeContext.Function( "", - implicitThisMembers = extTypeName != null, - implicitThisTypeName = extTypeName + implicitThisMembers = accessorImplicitThisMembers, + implicitThisTypeName = accessorImplicitThisTypeName ) ) { parseExpressionBlockWithPredeclared(listOf(setArg.value to true)) } - object : Statement() { - override val pos: Pos = st.pos - override suspend fun execute(scope: Scope): Obj { - val value = scope.args.list.firstOrNull() ?: ObjNull - scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument) - val prev = scope.skipScopeCreation - scope.skipScopeCreation = true - return try { - st.execute(scope) - } finally { - scope.skipScopeCreation = prev - } - } - } + val wrapped = wrapFunctionBytecode(st, "") + PropertyAccessorStatement(wrapped, setArg.value, st.pos) } else { throw ScriptError(cc.current().pos, "Expected { or = after set(...)") } @@ -8353,257 +8103,154 @@ class Compiler( ) } + if (declaringClassNameCaptured != null) { + val storageName = "$declaringClassNameCaptured::$name" + if (isDelegate) { + val initExpr = initialExpression ?: throw ScriptError(start, "Delegate must be initialized") + val initStmt = if (!isAbstract) { + val accessType = if (isMutable) "Var" else "Val" + val initStatement = InstanceDelegatedInitStatement( + storageName = storageName, + memberName = name, + isMutable = isMutable, + visibility = visibility, + writeVisibility = setterVisibility, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient, + accessTypeLabel = accessType, + initializer = initExpr, + pos = start + ) + wrapInstanceInitBytecode(initStatement, "delegated@${storageName}") + } else null + return ClassInstanceDelegatedDeclStatement( + name = name, + isMutable = isMutable, + visibility = visibility, + writeVisibility = setterVisibility, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient, + methodId = memberMethodId, + initStatement = initStmt, + pos = start + ) + } + if (getter != null || setter != null) { + val prop = ObjProperty(name, getter, setter) + val initStmt = if (!isAbstract) { + val initStatement = InstancePropertyInitStatement( + storageName = storageName, + isMutable = isMutable, + visibility = visibility, + writeVisibility = setterVisibility, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient, + prop = prop, + pos = start + ) + wrapInstanceInitBytecode(initStatement, "property@${storageName}") + } else null + return ClassInstancePropertyDeclStatement( + name = name, + isMutable = isMutable, + visibility = visibility, + writeVisibility = setterVisibility, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient, + prop = prop, + methodId = memberMethodId, + initStatement = initStmt, + pos = start + ) + } + val isLateInitVal = !isMutable && initialExpression == null + val initStmt = if (!isAbstract) { + val initStatement = InstanceFieldInitStatement( + storageName = storageName, + isMutable = isMutable, + visibility = visibility, + writeVisibility = setterVisibility, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient, + isLateInitVal = isLateInitVal, + initializer = initialExpression, + pos = start + ) + wrapInstanceInitBytecode(initStatement, "field@${storageName}") + } else null + return ClassInstanceFieldDeclStatement( + name = name, + isMutable = isMutable, + visibility = visibility, + writeVisibility = setterVisibility, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient, + fieldId = memberFieldId, + initStatement = initStmt, + pos = start + ) + } + return object : Statement() { override val pos: Pos = start override suspend fun execute(context: Scope): Obj { - // In true class bodies (not inside a function), store fields under a class-qualified key to support MI collisions - // Do NOT infer declaring class from runtime thisObj here; only the compile-time captured - // ClassBody qualifies for class-field storage. Otherwise, this is a plain local. - isProperty = getter != null || setter != null - val declaringClassName = declaringClassNameCaptured - if (declaringClassName == null) { - if (context.containsLocal(name)) - throw ScriptError(start, "Variable $name is already defined") + if (context.containsLocal(name)) { + throw ScriptError(start, "Variable $name is already defined") } - // Register the local name so subsequent identifiers can be emitted as fast locals - if (!isStatic) declareLocalName(name, isMutable) - if (isDelegate) { - val declaringClassName = declaringClassNameCaptured - if (declaringClassName != null) { - val storageName = "$declaringClassName::$name" - val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance) - if (isClassScope) { - val cls = context.thisObj as ObjClass - cls.createField( - name, - ObjUnset, - isMutable, - visibility, - setterVisibility, - start, - isTransient = isTransient, - type = ObjRecord.Type.Delegated, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - methodId = memberMethodId - ) - cls.instanceInitializers += object : Statement() { - override val pos: Pos = start - override suspend fun execute(scp: Scope): Obj { - val initValue = initialExpression!!.execute(scp) - val accessTypeStr = if (isMutable) "Var" else "Val" - val accessType = ObjString(accessTypeStr) - val finalDelegate = try { - initValue.invokeInstanceMethod( - scp, - "bind", - Arguments(ObjString(name), accessType, scp.thisObj) - ) - } catch (e: Exception) { - initValue - } - scp.addItem( - storageName, isMutable, ObjUnset, visibility, setterVisibility, - recordType = ObjRecord.Type.Delegated, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - isTransient = isTransient - ).apply { - delegate = finalDelegate - } - return ObjVoid - } - } - return ObjVoid - } else { - val initValue = initialExpression!!.execute(context) - val accessTypeStr = if (isMutable) "Var" else "Val" - val accessType = ObjString(accessTypeStr) - val finalDelegate = try { - initValue.invokeInstanceMethod( - context, - "bind", - Arguments(ObjString(name), accessType, context.thisObj) - ) - } catch (e: Exception) { - initValue - } - val rec = context.addItem( - storageName, isMutable, ObjUnset, visibility, setterVisibility, - recordType = ObjRecord.Type.Delegated, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - isTransient = isTransient - ) - rec.delegate = finalDelegate - return finalDelegate - } - } else { - val initValue = initialExpression!!.execute(context) - val accessTypeStr = if (isMutable) "Var" else "Val" - val accessType = ObjString(accessTypeStr) - val finalDelegate = try { - initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, ObjNull)) - } catch (e: Exception) { - initValue - } - val rec = context.addItem( - name, isMutable, ObjUnset, visibility, setterVisibility, - recordType = ObjRecord.Type.Delegated, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - isTransient = isTransient - ) - rec.delegate = finalDelegate - return finalDelegate - } - } else if (getter != null || setter != null) { - val declaringClassName = declaringClassNameCaptured!! - val storageName = "$declaringClassName::$name" - val prop = ObjProperty(name, getter, setter) - - // If we are in class scope now (defining instance field), defer initialization to instance time - val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance) - if (isClassScope) { - val cls = context.thisObj as ObjClass - // Register in class members for reflection/MRO/satisfaction checks - if (isProperty) { - cls.addProperty( - name, - visibility = visibility, - writeVisibility = setterVisibility, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - pos = start, - prop = prop, - methodId = memberMethodId - ) - } else { - cls.createField( - name, - ObjNull, - isMutable = isMutable, - visibility = visibility, - writeVisibility = setterVisibility, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - isTransient = isTransient, - type = ObjRecord.Type.Field, - fieldId = memberFieldId - ) - } - - // Register the property/field initialization thunk - if (!isAbstract) { - cls.instanceInitializers += object : Statement() { - override val pos: Pos = start - override suspend fun execute(scp: Scope): Obj { - scp.addItem( - storageName, - isMutable, - prop, - visibility, - setterVisibility, - recordType = ObjRecord.Type.Property, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride - ) - return ObjVoid - } - } - } - return ObjVoid - } else { - // We are in instance scope already: perform initialization immediately - context.addItem( - storageName, isMutable, prop, visibility, setterVisibility, - recordType = ObjRecord.Type.Property, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - isTransient = isTransient - ) - return prop - } - } else { - val isLateInitVal = !isMutable && initialExpression == null - if (declaringClassName != null && !isStatic) { - val storageName = "$declaringClassName::$name" - // If we are in class scope now (defining instance field), defer initialization to instance time - val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance) - if (isClassScope) { - val cls = context.thisObj as ObjClass - // Register in class members for reflection/MRO/satisfaction checks - cls.createField( - name, - ObjNull, - isMutable = isMutable, - visibility = visibility, - writeVisibility = setterVisibility, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - pos = start, - isTransient = isTransient, - type = ObjRecord.Type.Field, - fieldId = memberFieldId - ) - - // Defer: at instance construction, evaluate initializer in instance scope and store under mangled name - if (!isAbstract) { - val initStmt = object : Statement() { - override val pos: Pos = start - override suspend fun execute(scp: Scope): Obj { - val initValue = - initialExpression?.execute(scp)?.byValueCopy() - ?: if (isLateInitVal) ObjUnset else ObjNull - // Preserve mutability of declaration: do NOT use addOrUpdateItem here, as it creates mutable records - scp.addItem( - storageName, isMutable, initValue, visibility, setterVisibility, - recordType = ObjRecord.Type.Field, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - isTransient = isTransient - ) - return ObjVoid - } - } - cls.instanceInitializers += initStmt - } - return ObjVoid - } else { - // We are in instance scope already: perform initialization immediately - val initValue = - initialExpression?.execute(context)?.byValueCopy() - ?: if (isLateInitVal) ObjUnset else ObjNull - // Preserve mutability of declaration: create record with correct mutability - context.addItem( - storageName, isMutable, initValue, visibility, setterVisibility, - recordType = ObjRecord.Type.Field, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - isTransient = isTransient - ) - return initValue - } - } else { - // Not in class body: regular local/var declaration - val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull - context.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Other, isTransient = isTransient) - return initValue + val initValue = initialExpression!!.execute(context) + val accessTypeStr = if (isMutable) "Var" else "Val" + val accessType = ObjString(accessTypeStr) + val finalDelegate = try { + initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, ObjNull)) + } catch (_: Exception) { + initValue } + val rec = context.addItem( + name, isMutable, ObjUnset, visibility, setterVisibility, + recordType = ObjRecord.Type.Delegated, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient + ) + rec.delegate = finalDelegate + return finalDelegate } + + if (getter != null || setter != null) { + val prop = ObjProperty(name, getter, setter) + context.addItem( + name, isMutable, prop, visibility, setterVisibility, + recordType = ObjRecord.Type.Property, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient + ) + return prop + } + + val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull + context.addItem( + name, isMutable, initValue, visibility, + recordType = ObjRecord.Type.Other, + isTransient = isTransient + ) + return initValue } } } finally { @@ -8653,8 +8300,7 @@ class Compiler( importManager: ImportProvider, miniSink: MiniAstSink? = null, resolutionSink: ResolutionSink? = null, - bytecodeFallbackReporter: ((Pos, String) -> Unit)? = null, - useBytecodeStatements: Boolean = true, + compileBytecode: Boolean = true, strictSlotRefs: Boolean = true, allowUnresolvedRefs: Boolean = false, seedScope: Scope? = null, @@ -8666,8 +8312,7 @@ class Compiler( Settings( miniAstSink = miniSink, resolutionSink = resolutionSink, - bytecodeFallbackReporter = bytecodeFallbackReporter, - useBytecodeStatements = useBytecodeStatements, + compileBytecode = compileBytecode, strictSlotRefs = strictSlotRefs, allowUnresolvedRefs = allowUnresolvedRefs, seedScope = seedScope, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt index bad5dc5..3202bc8 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt @@ -28,6 +28,7 @@ import net.sergeych.lyng.obj.ObjVoid class FunctionClosureBox( var closure: Scope? = null, var captureContext: Scope? = null, + var captureRecords: List? = null, ) data class FunctionDeclSpec( @@ -40,6 +41,7 @@ data class FunctionDeclSpec( val isTransient: Boolean, val isDelegated: Boolean, val delegateExpression: Statement?, + val delegateInitStatement: Statement?, val extTypeName: String?, val extensionWrapperName: String?, val memberMethodId: Int?, @@ -50,10 +52,17 @@ data class FunctionDeclSpec( val fnBody: Statement, val closureBox: FunctionClosureBox, val captureSlots: List, + val slotIndex: Int?, + val scopeId: Int?, val startPos: Pos, ) -internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec): Obj { +internal suspend fun executeFunctionDecl( + scope: Scope, + spec: FunctionDeclSpec, + captureRecords: List? = null +): Obj { + spec.closureBox.captureRecords = captureRecords if (spec.actualExtern && spec.extTypeName == null && !spec.parentIsClassBody) { val existing = scope.get(spec.name) if (existing != null) { @@ -88,8 +97,8 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec): delegate = finalDelegate } ) - return ObjVoid - } + return ObjVoid +} val th = scope.thisObj if (spec.isStatic) { @@ -117,7 +126,6 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec): } } else if (th is ObjClass) { val cls: ObjClass = th - val storageName = "${cls.className}::${spec.name}" cls.createField( spec.name, ObjUnset, @@ -133,33 +141,9 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec): type = ObjRecord.Type.Delegated, methodId = spec.memberMethodId ) - cls.instanceInitializers += object : Statement() { - override val pos: Pos = spec.startPos - override suspend fun execute(scp: Scope): Obj { - val accessType2 = ObjString("Callable") - val initValue2 = delegateExpr.execute(scp) - val finalDelegate2 = try { - initValue2.invokeInstanceMethod(scp, "bind", Arguments(ObjString(spec.name), accessType2, scp.thisObj)) - } catch (e: Exception) { - initValue2 - } - scp.addItem( - storageName, - false, - ObjUnset, - spec.visibility, - null, - recordType = ObjRecord.Type.Delegated, - isAbstract = spec.isAbstract, - isClosed = spec.isClosed, - isOverride = spec.isOverride, - isTransient = spec.isTransient - ).apply { - delegate = finalDelegate2 - } - return ObjVoid - } - } + val initStmt = spec.delegateInitStatement + ?: scope.raiseIllegalState("missing delegated init statement for ${spec.name}") + cls.instanceInitializers += initStmt } else { scope.addItem( spec.name, @@ -178,7 +162,7 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec): if (spec.isStatic || !spec.parentIsClassBody) { spec.closureBox.closure = scope } - if (spec.parentIsClassBody && spec.captureSlots.isNotEmpty()) { + if (spec.parentIsClassBody) { spec.closureBox.captureContext = scope } @@ -188,25 +172,18 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec): spec.extTypeName?.let { typeName -> val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found") if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance") - val stmt = object : Statement() { - override val pos: Pos = spec.startPos - override suspend fun execute(scope: Scope): Obj { - val result = (scope.thisObj as? ObjInstance)?.let { i -> - val execScope = if (compiledFnBody is net.sergeych.lyng.bytecode.BytecodeStatement) { - scope.applyClosureForBytecode(i.instanceScope).also { - it.args = scope.args - } - } else { - ClosureScope(scope, i.instanceScope) - } - compiledFnBody.execute(execScope) - } ?: compiledFnBody.execute(scope.thisObj.autoInstanceScope(scope)) - return result - } + val callable = net.sergeych.lyng.obj.ObjNativeCallable { + val result = (thisObj as? ObjInstance)?.let { i -> + val execScope = applyClosureForBytecode(i.instanceScope).also { + it.args = args + } + compiledFnBody.execute(execScope) + } ?: compiledFnBody.execute(thisObj.autoInstanceScope(this)) + result } - scope.addExtension(type, spec.name, ObjRecord(stmt, isMutable = false, visibility = spec.visibility, declaringClass = null)) + scope.addExtension(type, spec.name, ObjRecord(callable, isMutable = false, visibility = spec.visibility, declaringClass = null)) val wrapperName = spec.extensionWrapperName ?: extensionCallableName(typeName, spec.name) - val wrapper = ObjExtensionMethodCallable(spec.name, stmt) + val wrapper = ObjExtensionMethodCallable(spec.name, callable) scope.addItem(wrapperName, false, wrapper, spec.visibility, recordType = ObjRecord.Type.Fun) } ?: run { val th = scope.thisObj @@ -238,7 +215,8 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec): this.currentClassCtx = savedCtx } } - scope.addItem(spec.name, false, compiledFnBody, spec.visibility, callSignature = spec.externCallSignature) + val memberValue = cls.members[spec.name]?.value ?: compiledFnBody + scope.addItem(spec.name, false, memberValue, spec.visibility, callSignature = spec.externCallSignature) compiledFnBody } else { scope.addItem(spec.name, false, compiledFnBody, spec.visibility, callSignature = spec.externCallSignature) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/InstanceInitStatements.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/InstanceInitStatements.kt new file mode 100644 index 0000000..eeedc0a --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/InstanceInitStatements.kt @@ -0,0 +1,130 @@ +/* + * 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 + +import net.sergeych.lyng.obj.Obj +import net.sergeych.lyng.obj.ObjNull +import net.sergeych.lyng.obj.ObjProperty +import net.sergeych.lyng.obj.ObjRecord +import net.sergeych.lyng.obj.ObjString +import net.sergeych.lyng.obj.ObjUnset +import net.sergeych.lyng.obj.ObjVoid + +class InstanceFieldInitStatement( + val storageName: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + val isTransient: Boolean, + val isLateInitVal: Boolean, + val initializer: Statement?, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + val initValue = initializer?.execute(scope)?.byValueCopy() + ?: if (isLateInitVal) ObjUnset else ObjNull + scope.addItem( + storageName, + isMutable, + initValue, + visibility, + writeVisibility, + recordType = ObjRecord.Type.Field, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient + ) + return ObjVoid + } +} + +class InstancePropertyInitStatement( + val storageName: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + val isTransient: Boolean, + val prop: ObjProperty, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + scope.addItem( + storageName, + isMutable, + prop, + visibility, + writeVisibility, + recordType = ObjRecord.Type.Property, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient + ) + return ObjVoid + } +} + +class InstanceDelegatedInitStatement( + val storageName: String, + val memberName: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + val isTransient: Boolean, + val accessTypeLabel: String, + val initializer: Statement, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + val initValue = initializer.execute(scope) + val accessType = ObjString(accessTypeLabel) + val finalDelegate = try { + initValue.invokeInstanceMethod( + scope, + "bind", + Arguments(ObjString(memberName), accessType, scope.thisObj) + ) + } catch (_: Exception) { + initValue + } + scope.addItem( + storageName, + isMutable, + ObjUnset, + visibility, + writeVisibility, + recordType = ObjRecord.Type.Delegated, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient + ).apply { + delegate = finalDelegate + } + return ObjVoid + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/PropertyAccessorStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/PropertyAccessorStatement.kt new file mode 100644 index 0000000..1dca112 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/PropertyAccessorStatement.kt @@ -0,0 +1,60 @@ +/* + * 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 + +import net.sergeych.lyng.bytecode.CmdFrame +import net.sergeych.lyng.bytecode.CmdVm +import net.sergeych.lyng.obj.Obj +import net.sergeych.lyng.obj.ObjNull +import net.sergeych.lyng.obj.ObjRecord + +class PropertyAccessorStatement( + val body: Statement, + val argName: String?, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + if (argName != null) { + val value = scope.args.list.firstOrNull() ?: ObjNull + val prev = scope.skipScopeCreation + scope.skipScopeCreation = true + return try { + if (body is net.sergeych.lyng.bytecode.BytecodeStatement) { + val fn = body.bytecodeFunction() + val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments -> + val slotPlan = fn.localSlotPlanByName() + val slotIndex = slotPlan[argName] + val argValue = arguments.list.firstOrNull() ?: ObjNull + if (slotIndex != null) { + frame.frame.setObj(slotIndex, argValue) + } else if (scope.getLocalRecordDirect(argName) == null) { + scope.addItem(argName, true, argValue, recordType = ObjRecord.Type.Argument) + } + } + scope.pos = pos + CmdVm().execute(fn, scope, scope.args, binder) + } else { + scope.addItem(argName, true, value, recordType = ObjRecord.Type.Argument) + body.execute(scope) + } + } finally { + scope.skipScopeCreation = prev + } + } + return body.execute(scope) + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index 1d7b233..9465dbf 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -39,7 +39,7 @@ fun nextFrameId(): Long = FrameIdGen.nextId() * * There are special types of scopes: * - * - [ClosureScope] - scope used to apply a closure to some thisObj scope + * - [BytecodeClosureScope] - scope used to apply a closure to some thisObj scope */ open class Scope( var parent: Scope?, @@ -76,11 +76,19 @@ open class Scope( internal fun setThisVariants(primary: Obj, extras: List) { thisObj = primary - thisVariants.clear() - thisVariants.add(primary) - for (obj in extras) { - if (obj !== primary && !thisVariants.contains(obj)) { - thisVariants.add(obj) + val extrasSnapshot = when { + extras.isEmpty() -> emptyList() + extras === thisVariants -> extras.toList() + extras is MutableList<*> -> synchronized(extras) { extras.toList() } + else -> extras.toList() + } + synchronized(thisVariants) { + thisVariants.clear() + thisVariants.add(primary) + for (obj in extrasSnapshot) { + if (obj !== primary && !thisVariants.contains(obj)) { + thisVariants.add(obj) + } } } } @@ -98,7 +106,7 @@ open class Scope( for (cls in receiverClass.mro) { s.extensions[cls]?.get(name)?.let { return it } } - if (s is ClosureScope) { + if (s is BytecodeClosureScope) { s.closureScope.findExtension(receiverClass, name)?.let { return it } } s = s.parent @@ -122,7 +130,7 @@ open class Scope( /** * Internal lookup helpers that deliberately avoid invoking overridden `get` implementations - * (notably in ClosureScope) to prevent accidental ping-pong and infinite recursion across + * (notably in BytecodeClosureScope) to prevent accidental ping-pong and infinite recursion across * intertwined closure frames. They traverse the plain parent chain and consult only locals * and bindings of each frame. Instance/class member fallback must be decided by the caller. */ @@ -165,23 +173,16 @@ open class Scope( val effectiveCaller = caller ?: currentClassCtx while (s != null && hops++ < 1024) { tryGetLocalRecord(s, name, effectiveCaller)?.let { return it } - s = if (followClosure && s is ClosureScope) s.closureScope else s.parent + s = if (followClosure && s is BytecodeClosureScope) s.closureScope else s.parent } return null } - internal fun resolveCaptureRecord(name: String): ObjRecord? { - if (captureRecords != null) { - raiseIllegalState("resolveCaptureRecord is interpreter-only; bytecode captures use captureRecords") - } - return chainLookupIgnoreClosure(name, followClosure = true, caller = currentClassCtx) - } - /** * Perform base Scope.get semantics for this frame without delegating into parent.get * virtual dispatch. This checks: * - locals/bindings in this frame - * - walks raw parent chain for locals/bindings (ignoring ClosureScope-specific overrides) + * - walks raw parent chain for locals/bindings (ignoring BytecodeClosureScope-specific overrides) * - finally falls back to this frame's `thisObj` instance/class members */ internal fun baseGetIgnoreClosure(name: String): ObjRecord? { @@ -211,7 +212,7 @@ open class Scope( * - locals/bindings of each frame * - then instance/class members of each frame's `thisObj`. * This completely avoids invoking overridden `get` implementations, preventing - * ping-pong recursion between `ClosureScope` frames. + * ping-pong recursion between `BytecodeClosureScope` frames. */ internal fun chainLookupWithMembers(name: String, caller: net.sergeych.lyng.obj.ObjClass? = currentClassCtx, followClosure: Boolean = false): ObjRecord? { var s: Scope? = this @@ -228,7 +229,7 @@ open class Scope( } else return rec } } - s = if (followClosure && s is ClosureScope) s.closureScope else s.parent + s = if (followClosure && s is BytecodeClosureScope) s.closureScope else s.parent } return null } @@ -635,11 +636,6 @@ open class Scope( it.value = value // keep local binding index consistent within the frame localBindings[name] = it - // If we are a ClosureScope, mirror binding into the caller frame to keep it discoverable - // across suspension when resumed on the call frame - if (this is ClosureScope) { - callScope.localBindings[name] = it - } bumpClassLayoutIfNeeded(name, value, recordType) it } ?: addItem(name, true, value, visibility, writeVisibility, recordType, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride) @@ -694,19 +690,6 @@ open class Scope( } // Index this binding within the current frame to help resolve locals across suspension localBindings[name] = rec - // If we are a ClosureScope, mirror binding into the caller frame to keep it discoverable - // across suspension when resumed on the call frame - if (this is ClosureScope) { - callScope.localBindings[name] = rec - // Additionally, expose the binding in caller's objects and slot map so identifier - // resolution after suspension can still find it even if the active scope is a child - // of the callScope (e.g., due to internal withChildFrame usage). - // This keeps visibility within the method body but prevents leaking outside the caller frame. - callScope.objects[name] = rec - if (callScope.getSlotIndexOf(name) == null) { - callScope.allocateSlotFor(name, rec) - } - } // Map to a slot for fast local access (ensure consistency) if (nameToSlot.isEmpty()) { allocateSlotFor(name, rec) @@ -751,12 +734,7 @@ open class Scope( } fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend Scope.() -> Obj) { - val newFn = object : Statement() { - override val pos: Pos = Pos.builtIn - - override suspend fun execute(scope: Scope): Obj = scope.fn() - - } + val newFn = net.sergeych.lyng.obj.ObjNativeCallable { fn() } for (name in names) { addItem( name, @@ -834,7 +812,7 @@ open class Scope( } open fun applyClosure(closure: Scope, preferredThisType: String? = null): Scope = - ClosureScope(this, closure, preferredThisType) + BytecodeClosureScope(this, closure, preferredThisType) internal fun applyClosureForBytecode(closure: Scope, preferredThisType: String? = null): Scope { return BytecodeClosureScope(this, closure, preferredThisType) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index 351cd98..d6f4f3e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -40,6 +40,7 @@ class Script( private val moduleBytecode: CmdFunction? = null, // private val catchReturn: Boolean = false, ) : Statement() { + fun statements(): List = statements override suspend fun execute(scope: Scope): Obj { scope.pos = pos @@ -86,11 +87,10 @@ class Script( moduleBytecode?.let { fn -> return CmdVm().execute(fn, scope, scope.args) } - var lastResult: Obj = ObjVoid - for (s in statements) { - lastResult = s.execute(scope) + if (statements.isNotEmpty()) { + scope.raiseIllegalState("interpreter execution is not supported; missing module bytecode") } - return lastResult + return ObjVoid } private suspend fun seedModuleSlots(scope: Scope) { @@ -332,7 +332,7 @@ class Script( addVoidFn("assert") { val cond = requiredArg(0) val message = if (args.size > 1) - ": " + (args[1] as Statement).execute(this).toString(this).value + ": " + (args[1] as Obj).callOn(this).toString(this).value else "" if (!cond.value == true) raiseError(ObjAssertionFailedException(this, "Assertion failed$message")) @@ -385,23 +385,23 @@ class Script( will be accepted. """.trimIndent() ) { - val code: Statement + val code: Obj val expectedClass: ObjClass? when (args.size) { 1 -> { - code = requiredArg(0) + code = requiredArg(0) expectedClass = null } 2 -> { - code = requiredArg(1) + code = requiredArg(1) expectedClass = requiredArg(0) } else -> raiseIllegalArgument("Expected 1 or 2 arguments, got ${args.size}") } val result = try { - code.execute(this) + code.callOn(this) null } catch (e: ExecutionError) { e.errorObject @@ -441,7 +441,7 @@ class Script( val condition = requiredArg(0) if (!condition.value) { var message = args.list.getOrNull(1) - if (message is Statement) message = message.execute(this) + if (message is Obj && message.objClass == Statement.type) message = message.callOn(this) raiseIllegalArgument(message?.toString() ?: "requirement not met") } ObjVoid @@ -450,7 +450,7 @@ class Script( val condition = requiredArg(0) if (!condition.value) { var message = args.list.getOrNull(1) - if (message is Statement) message = message.execute(this) + if (message is Obj && message.objClass == Statement.type) message = message.callOn(this) raiseIllegalState(message?.toString() ?: "check failed") } ObjVoid @@ -460,27 +460,23 @@ class Script( ObjVoid } addFn("run") { - requireOnlyArg().execute(this) + requireOnlyArg().callOn(this) } addFn("cached") { - val builder = requireOnlyArg() + val builder = requireOnlyArg() val capturedScope = this var calculated = false var cachedValue: Obj = ObjVoid - val thunk = object : Statement() { - override val pos: Pos = Pos.builtIn - override suspend fun execute(scope: Scope): Obj { - if (!calculated) { - cachedValue = builder.execute(capturedScope) - calculated = true - } - return cachedValue + ObjNativeCallable { + if (!calculated) { + cachedValue = builder.callOn(capturedScope) + calculated = true } + cachedValue } - thunk } addFn("lazy") { - val builder = requireOnlyArg() + val builder = requireOnlyArg() ObjLazyDelegate(builder, this) } addVoidFn("delay") { @@ -527,9 +523,9 @@ class Script( addConst("MapEntry", ObjMapEntry.type) addFn("launch") { - val callable = requireOnlyArg() + val callable = requireOnlyArg() ObjDeferred(globalDefer { - callable.execute(this@addFn) + callable.callOn(this@addFn) }) } @@ -541,7 +537,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(), this) } val pi = ObjReal(PI) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index 022de45..58c8941 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -113,6 +113,96 @@ class BytecodeCompiler( is BlockStatement -> compileBlock(name, stmt) is net.sergeych.lyng.InlineBlockStatement -> compileInlineBlock(name, stmt) is VarDeclStatement -> compileVarDecl(name, stmt) + is net.sergeych.lyng.ClassStaticFieldInitStatement -> { + val value = emitClassStaticFieldInit(stmt) ?: return null + builder.emit(Opcode.RET, value.slot) + val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount + builder.build( + name, + localCount, + addrCount = nextAddrSlot, + returnLabels = returnLabels, + scopeSlotIndices, + scopeSlotNames, + scopeSlotIsModule, + localSlotNames, + localSlotMutables, + localSlotDelegated, + localSlotCaptures + ) + } + is net.sergeych.lyng.ClassInstanceInitDeclStatement -> { + val value = emitClassInstanceInitDecl(stmt) ?: return null + builder.emit(Opcode.RET, value.slot) + val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount + builder.build( + name, + localCount, + addrCount = nextAddrSlot, + returnLabels = returnLabels, + scopeSlotIndices, + scopeSlotNames, + scopeSlotIsModule, + localSlotNames, + localSlotMutables, + localSlotDelegated, + localSlotCaptures + ) + } + is net.sergeych.lyng.ClassInstanceFieldDeclStatement -> { + val value = emitClassInstanceFieldDecl(stmt) ?: return null + builder.emit(Opcode.RET, value.slot) + val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount + builder.build( + name, + localCount, + addrCount = nextAddrSlot, + returnLabels = returnLabels, + scopeSlotIndices, + scopeSlotNames, + scopeSlotIsModule, + localSlotNames, + localSlotMutables, + localSlotDelegated, + localSlotCaptures + ) + } + is net.sergeych.lyng.ClassInstancePropertyDeclStatement -> { + val value = emitClassInstancePropertyDecl(stmt) ?: return null + builder.emit(Opcode.RET, value.slot) + val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount + builder.build( + name, + localCount, + addrCount = nextAddrSlot, + returnLabels = returnLabels, + scopeSlotIndices, + scopeSlotNames, + scopeSlotIsModule, + localSlotNames, + localSlotMutables, + localSlotDelegated, + localSlotCaptures + ) + } + is net.sergeych.lyng.ClassInstanceDelegatedDeclStatement -> { + val value = emitClassInstanceDelegatedDecl(stmt) ?: return null + builder.emit(Opcode.RET, value.slot) + val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount + builder.build( + name, + localCount, + addrCount = nextAddrSlot, + returnLabels = returnLabels, + scopeSlotIndices, + scopeSlotNames, + scopeSlotIsModule, + localSlotNames, + localSlotMutables, + localSlotDelegated, + localSlotCaptures + ) + } is DelegatedVarDeclStatement -> { val value = emitDelegatedVarDecl(stmt) ?: return null builder.emit(Opcode.RET, value.slot) @@ -131,6 +221,60 @@ class BytecodeCompiler( localSlotCaptures ) } + is net.sergeych.lyng.InstanceFieldInitStatement -> { + val value = emitInstanceFieldInit(stmt) ?: return null + builder.emit(Opcode.RET, value.slot) + val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount + builder.build( + name, + localCount, + addrCount = nextAddrSlot, + returnLabels = returnLabels, + scopeSlotIndices, + scopeSlotNames, + scopeSlotIsModule, + localSlotNames, + localSlotMutables, + localSlotDelegated, + localSlotCaptures + ) + } + is net.sergeych.lyng.InstancePropertyInitStatement -> { + val value = emitInstancePropertyInit(stmt) ?: return null + builder.emit(Opcode.RET, value.slot) + val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount + builder.build( + name, + localCount, + addrCount = nextAddrSlot, + returnLabels = returnLabels, + scopeSlotIndices, + scopeSlotNames, + scopeSlotIsModule, + localSlotNames, + localSlotMutables, + localSlotDelegated, + localSlotCaptures + ) + } + is net.sergeych.lyng.InstanceDelegatedInitStatement -> { + val value = emitInstanceDelegatedInit(stmt) ?: return null + builder.emit(Opcode.RET, value.slot) + val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount + builder.build( + name, + localCount, + addrCount = nextAddrSlot, + returnLabels = returnLabels, + scopeSlotIndices, + scopeSlotNames, + scopeSlotIsModule, + localSlotNames, + localSlotMutables, + localSlotDelegated, + localSlotCaptures + ) + } is DestructuringVarDeclStatement -> { val value = emitDestructuringVarDecl(stmt) ?: return null builder.emit(Opcode.RET, value.slot) @@ -317,6 +461,17 @@ class BytecodeCompiler( return id } + private fun missingMemberMessage(receiverClass: ObjClass, name: String): String { + return "no such member: $name on ${receiverClass.className}. " + + "Considered order: ${receiverClass.renderLinearization(true)}. " + + "Tip: try this@Base.$name(...) or (obj as Base).$name(...) if ambiguous" + } + + private fun missingFieldMessage(receiverClass: ObjClass, name: String): String { + return "no such field: $name on ${receiverClass.className}. " + + "Considered order: ${receiverClass.renderLinearization(true)}" + } + private fun compileRef(ref: ObjRef): CompiledValue? { return when (ref) { is ConstRef -> compileConst(ref.constValue) @@ -2535,15 +2690,7 @@ class BytecodeCompiler( } private fun compileFieldRef(ref: FieldRef): CompiledValue? { - val receiverClass = resolveReceiverClass(ref.target) - ?: if (isAllowedObjectMember(ref.name)) { - Obj.rootObjectType - } else { - throw BytecodeCompileException( - "Member access requires compile-time receiver type: ${ref.name}", - Pos.builtIn - ) - } + val receiverClass = resolveReceiverClass(ref.target) ?: ObjDynamic.type if (receiverClass == ObjDynamic.type) { val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null val dst = allocSlot() @@ -2625,7 +2772,7 @@ class BytecodeCompiler( } val extSlot = resolveExtensionGetterSlot(receiverClass, ref.name) ?: throw BytecodeCompileException( - "Unknown member ${ref.name} on ${receiverClass.className}", + missingFieldMessage(receiverClass, ref.name), Pos.builtIn ) val callee = ensureObjSlot(extSlot) @@ -3627,15 +3774,7 @@ class BytecodeCompiler( private fun compileMethodCall(ref: MethodCallRef): CompiledValue? { val callPos = callSitePos() - val receiverClass = resolveReceiverClass(ref.receiver) - ?: if (isAllowedObjectMember(ref.name)) { - Obj.rootObjectType - } else { - throw BytecodeCompileException( - "Member call requires compile-time receiver type: ${ref.name}", - Pos.builtIn - ) - } + val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null val dst = allocSlot() if (receiverClass == ObjDynamic.type) { @@ -3695,6 +3834,41 @@ class BytecodeCompiler( builder.mark(endLabel) return CompiledValue(dst, SlotType.OBJ) } + val fieldId = receiverClass.instanceFieldIdMap()[ref.name] + if (fieldId != null) { + val encodedFieldId = encodeMemberId(receiverClass, fieldId) ?: fieldId + val calleeSlot = allocSlot() + if (!ref.isOptional) { + builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, encodedFieldId, -1, calleeSlot) + } + if (!ref.isOptional) { + val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null + val encodedCount = encodeCallArgCount(args) ?: return null + setPos(callPos) + builder.emit(Opcode.CALL_SLOT, calleeSlot, args.base, encodedCount, dst) + } else { + val nullSlot = allocSlot() + builder.emit(Opcode.CONST_NULL, nullSlot) + val cmpSlot = allocSlot() + builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot) + val nullLabel = builder.label() + val endLabel = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel)) + ) + builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, encodedFieldId, -1, calleeSlot) + val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null + val encodedCount = encodeCallArgCount(args) ?: return null + setPos(callPos) + builder.emit(Opcode.CALL_SLOT, calleeSlot, args.base, encodedCount, dst) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) + builder.mark(nullLabel) + builder.emit(Opcode.CONST_NULL, dst) + builder.mark(endLabel) + } + return CompiledValue(dst, SlotType.OBJ) + } if (isKnownClassReceiver(ref.receiver)) { val nameId = builder.addConst(BytecodeConst.StringVal(ref.name)) val memberSlot = allocSlot() @@ -3725,7 +3899,7 @@ class BytecodeCompiler( } val extSlot = resolveExtensionCallableSlot(receiverClass, ref.name) ?: throw BytecodeCompileException( - "Unknown member ${ref.name} on ${receiverClass.className}", + missingMemberMessage(receiverClass, ref.name), Pos.builtIn ) val callee = ensureObjSlot(extSlot) @@ -3932,15 +4106,16 @@ class BytecodeCompiler( return CallArgs(base = argSlots[0], count = argSlots.size, planId = planId) } - private fun compileArgValue(stmt: Statement): CompiledValue? { - return when (stmt) { - is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos) - else -> { + private fun compileArgValue(value: Obj): CompiledValue? { + return when (value) { + is ExpressionStatement -> compileRefWithFallback(value.ref, null, value.pos) + is Statement -> { throw BytecodeCompileException( "Bytecode compile error: unsupported argument expression", - stmt.pos + value.pos ) } + else -> compileConst(value) } } @@ -4137,7 +4312,12 @@ class BytecodeCompiler( private fun emitDeclFunction(stmt: net.sergeych.lyng.FunctionDeclStatement): CompiledValue { val constId = builder.addConst(BytecodeConst.FunctionDecl(stmt.spec)) - val dst = allocSlot() + val dst = stmt.spec.slotIndex?.let { slotIndex -> + val scopeId = stmt.spec.scopeId ?: 0 + val key = ScopeSlotKey(scopeId, slotIndex) + localSlotIndexByKey[key]?.let { scopeSlotCount + it } + ?: scopeSlotMap[key] + } ?: allocSlot() builder.emit(Opcode.DECL_FUNCTION, constId, dst) updateSlotType(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ) @@ -4151,6 +4331,190 @@ class BytecodeCompiler( return CompiledValue(dst, SlotType.OBJ) } + private fun emitClassStaticFieldInit(stmt: net.sergeych.lyng.ClassStaticFieldInitStatement): CompiledValue? { + val initValue = if (stmt.initializer != null) { + val compiled = compileStatementValueOrFallback(stmt.initializer) ?: return null + ensureObjSlot(compiled) + } else { + val slot = allocSlot() + builder.emit(Opcode.CONST_NULL, slot) + updateSlotType(slot, SlotType.OBJ) + CompiledValue(slot, SlotType.OBJ) + } + val constId = if (stmt.isDelegated) { + builder.addConst( + BytecodeConst.ClassDelegatedDecl( + name = stmt.name, + isMutable = stmt.isMutable, + visibility = stmt.visibility, + writeVisibility = stmt.writeVisibility, + isTransient = stmt.isTransient + ) + ) + } else { + builder.addConst( + BytecodeConst.ClassFieldDecl( + name = stmt.name, + isMutable = stmt.isMutable, + visibility = stmt.visibility, + writeVisibility = stmt.writeVisibility, + isTransient = stmt.isTransient + ) + ) + } + val opcode = if (stmt.isDelegated) Opcode.DECL_CLASS_DELEGATED else Opcode.DECL_CLASS_FIELD + builder.emit(opcode, constId, initValue.slot) + updateSlotType(initValue.slot, SlotType.OBJ) + return initValue + } + + private fun emitClassInstanceInitDecl(stmt: net.sergeych.lyng.ClassInstanceInitDeclStatement): CompiledValue? { + val constId = builder.addConst(BytecodeConst.ClassInstanceInitDecl(stmt.initStatement)) + val slot = allocSlot() + builder.emit(Opcode.DECL_CLASS_INSTANCE_INIT, constId, slot) + updateSlotType(slot, SlotType.OBJ) + return CompiledValue(slot, SlotType.OBJ) + } + + private fun emitClassInstanceFieldDecl(stmt: net.sergeych.lyng.ClassInstanceFieldDeclStatement): CompiledValue? { + val constId = builder.addConst( + BytecodeConst.ClassInstanceFieldDecl( + name = stmt.name, + isMutable = stmt.isMutable, + visibility = stmt.visibility, + writeVisibility = stmt.writeVisibility, + isTransient = stmt.isTransient, + isAbstract = stmt.isAbstract, + isClosed = stmt.isClosed, + isOverride = stmt.isOverride, + fieldId = stmt.fieldId, + initStatement = stmt.initStatement, + pos = stmt.pos + ) + ) + val slot = allocSlot() + builder.emit(Opcode.DECL_CLASS_INSTANCE_FIELD, constId, slot) + updateSlotType(slot, SlotType.OBJ) + return CompiledValue(slot, SlotType.OBJ) + } + + private fun emitClassInstancePropertyDecl(stmt: net.sergeych.lyng.ClassInstancePropertyDeclStatement): CompiledValue? { + val constId = builder.addConst( + BytecodeConst.ClassInstancePropertyDecl( + name = stmt.name, + isMutable = stmt.isMutable, + visibility = stmt.visibility, + writeVisibility = stmt.writeVisibility, + isTransient = stmt.isTransient, + isAbstract = stmt.isAbstract, + isClosed = stmt.isClosed, + isOverride = stmt.isOverride, + prop = stmt.prop, + methodId = stmt.methodId, + initStatement = stmt.initStatement, + pos = stmt.pos + ) + ) + val slot = allocSlot() + builder.emit(Opcode.DECL_CLASS_INSTANCE_PROPERTY, constId, slot) + updateSlotType(slot, SlotType.OBJ) + return CompiledValue(slot, SlotType.OBJ) + } + + private fun emitClassInstanceDelegatedDecl(stmt: net.sergeych.lyng.ClassInstanceDelegatedDeclStatement): CompiledValue? { + val constId = builder.addConst( + BytecodeConst.ClassInstanceDelegatedDecl( + name = stmt.name, + isMutable = stmt.isMutable, + visibility = stmt.visibility, + writeVisibility = stmt.writeVisibility, + isTransient = stmt.isTransient, + isAbstract = stmt.isAbstract, + isClosed = stmt.isClosed, + isOverride = stmt.isOverride, + methodId = stmt.methodId, + initStatement = stmt.initStatement, + pos = stmt.pos + ) + ) + val slot = allocSlot() + builder.emit(Opcode.DECL_CLASS_INSTANCE_DELEGATED, constId, slot) + updateSlotType(slot, SlotType.OBJ) + return CompiledValue(slot, SlotType.OBJ) + } + + private fun emitInstanceFieldInit(stmt: net.sergeych.lyng.InstanceFieldInitStatement): CompiledValue? { + val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run { + val slot = allocSlot() + val constId = if (stmt.isLateInitVal) { + builder.addConst(BytecodeConst.ObjRef(ObjUnset)) + } else { + builder.addConst(BytecodeConst.ObjRef(ObjNull)) + } + builder.emit(Opcode.CONST_OBJ, constId, slot) + updateSlotType(slot, SlotType.OBJ) + CompiledValue(slot, SlotType.OBJ) + } + val declId = builder.addConst( + BytecodeConst.InstanceFieldDecl( + name = stmt.storageName, + isMutable = stmt.isMutable, + visibility = stmt.visibility, + writeVisibility = stmt.writeVisibility, + isTransient = stmt.isTransient, + isAbstract = stmt.isAbstract, + isClosed = stmt.isClosed, + isOverride = stmt.isOverride + ) + ) + builder.emit(Opcode.DECL_INSTANCE_FIELD, declId, value.slot) + updateSlotType(value.slot, SlotType.OBJ) + return value + } + + private fun emitInstancePropertyInit(stmt: net.sergeych.lyng.InstancePropertyInitStatement): CompiledValue? { + val slot = allocSlot() + val constId = builder.addConst(BytecodeConst.ObjRef(stmt.prop)) + builder.emit(Opcode.CONST_OBJ, constId, slot) + updateSlotType(slot, SlotType.OBJ) + val declId = builder.addConst( + BytecodeConst.InstancePropertyDecl( + name = stmt.storageName, + isMutable = stmt.isMutable, + visibility = stmt.visibility, + writeVisibility = stmt.writeVisibility, + isTransient = stmt.isTransient, + isAbstract = stmt.isAbstract, + isClosed = stmt.isClosed, + isOverride = stmt.isOverride + ) + ) + builder.emit(Opcode.DECL_INSTANCE_PROPERTY, declId, slot) + updateSlotType(slot, SlotType.OBJ) + return CompiledValue(slot, SlotType.OBJ) + } + + private fun emitInstanceDelegatedInit(stmt: net.sergeych.lyng.InstanceDelegatedInitStatement): CompiledValue? { + val value = compileStatementValueOrFallback(stmt.initializer) ?: return null + val declId = builder.addConst( + BytecodeConst.InstanceDelegatedDecl( + storageName = stmt.storageName, + memberName = stmt.memberName, + isMutable = stmt.isMutable, + visibility = stmt.visibility, + writeVisibility = stmt.writeVisibility, + isTransient = stmt.isTransient, + isAbstract = stmt.isAbstract, + isClosed = stmt.isClosed, + isOverride = stmt.isOverride, + accessTypeLabel = stmt.accessTypeLabel + ) + ) + builder.emit(Opcode.DECL_INSTANCE_DELEGATED, declId, value.slot) + updateSlotType(value.slot, SlotType.OBJ) + return CompiledValue(value.slot, SlotType.OBJ) + } + private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? { val target = if (stmt is BytecodeStatement) stmt.original else stmt setPos(target.pos) @@ -4181,8 +4545,16 @@ class BytecodeCompiler( } is BlockStatement -> emitBlock(target, true) is VarDeclStatement -> emitVarDecl(target) + is net.sergeych.lyng.ClassStaticFieldInitStatement -> emitClassStaticFieldInit(target) + is net.sergeych.lyng.ClassInstanceInitDeclStatement -> emitClassInstanceInitDecl(target) + is net.sergeych.lyng.ClassInstanceFieldDeclStatement -> emitClassInstanceFieldDecl(target) + is net.sergeych.lyng.ClassInstancePropertyDeclStatement -> emitClassInstancePropertyDecl(target) + is net.sergeych.lyng.ClassInstanceDelegatedDeclStatement -> emitClassInstanceDelegatedDecl(target) is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target) is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target) + is net.sergeych.lyng.InstanceFieldInitStatement -> emitInstanceFieldInit(target) + is net.sergeych.lyng.InstancePropertyInitStatement -> emitInstancePropertyInit(target) + is net.sergeych.lyng.InstanceDelegatedInitStatement -> emitInstanceDelegatedInit(target) is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target) is net.sergeych.lyng.ClassDeclStatement -> emitDeclClass(target) is net.sergeych.lyng.FunctionDeclStatement -> emitDeclFunction(target) @@ -4215,7 +4587,15 @@ class BytecodeCompiler( } } is VarDeclStatement -> emitVarDecl(target) + is net.sergeych.lyng.ClassStaticFieldInitStatement -> emitClassStaticFieldInit(target) + is net.sergeych.lyng.ClassInstanceInitDeclStatement -> emitClassInstanceInitDecl(target) + is net.sergeych.lyng.ClassInstanceFieldDeclStatement -> emitClassInstanceFieldDecl(target) + is net.sergeych.lyng.ClassInstancePropertyDeclStatement -> emitClassInstancePropertyDecl(target) + is net.sergeych.lyng.ClassInstanceDelegatedDeclStatement -> emitClassInstanceDelegatedDecl(target) is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target) + is net.sergeych.lyng.InstanceFieldInitStatement -> emitInstanceFieldInit(target) + is net.sergeych.lyng.InstancePropertyInitStatement -> emitInstancePropertyInit(target) + is net.sergeych.lyng.InstanceDelegatedInitStatement -> emitInstanceDelegatedInit(target) is IfStatement -> compileIfStatement(target) is net.sergeych.lyng.ClassDeclStatement -> emitDeclClass(target) is net.sergeych.lyng.FunctionDeclStatement -> emitDeclFunction(target) @@ -5858,6 +6238,11 @@ class BytecodeCompiler( private fun resolveTypeRefClass(ref: ObjRef): ObjClass? { return when (ref) { is ConstRef -> ref.constValue as? ObjClass + is TypeDeclRef -> when (val decl = ref.decl()) { + is TypeDecl.Simple -> resolveTypeNameClass(decl.name) ?: nameObjClass[decl.name] + is TypeDecl.Generic -> resolveTypeNameClass(decl.name) ?: nameObjClass[decl.name] + else -> null + } is LocalSlotRef -> resolveTypeNameClass(ref.name) ?: nameObjClass[ref.name] is LocalVarRef -> resolveTypeNameClass(ref.name) ?: nameObjClass[ref.name] is FastLocalVarRef -> resolveTypeNameClass(ref.name) ?: nameObjClass[ref.name] @@ -6336,6 +6721,25 @@ class BytecodeCompiler( } stmt.initializer?.let { collectScopeSlots(it) } } + is net.sergeych.lyng.FunctionDeclStatement -> { + val slotIndex = stmt.spec.slotIndex + val scopeId = stmt.spec.scopeId ?: 0 + if (slotIndex != null) { + val key = ScopeSlotKey(scopeId, slotIndex) + if (allowLocalSlots) { + if (!localSlotInfoMap.containsKey(key)) { + localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false) + } + } else { + if (!scopeSlotMap.containsKey(key)) { + scopeSlotMap[key] = scopeSlotMap.size + } + if (!scopeSlotNameMap.containsKey(key)) { + scopeSlotNameMap[key] = stmt.spec.name + } + } + } + } is DelegatedVarDeclStatement -> { val slotIndex = stmt.slotIndex val scopeId = stmt.scopeId ?: 0 @@ -6399,6 +6803,15 @@ class BytecodeCompiler( is net.sergeych.lyng.ReturnStatement -> { stmt.resultExpr?.let { collectScopeSlots(it) } } + is net.sergeych.lyng.ClassStaticFieldInitStatement -> { + stmt.initializer?.let { collectScopeSlots(it) } + } + is net.sergeych.lyng.InstanceFieldInitStatement -> { + stmt.initializer?.let { collectScopeSlots(it) } + } + is net.sergeych.lyng.InstanceDelegatedInitStatement -> { + collectScopeSlots(stmt.initializer) + } is net.sergeych.lyng.ThrowStatement -> { collectScopeSlots(stmt.throwExpr) } @@ -6466,7 +6879,11 @@ class BytecodeCompiler( } private fun isModuleSlot(scopeId: Int, name: String?): Boolean { - val moduleId = moduleScopeId ?: 0 + val moduleId = moduleScopeId + if (moduleId == null) { + if (allowedScopeNames == null || name == null) return false + return allowedScopeNames.contains(name) + } if (scopeId != moduleId) return false if (allowedScopeNames == null || name == null) return true return allowedScopeNames.contains(name) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt index 55d2fab..467017f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt @@ -75,6 +75,95 @@ sealed class BytecodeConst { val visibility: Visibility, val isTransient: Boolean, ) : BytecodeConst() + data class ClassFieldDecl( + val name: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isTransient: Boolean, + ) : BytecodeConst() + data class ClassDelegatedDecl( + val name: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isTransient: Boolean, + ) : BytecodeConst() + data class ClassInstanceInitDecl( + val initStatement: Obj, + ) : BytecodeConst() + data class ClassInstanceFieldDecl( + val name: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isTransient: Boolean, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + val fieldId: Int?, + val initStatement: Obj?, + val pos: Pos, + ) : BytecodeConst() + data class ClassInstancePropertyDecl( + val name: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isTransient: Boolean, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + val prop: ObjProperty, + val methodId: Int?, + val initStatement: Obj?, + val pos: Pos, + ) : BytecodeConst() + data class ClassInstanceDelegatedDecl( + val name: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isTransient: Boolean, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + val methodId: Int?, + val initStatement: Obj?, + val pos: Pos, + ) : BytecodeConst() + data class InstanceFieldDecl( + val name: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isTransient: Boolean, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + ) : BytecodeConst() + data class InstancePropertyDecl( + val name: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isTransient: Boolean, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + ) : BytecodeConst() + data class InstanceDelegatedDecl( + val storageName: String, + val memberName: String, + val isMutable: Boolean, + val visibility: Visibility, + val writeVisibility: Visibility?, + val isTransient: Boolean, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + val accessTypeLabel: String, + ) : BytecodeConst() data class DestructureDecl( val pattern: ListLiteralRef, val names: List, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt index 202d8f1..afddce2 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt @@ -144,6 +144,21 @@ class BytecodeStatement private constructor( is net.sergeych.lyng.ClassDeclStatement -> false is net.sergeych.lyng.FunctionDeclStatement -> false is net.sergeych.lyng.EnumDeclStatement -> false + is net.sergeych.lyng.ClassStaticFieldInitStatement -> + target.initializer?.let { containsUnsupportedStatement(it) } ?: false + is net.sergeych.lyng.ClassInstanceInitDeclStatement -> + containsUnsupportedStatement(target.initStatement) + is net.sergeych.lyng.ClassInstanceFieldDeclStatement -> + target.initStatement?.let { containsUnsupportedStatement(it) } ?: false + is net.sergeych.lyng.ClassInstancePropertyDeclStatement -> + target.initStatement?.let { containsUnsupportedStatement(it) } ?: false + is net.sergeych.lyng.ClassInstanceDelegatedDeclStatement -> + target.initStatement?.let { containsUnsupportedStatement(it) } ?: false + is net.sergeych.lyng.InstanceFieldInitStatement -> + target.initializer?.let { containsUnsupportedStatement(it) } ?: false + is net.sergeych.lyng.InstancePropertyInitStatement -> false + is net.sergeych.lyng.InstanceDelegatedInitStatement -> + containsUnsupportedStatement(target.initializer) is net.sergeych.lyng.TryStatement -> { containsUnsupportedStatement(target.body) || target.catches.any { containsUnsupportedStatement(it.block) } || @@ -266,6 +281,115 @@ class BytecodeStatement private constructor( stmt.pos ) } + is net.sergeych.lyng.ClassStaticFieldInitStatement -> { + net.sergeych.lyng.ClassStaticFieldInitStatement( + stmt.name, + stmt.isMutable, + stmt.visibility, + stmt.writeVisibility, + stmt.initializer?.let { unwrapDeep(it) }, + stmt.isDelegated, + stmt.isTransient, + stmt.pos + ) + } + is net.sergeych.lyng.ClassInstanceInitDeclStatement -> { + net.sergeych.lyng.ClassInstanceInitDeclStatement( + unwrapDeep(stmt.initStatement), + stmt.pos + ) + } + is net.sergeych.lyng.ClassInstanceFieldDeclStatement -> { + net.sergeych.lyng.ClassInstanceFieldDeclStatement( + stmt.name, + stmt.isMutable, + stmt.visibility, + stmt.writeVisibility, + stmt.isAbstract, + stmt.isClosed, + stmt.isOverride, + stmt.isTransient, + stmt.fieldId, + stmt.initStatement?.let { unwrapDeep(it) }, + stmt.pos + ) + } + is net.sergeych.lyng.ClassInstancePropertyDeclStatement -> { + net.sergeych.lyng.ClassInstancePropertyDeclStatement( + stmt.name, + stmt.isMutable, + stmt.visibility, + stmt.writeVisibility, + stmt.isAbstract, + stmt.isClosed, + stmt.isOverride, + stmt.isTransient, + stmt.prop, + stmt.methodId, + stmt.initStatement?.let { unwrapDeep(it) }, + stmt.pos + ) + } + is net.sergeych.lyng.ClassInstanceDelegatedDeclStatement -> { + net.sergeych.lyng.ClassInstanceDelegatedDeclStatement( + stmt.name, + stmt.isMutable, + stmt.visibility, + stmt.writeVisibility, + stmt.isAbstract, + stmt.isClosed, + stmt.isOverride, + stmt.isTransient, + stmt.methodId, + stmt.initStatement?.let { unwrapDeep(it) }, + stmt.pos + ) + } + is net.sergeych.lyng.InstanceFieldInitStatement -> { + net.sergeych.lyng.InstanceFieldInitStatement( + stmt.storageName, + stmt.isMutable, + stmt.visibility, + stmt.writeVisibility, + stmt.isAbstract, + stmt.isClosed, + stmt.isOverride, + stmt.isTransient, + stmt.isLateInitVal, + stmt.initializer?.let { unwrapDeep(it) }, + stmt.pos + ) + } + is net.sergeych.lyng.InstancePropertyInitStatement -> { + net.sergeych.lyng.InstancePropertyInitStatement( + stmt.storageName, + stmt.isMutable, + stmt.visibility, + stmt.writeVisibility, + stmt.isAbstract, + stmt.isClosed, + stmt.isOverride, + stmt.isTransient, + stmt.prop, + stmt.pos + ) + } + is net.sergeych.lyng.InstanceDelegatedInitStatement -> { + net.sergeych.lyng.InstanceDelegatedInitStatement( + stmt.storageName, + stmt.memberName, + stmt.isMutable, + stmt.visibility, + stmt.writeVisibility, + stmt.isAbstract, + stmt.isClosed, + stmt.isOverride, + stmt.isTransient, + stmt.accessTypeLabel, + unwrapDeep(stmt.initializer), + stmt.pos + ) + } else -> stmt } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt index 1f5b84a..ec3ebf8 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -161,7 +161,10 @@ class CmdBuilder { Opcode.PUSH_TRY -> listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP) Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE, - Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.DECL_CLASS, + Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.DECL_CLASS, Opcode.DECL_CLASS_FIELD, + Opcode.DECL_CLASS_DELEGATED, Opcode.DECL_CLASS_INSTANCE_INIT, Opcode.DECL_CLASS_INSTANCE_FIELD, + Opcode.DECL_CLASS_INSTANCE_PROPERTY, Opcode.DECL_CLASS_INSTANCE_DELEGATED, Opcode.DECL_INSTANCE_FIELD, + Opcode.DECL_INSTANCE_PROPERTY, Opcode.DECL_INSTANCE_DELEGATED, Opcode.ASSIGN_DESTRUCTURE -> listOf(OperandKind.CONST, OperandKind.SLOT) Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, @@ -418,6 +421,15 @@ class CmdBuilder { Opcode.DECL_ENUM -> CmdDeclEnum(operands[0], operands[1]) Opcode.DECL_FUNCTION -> CmdDeclFunction(operands[0], operands[1]) Opcode.DECL_CLASS -> CmdDeclClass(operands[0], operands[1]) + Opcode.DECL_CLASS_FIELD -> CmdDeclClassField(operands[0], operands[1]) + Opcode.DECL_CLASS_DELEGATED -> CmdDeclClassDelegated(operands[0], operands[1]) + Opcode.DECL_CLASS_INSTANCE_INIT -> CmdDeclClassInstanceInit(operands[0], operands[1]) + Opcode.DECL_CLASS_INSTANCE_FIELD -> CmdDeclClassInstanceField(operands[0], operands[1]) + Opcode.DECL_CLASS_INSTANCE_PROPERTY -> CmdDeclClassInstanceProperty(operands[0], operands[1]) + Opcode.DECL_CLASS_INSTANCE_DELEGATED -> CmdDeclClassInstanceDelegated(operands[0], operands[1]) + Opcode.DECL_INSTANCE_FIELD -> CmdDeclInstanceField(operands[0], operands[1]) + Opcode.DECL_INSTANCE_PROPERTY -> CmdDeclInstanceProperty(operands[0], operands[1]) + Opcode.DECL_INSTANCE_DELEGATED -> CmdDeclInstanceDelegated(operands[0], operands[1]) Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1]) Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3]) Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1]) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt index d2f327f..14c8185 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt @@ -215,6 +215,15 @@ object CmdDisassembler { is CmdDeclEnum -> Opcode.DECL_ENUM to intArrayOf(cmd.constId, cmd.slot) is CmdDeclFunction -> Opcode.DECL_FUNCTION to intArrayOf(cmd.constId, cmd.slot) is CmdDeclClass -> Opcode.DECL_CLASS to intArrayOf(cmd.constId, cmd.slot) + is CmdDeclClassField -> Opcode.DECL_CLASS_FIELD to intArrayOf(cmd.constId, cmd.slot) + is CmdDeclClassDelegated -> Opcode.DECL_CLASS_DELEGATED to intArrayOf(cmd.constId, cmd.slot) + is CmdDeclClassInstanceInit -> Opcode.DECL_CLASS_INSTANCE_INIT to intArrayOf(cmd.constId, cmd.slot) + is CmdDeclClassInstanceField -> Opcode.DECL_CLASS_INSTANCE_FIELD to intArrayOf(cmd.constId, cmd.slot) + is CmdDeclClassInstanceProperty -> Opcode.DECL_CLASS_INSTANCE_PROPERTY to intArrayOf(cmd.constId, cmd.slot) + is CmdDeclClassInstanceDelegated -> Opcode.DECL_CLASS_INSTANCE_DELEGATED to intArrayOf(cmd.constId, cmd.slot) + is CmdDeclInstanceField -> Opcode.DECL_INSTANCE_FIELD to intArrayOf(cmd.constId, cmd.slot) + is CmdDeclInstanceProperty -> Opcode.DECL_INSTANCE_PROPERTY to intArrayOf(cmd.constId, cmd.slot) + is CmdDeclInstanceDelegated -> Opcode.DECL_INSTANCE_DELEGATED to intArrayOf(cmd.constId, cmd.slot) is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot) is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst) is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot) @@ -287,7 +296,10 @@ object CmdDisassembler { Opcode.PUSH_TRY -> listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP) Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE, - Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.DECL_CLASS, + Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.DECL_CLASS, Opcode.DECL_CLASS_FIELD, + Opcode.DECL_CLASS_DELEGATED, Opcode.DECL_CLASS_INSTANCE_INIT, Opcode.DECL_CLASS_INSTANCE_FIELD, + Opcode.DECL_CLASS_INSTANCE_PROPERTY, Opcode.DECL_CLASS_INSTANCE_DELEGATED, Opcode.DECL_INSTANCE_FIELD, + Opcode.DECL_INSTANCE_PROPERTY, Opcode.DECL_INSTANCE_DELEGATED, Opcode.ASSIGN_DESTRUCTURE -> listOf(OperandKind.CONST, OperandKind.SLOT) Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, 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 65f87d8..46b700f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -72,14 +72,24 @@ class CmdNop : Cmd() { class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setObj(dst, frame.slotToObj(src)) + val value = frame.slotToObj(src) + if (frame.shouldBypassImmutableWrite(dst)) { + frame.setObjUnchecked(dst, value) + } else { + frame.setObj(dst, value) + } return } } class CmdMoveInt(internal val src: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setInt(dst, frame.getInt(src)) + val value = frame.getInt(src) + if (frame.shouldBypassImmutableWrite(dst)) { + frame.setIntUnchecked(dst, value) + } else { + frame.setInt(dst, value) + } return } } @@ -93,14 +103,24 @@ class CmdMoveIntLocal(internal val src: Int, internal val dst: Int) : Cmd() { class CmdMoveReal(internal val src: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setReal(dst, frame.getReal(src)) + val value = frame.getReal(src) + if (frame.shouldBypassImmutableWrite(dst)) { + frame.setRealUnchecked(dst, value) + } else { + frame.setReal(dst, value) + } return } } class CmdMoveBool(internal val src: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setBool(dst, frame.getBool(src)) + val value = frame.getBool(src) + if (frame.shouldBypassImmutableWrite(dst)) { + frame.setBoolUnchecked(dst, value) + } else { + frame.setBool(dst, value) + } return } } @@ -336,7 +356,12 @@ class CmdResolveScopeSlot(internal val scopeSlot: Int, internal val addrSlot: In class CmdLoadObjAddr(internal val addrSlot: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setObj(dst, frame.getAddrObj(addrSlot)) + val value = frame.getAddrObj(addrSlot) + if (frame.shouldBypassImmutableWrite(dst)) { + frame.setObjUnchecked(dst, value) + } else { + frame.setObj(dst, value) + } return } } @@ -350,7 +375,12 @@ class CmdStoreObjAddr(internal val src: Int, internal val addrSlot: Int) : Cmd() class CmdLoadIntAddr(internal val addrSlot: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setInt(dst, frame.getAddrInt(addrSlot)) + val value = frame.getAddrInt(addrSlot) + if (frame.shouldBypassImmutableWrite(dst)) { + frame.setIntUnchecked(dst, value) + } else { + frame.setInt(dst, value) + } return } } @@ -364,7 +394,12 @@ class CmdStoreIntAddr(internal val src: Int, internal val addrSlot: Int) : Cmd() class CmdLoadRealAddr(internal val addrSlot: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setReal(dst, frame.getAddrReal(addrSlot)) + val value = frame.getAddrReal(addrSlot) + if (frame.shouldBypassImmutableWrite(dst)) { + frame.setRealUnchecked(dst, value) + } else { + frame.setReal(dst, value) + } return } } @@ -378,7 +413,12 @@ class CmdStoreRealAddr(internal val src: Int, internal val addrSlot: Int) : Cmd( class CmdLoadBoolAddr(internal val addrSlot: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setBool(dst, frame.getAddrBool(addrSlot)) + val value = frame.getAddrBool(addrSlot) + if (frame.shouldBypassImmutableWrite(dst)) { + frame.setBoolUnchecked(dst, value) + } else { + frame.setBool(dst, value) + } return } } @@ -1302,17 +1342,23 @@ class CmdClearPendingThrowable : Cmd() { class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - val decl = frame.fn.constants[constId] as? BytecodeConst.LocalDecl - ?: error("DECL_LOCAL expects LocalDecl at $constId") - val value = frame.slotToObj(slot).byValueCopy() - frame.ensureScope().addItem( - decl.name, - decl.isMutable, - value, - decl.visibility, - recordType = ObjRecord.Type.Other, - isTransient = decl.isTransient - ) + if (slot < frame.fn.scopeSlotCount) { + val decl = frame.fn.constants[constId] as? BytecodeConst.LocalDecl + ?: error("DECL_LOCAL expects LocalDecl at $constId") + val target = frame.scopeTarget(slot) + frame.ensureScopeSlot(target, slot) + val value = frame.slotToObj(slot).byValueCopy() + target.updateSlotFor( + decl.name, + ObjRecord( + value, + decl.isMutable, + decl.visibility, + isTransient = decl.isTransient, + type = ObjRecord.Type.Other + ) + ) + } return } } @@ -1332,16 +1378,21 @@ class CmdDeclDelegated(internal val constId: Int, internal val slot: Int) : Cmd( } catch (_: Exception) { initValue } - val rec = frame.ensureScope().addItem( - decl.name, - decl.isMutable, - ObjNull, - decl.visibility, - recordType = ObjRecord.Type.Delegated, - isTransient = decl.isTransient - ) - rec.delegate = finalDelegate - frame.storeObjResult(slot, finalDelegate) + if (slot < frame.fn.scopeSlotCount) { + val target = frame.scopeTarget(slot) + frame.ensureScopeSlot(target, slot) + target.updateSlotFor( + decl.name, + ObjRecord( + ObjNull, + decl.isMutable, + decl.visibility, + isTransient = decl.isTransient, + type = ObjRecord.Type.Delegated + ).also { it.delegate = finalDelegate } + ) + } + frame.setObjUnchecked(slot, finalDelegate) return } } @@ -1361,7 +1412,7 @@ class CmdDeclEnum(internal val constId: Int, internal val slot: Int) : Cmd() { } } } - frame.storeObjResult(slot, enumClass) + frame.setObjUnchecked(slot, enumClass) return } } @@ -1370,18 +1421,387 @@ class CmdDeclFunction(internal val constId: Int, internal val slot: Int) : Cmd() override suspend fun perform(frame: CmdFrame) { val decl = frame.fn.constants[constId] as? BytecodeConst.FunctionDecl ?: error("DECL_FUNCTION expects FunctionDecl at $constId") - val result = executeFunctionDecl(frame.ensureScope(), decl.spec) - frame.storeObjResult(slot, result) + val captureNames = captureNamesForFunctionDecl(decl.spec) + val captureRecords = buildFunctionCaptureRecords(frame, captureNames) + val result = executeFunctionDecl(frame.ensureScope(), decl.spec, captureRecords) + frame.setObjUnchecked(slot, result) return } } +private fun captureNamesForFunctionDecl(spec: net.sergeych.lyng.FunctionDeclSpec): List { + if (spec.captureSlots.isNotEmpty()) { + return spec.captureSlots.map { it.name } + } + return captureNamesForStatement(spec.fnBody) +} + +private fun captureNamesForStatement(stmt: Statement?): List { + if (stmt == null) return emptyList() + val bytecode = when (stmt) { + is BytecodeStatement -> stmt.bytecodeFunction() + is BytecodeBodyProvider -> stmt.bytecodeBody()?.bytecodeFunction() + else -> null + } ?: return emptyList() + val names = bytecode.localSlotNames + val captures = bytecode.localSlotCaptures + val ordered = LinkedHashSet() + for (i in names.indices) { + if (captures.getOrNull(i) != true) continue + val name = names[i] ?: continue + ordered.add(name) + } + return ordered.toList() +} + +private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List): List? { + if (captureNames.isEmpty()) return null + val records = ArrayList(captureNames.size) + for (name in captureNames) { + val localIndex = resolveLocalSlotIndex(frame.fn, name, preferCapture = true) + if (localIndex != null) { + val isMutable = frame.fn.localSlotMutables.getOrNull(localIndex) ?: false + val isDelegated = frame.fn.localSlotDelegated.getOrNull(localIndex) ?: false + if (isDelegated) { + val delegate = frame.frame.getObj(localIndex) + records += ObjRecord(ObjNull, isMutable, type = ObjRecord.Type.Delegated).also { + it.delegate = delegate + } + } else { + records += ObjRecord(FrameSlotRef(frame.frame, localIndex), isMutable) + } + continue + } + val scopeSlot = frame.fn.scopeSlotNames.indexOfFirst { it == name } + if (scopeSlot >= 0) { + val target = frame.scopeTarget(scopeSlot) + val index = frame.fn.scopeSlotIndices[scopeSlot] + records += target.getSlotRecord(index) + continue + } + val scopeCaptures = frame.scope.captureRecords + val scopeCaptureNames = frame.scope.captureNames + if (scopeCaptures != null && scopeCaptureNames != null) { + val idx = scopeCaptureNames.indexOf(name) + if (idx >= 0) { + val rec = scopeCaptures.getOrNull(idx) + if (rec != null) { + records += rec + continue + } + } + } + frame.ensureScope().raiseSymbolNotFound("capture $name not found") + } + return records +} + class CmdDeclClass(internal val constId: Int, internal val slot: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { val decl = frame.fn.constants[constId] as? BytecodeConst.ClassDecl ?: error("DECL_CLASS expects ClassDecl at $constId") - val result = executeClassDecl(frame.ensureScope(), decl.spec) - frame.storeObjResult(slot, result) + val bodyCaptureNames = mergeCaptureNames( + captureNamesForStatement(decl.spec.bodyInit), + captureNamesFromFrame(frame) + ) + val bodyCaptureRecords = buildFunctionCaptureRecords(frame, bodyCaptureNames) + val result = executeClassDecl(frame.ensureScope(), decl.spec, bodyCaptureRecords, bodyCaptureNames) + frame.setObjUnchecked(slot, result) + return + } +} + +private fun mergeCaptureNames(primary: List, fallback: List): List { + if (fallback.isEmpty()) return primary + if (primary.isEmpty()) return fallback + val ordered = LinkedHashSet(primary.size + fallback.size) + ordered.addAll(primary) + ordered.addAll(fallback) + return ordered.toList() +} + +private fun captureNamesFromFrame(frame: CmdFrame): List { + val ordered = LinkedHashSet() + for (name in frame.fn.localSlotNames) { + if (name != null) ordered.add(name) + } + return ordered.toList() +} + +class CmdDeclClassField(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val decl = frame.fn.constants[constId] as? BytecodeConst.ClassFieldDecl + ?: error("DECL_CLASS_FIELD expects ClassFieldDecl at $constId") + val scope = frame.ensureScope() + val cls = scope.thisObj as? ObjClass + ?: scope.raiseIllegalState("class field init requires class scope") + val value = frame.slotToObj(slot).byValueCopy() + cls.createClassField( + decl.name, + value, + decl.isMutable, + decl.visibility, + decl.writeVisibility, + Pos.builtIn, + isTransient = decl.isTransient + ) + scope.addItem( + decl.name, + decl.isMutable, + value, + decl.visibility, + decl.writeVisibility, + recordType = ObjRecord.Type.Field, + isTransient = decl.isTransient + ) + return + } +} + +class CmdDeclClassDelegated(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val decl = frame.fn.constants[constId] as? BytecodeConst.ClassDelegatedDecl + ?: error("DECL_CLASS_DELEGATED expects ClassDelegatedDecl at $constId") + val scope = frame.ensureScope() + val cls = scope.thisObj as? ObjClass + ?: scope.raiseIllegalState("class delegated init requires class scope") + val initValue = frame.slotToObj(slot) + val accessTypeStr = if (decl.isMutable) "Var" else "Val" + val accessType = ObjString(accessTypeStr) + val finalDelegate = try { + initValue.invokeInstanceMethod( + scope, + "bind", + Arguments(ObjString(decl.name), accessType, scope.thisObj) + ) + } catch (_: Exception) { + initValue + } + cls.createClassField( + decl.name, + ObjUnset, + decl.isMutable, + decl.visibility, + decl.writeVisibility, + Pos.builtIn, + isTransient = decl.isTransient, + type = ObjRecord.Type.Delegated + ).apply { + delegate = finalDelegate + } + scope.addItem( + decl.name, + decl.isMutable, + ObjUnset, + decl.visibility, + decl.writeVisibility, + recordType = ObjRecord.Type.Delegated, + isTransient = decl.isTransient + ).apply { + delegate = finalDelegate + } + frame.storeObjResult(slot, finalDelegate) + return + } +} + +class CmdDeclClassInstanceInit(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val decl = frame.fn.constants[constId] as? BytecodeConst.ClassInstanceInitDecl + ?: error("DECL_CLASS_INSTANCE_INIT expects ClassInstanceInitDecl at $constId") + val scope = frame.ensureScope() + val cls = scope.thisObj as? ObjClass + ?: scope.raiseIllegalState("class instance init requires class scope") + cls.instanceInitializers += decl.initStatement + frame.storeObjResult(slot, ObjVoid) + return + } +} + +class CmdDeclClassInstanceField(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val decl = frame.fn.constants[constId] as? BytecodeConst.ClassInstanceFieldDecl + ?: error("DECL_CLASS_INSTANCE_FIELD expects ClassInstanceFieldDecl at $constId") + val scope = frame.ensureScope() + val cls = scope.thisObj as? ObjClass + ?: scope.raiseIllegalState("class instance field requires class scope") + cls.createField( + decl.name, + ObjNull, + isMutable = decl.isMutable, + visibility = decl.visibility, + writeVisibility = decl.writeVisibility, + pos = decl.pos, + declaringClass = cls, + isAbstract = decl.isAbstract, + isClosed = decl.isClosed, + isOverride = decl.isOverride, + isTransient = decl.isTransient, + type = ObjRecord.Type.Field, + fieldId = decl.fieldId + ) + if (!decl.isAbstract) { + decl.initStatement?.let { cls.instanceInitializers += it } + } + frame.storeObjResult(slot, ObjVoid) + return + } +} + +class CmdDeclClassInstanceProperty(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val decl = frame.fn.constants[constId] as? BytecodeConst.ClassInstancePropertyDecl + ?: error("DECL_CLASS_INSTANCE_PROPERTY expects ClassInstancePropertyDecl at $constId") + val scope = frame.ensureScope() + val cls = scope.thisObj as? ObjClass + ?: scope.raiseIllegalState("class instance property requires class scope") + cls.addProperty( + name = decl.name, + visibility = decl.visibility, + writeVisibility = decl.writeVisibility, + declaringClass = cls, + isAbstract = decl.isAbstract, + isClosed = decl.isClosed, + isOverride = decl.isOverride, + pos = decl.pos, + prop = decl.prop, + methodId = decl.methodId + ) + if (!decl.isAbstract) { + decl.initStatement?.let { cls.instanceInitializers += it } + } + frame.storeObjResult(slot, ObjVoid) + return + } +} + +class CmdDeclClassInstanceDelegated(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val decl = frame.fn.constants[constId] as? BytecodeConst.ClassInstanceDelegatedDecl + ?: error("DECL_CLASS_INSTANCE_DELEGATED expects ClassInstanceDelegatedDecl at $constId") + val scope = frame.ensureScope() + val cls = scope.thisObj as? ObjClass + ?: scope.raiseIllegalState("class instance delegated requires class scope") + cls.createField( + decl.name, + ObjUnset, + isMutable = decl.isMutable, + visibility = decl.visibility, + writeVisibility = decl.writeVisibility, + pos = decl.pos, + declaringClass = cls, + isAbstract = decl.isAbstract, + isClosed = decl.isClosed, + isOverride = decl.isOverride, + isTransient = decl.isTransient, + type = ObjRecord.Type.Delegated, + methodId = decl.methodId + ) + if (!decl.isAbstract) { + decl.initStatement?.let { cls.instanceInitializers += it } + } + frame.storeObjResult(slot, ObjVoid) + return + } +} + +class CmdDeclInstanceField(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val decl = frame.fn.constants[constId] as? BytecodeConst.InstanceFieldDecl + ?: error("DECL_INSTANCE_FIELD expects InstanceFieldDecl at $constId") + val scope = frame.ensureScope() + val value = frame.slotToObj(slot).byValueCopy() + scope.addItem( + decl.name, + decl.isMutable, + value, + decl.visibility, + decl.writeVisibility, + recordType = ObjRecord.Type.Field, + isAbstract = decl.isAbstract, + isClosed = decl.isClosed, + isOverride = decl.isOverride, + isTransient = decl.isTransient + ) + if (slot >= frame.fn.scopeSlotCount) { + frame.storeObjResult(slot, ObjVoid) + } + return + } +} + +class CmdDeclInstanceProperty(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val decl = frame.fn.constants[constId] as? BytecodeConst.InstancePropertyDecl + ?: error("DECL_INSTANCE_PROPERTY expects InstancePropertyDecl at $constId") + val scope = frame.ensureScope() + val prop = frame.slotToObj(slot) + scope.addItem( + decl.name, + decl.isMutable, + prop, + decl.visibility, + decl.writeVisibility, + recordType = ObjRecord.Type.Property, + isAbstract = decl.isAbstract, + isClosed = decl.isClosed, + isOverride = decl.isOverride, + isTransient = decl.isTransient + ) + if (slot >= frame.fn.scopeSlotCount) { + frame.storeObjResult(slot, ObjVoid) + } + return + } +} + +class CmdDeclInstanceDelegated(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val decl = frame.fn.constants[constId] as? BytecodeConst.InstanceDelegatedDecl + ?: error("DECL_INSTANCE_DELEGATED expects InstanceDelegatedDecl at $constId") + val scope = frame.ensureScope() + var initValue = frame.slotToObj(slot) + val debugName = if (slot < frame.fn.scopeSlotCount) { + frame.fn.scopeSlotNames.getOrNull(slot) + } else { + val localIndex = slot - frame.fn.scopeSlotCount + frame.fn.localSlotNames.getOrNull(localIndex) + } + if (initValue === ObjUnset) { + if (debugName != null) { + val resolved = scope.get(debugName) + if (resolved != null && resolved.value !== ObjUnset) { + initValue = resolved.value + } + } + } + val accessType = ObjString(decl.accessTypeLabel) + val finalDelegate = try { + initValue.invokeInstanceMethod( + scope, + "bind", + Arguments(ObjString(decl.memberName), accessType, scope.thisObj) + ) + } catch (_: Exception) { + initValue + } + scope.addItem( + decl.storageName, + decl.isMutable, + ObjUnset, + decl.visibility, + decl.writeVisibility, + recordType = ObjRecord.Type.Delegated, + isAbstract = decl.isAbstract, + isClosed = decl.isClosed, + isOverride = decl.isOverride, + isTransient = decl.isTransient + ).apply { + delegate = finalDelegate + } + if (slot >= frame.fn.scopeSlotCount) { + frame.storeObjResult(slot, ObjVoid) + } return } } @@ -1504,33 +1924,6 @@ private fun resolveLocalSlotIndex(fn: CmdFunction, name: String, preferCapture: return null } -private fun isAstStatement(stmt: Statement): Boolean { - return when (stmt) { - is ExpressionStatement, - is IfStatement, - is ForInStatement, - is WhileStatement, - is DoWhileStatement, - is BlockStatement, - is InlineBlockStatement, - is VarDeclStatement, - is DelegatedVarDeclStatement, - is DestructuringVarDeclStatement, - is BreakStatement, - is ContinueStatement, - is ReturnStatement, - is ThrowStatement, - is net.sergeych.lyng.NopStatement, - is ExtensionPropertyDeclStatement, - is ClassDeclStatement, - is FunctionDeclStatement, - is EnumDeclStatement, - is TryStatement, - is WhenStatement -> true - else -> false - } -} - class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { val decl = frame.fn.constants[constId] as? BytecodeConst.ExtensionPropertyDecl @@ -1578,7 +1971,7 @@ class CmdCallDirect( val args = frame.buildArguments(argBase, argCount) if (callee is Statement) { val bytecodeBody = (callee as? BytecodeBodyProvider)?.bytecodeBody() - if (callee !is BytecodeStatement && callee !is BytecodeCallable && bytecodeBody == null && isAstStatement(callee)) { + if (callee !is BytecodeStatement && callee !is BytecodeCallable && bytecodeBody == null) { frame.ensureScope().raiseIllegalState("bytecode runtime cannot call non-bytecode Statement") } } @@ -1619,7 +2012,7 @@ class CmdCallSlot( val scope = frame.ensureScope() if (callee is Statement) { val bytecodeBody = (callee as? BytecodeBodyProvider)?.bytecodeBody() - if (callee !is BytecodeStatement && callee !is BytecodeCallable && bytecodeBody == null && isAstStatement(callee)) { + if (callee !is BytecodeStatement && callee !is BytecodeCallable && bytecodeBody == null) { scope.raiseIllegalState("bytecode runtime cannot call non-bytecode Statement") } } @@ -1897,18 +2290,26 @@ class CmdCallMemberSlot( frame.storeObjResult(dst, result) return } + val scope = frame.ensureScope() val decl = rec.declaringClass ?: receiver.objClass + if (!canAccessMember(rec.visibility, decl, scope.currentClassCtx, name)) { + scope.raiseError( + ObjIllegalAccessException( + scope, + "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${scope.currentClassCtx?.className ?: "?"})" + ) + ) + } val result = when (rec.type) { ObjRecord.Type.Property -> { - if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(frame.ensureScope(), receiver, decl) - else frame.ensureScope().raiseError("property $name cannot be called with arguments") + if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, receiver, decl) + else scope.raiseError("property $name cannot be called with arguments") } ObjRecord.Type.Fun -> { - val callScope = inst?.instanceScope ?: frame.ensureScope() + val callScope = inst?.instanceScope ?: scope rec.value.invoke(callScope, receiver, callArgs, decl) } ObjRecord.Type.Delegated -> { - val scope = frame.ensureScope() val delegate = when (receiver) { is ObjInstance -> { val storageName = decl.mangledName(name) @@ -2002,7 +2403,6 @@ class BytecodeLambdaCallable( val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also { it.args = scope.args } - if (paramSlotPlan.isNotEmpty()) context.applySlotPlan(paramSlotPlan) if (captureRecords != null) { context.captureRecords = captureRecords context.captureNames = captureNames @@ -2027,19 +2427,13 @@ class BytecodeLambdaCallable( val itSlot = slotPlan["it"] if (itSlot != null) { frame.frame.setObj(itSlot, itValue) - if (context.getLocalRecordDirect("it") == null) { - context.addItem("it", false, FrameSlotRef(frame.frame, itSlot), recordType = ObjRecord.Type.Argument) - } - } else if (context.getLocalRecordDirect("it") == null) { - context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument) } } else { argsDeclaration.assignToFrame( context, arguments, slotPlan, - frame.frame, - defaultAccessType = AccessType.Val + frame.frame ) } } @@ -2235,9 +2629,7 @@ class CmdFrame( if (!visited.add(current)) continue if (current.getSlotIndexOf(slotName) != null) return current current.parent?.let { queue.add(it) } - if (current is ClosureScope) { - queue.add(current.closureScope) - } else if (current is BytecodeClosureScope) { + if (current is BytecodeClosureScope) { queue.add(current.closureScope) } else if (current is ApplyScope) { queue.add(current.applied) @@ -2255,9 +2647,7 @@ class CmdFrame( if (!visited.add(current)) continue if (current.getLocalRecordDirect(name) != null) return current current.parent?.let { queue.add(it) } - if (current is ClosureScope) { - queue.add(current.closureScope) - } else if (current is BytecodeClosureScope) { + if (current is BytecodeClosureScope) { queue.add(current.closureScope) } else if (current is ApplyScope) { queue.add(current.applied) @@ -2276,9 +2666,7 @@ class CmdFrame( if (current is ModuleScope) return current if (current.parent is ModuleScope) return current current.parent?.let { queue.add(it) } - if (current is ClosureScope) { - queue.add(current.closureScope) - } else if (current is BytecodeClosureScope) { + if (current is BytecodeClosureScope) { queue.add(current.closureScope) } else if (current is ApplyScope) { queue.add(current.applied) @@ -2360,7 +2748,7 @@ class CmdFrame( fun pushScope(plan: Map, captures: List) { if (scope.skipScopeCreation) { - val snapshot = scope.applySlotPlanWithSnapshot(plan) + val snapshot = emptyMap() slotPlanStack.addLast(snapshot) virtualDepth += 1 scopeStack.addLast(scope) @@ -2369,9 +2757,6 @@ class CmdFrame( scopeStack.addLast(scope) scopeVirtualStack.addLast(false) scope = scope.createChildScope() - if (plan.isNotEmpty()) { - scope.applySlotPlan(plan) - } } captureStack.addLast(captures) scopeDepth += 1 @@ -2417,11 +2802,8 @@ class CmdFrame( scopeStack.addLast(scope) slotPlanScopeStack.addLast(true) scope = scope.createChildScope() - if (plan.isNotEmpty()) { - scope.applySlotPlan(plan) - } } else { - val snapshot = scope.applySlotPlanWithSnapshot(plan) + val snapshot = emptyMap() slotPlanStack.addLast(snapshot) slotPlanScopeStack.addLast(false) virtualDepth += 1 @@ -2456,9 +2838,15 @@ class CmdFrame( if (slot < fn.scopeSlotCount) { val target = scopeTarget(slot) val index = ensureScopeSlot(target, slot) + val record = target.getSlotRecord(index) + if (!record.isMutable) { + val name = fn.scopeSlotNames[slot] ?: "slot#$index" + ensureScope().raiseError("can't assign to read-only variable: $name") + } target.setSlotValue(index, value) } else { val localIndex = slot - fn.scopeSlotCount + ensureLocalMutable(localIndex) when (val existing = frame.getRawObj(localIndex)) { is FrameSlotRef -> { existing.write(value) @@ -2474,6 +2862,15 @@ class CmdFrame( } } + fun shouldBypassImmutableWrite(slot: Int): Boolean { + val next = fn.cmds.getOrNull(ip) ?: return false + return when (next) { + is CmdDeclLocal -> next.slot == slot + is CmdDeclDelegated -> next.slot == slot + else -> false + } + } + suspend fun getInt(slot: Int): Long { return if (slot < fn.scopeSlotCount) { getScopeSlotValue(slot).toLong() @@ -2498,7 +2895,7 @@ class CmdFrame( fun getLocalInt(local: Int): Long = frame.getInt(local) - fun setInt(slot: Int, value: Long) { + fun setIntUnchecked(slot: Int, value: Long) { if (slot < fn.scopeSlotCount) { val target = scopeTarget(slot) val index = ensureScopeSlot(target, slot) @@ -2520,6 +2917,34 @@ class CmdFrame( } } + fun setInt(slot: Int, value: Long) { + if (slot < fn.scopeSlotCount) { + val target = scopeTarget(slot) + val index = ensureScopeSlot(target, slot) + val record = target.getSlotRecord(index) + if (!record.isMutable) { + val name = fn.scopeSlotNames[slot] ?: "slot#$index" + ensureScope().raiseError("can't assign to read-only variable: $name") + } + target.setSlotValue(index, ObjInt.of(value)) + } else { + val localIndex = slot - fn.scopeSlotCount + ensureLocalMutable(localIndex) + when (val existing = frame.getRawObj(localIndex)) { + is FrameSlotRef -> { + existing.write(ObjInt.of(value)) + return + } + is RecordSlotRef -> { + existing.write(ObjInt.of(value)) + return + } + else -> {} + } + frame.setInt(localIndex, value) + } + } + fun setLocalInt(local: Int, value: Long) { frame.setInt(local, value) } @@ -2547,6 +2972,34 @@ class CmdFrame( } fun setReal(slot: Int, value: Double) { + if (slot < fn.scopeSlotCount) { + val target = scopeTarget(slot) + val index = ensureScopeSlot(target, slot) + val record = target.getSlotRecord(index) + if (!record.isMutable) { + val name = fn.scopeSlotNames[slot] ?: "slot#$index" + ensureScope().raiseError("can't assign to read-only variable: $name") + } + target.setSlotValue(index, ObjReal.of(value)) + } else { + val localIndex = slot - fn.scopeSlotCount + ensureLocalMutable(localIndex) + when (val existing = frame.getRawObj(localIndex)) { + is FrameSlotRef -> { + existing.write(ObjReal.of(value)) + return + } + is RecordSlotRef -> { + existing.write(ObjReal.of(value)) + return + } + else -> {} + } + frame.setReal(localIndex, value) + } + } + + fun setRealUnchecked(slot: Int, value: Double) { if (slot < fn.scopeSlotCount) { val target = scopeTarget(slot) val index = ensureScopeSlot(target, slot) @@ -2593,6 +3046,34 @@ class CmdFrame( fun getLocalBool(local: Int): Boolean = frame.getBool(local) fun setBool(slot: Int, value: Boolean) { + if (slot < fn.scopeSlotCount) { + val target = scopeTarget(slot) + val index = ensureScopeSlot(target, slot) + val record = target.getSlotRecord(index) + if (!record.isMutable) { + val name = fn.scopeSlotNames[slot] ?: "slot#$index" + ensureScope().raiseError("can't assign to read-only variable: $name") + } + target.setSlotValue(index, if (value) ObjTrue else ObjFalse) + } else { + val localIndex = slot - fn.scopeSlotCount + ensureLocalMutable(localIndex) + when (val existing = frame.getRawObj(localIndex)) { + is FrameSlotRef -> { + existing.write(if (value) ObjTrue else ObjFalse) + return + } + is RecordSlotRef -> { + existing.write(if (value) ObjTrue else ObjFalse) + return + } + else -> {} + } + frame.setBool(localIndex, value) + } + } + + fun setBoolUnchecked(slot: Int, value: Boolean) { if (slot < fn.scopeSlotCount) { val target = scopeTarget(slot) val index = ensureScopeSlot(target, slot) @@ -2687,6 +3168,16 @@ class CmdFrame( } } + fun setObjUnchecked(slot: Int, value: Obj) { + if (slot < fn.scopeSlotCount) { + val target = scopeTarget(slot) + val index = ensureScopeSlot(target, slot) + target.setSlotValue(index, value) + } else { + frame.setObj(slot - fn.scopeSlotCount, value) + } + } + suspend fun throwObj(pos: Pos, value: Obj) { var errorObject = value val throwScope = ensureScope().createChildScope(pos = pos) @@ -2889,5 +3380,17 @@ class CmdFrame( return index } + private fun ensureLocalMutable(localIndex: Int) { + val name = fn.localSlotNames.getOrNull(localIndex) ?: return + val isMutable = fn.localSlotMutables.getOrNull(localIndex) ?: true + if (!isMutable) { + val typeCode = frame.getSlotTypeCode(localIndex) + if (typeCode == SlotType.UNKNOWN.code) return + val rawObj = frame.getRawObj(localIndex) + if (rawObj === ObjUnset) return + ensureScope().raiseError("can't assign to read-only variable: $name") + } + } + // Scope depth resolution is no longer used; all scope slots are resolved against the current frame. } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt index 1b07167..fb29c8d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -168,6 +168,15 @@ enum class Opcode(val code: Int) { BIND_DELEGATE_LOCAL(0xC4), DECL_FUNCTION(0xC5), DECL_CLASS(0xC6), + DECL_CLASS_FIELD(0xC7), + DECL_CLASS_DELEGATED(0xC8), + DECL_CLASS_INSTANCE_INIT(0xC9), + DECL_CLASS_INSTANCE_FIELD(0xCA), + DECL_CLASS_INSTANCE_PROPERTY(0xCB), + DECL_CLASS_INSTANCE_DELEGATED(0xCC), + DECL_INSTANCE_FIELD(0xCD), + DECL_INSTANCE_PROPERTY(0xCE), + DECL_INSTANCE_DELEGATED(0xCF), ; companion object { 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 01eadc2..be3bb72 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt @@ -447,7 +447,7 @@ open class Obj { caller.members[name]?.let { rec -> if (rec.visibility == Visibility.Private && !rec.isAbstract) { val resolved = resolveRecord(scope, rec, name, caller) - if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement) + if (resolved.type == ObjRecord.Type.Fun) return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, caller)) return resolved } @@ -461,7 +461,7 @@ open class Obj { if (rec != null && !rec.isAbstract) { val decl = rec.declaringClass ?: cls val resolved = resolveRecord(scope, rec, name, decl) - if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement) + if (resolved.type == ObjRecord.Type.Fun) return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl)) return resolved } @@ -476,7 +476,7 @@ open class Obj { if (!canAccessMember(rec.visibility, decl, caller, name)) scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) val resolved = resolveRecord(scope, rec, name, decl) - if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement) + if (resolved.type == ObjRecord.Type.Fun) return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl)) return resolved } @@ -500,16 +500,17 @@ open class Obj { if (obj.type == ObjRecord.Type.Delegated) { val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate") val th = if (this === ObjVoid) ObjNull else this - val getValueRec = del.objClass.getInstanceMemberOrNull("getValue") + val getValueRec = when (del) { + is ObjInstance -> del.methodRecordForKey("getValue") + ?: del.instanceScope.objects["getValue"] + ?: del.objClass.getInstanceMemberOrNull("getValue") + else -> del.objClass.getInstanceMemberOrNull("getValue") + } if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") { - val wrapper = object : Statement() { - override val pos: Pos = Pos.builtIn - - override suspend fun execute(s: Scope): Obj { - val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj - val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray() - return del.invokeInstanceMethod(s, "invoke", Arguments(*allArgs)) - } + val wrapper = ObjNativeCallable { + val th2 = if (thisObj === ObjVoid) ObjNull else thisObj + val allArgs = (listOf(th2, ObjString(name)) + args.list).toTypedArray() + del.invokeInstanceMethod(this, "invoke", Arguments(*allArgs)) } return obj.copy( value = wrapper, @@ -517,14 +518,11 @@ open class Obj { ) } val res = del.invokeInstanceMethod(scope, "getValue", Arguments(th, ObjString(name))) - return obj.copy( - value = res, - type = ObjRecord.Type.Other - ) + return obj.copy(value = res, type = ObjRecord.Type.Other) } val value = obj.value if (value is ObjProperty || obj.type == ObjRecord.Type.Property) { - val prop = if (value is ObjProperty) value else (value as? Statement)?.execute(scope.createChildScope(scope.pos, newThisObj = this)) as? ObjProperty + val prop = (value as? ObjProperty) ?: scope.raiseError("Expected ObjProperty for property member $name, got ${value::class}") val res = prop.callGetter(scope, this, decl) return ObjRecord(res, obj.isMutable) 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 6b69e1b..0c67917 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt @@ -61,7 +61,7 @@ val ObjClassType by lazy { val names = mutableListOf() for (c in cls.mro) { for ((n, rec) in c.members) { - if (rec.value !is Statement && seen.add(n)) names += ObjString(n) + if (rec.type != ObjRecord.Type.Fun && seen.add(n)) names += ObjString(n) } } ObjList(names.toMutableList()) @@ -79,7 +79,7 @@ val ObjClassType by lazy { val names = mutableListOf() for (c in cls.mro) { for ((n, rec) in c.members) { - if (rec.value is Statement && seen.add(n)) names += ObjString(n) + if (rec.type == ObjRecord.Type.Fun && seen.add(n)) names += ObjString(n) } } ObjList(names.toMutableList()) @@ -136,7 +136,7 @@ open class ObjClass( } } cls.classScope?.objects?.forEach { (name, rec) -> - if (rec.visibility == Visibility.Public && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) { + if (rec.visibility == Visibility.Public && (rec.type == ObjRecord.Type.Fun || rec.type == ObjRecord.Type.Delegated)) { val key = if (rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name res[name] = key } @@ -150,13 +150,13 @@ open class ObjClass( val classNameObj by lazy { ObjString(className) } var constructorMeta: ArgsDeclaration? = null - var instanceConstructor: Statement? = null + var instanceConstructor: Obj? = null /** * Per-instance initializers collected from class body (for instance fields). These are executed * during construction in the instance scope of the object, once per class in the hierarchy. */ - val instanceInitializers: MutableList = mutableListOf() + val instanceInitializers: MutableList = mutableListOf() /** * the scope for class methods, initialize class vars, etc. @@ -349,8 +349,7 @@ open class ObjClass( if (cls.className == "Obj") break for ((name, rec) in cls.members) { if (rec.isAbstract) continue - if (rec.value !is Statement && - rec.type != ObjRecord.Type.Delegated && + if (rec.type != ObjRecord.Type.Delegated && rec.type != ObjRecord.Type.Fun && rec.type != ObjRecord.Type.Property) { continue @@ -363,9 +362,9 @@ open class ObjClass( } cls.classScope?.objects?.forEach { (name, rec) -> if (rec.isAbstract) return@forEach - if (rec.value !is Statement && - rec.type != ObjRecord.Type.Delegated && - rec.type != ObjRecord.Type.Property) return@forEach + if (rec.type != ObjRecord.Type.Delegated && + rec.type != ObjRecord.Type.Property && + rec.type != ObjRecord.Type.Fun) return@forEach val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name if (res.containsKey(key)) return@forEach val methodId = rec.methodId ?: cls.assignMethodId(name, rec) @@ -533,7 +532,7 @@ open class ObjClass( for (cls in mro) { // 1) members-defined methods and fields for ((k, v) in cls.members) { - if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField)) { + if (!v.isAbstract && (v.type == ObjRecord.Type.Fun || v.type == ObjRecord.Type.Property || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField)) { val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField || v.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k if (!res.containsKey(key)) { res[key] = v @@ -544,7 +543,7 @@ open class ObjClass( cls.classScope?.objects?.forEach { (k, rec) -> // ONLY copy methods and delegated members from class scope to instance scope. // Fields in class scope are static fields and must NOT be per-instance. - if (!rec.isAbstract && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) { + if (!rec.isAbstract && (rec.type == ObjRecord.Type.Fun || rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) { val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k // if not already present, copy reference for dispatch if (!res.containsKey(key)) { @@ -715,7 +714,7 @@ open class ObjClass( instance.instanceScope.currentClassCtx = c try { for (initStmt in c.instanceInitializers) { - initStmt.execute(instance.instanceScope) + initStmt.callOn(instance.instanceScope) } } finally { instance.instanceScope.currentClassCtx = savedCtx @@ -726,7 +725,7 @@ open class ObjClass( c.instanceConstructor?.let { ctor -> val execScope = instance.instanceScope.createChildScope(args = argsForThis ?: Arguments.EMPTY, newThisObj = instance) - ctor.execute(execScope) + ctor.callOn(execScope) } } } @@ -933,7 +932,7 @@ open class ObjClass( methodId: Int? = null, code: (suspend Scope.() -> Obj)? = null ) { - val stmt = code?.let { statement { it() } } ?: ObjNull + val stmt = code?.let { ObjNativeCallable { it() } } ?: ObjNull createField( name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, @@ -958,8 +957,8 @@ open class ObjClass( prop: ObjProperty? = null, methodId: Int? = null ) { - val g = getter?.let { statement { it() } } - val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } } + val g = getter?.let { ObjNativeCallable { it() } } + val s = setter?.let { ObjNativeCallable { it(requiredArg(0)); ObjVoid } } val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s) createField( name, finalProp, false, visibility, writeVisibility, pos, declaringClass, @@ -971,7 +970,7 @@ 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, statement { code() }, isOpen, type = ObjRecord.Type.Fun) + createClassField(name, ObjNativeCallable { 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 002c6f6..60d24ac 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDateTime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDateTime.kt @@ -21,7 +21,6 @@ import kotlinx.datetime.* import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import net.sergeych.lyng.Scope -import net.sergeych.lyng.Statement import net.sergeych.lyng.miniast.addClassFnDoc import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addPropertyDoc @@ -47,14 +46,19 @@ class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() { if (rec != null) { if (rec.type == ObjRecord.Type.Property) { val prop = rec.value as? ObjProperty - ?: (rec.value as? Statement)?.execute(scope) as? ObjProperty if (prop != null) { return ObjRecord(prop.callGetter(scope, this, rec.declaringClass ?: cls), rec.isMutable) } } - if (rec.type == ObjRecord.Type.Fun || rec.value is Statement) { - val s = rec.value as Statement - return ObjRecord(net.sergeych.lyng.statement { s.execute(this.createChildScope(newThisObj = this@ObjDateTime)) }, rec.isMutable) + if (rec.type == ObjRecord.Type.Fun) { + val target = rec.value + return ObjRecord( + ObjNativeCallable { + val callScope = createChildScope(args = args, newThisObj = this@ObjDateTime) + target.callOn(callScope) + }, + rec.isMutable + ) } return resolveRecord(scope, rec, name, rec.declaringClass ?: cls) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt index 44d646a..94f526e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt @@ -19,7 +19,6 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.Arguments import net.sergeych.lyng.Scope -import net.sergeych.lyng.Statement class ObjDynamicContext(val delegate: ObjDynamic) : Obj() { override val objClass: ObjClass get() = type @@ -51,7 +50,7 @@ class ObjDynamicContext(val delegate: ObjDynamic) : Obj() { * Object that delegates all its field access/invocation operations to a callback. It is used to implement dynamic * objects using "dynamic" keyword. */ -open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: Statement? = null) : Obj() { +open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = null) : Obj() { override val objClass: ObjClass get() = type // Capture the lexical scope used to build this dynamic so callbacks can see outer locals @@ -63,7 +62,7 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St */ override suspend fun readField(scope: Scope, name: String): ObjRecord { val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope - return readCallback?.execute(execBase.createChildScope(Arguments(ObjString(name))))?.let { + return readCallback?.callOn(execBase.createChildScope(Arguments(ObjString(name))))?.let { if (writeCallback != null) it.asMutable else @@ -83,32 +82,32 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St onNotFoundResult: (suspend () -> Obj?)? ): Obj { val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope - val over = readCallback?.execute(execBase.createChildScope(Arguments(ObjString(name)))) + val over = readCallback?.callOn(execBase.createChildScope(Arguments(ObjString(name)))) return over?.invoke(scope, scope.thisObj, args) ?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult) } override suspend fun writeField(scope: Scope, name: String, newValue: Obj) { val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope - writeCallback?.execute(execBase.createChildScope(Arguments(ObjString(name), newValue))) + writeCallback?.callOn(execBase.createChildScope(Arguments(ObjString(name), newValue))) ?: super.writeField(scope, name, newValue) } override suspend fun getAt(scope: Scope, index: Obj): Obj { val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope - return readCallback?.execute(execBase.createChildScope(Arguments(index))) + return readCallback?.callOn(execBase.createChildScope(Arguments(index))) ?: super.getAt(scope, index) } override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) { val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope - writeCallback?.execute(execBase.createChildScope(Arguments(index, newValue))) + writeCallback?.callOn(execBase.createChildScope(Arguments(index, newValue))) ?: super.putAt(scope, index, newValue) } companion object { - suspend fun create(scope: Scope, builder: Statement): ObjDynamic { + suspend fun create(scope: Scope, builder: Obj): ObjDynamic { val delegate = ObjDynamic() val context = ObjDynamicContext(delegate) // Capture the function's lexical scope (scope) so callbacks can see outer locals like parameters. @@ -116,7 +115,7 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St val buildScope = scope.createChildScope(newThisObj = context) // Snapshot the caller scope to capture locals/args even if the runtime pools/reuses frames delegate.builderScope = scope.snapshotForClosure() - builder.execute(buildScope) + builder.callOn(buildScope) return delegate } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt index a76a8f7..b9d81ad 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 = statement { ObjString(name) })), + listOf(ArgsDeclaration.Item("message", defaultValue = ObjNativeCallable { ObjString(name) })), Token.Type.RPAREN ) } @@ -177,7 +177,7 @@ open class ObjException( } val Root = ExceptionClass("Exception").apply { - instanceInitializers.add(statement { + instanceInitializers.add(ObjNativeCallable { if (thisObj is ObjInstance) { val msg = get("message")?.value ?: ObjString("Exception") (thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg) @@ -187,7 +187,7 @@ open class ObjException( } ObjVoid }) - instanceConstructor = statement { ObjVoid } + instanceConstructor = ObjNativeCallable { ObjVoid } addPropertyDoc( name = "message", doc = "Human‑readable error message.", 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 b6a9dc1..d3c6d40 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt @@ -71,13 +71,13 @@ class ObjFlowBuilder(val output: SendChannel) : Obj() { } } -private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChannel { +private fun createLyngFlowInput(scope: Scope, producer: Obj): ReceiveChannel { val channel = Channel(Channel.RENDEZVOUS) val builder = ObjFlowBuilder(channel) val builderScope = scope.createChildScope(newThisObj = builder) globalLaunch { try { - producer.execute(builderScope) + producer.callOn(builderScope) } catch (x: ScriptFlowIsNoMoreCollected) { // premature flow closing, OK } catch (x: Exception) { @@ -89,7 +89,7 @@ private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChann return channel } -class ObjFlow(val producer: Statement, val scope: Scope) : Obj() { +class ObjFlow(val producer: Obj, val scope: Scope) : Obj() { override val objClass get() = type @@ -106,8 +106,8 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() { moduleName = "lyng.stdlib" ) { val objFlow = thisAs() - ObjFlowIterator(statement { - objFlow.producer.execute(this) + ObjFlowIterator(ObjNativeCallable { + objFlow.producer.callOn(this) }) } } @@ -115,7 +115,7 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() { } -class ObjFlowIterator(val producer: Statement) : Obj() { +class ObjFlowIterator(val producer: Obj) : Obj() { override val objClass: ObjClass get() = type 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 58f956f..f0e6d6b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt @@ -170,16 +170,17 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { } } del = del ?: scope.raiseError("Internal error: delegated property $name has no delegate") - val getValueRec = del.objClass.getInstanceMemberOrNull("getValue") + val getValueRec = when (del) { + is ObjInstance -> del.methodRecordForKey("getValue") + ?: del.instanceScope.objects["getValue"] + ?: del.objClass.getInstanceMemberOrNull("getValue") + else -> del.objClass.getInstanceMemberOrNull("getValue") + } if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") { - val wrapper = object : Statement() { - override val pos: Pos = Pos.builtIn - - override suspend fun execute(s: Scope): Obj { - val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj - val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray() - return del.invokeInstanceMethod(s, "invoke", Arguments(*allArgs)) - } + val wrapper = ObjNativeCallable { + val th2 = if (thisObj === ObjVoid) ObjNull else thisObj + val allArgs = (listOf(th2, ObjString(name)) + args.list).toTypedArray() + del.invokeInstanceMethod(this, "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 fc13b20..e717034 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt @@ -18,7 +18,6 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.Arguments -import net.sergeych.lyng.Statement import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addPropertyDoc @@ -131,10 +130,11 @@ val ObjIterable by lazy { returns = type("lyng.Map"), moduleName = "lyng.stdlib" ) { - val association = requireOnlyArg() + val association = requireOnlyArg() val result = ObjMap() thisObj.toFlow(this).collect { - result.map[association.call(this, it)] = it + val callScope = createChildScope(args = Arguments(it)) + result.map[association.callOn(callScope)] = it } result } @@ -147,10 +147,10 @@ val ObjIterable by lazy { moduleName = "lyng.stdlib" ) { val it = thisObj.invokeInstanceMethod(this, "iterator") - val fn = requiredArg(0) + val fn = requiredArg(0) while (it.invokeInstanceMethod(this, "hasNext").toBool()) { val x = it.invokeInstanceMethod(this, "next") - fn.execute(this.createChildScope(Arguments(listOf(x)))) + fn.callOn(this.createChildScope(Arguments(listOf(x)))) } ObjVoid } @@ -163,10 +163,11 @@ val ObjIterable by lazy { isOpen = true, moduleName = "lyng.stdlib" ) { - val fn = requiredArg(0) + val fn = requiredArg(0) val result = mutableListOf() thisObj.toFlow(this).collect { - result.add(fn.call(this, it)) + val callScope = createChildScope(args = Arguments(it)) + result.add(fn.callOn(callScope)) } ObjList(result) } @@ -179,10 +180,11 @@ val ObjIterable by lazy { isOpen = true, moduleName = "lyng.stdlib" ) { - val fn = requiredArg(0) + val fn = requiredArg(0) val result = mutableListOf() thisObj.toFlow(this).collect { - val transformed = fn.call(this, it) + val callScope = createChildScope(args = Arguments(it)) + val transformed = fn.callOn(callScope) if( transformed != ObjNull) result.add(transformed) } ObjList(result) @@ -228,9 +230,10 @@ val ObjIterable by lazy { moduleName = "lyng.stdlib" ) { val list = thisObj.callMethod(this, "toList") - val comparator = requireOnlyArg() + val comparator = requireOnlyArg() list.quicksort { a, b -> - comparator.call(this, a, b).toInt() + val callScope = createChildScope(args = Arguments(a, b)) + comparator.callOn(callScope).toInt() } list } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjLazyDelegate.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjLazyDelegate.kt index b73be26..da75075 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjLazyDelegate.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjLazyDelegate.kt @@ -23,7 +23,7 @@ import net.sergeych.lyng.* * Lazy delegate used by `val x by lazy { ... }`. */ class ObjLazyDelegate( - private val builder: Statement, + private val builder: Obj, private val capturedScope: Scope, ) : Obj() { override val objClass: ObjClass = type 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 07d9cc3..478d696 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt @@ -20,7 +20,7 @@ package net.sergeych.lyng.obj import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement import net.sergeych.lyng.Scope -import net.sergeych.lyng.Statement +import net.sergeych.lyng.Arguments import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addPropertyDoc @@ -371,8 +371,11 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { params = listOf(ParamDoc("comparator")), moduleName = "lyng.stdlib" ) { - val comparator = requireOnlyArg() - thisAs().quicksort { a, b -> comparator.call(this, a, b).toInt() } + val comparator = requireOnlyArg() + thisAs().quicksort { a, b -> + val callScope = createChildScope(args = Arguments(a, b)) + comparator.callOn(callScope).toInt() + } ObjVoid } addFnDoc( @@ -522,4 +525,3 @@ fun MutableList.swap(i: Int, j: Int) { this[j] = temp } } - 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 7095494..b2eb14e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt @@ -20,7 +20,6 @@ package net.sergeych.lyng.obj import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import net.sergeych.lyng.Scope -import net.sergeych.lyng.Statement import net.sergeych.lyng.miniast.* import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonEncoder @@ -261,8 +260,8 @@ class ObjMap(val map: MutableMap = mutableMapOf()) : Obj() { ) { val key = requiredArg(0) thisAs().map.getOrPut(key) { - val lambda = requiredArg(1) - lambda.execute(this) + val lambda = requiredArg(1) + lambda.callOn(this) } } addPropertyDoc( @@ -358,4 +357,4 @@ class ObjMap(val map: MutableMap = mutableMapOf()) : Obj() { else -> scope.raiseIllegalArgument("map can only be merged with Map, MapEntry, or List") } } -} \ No newline at end of file +} 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 54197c8..f61138b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt @@ -20,7 +20,6 @@ package net.sergeych.lyng.obj import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import net.sergeych.lyng.Scope -import net.sergeych.lyng.Statement import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.type @@ -41,12 +40,12 @@ class ObjMutex(val mutex: Mutex): Obj() { returns = type("lyng.Any"), moduleName = "lyng.stdlib" ) { - val f = requiredArg(0) + val f = requiredArg(0) // Execute user lambda directly in the current scope to preserve the active scope - // ancestry across suspension points. The lambda still constructs a ClosureScope + // 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.execute(this) } + thisAs().mutex.withLock { f.callOn(this) } } } } -} \ No newline at end of file +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DeclExecutable.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjNativeCallable.kt similarity index 61% rename from lynglib/src/commonMain/kotlin/net/sergeych/lyng/DeclExecutable.kt rename to lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjNativeCallable.kt index 5565ac2..305aa19 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DeclExecutable.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjNativeCallable.kt @@ -12,16 +12,22 @@ * 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 +package net.sergeych.lyng.obj -import net.sergeych.lyng.obj.Obj +import net.sergeych.lyng.Scope +import net.sergeych.lyng.Statement -interface DeclExecutable { - suspend fun execute(scope: Scope): Obj -} - -class StatementDeclExecutable(private val delegate: Statement) : DeclExecutable { - override suspend fun execute(scope: Scope): Obj = delegate.execute(scope) +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/ObjProperty.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjProperty.kt index 437d05f..295cd57 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjProperty.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjProperty.kt @@ -19,7 +19,6 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.Arguments import net.sergeych.lyng.Scope -import net.sergeych.lyng.Statement /** * Property accessor storage. Per instructions, properties do NOT have @@ -27,8 +26,8 @@ import net.sergeych.lyng.Statement */ class ObjProperty( val name: String, - val getter: Statement?, - val setter: Statement? + val getter: Obj?, + val setter: Obj? ) : Obj() { suspend fun callGetter(scope: Scope, instance: Obj, declaringClass: ObjClass? = null): Obj { @@ -38,7 +37,7 @@ class ObjProperty( val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope) val execScope = scope.applyClosure(instanceScope).createChildScope(newThisObj = instance) execScope.currentClassCtx = declaringClass - return g.execute(execScope) + return g.callOn(execScope) } suspend fun callSetter(scope: Scope, instance: Obj, value: Obj, declaringClass: ObjClass? = null) { @@ -48,7 +47,7 @@ class ObjProperty( val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope) val execScope = scope.applyClosure(instanceScope).createChildScope(args = Arguments(value), newThisObj = instance) execScope.currentClassCtx = declaringClass - s.execute(execScope) + s.callOn(execScope) } override fun toString(): String = "Property($name)" 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 e8ce96f..ce1387a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt @@ -126,8 +126,8 @@ data class ObjReal(val value: Double) : Obj(), Numeric { // roundToInt: number rounded to the nearest integer addConstDoc( name = "roundToInt", - value = statement(Pos.builtIn) { - (it.thisObj as ObjReal).value.roundToLong().toObj() + value = ObjNativeCallable { + (thisObj as ObjReal).value.roundToLong().toObj() }, doc = "This real number rounded to the nearest integer.", type = type("lyng.Int"), @@ -143,4 +143,4 @@ data class ObjReal(val value: Double) : Obj(), Numeric { } } } -} \ No newline at end of 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 d3408a8..ddf26d4 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt @@ -21,7 +21,6 @@ import net.sergeych.lyng.PerfFlags import net.sergeych.lyng.Pos import net.sergeych.lyng.RegexCache import net.sergeych.lyng.Scope -import net.sergeych.lyng.Statement import net.sergeych.lyng.miniast.* class ObjRegex(val regex: Regex) : Obj() { @@ -76,14 +75,10 @@ class ObjRegex(val regex: Regex) : Obj() { } createField( name = "operatorMatch", - initialValue = object : Statement() { - override val pos: Pos = Pos.builtIn - - override suspend fun execute(scope: Scope): Obj { - val other = scope.args.firstAndOnly(pos) - val targetScope = scope.parent ?: scope - return (scope.thisObj as ObjRegex).operatorMatch(targetScope, other) - } + initialValue = ObjNativeCallable { + val other = args.firstAndOnly(Pos.builtIn) + val targetScope = parent ?: this + (thisObj as ObjRegex).operatorMatch(targetScope, other) }, type = ObjRecord.Type.Fun ) 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 16254e9..da5b0a7 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt @@ -25,7 +25,6 @@ import net.sergeych.lyng.PerfFlags import net.sergeych.lyng.Pos import net.sergeych.lyng.RegexCache import net.sergeych.lyng.Scope -import net.sergeych.lyng.Statement import net.sergeych.lyng.miniast.* import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonEncoder @@ -344,14 +343,10 @@ data class ObjString(val value: String) : Obj() { name = "re", initialValue = ObjProperty( name = "re", - getter = object : Statement() { - override val pos: Pos = Pos.builtIn - - override suspend fun execute(scope: Scope): Obj { - val pattern = (scope.thisObj as ObjString).value - val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(pattern) else pattern.toRegex() - return ObjRegex(re) - } + getter = ObjNativeCallable { + val pattern = (thisObj as ObjString).value + val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(pattern) else pattern.toRegex() + ObjRegex(re) }, setter = null ), @@ -359,14 +354,10 @@ data class ObjString(val value: String) : Obj() { ) createField( name = "operatorMatch", - initialValue = object : Statement() { - override val pos: Pos = Pos.builtIn - - override suspend fun execute(scope: Scope): Obj { - val other = scope.args.firstAndOnly(pos) - val targetScope = scope.parent ?: scope - return (scope.thisObj as ObjString).operatorMatch(targetScope, other) - } + initialValue = ObjNativeCallable { + val other = args.firstAndOnly(Pos.builtIn) + val targetScope = parent ?: this + (thisObj as ObjString).operatorMatch(targetScope, other) }, type = ObjRecord.Type.Fun ) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/resolution/CompileTimeResolution.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/resolution/CompileTimeResolution.kt index 7da8beb..dafe005 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/resolution/CompileTimeResolution.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/resolution/CompileTimeResolution.kt @@ -70,7 +70,7 @@ object CompileTimeResolver { source, importProvider, resolutionSink = collector, - useBytecodeStatements = false, + compileBytecode = false, allowUnresolvedRefs = true ) return collector.buildReport() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/tools/LyngLanguageTools.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/tools/LyngLanguageTools.kt index 8aac41f..5a0d825 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/tools/LyngLanguageTools.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/tools/LyngLanguageTools.kt @@ -106,7 +106,7 @@ object LyngLanguageTools { request.importProvider, miniSink = miniSink, resolutionSink = resolutionCollector, - useBytecodeStatements = false, + compileBytecode = false, allowUnresolvedRefs = true, seedScope = request.seedScope ) diff --git a/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt b/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt index c97cea1..14de1d3 100644 --- a/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt +++ b/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt @@ -392,7 +392,6 @@ class BytecodeRecentOpsTest { val script = Compiler.compileWithResolution( Source("", code), Script.defaultImportManager, - useBytecodeStatements = true, useFastLocalRefs = true ) val result = script.execute(Script.defaultImportManager.newStdScope()) diff --git a/lynglib/src/commonTest/kotlin/CompileTimeResolutionRuntimeTest.kt b/lynglib/src/commonTest/kotlin/CompileTimeResolutionRuntimeTest.kt index 37a0298..0e64487 100644 --- a/lynglib/src/commonTest/kotlin/CompileTimeResolutionRuntimeTest.kt +++ b/lynglib/src/commonTest/kotlin/CompileTimeResolutionRuntimeTest.kt @@ -38,7 +38,6 @@ class CompileTimeResolutionRuntimeTest { val script = Compiler.compileWithResolution( Source("", code), Script.defaultImportManager, - useBytecodeStatements = false, strictSlotRefs = true ) val result = script.execute(Script.defaultImportManager.newStdScope()) @@ -61,7 +60,6 @@ class CompileTimeResolutionRuntimeTest { val script = Compiler.compileWithResolution( Source("", code), Script.defaultImportManager, - useBytecodeStatements = true, strictSlotRefs = true ) val result = script.execute(Script.defaultImportManager.newStdScope()) @@ -85,7 +83,6 @@ class CompileTimeResolutionRuntimeTest { val script = Compiler.compileWithResolution( Source("", code), Script.defaultImportManager, - useBytecodeStatements = true, strictSlotRefs = true ) val result = script.execute(Script.defaultImportManager.newStdScope()) diff --git a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/miniast/ParamTypeInferenceTest.kt b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/miniast/ParamTypeInferenceTest.kt index 194c708..9dc70c4 100644 --- a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/miniast/ParamTypeInferenceTest.kt +++ b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/miniast/ParamTypeInferenceTest.kt @@ -45,8 +45,7 @@ class ParamTypeInferenceTest { Compiler.compileWithResolution( Source("", code.trimIndent()), Script.defaultImportManager, - miniSink = sink, - useBytecodeStatements = false + miniSink = sink ) val mini = sink.build()!! val binding = Binder.bind(code, mini)