Remove interpreter paths and enforce bytecode-only execution

This commit is contained in:
Sergey Chernov 2026-02-13 00:19:47 +03:00
parent 4cd8e5ded2
commit 90826b519d
45 changed files with 2517 additions and 1391 deletions

View File

@ -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] Keep scope sync only for reflection/Kotlin interop, not for execution.
- [x] Replace bytecode entry seeding from Scope with frame-only arg/local binding. - [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 ## Notes
- Keep imports bound to module frame slots; no scope map writes for imports. - Keep imports bound to module frame slots; no scope map writes for imports.

View File

@ -217,7 +217,7 @@ private suspend inline fun Scope.processGuard(crossinline block: suspend () -> O
private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow { private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow {
val producer = statement { 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.thisObj as? ObjFlowBuilder
this@toLyngFlow.collect { this@toLyngFlow.collect {

View File

@ -113,7 +113,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
} }
suspend fun missingValue(a: Item, error: String): Obj { 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) ?: if (a.type.isNullable) ObjNull else scope.raiseIllegalArgument(error)
} }
@ -252,17 +252,14 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
/** /**
* Assign arguments directly into frame slots using [paramSlotPlan] without creating scope locals. * 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( suspend fun assignToFrame(
scope: Scope, scope: Scope,
arguments: Arguments = scope.args, arguments: Arguments = scope.args,
paramSlotPlan: Map<String, Int>, paramSlotPlan: Map<String, Int>,
frame: FrameAccess, frame: FrameAccess,
slotOffset: Int = 0, slotOffset: Int = 0
defaultAccessType: AccessType = AccessType.Var,
defaultVisibility: Visibility = Visibility.Public,
declaringClass: net.sergeych.lyng.obj.ObjClass? = scope.currentClassCtx
) { ) {
fun slotFor(name: String): Int { fun slotFor(name: String): Int {
val full = paramSlotPlan[name] ?: scope.raiseIllegalState("parameter slot for '$name' is missing") val full = paramSlotPlan[name] ?: scope.raiseIllegalState("parameter slot for '$name' is missing")
@ -271,19 +268,6 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
return slot 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) { fun setFrameValue(slot: Int, value: Obj) {
when (value) { when (value) {
is net.sergeych.lyng.obj.ObjInt -> frame.setInt(slot, value.value) is net.sergeych.lyng.obj.ObjInt -> frame.setInt(slot, value.value)
@ -294,18 +278,12 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
} }
fun assign(a: Item, value: Obj) { 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) val slot = slotFor(a.name)
setFrameValue(slot, value.byValueCopy()) setFrameValue(slot, value.byValueCopy())
ensureScopeRef(a, slot, recordType)
} }
suspend fun missingValue(a: Item, error: String): Obj { 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) ?: if (a.type.isNullable) ObjNull else scope.raiseIllegalArgument(error)
} }
@ -459,7 +437,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
/** /**
* Single argument declaration descriptor. * 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. * If not null, could be executed on __caller context__ only.
*/ */
data class Item( data class Item(
@ -472,7 +450,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
* Default value, if set, can't be an [Obj] as it can depend on the call site, call args, etc. * 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__. * 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 accessType: AccessType? = null,
val visibility: Visibility? = null, val visibility: Visibility? = null,
val isTransient: Boolean = false, val isTransient: Boolean = false,

View File

@ -20,7 +20,7 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.* import net.sergeych.lyng.obj.*
data class ParsedArgument( data class ParsedArgument(
val value: Statement, val value: Obj,
val pos: Pos, val pos: Pos,
val isSplat: Boolean = false, val isSplat: Boolean = false,
val name: String? = null, val name: String? = null,
@ -40,115 +40,115 @@ data class ParsedArgument(
if (!hasSplatOrNamed && count == this.size) { if (!hasSplatOrNamed && count == this.size) {
val quick = when (count) { val quick = when (count) {
0 -> Arguments.EMPTY 0 -> Arguments.EMPTY
1 -> Arguments(listOf(this.elementAt(0).value.execute(scope)), tailBlockMode) 1 -> Arguments(listOf(this.elementAt(0).value.callOn(scope)), tailBlockMode)
2 -> { 2 -> {
val a0 = this.elementAt(0).value.execute(scope) val a0 = this.elementAt(0).value.callOn(scope)
val a1 = this.elementAt(1).value.execute(scope) val a1 = this.elementAt(1).value.callOn(scope)
Arguments(listOf(a0, a1), tailBlockMode) Arguments(listOf(a0, a1), tailBlockMode)
} }
3 -> { 3 -> {
val a0 = this.elementAt(0).value.execute(scope) val a0 = this.elementAt(0).value.callOn(scope)
val a1 = this.elementAt(1).value.execute(scope) val a1 = this.elementAt(1).value.callOn(scope)
val a2 = this.elementAt(2).value.execute(scope) val a2 = this.elementAt(2).value.callOn(scope)
Arguments(listOf(a0, a1, a2), tailBlockMode) Arguments(listOf(a0, a1, a2), tailBlockMode)
} }
4 -> { 4 -> {
val a0 = this.elementAt(0).value.execute(scope) val a0 = this.elementAt(0).value.callOn(scope)
val a1 = this.elementAt(1).value.execute(scope) val a1 = this.elementAt(1).value.callOn(scope)
val a2 = this.elementAt(2).value.execute(scope) val a2 = this.elementAt(2).value.callOn(scope)
val a3 = this.elementAt(3).value.execute(scope) val a3 = this.elementAt(3).value.callOn(scope)
Arguments(listOf(a0, a1, a2, a3), tailBlockMode) Arguments(listOf(a0, a1, a2, a3), tailBlockMode)
} }
5 -> { 5 -> {
val a0 = this.elementAt(0).value.execute(scope) val a0 = this.elementAt(0).value.callOn(scope)
val a1 = this.elementAt(1).value.execute(scope) val a1 = this.elementAt(1).value.callOn(scope)
val a2 = this.elementAt(2).value.execute(scope) val a2 = this.elementAt(2).value.callOn(scope)
val a3 = this.elementAt(3).value.execute(scope) val a3 = this.elementAt(3).value.callOn(scope)
val a4 = this.elementAt(4).value.execute(scope) val a4 = this.elementAt(4).value.callOn(scope)
Arguments(listOf(a0, a1, a2, a3, a4), tailBlockMode) Arguments(listOf(a0, a1, a2, a3, a4), tailBlockMode)
} }
6 -> { 6 -> {
val a0 = this.elementAt(0).value.execute(scope) val a0 = this.elementAt(0).value.callOn(scope)
val a1 = this.elementAt(1).value.execute(scope) val a1 = this.elementAt(1).value.callOn(scope)
val a2 = this.elementAt(2).value.execute(scope) val a2 = this.elementAt(2).value.callOn(scope)
val a3 = this.elementAt(3).value.execute(scope) val a3 = this.elementAt(3).value.callOn(scope)
val a4 = this.elementAt(4).value.execute(scope) val a4 = this.elementAt(4).value.callOn(scope)
val a5 = this.elementAt(5).value.execute(scope) val a5 = this.elementAt(5).value.callOn(scope)
Arguments(listOf(a0, a1, a2, a3, a4, a5), tailBlockMode) Arguments(listOf(a0, a1, a2, a3, a4, a5), tailBlockMode)
} }
7 -> { 7 -> {
val a0 = this.elementAt(0).value.execute(scope) val a0 = this.elementAt(0).value.callOn(scope)
val a1 = this.elementAt(1).value.execute(scope) val a1 = this.elementAt(1).value.callOn(scope)
val a2 = this.elementAt(2).value.execute(scope) val a2 = this.elementAt(2).value.callOn(scope)
val a3 = this.elementAt(3).value.execute(scope) val a3 = this.elementAt(3).value.callOn(scope)
val a4 = this.elementAt(4).value.execute(scope) val a4 = this.elementAt(4).value.callOn(scope)
val a5 = this.elementAt(5).value.execute(scope) val a5 = this.elementAt(5).value.callOn(scope)
val a6 = this.elementAt(6).value.execute(scope) val a6 = this.elementAt(6).value.callOn(scope)
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6), tailBlockMode) Arguments(listOf(a0, a1, a2, a3, a4, a5, a6), tailBlockMode)
} }
8 -> { 8 -> {
val a0 = this.elementAt(0).value.execute(scope) val a0 = this.elementAt(0).value.callOn(scope)
val a1 = this.elementAt(1).value.execute(scope) val a1 = this.elementAt(1).value.callOn(scope)
val a2 = this.elementAt(2).value.execute(scope) val a2 = this.elementAt(2).value.callOn(scope)
val a3 = this.elementAt(3).value.execute(scope) val a3 = this.elementAt(3).value.callOn(scope)
val a4 = this.elementAt(4).value.execute(scope) val a4 = this.elementAt(4).value.callOn(scope)
val a5 = this.elementAt(5).value.execute(scope) val a5 = this.elementAt(5).value.callOn(scope)
val a6 = this.elementAt(6).value.execute(scope) val a6 = this.elementAt(6).value.callOn(scope)
val a7 = this.elementAt(7).value.execute(scope) val a7 = this.elementAt(7).value.callOn(scope)
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7), tailBlockMode) Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7), tailBlockMode)
} }
9 -> if (PerfFlags.ARG_SMALL_ARITY_12) { 9 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
val a0 = this.elementAt(0).value.execute(scope) val a0 = this.elementAt(0).value.callOn(scope)
val a1 = this.elementAt(1).value.execute(scope) val a1 = this.elementAt(1).value.callOn(scope)
val a2 = this.elementAt(2).value.execute(scope) val a2 = this.elementAt(2).value.callOn(scope)
val a3 = this.elementAt(3).value.execute(scope) val a3 = this.elementAt(3).value.callOn(scope)
val a4 = this.elementAt(4).value.execute(scope) val a4 = this.elementAt(4).value.callOn(scope)
val a5 = this.elementAt(5).value.execute(scope) val a5 = this.elementAt(5).value.callOn(scope)
val a6 = this.elementAt(6).value.execute(scope) val a6 = this.elementAt(6).value.callOn(scope)
val a7 = this.elementAt(7).value.execute(scope) val a7 = this.elementAt(7).value.callOn(scope)
val a8 = this.elementAt(8).value.execute(scope) val a8 = this.elementAt(8).value.callOn(scope)
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8), tailBlockMode) Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8), tailBlockMode)
} else null } else null
10 -> if (PerfFlags.ARG_SMALL_ARITY_12) { 10 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
val a0 = this.elementAt(0).value.execute(scope) val a0 = this.elementAt(0).value.callOn(scope)
val a1 = this.elementAt(1).value.execute(scope) val a1 = this.elementAt(1).value.callOn(scope)
val a2 = this.elementAt(2).value.execute(scope) val a2 = this.elementAt(2).value.callOn(scope)
val a3 = this.elementAt(3).value.execute(scope) val a3 = this.elementAt(3).value.callOn(scope)
val a4 = this.elementAt(4).value.execute(scope) val a4 = this.elementAt(4).value.callOn(scope)
val a5 = this.elementAt(5).value.execute(scope) val a5 = this.elementAt(5).value.callOn(scope)
val a6 = this.elementAt(6).value.execute(scope) val a6 = this.elementAt(6).value.callOn(scope)
val a7 = this.elementAt(7).value.execute(scope) val a7 = this.elementAt(7).value.callOn(scope)
val a8 = this.elementAt(8).value.execute(scope) val a8 = this.elementAt(8).value.callOn(scope)
val a9 = this.elementAt(9).value.execute(scope) val a9 = this.elementAt(9).value.callOn(scope)
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9), tailBlockMode) Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9), tailBlockMode)
} else null } else null
11 -> if (PerfFlags.ARG_SMALL_ARITY_12) { 11 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
val a0 = this.elementAt(0).value.execute(scope) val a0 = this.elementAt(0).value.callOn(scope)
val a1 = this.elementAt(1).value.execute(scope) val a1 = this.elementAt(1).value.callOn(scope)
val a2 = this.elementAt(2).value.execute(scope) val a2 = this.elementAt(2).value.callOn(scope)
val a3 = this.elementAt(3).value.execute(scope) val a3 = this.elementAt(3).value.callOn(scope)
val a4 = this.elementAt(4).value.execute(scope) val a4 = this.elementAt(4).value.callOn(scope)
val a5 = this.elementAt(5).value.execute(scope) val a5 = this.elementAt(5).value.callOn(scope)
val a6 = this.elementAt(6).value.execute(scope) val a6 = this.elementAt(6).value.callOn(scope)
val a7 = this.elementAt(7).value.execute(scope) val a7 = this.elementAt(7).value.callOn(scope)
val a8 = this.elementAt(8).value.execute(scope) val a8 = this.elementAt(8).value.callOn(scope)
val a9 = this.elementAt(9).value.execute(scope) val a9 = this.elementAt(9).value.callOn(scope)
val a10 = this.elementAt(10).value.execute(scope) val a10 = this.elementAt(10).value.callOn(scope)
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10), tailBlockMode) Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10), tailBlockMode)
} else null } else null
12 -> if (PerfFlags.ARG_SMALL_ARITY_12) { 12 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
val a0 = this.elementAt(0).value.execute(scope) val a0 = this.elementAt(0).value.callOn(scope)
val a1 = this.elementAt(1).value.execute(scope) val a1 = this.elementAt(1).value.callOn(scope)
val a2 = this.elementAt(2).value.execute(scope) val a2 = this.elementAt(2).value.callOn(scope)
val a3 = this.elementAt(3).value.execute(scope) val a3 = this.elementAt(3).value.callOn(scope)
val a4 = this.elementAt(4).value.execute(scope) val a4 = this.elementAt(4).value.callOn(scope)
val a5 = this.elementAt(5).value.execute(scope) val a5 = this.elementAt(5).value.callOn(scope)
val a6 = this.elementAt(6).value.execute(scope) val a6 = this.elementAt(6).value.callOn(scope)
val a7 = this.elementAt(7).value.execute(scope) val a7 = this.elementAt(7).value.callOn(scope)
val a8 = this.elementAt(8).value.execute(scope) val a8 = this.elementAt(8).value.callOn(scope)
val a9 = this.elementAt(9).value.execute(scope) val a9 = this.elementAt(9).value.callOn(scope)
val a10 = this.elementAt(10).value.execute(scope) val a10 = this.elementAt(10).value.callOn(scope)
val a11 = this.elementAt(11).value.execute(scope) val a11 = this.elementAt(11).value.callOn(scope)
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11), tailBlockMode) Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11), tailBlockMode)
} else null } else null
else -> null else -> null
@ -166,12 +166,12 @@ data class ParsedArgument(
// Named argument // Named argument
if (named == null) named = linkedMapOf() if (named == null) named = linkedMapOf()
if (named.containsKey(x.name)) scope.raiseIllegalArgument("argument '${x.name}' is already set") 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 named[x.name] = v
namedSeen = true namedSeen = true
continue continue
} }
val value = x.value.execute(scope) val value = x.value.callOn(scope)
if (x.isSplat) { if (x.isSplat) {
when { when {
// IMPORTANT: handle ObjMap BEFORE generic Iterable to ensure map splats // IMPORTANT: handle ObjMap BEFORE generic Iterable to ensure map splats

View File

@ -32,33 +32,15 @@ class BlockStatement(
if (slotPlan.isNotEmpty()) target.applySlotPlan(slotPlan) if (slotPlan.isNotEmpty()) target.applySlotPlan(slotPlan)
if (captureSlots.isNotEmpty()) { if (captureSlots.isNotEmpty()) {
val captureRecords = scope.captureRecords val captureRecords = scope.captureRecords
if (captureRecords != null) { if (captureRecords == null) {
scope.raiseIllegalState("missing bytecode capture records")
}
for (i in captureSlots.indices) { for (i in captureSlots.indices) {
val capture = captureSlots[i] val capture = captureSlots[i]
val rec = captureRecords.getOrNull(i) val rec = captureRecords.getOrNull(i)
?: scope.raiseSymbolNotFound("capture ${capture.name} not found") ?: scope.raiseSymbolNotFound("capture ${capture.name} not found")
target.updateSlotFor(capture.name, rec) 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)
}
}
} }
return block.execute(target) return block.execute(target)
} }

View File

@ -19,4 +19,6 @@ package net.sergeych.lyng
data class CaptureSlot( data class CaptureSlot(
val name: String, val name: String,
val ownerScopeId: Int? = null,
val ownerSlot: Int? = null,
) )

View File

@ -45,7 +45,12 @@ data class ClassDeclSpec(
val initScope: List<Statement>, val initScope: List<Statement>,
) )
internal suspend fun executeClassDecl(scope: Scope, spec: ClassDeclSpec): Obj { internal suspend fun executeClassDecl(
scope: Scope,
spec: ClassDeclSpec,
bodyCaptureRecords: List<ObjRecord>? = null,
bodyCaptureNames: List<String>? = null
): Obj {
if (spec.isObject) { if (spec.isObject) {
val parentClasses = spec.baseSpecs.map { baseSpec -> val parentClasses = spec.baseSpecs.map { baseSpec ->
val rec = scope[baseSpec.name] ?: throw ScriptError(spec.startPos, "unknown base class: ${baseSpec.name}") 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) val classScope = scope.createChildScope(newThisObj = newClass)
if (!bodyCaptureRecords.isNullOrEmpty() && !bodyCaptureNames.isNullOrEmpty()) {
classScope.captureRecords = bodyCaptureRecords
classScope.captureNames = bodyCaptureNames
}
classScope.currentClassCtx = newClass classScope.currentClassCtx = newClass
newClass.classScope = classScope newClass.classScope = classScope
classScope.addConst("object", newClass) 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) } spec.declaredName?.let { scope.addItem(it, false, newClass) }
val classScope = scope.createChildScope(newThisObj = newClass) val classScope = scope.createChildScope(newThisObj = newClass)
if (!bodyCaptureRecords.isNullOrEmpty() && !bodyCaptureNames.isNullOrEmpty()) {
classScope.captureRecords = bodyCaptureRecords
classScope.captureNames = bodyCaptureNames
}
classScope.currentClassCtx = newClass classScope.currentClassCtx = newClass
newClass.classScope = classScope newClass.classScope = classScope
spec.bodyInit?.execute(classScope) spec.bodyInit?.execute(classScope)

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -18,76 +18,12 @@
package net.sergeych.lyng package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjRecord 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<Obj>(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 * 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. * 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( class BytecodeClosureScope(
val callScope: Scope, val callScope: Scope,
@ -118,7 +54,7 @@ class ApplyScope(val callScope: Scope, val applied: Scope) :
} }
override fun applyClosure(closure: Scope, preferredThisType: String?): Scope { override fun applyClosure(closure: Scope, preferredThisType: String?): Scope {
return ClosureScope(this, closure, preferredThisType) return BytecodeClosureScope(this, closure, preferredThisType)
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,7 @@ import net.sergeych.lyng.obj.ObjVoid
class FunctionClosureBox( class FunctionClosureBox(
var closure: Scope? = null, var closure: Scope? = null,
var captureContext: Scope? = null, var captureContext: Scope? = null,
var captureRecords: List<ObjRecord>? = null,
) )
data class FunctionDeclSpec( data class FunctionDeclSpec(
@ -40,6 +41,7 @@ data class FunctionDeclSpec(
val isTransient: Boolean, val isTransient: Boolean,
val isDelegated: Boolean, val isDelegated: Boolean,
val delegateExpression: Statement?, val delegateExpression: Statement?,
val delegateInitStatement: Statement?,
val extTypeName: String?, val extTypeName: String?,
val extensionWrapperName: String?, val extensionWrapperName: String?,
val memberMethodId: Int?, val memberMethodId: Int?,
@ -50,10 +52,17 @@ data class FunctionDeclSpec(
val fnBody: Statement, val fnBody: Statement,
val closureBox: FunctionClosureBox, val closureBox: FunctionClosureBox,
val captureSlots: List<CaptureSlot>, val captureSlots: List<CaptureSlot>,
val slotIndex: Int?,
val scopeId: Int?,
val startPos: Pos, val startPos: Pos,
) )
internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec): Obj { internal suspend fun executeFunctionDecl(
scope: Scope,
spec: FunctionDeclSpec,
captureRecords: List<ObjRecord>? = null
): Obj {
spec.closureBox.captureRecords = captureRecords
if (spec.actualExtern && spec.extTypeName == null && !spec.parentIsClassBody) { if (spec.actualExtern && spec.extTypeName == null && !spec.parentIsClassBody) {
val existing = scope.get(spec.name) val existing = scope.get(spec.name)
if (existing != null) { if (existing != null) {
@ -117,7 +126,6 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec):
} }
} else if (th is ObjClass) { } else if (th is ObjClass) {
val cls: ObjClass = th val cls: ObjClass = th
val storageName = "${cls.className}::${spec.name}"
cls.createField( cls.createField(
spec.name, spec.name,
ObjUnset, ObjUnset,
@ -133,33 +141,9 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec):
type = ObjRecord.Type.Delegated, type = ObjRecord.Type.Delegated,
methodId = spec.memberMethodId methodId = spec.memberMethodId
) )
cls.instanceInitializers += object : Statement() { val initStmt = spec.delegateInitStatement
override val pos: Pos = spec.startPos ?: scope.raiseIllegalState("missing delegated init statement for ${spec.name}")
override suspend fun execute(scp: Scope): Obj { cls.instanceInitializers += initStmt
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
}
}
} else { } else {
scope.addItem( scope.addItem(
spec.name, spec.name,
@ -178,7 +162,7 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec):
if (spec.isStatic || !spec.parentIsClassBody) { if (spec.isStatic || !spec.parentIsClassBody) {
spec.closureBox.closure = scope spec.closureBox.closure = scope
} }
if (spec.parentIsClassBody && spec.captureSlots.isNotEmpty()) { if (spec.parentIsClassBody) {
spec.closureBox.captureContext = scope spec.closureBox.captureContext = scope
} }
@ -188,25 +172,18 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec):
spec.extTypeName?.let { typeName -> spec.extTypeName?.let { typeName ->
val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found") val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found")
if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance") if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance")
val stmt = object : Statement() { val callable = net.sergeych.lyng.obj.ObjNativeCallable {
override val pos: Pos = spec.startPos val result = (thisObj as? ObjInstance)?.let { i ->
override suspend fun execute(scope: Scope): Obj { val execScope = applyClosureForBytecode(i.instanceScope).also {
val result = (scope.thisObj as? ObjInstance)?.let { i -> it.args = args
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(execScope)
} ?: compiledFnBody.execute(scope.thisObj.autoInstanceScope(scope)) } ?: compiledFnBody.execute(thisObj.autoInstanceScope(this))
return result result
} }
} scope.addExtension(type, spec.name, ObjRecord(callable, isMutable = false, visibility = spec.visibility, declaringClass = null))
scope.addExtension(type, spec.name, ObjRecord(stmt, isMutable = false, visibility = spec.visibility, declaringClass = null))
val wrapperName = spec.extensionWrapperName ?: extensionCallableName(typeName, spec.name) 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) scope.addItem(wrapperName, false, wrapper, spec.visibility, recordType = ObjRecord.Type.Fun)
} ?: run { } ?: run {
val th = scope.thisObj val th = scope.thisObj
@ -238,7 +215,8 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec):
this.currentClassCtx = savedCtx 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 compiledFnBody
} else { } else {
scope.addItem(spec.name, false, compiledFnBody, spec.visibility, callSignature = spec.externCallSignature) scope.addItem(spec.name, false, compiledFnBody, spec.visibility, callSignature = spec.externCallSignature)

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -39,7 +39,7 @@ fun nextFrameId(): Long = FrameIdGen.nextId()
* *
* There are special types of scopes: * 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( open class Scope(
var parent: Scope?, var parent: Scope?,
@ -76,14 +76,22 @@ open class Scope(
internal fun setThisVariants(primary: Obj, extras: List<Obj>) { internal fun setThisVariants(primary: Obj, extras: List<Obj>) {
thisObj = primary thisObj = primary
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.clear()
thisVariants.add(primary) thisVariants.add(primary)
for (obj in extras) { for (obj in extrasSnapshot) {
if (obj !== primary && !thisVariants.contains(obj)) { if (obj !== primary && !thisVariants.contains(obj)) {
thisVariants.add(obj) thisVariants.add(obj)
} }
} }
} }
}
fun addExtension(cls: ObjClass, name: String, record: ObjRecord) { fun addExtension(cls: ObjClass, name: String, record: ObjRecord) {
extensions.getOrPut(cls) { mutableMapOf() }[name] = record extensions.getOrPut(cls) { mutableMapOf() }[name] = record
@ -98,7 +106,7 @@ open class Scope(
for (cls in receiverClass.mro) { for (cls in receiverClass.mro) {
s.extensions[cls]?.get(name)?.let { return it } 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.closureScope.findExtension(receiverClass, name)?.let { return it }
} }
s = s.parent s = s.parent
@ -122,7 +130,7 @@ open class Scope(
/** /**
* Internal lookup helpers that deliberately avoid invoking overridden `get` implementations * 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 * 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. * 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 val effectiveCaller = caller ?: currentClassCtx
while (s != null && hops++ < 1024) { while (s != null && hops++ < 1024) {
tryGetLocalRecord(s, name, effectiveCaller)?.let { return it } 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 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 * Perform base Scope.get semantics for this frame without delegating into parent.get
* virtual dispatch. This checks: * virtual dispatch. This checks:
* - locals/bindings in this frame * - 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 * - finally falls back to this frame's `thisObj` instance/class members
*/ */
internal fun baseGetIgnoreClosure(name: String): ObjRecord? { internal fun baseGetIgnoreClosure(name: String): ObjRecord? {
@ -211,7 +212,7 @@ open class Scope(
* - locals/bindings of each frame * - locals/bindings of each frame
* - then instance/class members of each frame's `thisObj`. * - then instance/class members of each frame's `thisObj`.
* This completely avoids invoking overridden `get` implementations, preventing * 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? { internal fun chainLookupWithMembers(name: String, caller: net.sergeych.lyng.obj.ObjClass? = currentClassCtx, followClosure: Boolean = false): ObjRecord? {
var s: Scope? = this var s: Scope? = this
@ -228,7 +229,7 @@ open class Scope(
} else return rec } 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 return null
} }
@ -635,11 +636,6 @@ open class Scope(
it.value = value it.value = value
// keep local binding index consistent within the frame // keep local binding index consistent within the frame
localBindings[name] = it 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) bumpClassLayoutIfNeeded(name, value, recordType)
it it
} ?: addItem(name, true, value, visibility, writeVisibility, recordType, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride) } ?: 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 // Index this binding within the current frame to help resolve locals across suspension
localBindings[name] = rec 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) // Map to a slot for fast local access (ensure consistency)
if (nameToSlot.isEmpty()) { if (nameToSlot.isEmpty()) {
allocateSlotFor(name, rec) allocateSlotFor(name, rec)
@ -751,12 +734,7 @@ open class Scope(
} }
fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend Scope.() -> Obj) { fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend Scope.() -> Obj) {
val newFn = object : Statement() { val newFn = net.sergeych.lyng.obj.ObjNativeCallable { fn() }
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj = scope.fn()
}
for (name in names) { for (name in names) {
addItem( addItem(
name, name,
@ -834,7 +812,7 @@ open class Scope(
} }
open fun applyClosure(closure: Scope, preferredThisType: String? = null): 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 { internal fun applyClosureForBytecode(closure: Scope, preferredThisType: String? = null): Scope {
return BytecodeClosureScope(this, closure, preferredThisType) return BytecodeClosureScope(this, closure, preferredThisType)

View File

@ -40,6 +40,7 @@ class Script(
private val moduleBytecode: CmdFunction? = null, private val moduleBytecode: CmdFunction? = null,
// private val catchReturn: Boolean = false, // private val catchReturn: Boolean = false,
) : Statement() { ) : Statement() {
fun statements(): List<Statement> = statements
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
scope.pos = pos scope.pos = pos
@ -86,11 +87,10 @@ class Script(
moduleBytecode?.let { fn -> moduleBytecode?.let { fn ->
return CmdVm().execute(fn, scope, scope.args) return CmdVm().execute(fn, scope, scope.args)
} }
var lastResult: Obj = ObjVoid if (statements.isNotEmpty()) {
for (s in statements) { scope.raiseIllegalState("interpreter execution is not supported; missing module bytecode")
lastResult = s.execute(scope)
} }
return lastResult return ObjVoid
} }
private suspend fun seedModuleSlots(scope: Scope) { private suspend fun seedModuleSlots(scope: Scope) {
@ -332,7 +332,7 @@ class Script(
addVoidFn("assert") { addVoidFn("assert") {
val cond = requiredArg<ObjBool>(0) val cond = requiredArg<ObjBool>(0)
val message = if (args.size > 1) 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 "" else ""
if (!cond.value == true) if (!cond.value == true)
raiseError(ObjAssertionFailedException(this, "Assertion failed$message")) raiseError(ObjAssertionFailedException(this, "Assertion failed$message"))
@ -385,23 +385,23 @@ class Script(
will be accepted. will be accepted.
""".trimIndent() """.trimIndent()
) { ) {
val code: Statement val code: Obj
val expectedClass: ObjClass? val expectedClass: ObjClass?
when (args.size) { when (args.size) {
1 -> { 1 -> {
code = requiredArg<Statement>(0) code = requiredArg<Obj>(0)
expectedClass = null expectedClass = null
} }
2 -> { 2 -> {
code = requiredArg<Statement>(1) code = requiredArg<Obj>(1)
expectedClass = requiredArg<ObjClass>(0) expectedClass = requiredArg<ObjClass>(0)
} }
else -> raiseIllegalArgument("Expected 1 or 2 arguments, got ${args.size}") else -> raiseIllegalArgument("Expected 1 or 2 arguments, got ${args.size}")
} }
val result = try { val result = try {
code.execute(this) code.callOn(this)
null null
} catch (e: ExecutionError) { } catch (e: ExecutionError) {
e.errorObject e.errorObject
@ -441,7 +441,7 @@ class Script(
val condition = requiredArg<ObjBool>(0) val condition = requiredArg<ObjBool>(0)
if (!condition.value) { if (!condition.value) {
var message = args.list.getOrNull(1) 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") raiseIllegalArgument(message?.toString() ?: "requirement not met")
} }
ObjVoid ObjVoid
@ -450,7 +450,7 @@ class Script(
val condition = requiredArg<ObjBool>(0) val condition = requiredArg<ObjBool>(0)
if (!condition.value) { if (!condition.value) {
var message = args.list.getOrNull(1) 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") raiseIllegalState(message?.toString() ?: "check failed")
} }
ObjVoid ObjVoid
@ -460,27 +460,23 @@ class Script(
ObjVoid ObjVoid
} }
addFn("run") { addFn("run") {
requireOnlyArg<Statement>().execute(this) requireOnlyArg<Obj>().callOn(this)
} }
addFn("cached") { addFn("cached") {
val builder = requireOnlyArg<Statement>() val builder = requireOnlyArg<Obj>()
val capturedScope = this val capturedScope = this
var calculated = false var calculated = false
var cachedValue: Obj = ObjVoid var cachedValue: Obj = ObjVoid
val thunk = object : Statement() { ObjNativeCallable {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj {
if (!calculated) { if (!calculated) {
cachedValue = builder.execute(capturedScope) cachedValue = builder.callOn(capturedScope)
calculated = true calculated = true
} }
return cachedValue cachedValue
} }
} }
thunk
}
addFn("lazy") { addFn("lazy") {
val builder = requireOnlyArg<Statement>() val builder = requireOnlyArg<Obj>()
ObjLazyDelegate(builder, this) ObjLazyDelegate(builder, this)
} }
addVoidFn("delay") { addVoidFn("delay") {
@ -527,9 +523,9 @@ class Script(
addConst("MapEntry", ObjMapEntry.type) addConst("MapEntry", ObjMapEntry.type)
addFn("launch") { addFn("launch") {
val callable = requireOnlyArg<Statement>() val callable = requireOnlyArg<Obj>()
ObjDeferred(globalDefer { ObjDeferred(globalDefer {
callable.execute(this@addFn) callable.callOn(this@addFn)
}) })
} }
@ -541,7 +537,7 @@ class Script(
addFn("flow", callSignature = CallSignature(tailBlockReceiverType = "FlowBuilder")) { addFn("flow", callSignature = CallSignature(tailBlockReceiverType = "FlowBuilder")) {
// important is: current context contains closure often used in call; // important is: current context contains closure often used in call;
// we'll need it for the producer // we'll need it for the producer
ObjFlow(requireOnlyArg<Statement>(), this) ObjFlow(requireOnlyArg<Obj>(), this)
} }
val pi = ObjReal(PI) val pi = ObjReal(PI)

View File

@ -113,6 +113,96 @@ class BytecodeCompiler(
is BlockStatement -> compileBlock(name, stmt) is BlockStatement -> compileBlock(name, stmt)
is net.sergeych.lyng.InlineBlockStatement -> compileInlineBlock(name, stmt) is net.sergeych.lyng.InlineBlockStatement -> compileInlineBlock(name, stmt)
is VarDeclStatement -> compileVarDecl(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 -> { is DelegatedVarDeclStatement -> {
val value = emitDelegatedVarDecl(stmt) ?: return null val value = emitDelegatedVarDecl(stmt) ?: return null
builder.emit(Opcode.RET, value.slot) builder.emit(Opcode.RET, value.slot)
@ -131,6 +221,60 @@ class BytecodeCompiler(
localSlotCaptures 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 -> { is DestructuringVarDeclStatement -> {
val value = emitDestructuringVarDecl(stmt) ?: return null val value = emitDestructuringVarDecl(stmt) ?: return null
builder.emit(Opcode.RET, value.slot) builder.emit(Opcode.RET, value.slot)
@ -317,6 +461,17 @@ class BytecodeCompiler(
return id 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? { private fun compileRef(ref: ObjRef): CompiledValue? {
return when (ref) { return when (ref) {
is ConstRef -> compileConst(ref.constValue) is ConstRef -> compileConst(ref.constValue)
@ -2535,15 +2690,7 @@ class BytecodeCompiler(
} }
private fun compileFieldRef(ref: FieldRef): CompiledValue? { private fun compileFieldRef(ref: FieldRef): CompiledValue? {
val receiverClass = resolveReceiverClass(ref.target) val receiverClass = resolveReceiverClass(ref.target) ?: ObjDynamic.type
?: if (isAllowedObjectMember(ref.name)) {
Obj.rootObjectType
} else {
throw BytecodeCompileException(
"Member access requires compile-time receiver type: ${ref.name}",
Pos.builtIn
)
}
if (receiverClass == ObjDynamic.type) { if (receiverClass == ObjDynamic.type) {
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
val dst = allocSlot() val dst = allocSlot()
@ -2625,7 +2772,7 @@ class BytecodeCompiler(
} }
val extSlot = resolveExtensionGetterSlot(receiverClass, ref.name) val extSlot = resolveExtensionGetterSlot(receiverClass, ref.name)
?: throw BytecodeCompileException( ?: throw BytecodeCompileException(
"Unknown member ${ref.name} on ${receiverClass.className}", missingFieldMessage(receiverClass, ref.name),
Pos.builtIn Pos.builtIn
) )
val callee = ensureObjSlot(extSlot) val callee = ensureObjSlot(extSlot)
@ -3627,15 +3774,7 @@ class BytecodeCompiler(
private fun compileMethodCall(ref: MethodCallRef): CompiledValue? { private fun compileMethodCall(ref: MethodCallRef): CompiledValue? {
val callPos = callSitePos() val callPos = callSitePos()
val receiverClass = resolveReceiverClass(ref.receiver) val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type
?: if (isAllowedObjectMember(ref.name)) {
Obj.rootObjectType
} else {
throw BytecodeCompileException(
"Member call requires compile-time receiver type: ${ref.name}",
Pos.builtIn
)
}
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
val dst = allocSlot() val dst = allocSlot()
if (receiverClass == ObjDynamic.type) { if (receiverClass == ObjDynamic.type) {
@ -3695,6 +3834,41 @@ class BytecodeCompiler(
builder.mark(endLabel) builder.mark(endLabel)
return CompiledValue(dst, SlotType.OBJ) 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)) { if (isKnownClassReceiver(ref.receiver)) {
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name)) val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
val memberSlot = allocSlot() val memberSlot = allocSlot()
@ -3725,7 +3899,7 @@ class BytecodeCompiler(
} }
val extSlot = resolveExtensionCallableSlot(receiverClass, ref.name) val extSlot = resolveExtensionCallableSlot(receiverClass, ref.name)
?: throw BytecodeCompileException( ?: throw BytecodeCompileException(
"Unknown member ${ref.name} on ${receiverClass.className}", missingMemberMessage(receiverClass, ref.name),
Pos.builtIn Pos.builtIn
) )
val callee = ensureObjSlot(extSlot) val callee = ensureObjSlot(extSlot)
@ -3932,15 +4106,16 @@ class BytecodeCompiler(
return CallArgs(base = argSlots[0], count = argSlots.size, planId = planId) return CallArgs(base = argSlots[0], count = argSlots.size, planId = planId)
} }
private fun compileArgValue(stmt: Statement): CompiledValue? { private fun compileArgValue(value: Obj): CompiledValue? {
return when (stmt) { return when (value) {
is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos) is ExpressionStatement -> compileRefWithFallback(value.ref, null, value.pos)
else -> { is Statement -> {
throw BytecodeCompileException( throw BytecodeCompileException(
"Bytecode compile error: unsupported argument expression", "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 { private fun emitDeclFunction(stmt: net.sergeych.lyng.FunctionDeclStatement): CompiledValue {
val constId = builder.addConst(BytecodeConst.FunctionDecl(stmt.spec)) 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) builder.emit(Opcode.DECL_FUNCTION, constId, dst)
updateSlotType(dst, SlotType.OBJ) updateSlotType(dst, SlotType.OBJ)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
@ -4151,6 +4331,190 @@ class BytecodeCompiler(
return CompiledValue(dst, SlotType.OBJ) 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? { private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? {
val target = if (stmt is BytecodeStatement) stmt.original else stmt val target = if (stmt is BytecodeStatement) stmt.original else stmt
setPos(target.pos) setPos(target.pos)
@ -4181,8 +4545,16 @@ class BytecodeCompiler(
} }
is BlockStatement -> emitBlock(target, true) is BlockStatement -> emitBlock(target, true)
is VarDeclStatement -> emitVarDecl(target) 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 DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
is DestructuringVarDeclStatement -> emitDestructuringVarDecl(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.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
is net.sergeych.lyng.ClassDeclStatement -> emitDeclClass(target) is net.sergeych.lyng.ClassDeclStatement -> emitDeclClass(target)
is net.sergeych.lyng.FunctionDeclStatement -> emitDeclFunction(target) is net.sergeych.lyng.FunctionDeclStatement -> emitDeclFunction(target)
@ -4215,7 +4587,15 @@ class BytecodeCompiler(
} }
} }
is VarDeclStatement -> emitVarDecl(target) 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 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 IfStatement -> compileIfStatement(target)
is net.sergeych.lyng.ClassDeclStatement -> emitDeclClass(target) is net.sergeych.lyng.ClassDeclStatement -> emitDeclClass(target)
is net.sergeych.lyng.FunctionDeclStatement -> emitDeclFunction(target) is net.sergeych.lyng.FunctionDeclStatement -> emitDeclFunction(target)
@ -5858,6 +6238,11 @@ class BytecodeCompiler(
private fun resolveTypeRefClass(ref: ObjRef): ObjClass? { private fun resolveTypeRefClass(ref: ObjRef): ObjClass? {
return when (ref) { return when (ref) {
is ConstRef -> ref.constValue as? ObjClass 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 LocalSlotRef -> resolveTypeNameClass(ref.name) ?: nameObjClass[ref.name]
is LocalVarRef -> resolveTypeNameClass(ref.name) ?: nameObjClass[ref.name] is LocalVarRef -> resolveTypeNameClass(ref.name) ?: nameObjClass[ref.name]
is FastLocalVarRef -> 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) } 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 -> { is DelegatedVarDeclStatement -> {
val slotIndex = stmt.slotIndex val slotIndex = stmt.slotIndex
val scopeId = stmt.scopeId ?: 0 val scopeId = stmt.scopeId ?: 0
@ -6399,6 +6803,15 @@ class BytecodeCompiler(
is net.sergeych.lyng.ReturnStatement -> { is net.sergeych.lyng.ReturnStatement -> {
stmt.resultExpr?.let { collectScopeSlots(it) } 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 -> { is net.sergeych.lyng.ThrowStatement -> {
collectScopeSlots(stmt.throwExpr) collectScopeSlots(stmt.throwExpr)
} }
@ -6466,7 +6879,11 @@ class BytecodeCompiler(
} }
private fun isModuleSlot(scopeId: Int, name: String?): Boolean { 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 (scopeId != moduleId) return false
if (allowedScopeNames == null || name == null) return true if (allowedScopeNames == null || name == null) return true
return allowedScopeNames.contains(name) return allowedScopeNames.contains(name)

View File

@ -75,6 +75,95 @@ sealed class BytecodeConst {
val visibility: Visibility, val visibility: Visibility,
val isTransient: Boolean, val isTransient: Boolean,
) : BytecodeConst() ) : 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( data class DestructureDecl(
val pattern: ListLiteralRef, val pattern: ListLiteralRef,
val names: List<String>, val names: List<String>,

View File

@ -144,6 +144,21 @@ class BytecodeStatement private constructor(
is net.sergeych.lyng.ClassDeclStatement -> false is net.sergeych.lyng.ClassDeclStatement -> false
is net.sergeych.lyng.FunctionDeclStatement -> false is net.sergeych.lyng.FunctionDeclStatement -> false
is net.sergeych.lyng.EnumDeclStatement -> 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 -> { is net.sergeych.lyng.TryStatement -> {
containsUnsupportedStatement(target.body) || containsUnsupportedStatement(target.body) ||
target.catches.any { containsUnsupportedStatement(it.block) } || target.catches.any { containsUnsupportedStatement(it.block) } ||
@ -266,6 +281,115 @@ class BytecodeStatement private constructor(
stmt.pos 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 else -> stmt
} }
} }

View File

@ -161,7 +161,10 @@ class CmdBuilder {
Opcode.PUSH_TRY -> Opcode.PUSH_TRY ->
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP) listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE, 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 -> Opcode.ASSIGN_DESTRUCTURE ->
listOf(OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, 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_ENUM -> CmdDeclEnum(operands[0], operands[1])
Opcode.DECL_FUNCTION -> CmdDeclFunction(operands[0], operands[1]) Opcode.DECL_FUNCTION -> CmdDeclFunction(operands[0], operands[1])
Opcode.DECL_CLASS -> CmdDeclClass(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.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3]) Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1]) Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1])

View File

@ -215,6 +215,15 @@ object CmdDisassembler {
is CmdDeclEnum -> Opcode.DECL_ENUM to intArrayOf(cmd.constId, cmd.slot) is CmdDeclEnum -> Opcode.DECL_ENUM to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclFunction -> Opcode.DECL_FUNCTION 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 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 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 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) is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
@ -287,7 +296,10 @@ object CmdDisassembler {
Opcode.PUSH_TRY -> Opcode.PUSH_TRY ->
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP) listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE, 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 -> Opcode.ASSIGN_DESTRUCTURE ->
listOf(OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,

View File

@ -72,14 +72,24 @@ class CmdNop : Cmd() {
class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() { class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdMoveInt(internal val src: Int, internal val dst: Int) : Cmd() { class CmdMoveInt(internal val src: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 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() { class CmdMoveReal(internal val src: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdMoveBool(internal val src: Int, internal val dst: Int) : Cmd() { class CmdMoveBool(internal val src: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 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() { class CmdLoadObjAddr(internal val addrSlot: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 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() { class CmdLoadIntAddr(internal val addrSlot: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 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() { class CmdLoadRealAddr(internal val addrSlot: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 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() { class CmdLoadBoolAddr(internal val addrSlot: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
@ -1302,17 +1342,23 @@ class CmdClearPendingThrowable : Cmd() {
class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() { class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
if (slot < frame.fn.scopeSlotCount) {
val decl = frame.fn.constants[constId] as? BytecodeConst.LocalDecl val decl = frame.fn.constants[constId] as? BytecodeConst.LocalDecl
?: error("DECL_LOCAL expects LocalDecl at $constId") ?: error("DECL_LOCAL expects LocalDecl at $constId")
val target = frame.scopeTarget(slot)
frame.ensureScopeSlot(target, slot)
val value = frame.slotToObj(slot).byValueCopy() val value = frame.slotToObj(slot).byValueCopy()
frame.ensureScope().addItem( target.updateSlotFor(
decl.name, decl.name,
decl.isMutable, ObjRecord(
value, value,
decl.isMutable,
decl.visibility, decl.visibility,
recordType = ObjRecord.Type.Other, isTransient = decl.isTransient,
isTransient = decl.isTransient type = ObjRecord.Type.Other
) )
)
}
return return
} }
} }
@ -1332,16 +1378,21 @@ class CmdDeclDelegated(internal val constId: Int, internal val slot: Int) : Cmd(
} catch (_: Exception) { } catch (_: Exception) {
initValue initValue
} }
val rec = frame.ensureScope().addItem( if (slot < frame.fn.scopeSlotCount) {
val target = frame.scopeTarget(slot)
frame.ensureScopeSlot(target, slot)
target.updateSlotFor(
decl.name, decl.name,
decl.isMutable, ObjRecord(
ObjNull, ObjNull,
decl.isMutable,
decl.visibility, decl.visibility,
recordType = ObjRecord.Type.Delegated, isTransient = decl.isTransient,
isTransient = decl.isTransient type = ObjRecord.Type.Delegated
).also { it.delegate = finalDelegate }
) )
rec.delegate = finalDelegate }
frame.storeObjResult(slot, finalDelegate) frame.setObjUnchecked(slot, finalDelegate)
return 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 return
} }
} }
@ -1370,18 +1421,387 @@ class CmdDeclFunction(internal val constId: Int, internal val slot: Int) : Cmd()
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val decl = frame.fn.constants[constId] as? BytecodeConst.FunctionDecl val decl = frame.fn.constants[constId] as? BytecodeConst.FunctionDecl
?: error("DECL_FUNCTION expects FunctionDecl at $constId") ?: error("DECL_FUNCTION expects FunctionDecl at $constId")
val result = executeFunctionDecl(frame.ensureScope(), decl.spec) val captureNames = captureNamesForFunctionDecl(decl.spec)
frame.storeObjResult(slot, result) val captureRecords = buildFunctionCaptureRecords(frame, captureNames)
val result = executeFunctionDecl(frame.ensureScope(), decl.spec, captureRecords)
frame.setObjUnchecked(slot, result)
return return
} }
} }
private fun captureNamesForFunctionDecl(spec: net.sergeych.lyng.FunctionDeclSpec): List<String> {
if (spec.captureSlots.isNotEmpty()) {
return spec.captureSlots.map { it.name }
}
return captureNamesForStatement(spec.fnBody)
}
private fun captureNamesForStatement(stmt: Statement?): List<String> {
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<String>()
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<String>): List<ObjRecord>? {
if (captureNames.isEmpty()) return null
val records = ArrayList<ObjRecord>(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() { class CmdDeclClass(internal val constId: Int, internal val slot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val decl = frame.fn.constants[constId] as? BytecodeConst.ClassDecl val decl = frame.fn.constants[constId] as? BytecodeConst.ClassDecl
?: error("DECL_CLASS expects ClassDecl at $constId") ?: error("DECL_CLASS expects ClassDecl at $constId")
val result = executeClassDecl(frame.ensureScope(), decl.spec) val bodyCaptureNames = mergeCaptureNames(
frame.storeObjResult(slot, result) 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<String>, fallback: List<String>): List<String> {
if (fallback.isEmpty()) return primary
if (primary.isEmpty()) return fallback
val ordered = LinkedHashSet<String>(primary.size + fallback.size)
ordered.addAll(primary)
ordered.addAll(fallback)
return ordered.toList()
}
private fun captureNamesFromFrame(frame: CmdFrame): List<String> {
val ordered = LinkedHashSet<String>()
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 return
} }
} }
@ -1504,33 +1924,6 @@ private fun resolveLocalSlotIndex(fn: CmdFunction, name: String, preferCapture:
return null 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() { class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val decl = frame.fn.constants[constId] as? BytecodeConst.ExtensionPropertyDecl val decl = frame.fn.constants[constId] as? BytecodeConst.ExtensionPropertyDecl
@ -1578,7 +1971,7 @@ class CmdCallDirect(
val args = frame.buildArguments(argBase, argCount) val args = frame.buildArguments(argBase, argCount)
if (callee is Statement) { if (callee is Statement) {
val bytecodeBody = (callee as? BytecodeBodyProvider)?.bytecodeBody() 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") frame.ensureScope().raiseIllegalState("bytecode runtime cannot call non-bytecode Statement")
} }
} }
@ -1619,7 +2012,7 @@ class CmdCallSlot(
val scope = frame.ensureScope() val scope = frame.ensureScope()
if (callee is Statement) { if (callee is Statement) {
val bytecodeBody = (callee as? BytecodeBodyProvider)?.bytecodeBody() 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") scope.raiseIllegalState("bytecode runtime cannot call non-bytecode Statement")
} }
} }
@ -1897,18 +2290,26 @@ class CmdCallMemberSlot(
frame.storeObjResult(dst, result) frame.storeObjResult(dst, result)
return return
} }
val scope = frame.ensureScope()
val decl = rec.declaringClass ?: receiver.objClass 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) { val result = when (rec.type) {
ObjRecord.Type.Property -> { ObjRecord.Type.Property -> {
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(frame.ensureScope(), receiver, decl) if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, receiver, decl)
else frame.ensureScope().raiseError("property $name cannot be called with arguments") else scope.raiseError("property $name cannot be called with arguments")
} }
ObjRecord.Type.Fun -> { ObjRecord.Type.Fun -> {
val callScope = inst?.instanceScope ?: frame.ensureScope() val callScope = inst?.instanceScope ?: scope
rec.value.invoke(callScope, receiver, callArgs, decl) rec.value.invoke(callScope, receiver, callArgs, decl)
} }
ObjRecord.Type.Delegated -> { ObjRecord.Type.Delegated -> {
val scope = frame.ensureScope()
val delegate = when (receiver) { val delegate = when (receiver) {
is ObjInstance -> { is ObjInstance -> {
val storageName = decl.mangledName(name) val storageName = decl.mangledName(name)
@ -2002,7 +2403,6 @@ class BytecodeLambdaCallable(
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also { val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also {
it.args = scope.args it.args = scope.args
} }
if (paramSlotPlan.isNotEmpty()) context.applySlotPlan(paramSlotPlan)
if (captureRecords != null) { if (captureRecords != null) {
context.captureRecords = captureRecords context.captureRecords = captureRecords
context.captureNames = captureNames context.captureNames = captureNames
@ -2027,19 +2427,13 @@ class BytecodeLambdaCallable(
val itSlot = slotPlan["it"] val itSlot = slotPlan["it"]
if (itSlot != null) { if (itSlot != null) {
frame.frame.setObj(itSlot, itValue) 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 { } else {
argsDeclaration.assignToFrame( argsDeclaration.assignToFrame(
context, context,
arguments, arguments,
slotPlan, slotPlan,
frame.frame, frame.frame
defaultAccessType = AccessType.Val
) )
} }
} }
@ -2235,9 +2629,7 @@ class CmdFrame(
if (!visited.add(current)) continue if (!visited.add(current)) continue
if (current.getSlotIndexOf(slotName) != null) return current if (current.getSlotIndexOf(slotName) != null) return current
current.parent?.let { queue.add(it) } current.parent?.let { queue.add(it) }
if (current is ClosureScope) { if (current is BytecodeClosureScope) {
queue.add(current.closureScope)
} else if (current is BytecodeClosureScope) {
queue.add(current.closureScope) queue.add(current.closureScope)
} else if (current is ApplyScope) { } else if (current is ApplyScope) {
queue.add(current.applied) queue.add(current.applied)
@ -2255,9 +2647,7 @@ class CmdFrame(
if (!visited.add(current)) continue if (!visited.add(current)) continue
if (current.getLocalRecordDirect(name) != null) return current if (current.getLocalRecordDirect(name) != null) return current
current.parent?.let { queue.add(it) } current.parent?.let { queue.add(it) }
if (current is ClosureScope) { if (current is BytecodeClosureScope) {
queue.add(current.closureScope)
} else if (current is BytecodeClosureScope) {
queue.add(current.closureScope) queue.add(current.closureScope)
} else if (current is ApplyScope) { } else if (current is ApplyScope) {
queue.add(current.applied) queue.add(current.applied)
@ -2276,9 +2666,7 @@ class CmdFrame(
if (current is ModuleScope) return current if (current is ModuleScope) return current
if (current.parent is ModuleScope) return current if (current.parent is ModuleScope) return current
current.parent?.let { queue.add(it) } current.parent?.let { queue.add(it) }
if (current is ClosureScope) { if (current is BytecodeClosureScope) {
queue.add(current.closureScope)
} else if (current is BytecodeClosureScope) {
queue.add(current.closureScope) queue.add(current.closureScope)
} else if (current is ApplyScope) { } else if (current is ApplyScope) {
queue.add(current.applied) queue.add(current.applied)
@ -2360,7 +2748,7 @@ class CmdFrame(
fun pushScope(plan: Map<String, Int>, captures: List<String>) { fun pushScope(plan: Map<String, Int>, captures: List<String>) {
if (scope.skipScopeCreation) { if (scope.skipScopeCreation) {
val snapshot = scope.applySlotPlanWithSnapshot(plan) val snapshot = emptyMap<String, Int?>()
slotPlanStack.addLast(snapshot) slotPlanStack.addLast(snapshot)
virtualDepth += 1 virtualDepth += 1
scopeStack.addLast(scope) scopeStack.addLast(scope)
@ -2369,9 +2757,6 @@ class CmdFrame(
scopeStack.addLast(scope) scopeStack.addLast(scope)
scopeVirtualStack.addLast(false) scopeVirtualStack.addLast(false)
scope = scope.createChildScope() scope = scope.createChildScope()
if (plan.isNotEmpty()) {
scope.applySlotPlan(plan)
}
} }
captureStack.addLast(captures) captureStack.addLast(captures)
scopeDepth += 1 scopeDepth += 1
@ -2417,11 +2802,8 @@ class CmdFrame(
scopeStack.addLast(scope) scopeStack.addLast(scope)
slotPlanScopeStack.addLast(true) slotPlanScopeStack.addLast(true)
scope = scope.createChildScope() scope = scope.createChildScope()
if (plan.isNotEmpty()) {
scope.applySlotPlan(plan)
}
} else { } else {
val snapshot = scope.applySlotPlanWithSnapshot(plan) val snapshot = emptyMap<String, Int?>()
slotPlanStack.addLast(snapshot) slotPlanStack.addLast(snapshot)
slotPlanScopeStack.addLast(false) slotPlanScopeStack.addLast(false)
virtualDepth += 1 virtualDepth += 1
@ -2456,9 +2838,15 @@ class CmdFrame(
if (slot < fn.scopeSlotCount) { if (slot < fn.scopeSlotCount) {
val target = scopeTarget(slot) val target = scopeTarget(slot)
val index = ensureScopeSlot(target, 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) target.setSlotValue(index, value)
} else { } else {
val localIndex = slot - fn.scopeSlotCount val localIndex = slot - fn.scopeSlotCount
ensureLocalMutable(localIndex)
when (val existing = frame.getRawObj(localIndex)) { when (val existing = frame.getRawObj(localIndex)) {
is FrameSlotRef -> { is FrameSlotRef -> {
existing.write(value) 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 { suspend fun getInt(slot: Int): Long {
return if (slot < fn.scopeSlotCount) { return if (slot < fn.scopeSlotCount) {
getScopeSlotValue(slot).toLong() getScopeSlotValue(slot).toLong()
@ -2498,7 +2895,7 @@ class CmdFrame(
fun getLocalInt(local: Int): Long = frame.getInt(local) fun getLocalInt(local: Int): Long = frame.getInt(local)
fun setInt(slot: Int, value: Long) { fun setIntUnchecked(slot: Int, value: Long) {
if (slot < fn.scopeSlotCount) { if (slot < fn.scopeSlotCount) {
val target = scopeTarget(slot) val target = scopeTarget(slot)
val index = ensureScopeSlot(target, 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) { fun setLocalInt(local: Int, value: Long) {
frame.setInt(local, value) frame.setInt(local, value)
} }
@ -2547,6 +2972,34 @@ class CmdFrame(
} }
fun setReal(slot: Int, value: Double) { 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) { if (slot < fn.scopeSlotCount) {
val target = scopeTarget(slot) val target = scopeTarget(slot)
val index = ensureScopeSlot(target, slot) val index = ensureScopeSlot(target, slot)
@ -2593,6 +3046,34 @@ class CmdFrame(
fun getLocalBool(local: Int): Boolean = frame.getBool(local) fun getLocalBool(local: Int): Boolean = frame.getBool(local)
fun setBool(slot: Int, value: Boolean) { 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) { if (slot < fn.scopeSlotCount) {
val target = scopeTarget(slot) val target = scopeTarget(slot)
val index = ensureScopeSlot(target, 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) { suspend fun throwObj(pos: Pos, value: Obj) {
var errorObject = value var errorObject = value
val throwScope = ensureScope().createChildScope(pos = pos) val throwScope = ensureScope().createChildScope(pos = pos)
@ -2889,5 +3380,17 @@ class CmdFrame(
return index 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. // Scope depth resolution is no longer used; all scope slots are resolved against the current frame.
} }

View File

@ -168,6 +168,15 @@ enum class Opcode(val code: Int) {
BIND_DELEGATE_LOCAL(0xC4), BIND_DELEGATE_LOCAL(0xC4),
DECL_FUNCTION(0xC5), DECL_FUNCTION(0xC5),
DECL_CLASS(0xC6), 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 { companion object {

View File

@ -447,7 +447,7 @@ open class Obj {
caller.members[name]?.let { rec -> caller.members[name]?.let { rec ->
if (rec.visibility == Visibility.Private && !rec.isAbstract) { if (rec.visibility == Visibility.Private && !rec.isAbstract) {
val resolved = resolveRecord(scope, rec, name, caller) 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.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, caller))
return resolved return resolved
} }
@ -461,7 +461,7 @@ open class Obj {
if (rec != null && !rec.isAbstract) { if (rec != null && !rec.isAbstract) {
val decl = rec.declaringClass ?: cls val decl = rec.declaringClass ?: cls
val resolved = resolveRecord(scope, rec, name, decl) 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.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl))
return resolved return resolved
} }
@ -476,7 +476,7 @@ open class Obj {
if (!canAccessMember(rec.visibility, decl, caller, name)) 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 ?: "?"})")) 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) 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.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl))
return resolved return resolved
} }
@ -500,16 +500,17 @@ open class Obj {
if (obj.type == ObjRecord.Type.Delegated) { if (obj.type == ObjRecord.Type.Delegated) {
val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate") val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
val th = if (this === ObjVoid) ObjNull else this val th = if (this === ObjVoid) ObjNull else this
val getValueRec = del.objClass.getInstanceMemberOrNull("getValue") val getValueRec = when (del) {
if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") { is ObjInstance -> del.methodRecordForKey("getValue")
val wrapper = object : Statement() { ?: del.instanceScope.objects["getValue"]
override val pos: Pos = Pos.builtIn ?: del.objClass.getInstanceMemberOrNull("getValue")
else -> del.objClass.getInstanceMemberOrNull("getValue")
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))
} }
if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") {
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( return obj.copy(
value = wrapper, value = wrapper,
@ -517,14 +518,11 @@ open class Obj {
) )
} }
val res = del.invokeInstanceMethod(scope, "getValue", Arguments(th, ObjString(name))) val res = del.invokeInstanceMethod(scope, "getValue", Arguments(th, ObjString(name)))
return obj.copy( return obj.copy(value = res, type = ObjRecord.Type.Other)
value = res,
type = ObjRecord.Type.Other
)
} }
val value = obj.value val value = obj.value
if (value is ObjProperty || obj.type == ObjRecord.Type.Property) { 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}") ?: scope.raiseError("Expected ObjProperty for property member $name, got ${value::class}")
val res = prop.callGetter(scope, this, decl) val res = prop.callGetter(scope, this, decl)
return ObjRecord(res, obj.isMutable) return ObjRecord(res, obj.isMutable)

View File

@ -61,7 +61,7 @@ val ObjClassType by lazy {
val names = mutableListOf<Obj>() val names = mutableListOf<Obj>()
for (c in cls.mro) { for (c in cls.mro) {
for ((n, rec) in c.members) { 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()) ObjList(names.toMutableList())
@ -79,7 +79,7 @@ val ObjClassType by lazy {
val names = mutableListOf<Obj>() val names = mutableListOf<Obj>()
for (c in cls.mro) { for (c in cls.mro) {
for ((n, rec) in c.members) { 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()) ObjList(names.toMutableList())
@ -136,7 +136,7 @@ open class ObjClass(
} }
} }
cls.classScope?.objects?.forEach { (name, rec) -> 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 val key = if (rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
res[name] = key res[name] = key
} }
@ -150,13 +150,13 @@ open class ObjClass(
val classNameObj by lazy { ObjString(className) } val classNameObj by lazy { ObjString(className) }
var constructorMeta: ArgsDeclaration? = null 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 * 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. * during construction in the instance scope of the object, once per class in the hierarchy.
*/ */
val instanceInitializers: MutableList<Statement> = mutableListOf() val instanceInitializers: MutableList<Obj> = mutableListOf()
/** /**
* the scope for class methods, initialize class vars, etc. * the scope for class methods, initialize class vars, etc.
@ -349,8 +349,7 @@ open class ObjClass(
if (cls.className == "Obj") break if (cls.className == "Obj") break
for ((name, rec) in cls.members) { for ((name, rec) in cls.members) {
if (rec.isAbstract) continue if (rec.isAbstract) continue
if (rec.value !is Statement && if (rec.type != ObjRecord.Type.Delegated &&
rec.type != ObjRecord.Type.Delegated &&
rec.type != ObjRecord.Type.Fun && rec.type != ObjRecord.Type.Fun &&
rec.type != ObjRecord.Type.Property) { rec.type != ObjRecord.Type.Property) {
continue continue
@ -363,9 +362,9 @@ open class ObjClass(
} }
cls.classScope?.objects?.forEach { (name, rec) -> cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.isAbstract) return@forEach if (rec.isAbstract) return@forEach
if (rec.value !is Statement && if (rec.type != ObjRecord.Type.Delegated &&
rec.type != ObjRecord.Type.Delegated && rec.type != ObjRecord.Type.Property &&
rec.type != ObjRecord.Type.Property) return@forEach rec.type != ObjRecord.Type.Fun) return@forEach
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
if (res.containsKey(key)) return@forEach if (res.containsKey(key)) return@forEach
val methodId = rec.methodId ?: cls.assignMethodId(name, rec) val methodId = rec.methodId ?: cls.assignMethodId(name, rec)
@ -533,7 +532,7 @@ open class ObjClass(
for (cls in mro) { for (cls in mro) {
// 1) members-defined methods and fields // 1) members-defined methods and fields
for ((k, v) in cls.members) { 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 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)) { if (!res.containsKey(key)) {
res[key] = v res[key] = v
@ -544,7 +543,7 @@ open class ObjClass(
cls.classScope?.objects?.forEach { (k, rec) -> cls.classScope?.objects?.forEach { (k, rec) ->
// ONLY copy methods and delegated members from class scope to instance scope. // 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. // 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 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 not already present, copy reference for dispatch
if (!res.containsKey(key)) { if (!res.containsKey(key)) {
@ -715,7 +714,7 @@ open class ObjClass(
instance.instanceScope.currentClassCtx = c instance.instanceScope.currentClassCtx = c
try { try {
for (initStmt in c.instanceInitializers) { for (initStmt in c.instanceInitializers) {
initStmt.execute(instance.instanceScope) initStmt.callOn(instance.instanceScope)
} }
} finally { } finally {
instance.instanceScope.currentClassCtx = savedCtx instance.instanceScope.currentClassCtx = savedCtx
@ -726,7 +725,7 @@ open class ObjClass(
c.instanceConstructor?.let { ctor -> c.instanceConstructor?.let { ctor ->
val execScope = val execScope =
instance.instanceScope.createChildScope(args = argsForThis ?: Arguments.EMPTY, newThisObj = instance) 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, methodId: Int? = null,
code: (suspend Scope.() -> Obj)? = null code: (suspend Scope.() -> Obj)? = null
) { ) {
val stmt = code?.let { statement { it() } } ?: ObjNull val stmt = code?.let { ObjNativeCallable { it() } } ?: ObjNull
createField( createField(
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass, name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
@ -958,8 +957,8 @@ open class ObjClass(
prop: ObjProperty? = null, prop: ObjProperty? = null,
methodId: Int? = null methodId: Int? = null
) { ) {
val g = getter?.let { statement { it() } } val g = getter?.let { ObjNativeCallable { it() } }
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } } val s = setter?.let { ObjNativeCallable { it(requiredArg(0)); ObjVoid } }
val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s) val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s)
createField( createField(
name, finalProp, false, visibility, writeVisibility, pos, declaringClass, name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
@ -971,7 +970,7 @@ open class ObjClass(
fun addClassConst(name: String, value: Obj) = createClassField(name, value) fun addClassConst(name: String, value: Obj) = createClassField(name, value)
fun addClassFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) { 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)
} }

View File

@ -21,7 +21,6 @@ import kotlinx.datetime.*
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.addClassFnDoc import net.sergeych.lyng.miniast.addClassFnDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.addPropertyDoc
@ -47,14 +46,19 @@ class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() {
if (rec != null) { if (rec != null) {
if (rec.type == ObjRecord.Type.Property) { if (rec.type == ObjRecord.Type.Property) {
val prop = rec.value as? ObjProperty val prop = rec.value as? ObjProperty
?: (rec.value as? Statement)?.execute(scope) as? ObjProperty
if (prop != null) { if (prop != null) {
return ObjRecord(prop.callGetter(scope, this, rec.declaringClass ?: cls), rec.isMutable) return ObjRecord(prop.callGetter(scope, this, rec.declaringClass ?: cls), rec.isMutable)
} }
} }
if (rec.type == ObjRecord.Type.Fun || rec.value is Statement) { if (rec.type == ObjRecord.Type.Fun) {
val s = rec.value as Statement val target = rec.value
return ObjRecord(net.sergeych.lyng.statement { s.execute(this.createChildScope(newThisObj = this@ObjDateTime)) }, rec.isMutable) return ObjRecord(
ObjNativeCallable {
val callScope = createChildScope(args = args, newThisObj = this@ObjDateTime)
target.callOn(callScope)
},
rec.isMutable
)
} }
return resolveRecord(scope, rec, name, rec.declaringClass ?: cls) return resolveRecord(scope, rec, name, rec.declaringClass ?: cls)
} }

View File

@ -19,7 +19,6 @@ package net.sergeych.lyng.obj
import net.sergeych.lyng.Arguments import net.sergeych.lyng.Arguments
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
class ObjDynamicContext(val delegate: ObjDynamic) : Obj() { class ObjDynamicContext(val delegate: ObjDynamic) : Obj() {
override val objClass: ObjClass get() = type 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 * Object that delegates all its field access/invocation operations to a callback. It is used to implement dynamic
* objects using "dynamic" keyword. * 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 override val objClass: ObjClass get() = type
// Capture the lexical scope used to build this dynamic so callbacks can see outer locals // 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 { override suspend fun readField(scope: Scope, name: String): ObjRecord {
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope 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) if (writeCallback != null)
it.asMutable it.asMutable
else else
@ -83,32 +82,32 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St
onNotFoundResult: (suspend () -> Obj?)? onNotFoundResult: (suspend () -> Obj?)?
): Obj { ): Obj {
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope 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) return over?.invoke(scope, scope.thisObj, args)
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult) ?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
} }
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) { override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope 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) ?: super.writeField(scope, name, newValue)
} }
override suspend fun getAt(scope: Scope, index: Obj): Obj { override suspend fun getAt(scope: Scope, index: Obj): Obj {
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope 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) ?: super.getAt(scope, index)
} }
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) { override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope 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) ?: super.putAt(scope, index, newValue)
} }
companion object { companion object {
suspend fun create(scope: Scope, builder: Statement): ObjDynamic { suspend fun create(scope: Scope, builder: Obj): ObjDynamic {
val delegate = ObjDynamic() val delegate = ObjDynamic()
val context = ObjDynamicContext(delegate) val context = ObjDynamicContext(delegate)
// Capture the function's lexical scope (scope) so callbacks can see outer locals like parameters. // 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) val buildScope = scope.createChildScope(newThisObj = context)
// Snapshot the caller scope to capture locals/args even if the runtime pools/reuses frames // Snapshot the caller scope to capture locals/args even if the runtime pools/reuses frames
delegate.builderScope = scope.snapshotForClosure() delegate.builderScope = scope.snapshotForClosure()
builder.execute(buildScope) builder.callOn(buildScope)
return delegate return delegate
} }

View File

@ -139,7 +139,7 @@ open class ObjException(
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) { class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
init { init {
constructorMeta = ArgsDeclaration( constructorMeta = ArgsDeclaration(
listOf(ArgsDeclaration.Item("message", defaultValue = statement { ObjString(name) })), listOf(ArgsDeclaration.Item("message", defaultValue = ObjNativeCallable { ObjString(name) })),
Token.Type.RPAREN Token.Type.RPAREN
) )
} }
@ -177,7 +177,7 @@ open class ObjException(
} }
val Root = ExceptionClass("Exception").apply { val Root = ExceptionClass("Exception").apply {
instanceInitializers.add(statement { instanceInitializers.add(ObjNativeCallable {
if (thisObj is ObjInstance) { if (thisObj is ObjInstance) {
val msg = get("message")?.value ?: ObjString("Exception") val msg = get("message")?.value ?: ObjString("Exception")
(thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg) (thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg)
@ -187,7 +187,7 @@ open class ObjException(
} }
ObjVoid ObjVoid
}) })
instanceConstructor = statement { ObjVoid } instanceConstructor = ObjNativeCallable { ObjVoid }
addPropertyDoc( addPropertyDoc(
name = "message", name = "message",
doc = "Human‑readable error message.", doc = "Human‑readable error message.",

View File

@ -71,13 +71,13 @@ class ObjFlowBuilder(val output: SendChannel<Obj>) : Obj() {
} }
} }
private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChannel<Obj> { private fun createLyngFlowInput(scope: Scope, producer: Obj): ReceiveChannel<Obj> {
val channel = Channel<Obj>(Channel.RENDEZVOUS) val channel = Channel<Obj>(Channel.RENDEZVOUS)
val builder = ObjFlowBuilder(channel) val builder = ObjFlowBuilder(channel)
val builderScope = scope.createChildScope(newThisObj = builder) val builderScope = scope.createChildScope(newThisObj = builder)
globalLaunch { globalLaunch {
try { try {
producer.execute(builderScope) producer.callOn(builderScope)
} catch (x: ScriptFlowIsNoMoreCollected) { } catch (x: ScriptFlowIsNoMoreCollected) {
// premature flow closing, OK // premature flow closing, OK
} catch (x: Exception) { } catch (x: Exception) {
@ -89,7 +89,7 @@ private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChann
return channel 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 override val objClass get() = type
@ -106,8 +106,8 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() {
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib"
) { ) {
val objFlow = thisAs<ObjFlow>() val objFlow = thisAs<ObjFlow>()
ObjFlowIterator(statement { ObjFlowIterator(ObjNativeCallable {
objFlow.producer.execute(this) 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 override val objClass: ObjClass get() = type

View File

@ -170,16 +170,17 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
} }
} }
del = del ?: scope.raiseError("Internal error: delegated property $name has no delegate") del = del ?: scope.raiseError("Internal error: delegated property $name has no delegate")
val getValueRec = del.objClass.getInstanceMemberOrNull("getValue") val getValueRec = when (del) {
if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") { is ObjInstance -> del.methodRecordForKey("getValue")
val wrapper = object : Statement() { ?: del.instanceScope.objects["getValue"]
override val pos: Pos = Pos.builtIn ?: del.objClass.getInstanceMemberOrNull("getValue")
else -> del.objClass.getInstanceMemberOrNull("getValue")
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))
} }
if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") {
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) return obj.copy(value = wrapper, type = ObjRecord.Type.Other)
} }

View File

@ -18,7 +18,6 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Arguments import net.sergeych.lyng.Arguments
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.addPropertyDoc
@ -131,10 +130,11 @@ val ObjIterable by lazy {
returns = type("lyng.Map"), returns = type("lyng.Map"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib"
) { ) {
val association = requireOnlyArg<Statement>() val association = requireOnlyArg<Obj>()
val result = ObjMap() val result = ObjMap()
thisObj.toFlow(this).collect { thisObj.toFlow(this).collect {
result.map[association.call(this, it)] = it val callScope = createChildScope(args = Arguments(it))
result.map[association.callOn(callScope)] = it
} }
result result
} }
@ -147,10 +147,10 @@ val ObjIterable by lazy {
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib"
) { ) {
val it = thisObj.invokeInstanceMethod(this, "iterator") val it = thisObj.invokeInstanceMethod(this, "iterator")
val fn = requiredArg<Statement>(0) val fn = requiredArg<Obj>(0)
while (it.invokeInstanceMethod(this, "hasNext").toBool()) { while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
val x = it.invokeInstanceMethod(this, "next") val x = it.invokeInstanceMethod(this, "next")
fn.execute(this.createChildScope(Arguments(listOf(x)))) fn.callOn(this.createChildScope(Arguments(listOf(x))))
} }
ObjVoid ObjVoid
} }
@ -163,10 +163,11 @@ val ObjIterable by lazy {
isOpen = true, isOpen = true,
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib"
) { ) {
val fn = requiredArg<Statement>(0) val fn = requiredArg<Obj>(0)
val result = mutableListOf<Obj>() val result = mutableListOf<Obj>()
thisObj.toFlow(this).collect { thisObj.toFlow(this).collect {
result.add(fn.call(this, it)) val callScope = createChildScope(args = Arguments(it))
result.add(fn.callOn(callScope))
} }
ObjList(result) ObjList(result)
} }
@ -179,10 +180,11 @@ val ObjIterable by lazy {
isOpen = true, isOpen = true,
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib"
) { ) {
val fn = requiredArg<Statement>(0) val fn = requiredArg<Obj>(0)
val result = mutableListOf<Obj>() val result = mutableListOf<Obj>()
thisObj.toFlow(this).collect { 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) if( transformed != ObjNull) result.add(transformed)
} }
ObjList(result) ObjList(result)
@ -228,9 +230,10 @@ val ObjIterable by lazy {
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib"
) { ) {
val list = thisObj.callMethod<ObjList>(this, "toList") val list = thisObj.callMethod<ObjList>(this, "toList")
val comparator = requireOnlyArg<Statement>() val comparator = requireOnlyArg<Obj>()
list.quicksort { a, b -> list.quicksort { a, b ->
comparator.call(this, a, b).toInt() val callScope = createChildScope(args = Arguments(a, b))
comparator.callOn(callScope).toInt()
} }
list list
} }

View File

@ -23,7 +23,7 @@ import net.sergeych.lyng.*
* Lazy delegate used by `val x by lazy { ... }`. * Lazy delegate used by `val x by lazy { ... }`.
*/ */
class ObjLazyDelegate( class ObjLazyDelegate(
private val builder: Statement, private val builder: Obj,
private val capturedScope: Scope, private val capturedScope: Scope,
) : Obj() { ) : Obj() {
override val objClass: ObjClass = type override val objClass: ObjClass = type

View File

@ -20,7 +20,7 @@ package net.sergeych.lyng.obj
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import net.sergeych.lyng.Scope 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.ParamDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.addPropertyDoc
@ -371,8 +371,11 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
params = listOf(ParamDoc("comparator")), params = listOf(ParamDoc("comparator")),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib"
) { ) {
val comparator = requireOnlyArg<Statement>() val comparator = requireOnlyArg<Obj>()
thisAs<ObjList>().quicksort { a, b -> comparator.call(this, a, b).toInt() } thisAs<ObjList>().quicksort { a, b ->
val callScope = createChildScope(args = Arguments(a, b))
comparator.callOn(callScope).toInt()
}
ObjVoid ObjVoid
} }
addFnDoc( addFnDoc(
@ -522,4 +525,3 @@ fun <T>MutableList<T>.swap(i: Int, j: Int) {
this[j] = temp this[j] = temp
} }
} }

View File

@ -20,7 +20,6 @@ package net.sergeych.lyng.obj
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
@ -261,8 +260,8 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
) { ) {
val key = requiredArg<Obj>(0) val key = requiredArg<Obj>(0)
thisAs<ObjMap>().map.getOrPut(key) { thisAs<ObjMap>().map.getOrPut(key) {
val lambda = requiredArg<Statement>(1) val lambda = requiredArg<Obj>(1)
lambda.execute(this) lambda.callOn(this)
} }
} }
addPropertyDoc( addPropertyDoc(

View File

@ -20,7 +20,6 @@ package net.sergeych.lyng.obj
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type import net.sergeych.lyng.miniast.type
@ -41,11 +40,11 @@ class ObjMutex(val mutex: Mutex): Obj() {
returns = type("lyng.Any"), returns = type("lyng.Any"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib"
) { ) {
val f = requiredArg<Statement>(0) val f = requiredArg<Obj>(0)
// Execute user lambda directly in the current scope to preserve the active scope // 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. // on top of this frame, and parseLambdaExpression sets skipScopeCreation for its body.
thisAs<ObjMutex>().mutex.withLock { f.execute(this) } thisAs<ObjMutex>().mutex.withLock { f.callOn(this) }
} }
} }
} }

View File

@ -12,16 +12,22 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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 { class ObjNativeCallable(
suspend fun execute(scope: Scope): Obj private val fn: suspend Scope.() -> Obj
} ) : Obj() {
class StatementDeclExecutable(private val delegate: Statement) : DeclExecutable { override val objClass: ObjClass
override suspend fun execute(scope: Scope): Obj = delegate.execute(scope) get() = Statement.type
override suspend fun callOn(scope: Scope): Obj = scope.fn()
override fun toString(): String = "NativeCallable@${hashCode()}"
} }

View File

@ -19,7 +19,6 @@ package net.sergeych.lyng.obj
import net.sergeych.lyng.Arguments import net.sergeych.lyng.Arguments
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
/** /**
* Property accessor storage. Per instructions, properties do NOT have * Property accessor storage. Per instructions, properties do NOT have
@ -27,8 +26,8 @@ import net.sergeych.lyng.Statement
*/ */
class ObjProperty( class ObjProperty(
val name: String, val name: String,
val getter: Statement?, val getter: Obj?,
val setter: Statement? val setter: Obj?
) : Obj() { ) : Obj() {
suspend fun callGetter(scope: Scope, instance: Obj, declaringClass: ObjClass? = null): 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 instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
val execScope = scope.applyClosure(instanceScope).createChildScope(newThisObj = instance) val execScope = scope.applyClosure(instanceScope).createChildScope(newThisObj = instance)
execScope.currentClassCtx = declaringClass execScope.currentClassCtx = declaringClass
return g.execute(execScope) return g.callOn(execScope)
} }
suspend fun callSetter(scope: Scope, instance: Obj, value: Obj, declaringClass: ObjClass? = null) { 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 instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
val execScope = scope.applyClosure(instanceScope).createChildScope(args = Arguments(value), newThisObj = instance) val execScope = scope.applyClosure(instanceScope).createChildScope(args = Arguments(value), newThisObj = instance)
execScope.currentClassCtx = declaringClass execScope.currentClassCtx = declaringClass
s.execute(execScope) s.callOn(execScope)
} }
override fun toString(): String = "Property($name)" override fun toString(): String = "Property($name)"

View File

@ -126,8 +126,8 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
// roundToInt: number rounded to the nearest integer // roundToInt: number rounded to the nearest integer
addConstDoc( addConstDoc(
name = "roundToInt", name = "roundToInt",
value = statement(Pos.builtIn) { value = ObjNativeCallable {
(it.thisObj as ObjReal).value.roundToLong().toObj() (thisObj as ObjReal).value.roundToLong().toObj()
}, },
doc = "This real number rounded to the nearest integer.", doc = "This real number rounded to the nearest integer.",
type = type("lyng.Int"), type = type("lyng.Int"),

View File

@ -21,7 +21,6 @@ import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.Pos import net.sergeych.lyng.Pos
import net.sergeych.lyng.RegexCache import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
class ObjRegex(val regex: Regex) : Obj() { class ObjRegex(val regex: Regex) : Obj() {
@ -76,14 +75,10 @@ class ObjRegex(val regex: Regex) : Obj() {
} }
createField( createField(
name = "operatorMatch", name = "operatorMatch",
initialValue = object : Statement() { initialValue = ObjNativeCallable {
override val pos: Pos = Pos.builtIn val other = args.firstAndOnly(Pos.builtIn)
val targetScope = parent ?: this
override suspend fun execute(scope: Scope): Obj { (thisObj as ObjRegex).operatorMatch(targetScope, other)
val other = scope.args.firstAndOnly(pos)
val targetScope = scope.parent ?: scope
return (scope.thisObj as ObjRegex).operatorMatch(targetScope, other)
}
}, },
type = ObjRecord.Type.Fun type = ObjRecord.Type.Fun
) )

View File

@ -25,7 +25,6 @@ import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.Pos import net.sergeych.lyng.Pos
import net.sergeych.lyng.RegexCache import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
@ -344,14 +343,10 @@ data class ObjString(val value: String) : Obj() {
name = "re", name = "re",
initialValue = ObjProperty( initialValue = ObjProperty(
name = "re", name = "re",
getter = object : Statement() { getter = ObjNativeCallable {
override val pos: Pos = Pos.builtIn val pattern = (thisObj as ObjString).value
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() val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(pattern) else pattern.toRegex()
return ObjRegex(re) ObjRegex(re)
}
}, },
setter = null setter = null
), ),
@ -359,14 +354,10 @@ data class ObjString(val value: String) : Obj() {
) )
createField( createField(
name = "operatorMatch", name = "operatorMatch",
initialValue = object : Statement() { initialValue = ObjNativeCallable {
override val pos: Pos = Pos.builtIn val other = args.firstAndOnly(Pos.builtIn)
val targetScope = parent ?: this
override suspend fun execute(scope: Scope): Obj { (thisObj as ObjString).operatorMatch(targetScope, other)
val other = scope.args.firstAndOnly(pos)
val targetScope = scope.parent ?: scope
return (scope.thisObj as ObjString).operatorMatch(targetScope, other)
}
}, },
type = ObjRecord.Type.Fun type = ObjRecord.Type.Fun
) )

View File

@ -70,7 +70,7 @@ object CompileTimeResolver {
source, source,
importProvider, importProvider,
resolutionSink = collector, resolutionSink = collector,
useBytecodeStatements = false, compileBytecode = false,
allowUnresolvedRefs = true allowUnresolvedRefs = true
) )
return collector.buildReport() return collector.buildReport()

View File

@ -106,7 +106,7 @@ object LyngLanguageTools {
request.importProvider, request.importProvider,
miniSink = miniSink, miniSink = miniSink,
resolutionSink = resolutionCollector, resolutionSink = resolutionCollector,
useBytecodeStatements = false, compileBytecode = false,
allowUnresolvedRefs = true, allowUnresolvedRefs = true,
seedScope = request.seedScope seedScope = request.seedScope
) )

View File

@ -392,7 +392,6 @@ class BytecodeRecentOpsTest {
val script = Compiler.compileWithResolution( val script = Compiler.compileWithResolution(
Source("<fast-local>", code), Source("<fast-local>", code),
Script.defaultImportManager, Script.defaultImportManager,
useBytecodeStatements = true,
useFastLocalRefs = true useFastLocalRefs = true
) )
val result = script.execute(Script.defaultImportManager.newStdScope()) val result = script.execute(Script.defaultImportManager.newStdScope())

View File

@ -38,7 +38,6 @@ class CompileTimeResolutionRuntimeTest {
val script = Compiler.compileWithResolution( val script = Compiler.compileWithResolution(
Source("<strict-slot>", code), Source("<strict-slot>", code),
Script.defaultImportManager, Script.defaultImportManager,
useBytecodeStatements = false,
strictSlotRefs = true strictSlotRefs = true
) )
val result = script.execute(Script.defaultImportManager.newStdScope()) val result = script.execute(Script.defaultImportManager.newStdScope())
@ -61,7 +60,6 @@ class CompileTimeResolutionRuntimeTest {
val script = Compiler.compileWithResolution( val script = Compiler.compileWithResolution(
Source("<shadow-slot>", code), Source("<shadow-slot>", code),
Script.defaultImportManager, Script.defaultImportManager,
useBytecodeStatements = true,
strictSlotRefs = true strictSlotRefs = true
) )
val result = script.execute(Script.defaultImportManager.newStdScope()) val result = script.execute(Script.defaultImportManager.newStdScope())
@ -85,7 +83,6 @@ class CompileTimeResolutionRuntimeTest {
val script = Compiler.compileWithResolution( val script = Compiler.compileWithResolution(
Source("<shadow-block>", code), Source("<shadow-block>", code),
Script.defaultImportManager, Script.defaultImportManager,
useBytecodeStatements = true,
strictSlotRefs = true strictSlotRefs = true
) )
val result = script.execute(Script.defaultImportManager.newStdScope()) val result = script.execute(Script.defaultImportManager.newStdScope())

View File

@ -45,8 +45,7 @@ class ParamTypeInferenceTest {
Compiler.compileWithResolution( Compiler.compileWithResolution(
Source("<eval>", code.trimIndent()), Source("<eval>", code.trimIndent()),
Script.defaultImportManager, Script.defaultImportManager,
miniSink = sink, miniSink = sink
useBytecodeStatements = false
) )
val mini = sink.build()!! val mini = sink.build()!!
val binding = Binder.bind(code, mini) val binding = Binder.bind(code, mini)