diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt index f9c056a..afa14b7 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt @@ -74,7 +74,7 @@ internal suspend fun executeClassDecl( newClass.classScope = classScope classScope.addConst("object", newClass) - spec.bodyInit?.execute(classScope) + spec.bodyInit?.let { requireBytecodeBody(scope, it, "object body init").execute(classScope) } val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY)) if (spec.declaredName != null) { @@ -148,16 +148,29 @@ internal suspend fun executeClassDecl( } classScope.currentClassCtx = newClass newClass.classScope = classScope - spec.bodyInit?.execute(classScope) + spec.bodyInit?.let { requireBytecodeBody(scope, it, "class body init").execute(classScope) } if (spec.initScope.isNotEmpty()) { for (s in spec.initScope) { - s.execute(classScope) + requireBytecodeBody(scope, s, "class init").execute(classScope) } } newClass.checkAbstractSatisfaction(spec.startPos) return newClass } +private suspend fun requireBytecodeBody( + scope: Scope, + stmt: Statement, + label: String +): net.sergeych.lyng.bytecode.BytecodeStatement { + val bytecode = when (stmt) { + is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt + is BytecodeBodyProvider -> stmt.bytecodeBody() + else -> null + } + return bytecode ?: scope.raiseIllegalState("$label requires bytecode statement") +} + class ClassDeclStatement( val spec: ClassDeclSpec, ) : Statement() { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index ee96d0f..e71b2ba 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -176,6 +176,7 @@ class Compiler( private val encodedPayloadTypeByScopeId: MutableMap> = mutableMapOf() private val encodedPayloadTypeByName: MutableMap = mutableMapOf() private val objectDeclNames: MutableSet = mutableSetOf() + private val externCallableNames: MutableSet = mutableSetOf() private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) { val plan = moduleSlotPlan() ?: return @@ -1853,6 +1854,7 @@ class Compiler( enumEntriesByName = enumEntriesByName, callableReturnTypeByScopeId = callableReturnTypeByScopeId, callableReturnTypeByName = callableReturnTypeByName, + externCallableNames = externCallableNames, lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef ) } @@ -1876,6 +1878,7 @@ class Compiler( enumEntriesByName = enumEntriesByName, callableReturnTypeByScopeId = callableReturnTypeByScopeId, callableReturnTypeByName = callableReturnTypeByName, + externCallableNames = externCallableNames, lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef ) } @@ -1920,6 +1923,7 @@ class Compiler( enumEntriesByName = enumEntriesByName, callableReturnTypeByScopeId = callableReturnTypeByScopeId, callableReturnTypeByName = callableReturnTypeByName, + externCallableNames = externCallableNames, lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef ) } @@ -6690,6 +6694,12 @@ class Compiler( if (extensionWrapperName != null) { declareLocalName(extensionWrapperName, isMutable = false) } + if (actualExtern && declKind != SymbolKind.MEMBER) { + externCallableNames.add(name) + } + if (actualExtern && extensionWrapperName != null) { + externCallableNames.add(extensionWrapperName) + } val declSlotPlan = if (declKind != SymbolKind.MEMBER) slotPlanStack.lastOrNull() else null val declSlotIndex = declSlotPlan?.slots?.get(name)?.index val declScopeId = declSlotPlan?.id diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt index 3202bc8..5845dd3 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt @@ -18,6 +18,7 @@ package net.sergeych.lyng import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.ObjClass +import net.sergeych.lyng.obj.ObjExternCallable import net.sergeych.lyng.obj.ObjExtensionMethodCallable import net.sergeych.lyng.obj.ObjInstance import net.sergeych.lyng.obj.ObjRecord @@ -66,21 +67,22 @@ internal suspend fun executeFunctionDecl( if (spec.actualExtern && spec.extTypeName == null && !spec.parentIsClassBody) { val existing = scope.get(spec.name) if (existing != null) { + val value = (existing.value as? ObjExternCallable) ?: ObjExternCallable.wrap(existing.value) scope.addItem( spec.name, false, - existing.value, + value, spec.visibility, callSignature = existing.callSignature ) - return existing.value + return value } } if (spec.isDelegated) { val delegateExpr = spec.delegateExpression ?: scope.raiseError("delegated function missing delegate") val accessType = ObjString("Callable") - val initValue = delegateExpr.execute(scope) + val initValue = requireBytecodeBody(scope, delegateExpr, "delegated function").execute(scope) val finalDelegate = try { initValue.invokeInstanceMethod(scope, "bind", Arguments(ObjString(spec.name), accessType, scope.thisObj)) } catch (e: Exception) { @@ -143,7 +145,7 @@ internal suspend fun executeFunctionDecl( ) val initStmt = spec.delegateInitStatement ?: scope.raiseIllegalState("missing delegated init statement for ${spec.name}") - cls.instanceInitializers += initStmt + cls.instanceInitializers += requireBytecodeBody(scope, initStmt, "delegated function init") } else { scope.addItem( spec.name, @@ -225,6 +227,19 @@ internal suspend fun executeFunctionDecl( return annotatedFnBody } +private suspend fun requireBytecodeBody( + scope: Scope, + stmt: Statement, + label: String +): net.sergeych.lyng.bytecode.BytecodeStatement { + val bytecode = when (stmt) { + is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt + is BytecodeBodyProvider -> stmt.bytecodeBody() + else -> null + } + return bytecode ?: scope.raiseIllegalState("$label requires bytecode statement") +} + class FunctionDeclStatement( val spec: FunctionDeclSpec, ) : Statement() { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/InlineBlockStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/InlineBlockStatement.kt index fe792d7..195fe34 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/InlineBlockStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/InlineBlockStatement.kt @@ -28,10 +28,19 @@ class InlineBlockStatement( override suspend fun execute(scope: Scope): Obj { var last: Obj = ObjVoid for (stmt in statements) { - last = stmt.execute(scope) + last = requireBytecodeBody(scope, stmt, "inline block").execute(scope) } return last } fun statements(): List = statements + + private suspend fun requireBytecodeBody(scope: Scope, stmt: Statement, label: String): net.sergeych.lyng.bytecode.BytecodeStatement { + val bytecode = when (stmt) { + is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt + is BytecodeBodyProvider -> stmt.bytecodeBody() + else -> null + } + return bytecode ?: scope.raiseIllegalState("$label requires bytecode statement") + } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/PropertyAccessorStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/PropertyAccessorStatement.kt index 1dca112..26be1b7 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/PropertyAccessorStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/PropertyAccessorStatement.kt @@ -20,7 +20,6 @@ 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, @@ -29,32 +28,33 @@ class PropertyAccessorStatement( ) : 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) + val bytecodeStmt = requireBytecodeBody(scope, body, "property accessor") + val fn = bytecodeStmt.bytecodeFunction() + val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments -> + val slotPlan = fn.localSlotPlanByName() + val slotIndex = slotPlan[argName] + ?: scope.raiseIllegalState("property accessor argument $argName missing from slot plan") + val argValue = arguments.list.firstOrNull() ?: ObjNull + frame.frame.setObj(slotIndex, argValue) } + scope.pos = pos + CmdVm().execute(fn, scope, scope.args, binder) } finally { scope.skipScopeCreation = prev } } - return body.execute(scope) + return requireBytecodeBody(scope, body, "property accessor").execute(scope) + } + + private suspend fun requireBytecodeBody(scope: Scope, stmt: Statement, label: String): net.sergeych.lyng.bytecode.BytecodeStatement { + val bytecode = when (stmt) { + is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt + is BytecodeBodyProvider -> stmt.bytecodeBody() + else -> null + } + return bytecode ?: scope.raiseIllegalState("$label requires bytecode statement") } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index 9465dbf..9132800 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -78,7 +78,6 @@ open class Scope( thisObj = primary val extrasSnapshot = when { extras.isEmpty() -> emptyList() - extras === thisVariants -> extras.toList() extras is MutableList<*> -> synchronized(extras) { extras.toList() } else -> extras.toList() } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ScopeFacade.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ScopeFacade.kt new file mode 100644 index 0000000..fec0256 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ScopeFacade.kt @@ -0,0 +1,53 @@ +/* + * 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.ObjRecord + +/** + * Limited facade for Kotlin bridge callables. + * Exposes only the minimal API needed to read/write vars and invoke methods. + */ +interface ScopeFacade { + val args: Arguments + var pos: Pos + var thisObj: Obj + operator fun get(name: String): ObjRecord? + suspend fun resolve(rec: ObjRecord, name: String): Obj + suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) + fun raiseError(message: String): Nothing + fun raiseSymbolNotFound(name: String): Nothing + fun raiseIllegalState(message: String = "Illegal argument error"): Nothing +} + +internal class ScopeBridge(private val scope: Scope) : ScopeFacade { + override val args: Arguments + get() = scope.args + override var pos: Pos + get() = scope.pos + set(value) { scope.pos = value } + override var thisObj: Obj + get() = scope.thisObj + set(value) { scope.thisObj = value } + override fun get(name: String): ObjRecord? = scope[name] + override suspend fun resolve(rec: ObjRecord, name: String): Obj = scope.resolve(rec, name) + override suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) = scope.assign(rec, name, newValue) + override fun raiseError(message: String): Nothing = scope.raiseError(message) + override fun raiseSymbolNotFound(name: String): Nothing = scope.raiseSymbolNotFound(name) + override fun raiseIllegalState(message: String): Nothing = scope.raiseIllegalState(message) +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index 58c8941..21c7009 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -35,6 +35,7 @@ class BytecodeCompiler( private val enumEntriesByName: Map> = emptyMap(), private val callableReturnTypeByScopeId: Map> = emptyMap(), private val callableReturnTypeByName: Map = emptyMap(), + private val externCallableNames: Set = emptySet(), private val lambdaCaptureEntriesByRef: Map> = emptyMap(), ) { private var builder = CmdBuilder() @@ -3688,6 +3689,7 @@ class BytecodeCompiler( private fun compileCall(ref: CallRef): CompiledValue? { val callPos = callSitePos() val localTarget = ref.target as? LocalVarRef + val isExternCall = localTarget != null && externCallableNames.contains(localTarget.name) if (localTarget != null) { val direct = resolveDirectNameSlot(localTarget.name) if (direct == null) { @@ -3711,7 +3713,13 @@ class BytecodeCompiler( val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null setPos(callPos) - builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst) + builder.emit( + if (isExternCall) Opcode.CALL_BRIDGE_SLOT else Opcode.CALL_SLOT, + callee.slot, + args.base, + encodedCount, + dst + ) if (initClass != null) { slotObjClass[dst] = initClass } @@ -3730,7 +3738,13 @@ class BytecodeCompiler( val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null setPos(callPos) - builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst) + builder.emit( + if (isExternCall) Opcode.CALL_BRIDGE_SLOT else Opcode.CALL_SLOT, + callee.slot, + args.base, + encodedCount, + dst + ) if (initClass != null) { slotObjClass[dst] = initClass } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt index afddce2..ed7ef80 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt @@ -53,6 +53,7 @@ class BytecodeStatement private constructor( enumEntriesByName: Map> = emptyMap(), callableReturnTypeByScopeId: Map> = emptyMap(), callableReturnTypeByName: Map = emptyMap(), + externCallableNames: Set = emptySet(), lambdaCaptureEntriesByRef: Map> = emptyMap(), ): Statement { if (statement is BytecodeStatement) return statement @@ -80,6 +81,7 @@ class BytecodeStatement private constructor( enumEntriesByName = enumEntriesByName, callableReturnTypeByScopeId = callableReturnTypeByScopeId, callableReturnTypeByName = callableReturnTypeByName, + externCallableNames = externCallableNames, lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef ) val compiled = compiler.compileStatement(nameHint, statement) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt index ec3ebf8..b1e73ac 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -197,7 +197,7 @@ class CmdBuilder { listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) Opcode.CALL_MEMBER_SLOT -> listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) - Opcode.CALL_SLOT -> + Opcode.CALL_SLOT, Opcode.CALL_BRIDGE_SLOT -> listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) Opcode.CALL_DYNAMIC_MEMBER -> listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) @@ -435,6 +435,7 @@ class CmdBuilder { Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1]) Opcode.CALL_MEMBER_SLOT -> CmdCallMemberSlot(operands[0], operands[1], operands[2], operands[3], operands[4]) Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3]) + Opcode.CALL_BRIDGE_SLOT -> CmdCallBridgeSlot(operands[0], operands[1], operands[2], operands[3]) Opcode.CALL_DYNAMIC_MEMBER -> CmdCallDynamicMember(operands[0], operands[1], operands[2], operands[3], operands[4]) Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2]) Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2]) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt index 14c8185..072b532 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt @@ -229,6 +229,7 @@ object CmdDisassembler { is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot) is CmdCallMemberSlot -> Opcode.CALL_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst) is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst) + is CmdCallBridgeSlot -> Opcode.CALL_BRIDGE_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst) is CmdCallDynamicMember -> Opcode.CALL_DYNAMIC_MEMBER to intArrayOf(cmd.recvSlot, cmd.nameId, cmd.argBase, cmd.argCount, cmd.dst) is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst) is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot) @@ -330,7 +331,7 @@ object CmdDisassembler { listOf(OperandKind.SLOT, OperandKind.IP) Opcode.CALL_DIRECT -> listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) - Opcode.CALL_SLOT -> + Opcode.CALL_SLOT, Opcode.CALL_BRIDGE_SLOT -> listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) Opcode.CALL_MEMBER_SLOT -> listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt index 46b700f..1186573 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -2023,6 +2023,40 @@ class CmdCallSlot( } } +class CmdCallBridgeSlot( + internal val calleeSlot: Int, + internal val argBase: Int, + internal val argCount: Int, + internal val dst: Int, +) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val callee = frame.slotToObj(calleeSlot) + if (callee === ObjUnset) { + val name = if (calleeSlot < frame.fn.scopeSlotCount) { + frame.fn.scopeSlotNames[calleeSlot] + } else { + val localIndex = calleeSlot - frame.fn.scopeSlotCount + frame.fn.localSlotNames.getOrNull(localIndex) + } + val message = name?.let { "property '$it' is unset (not initialized)" } + ?: "property is unset (not initialized) in ${frame.fn.name} at slot $calleeSlot" + frame.ensureScope().raiseUnset(message) + } + if (callee !is net.sergeych.lyng.obj.ObjExternCallable) { + frame.ensureScope().raiseIllegalState("CALL_BRIDGE_SLOT expects extern callable") + } + val args = frame.buildArguments(argBase, argCount) + val result = if (PerfFlags.SCOPE_POOL) { + frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) } + } else { + val scope = frame.ensureScope() + callee.callOn(scope.createChildScope(scope.pos, args = args)) + } + frame.storeObjResult(dst, result) + return + } +} + class CmdListLiteral( internal val planId: Int, internal val baseSlot: Int, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt index fb29c8d..3e76136 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -136,6 +136,7 @@ enum class Opcode(val code: Int) { ASSIGN_DESTRUCTURE(0x91), CALL_MEMBER_SLOT(0x92), CALL_SLOT(0x93), + CALL_BRIDGE_SLOT(0x94), GET_INDEX(0xA2), SET_INDEX(0xA3), diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjExternCallable.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjExternCallable.kt new file mode 100644 index 0000000..a72780a --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjExternCallable.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2026 Sergey S. Chernov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sergeych.lyng.obj + +import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeBridge +import net.sergeych.lyng.ScopeFacade +import net.sergeych.lyng.Statement + +class ObjExternCallable private constructor( + private val target: Obj?, + private val fn: (suspend ScopeFacade.() -> Obj)? +) : Obj() { + + override val objClass: ObjClass + get() = Statement.type + + override suspend fun callOn(scope: Scope): Obj { + val facade = ScopeBridge(scope) + return when { + fn != null -> facade.fn() + target != null -> target.callOn(scope) + else -> ObjVoid + } + } + + override fun toString(): String = "ExternCallable@${hashCode()}" + + companion object { + fun wrap(target: Obj): ObjExternCallable = ObjExternCallable(target, null) + fun fromBridge(fn: suspend ScopeFacade.() -> Obj): ObjExternCallable = ObjExternCallable(null, fn) + } +}