diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt index 70f9640..28b153c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt @@ -19,18 +19,18 @@ package net.sergeych.lyng import net.sergeych.lyng.obj.Obj class ClassDeclStatement( - private val delegate: Statement, + val executable: DeclExecutable, private val startPos: Pos, val declaredName: String?, ) : Statement() { override val pos: Pos = startPos override suspend fun execute(scope: Scope): Obj { - return delegate.execute(scope) + return executable.execute(scope) } override suspend fun callOn(scope: Scope): Obj { val target = scope.parent ?: scope - return delegate.execute(target) + return executable.execute(target) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 4245928..1488b48 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -5784,7 +5784,7 @@ class Compiler( return enumClass } } - return EnumDeclStatement(enumDeclStatement, stmtPos) + return EnumDeclStatement(StatementDeclExecutable(enumDeclStatement), stmtPos) } private suspend fun parseObjectDeclaration(isExtern: Boolean = false): Statement { @@ -5965,7 +5965,7 @@ class Compiler( return instance } } - return ClassDeclStatement(declStatement, startPos, className) + return ClassDeclStatement(StatementDeclExecutable(declStatement), startPos, className) } private suspend fun parseClassDeclaration(isAbstract: Boolean = false, isExtern: Boolean = false): Statement { @@ -6372,7 +6372,7 @@ class Compiler( return newClass } } - ClassDeclStatement(classDeclStatement, startPos, qualifiedName) + ClassDeclStatement(StatementDeclExecutable(classDeclStatement), startPos, qualifiedName) } } @@ -7186,7 +7186,7 @@ class Compiler( return annotatedFnBody } } - val declaredFn = FunctionDeclStatement(fnCreateStatement, start) + val declaredFn = FunctionDeclStatement(StatementDeclExecutable(fnCreateStatement), start) if (isStatic) { currentInitScope += declaredFn NopStatement diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DeclExecutable.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DeclExecutable.kt new file mode 100644 index 0000000..5565ac2 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DeclExecutable.kt @@ -0,0 +1,27 @@ +/* + * 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 + +interface DeclExecutable { + suspend fun execute(scope: Scope): Obj +} + +class StatementDeclExecutable(private val delegate: Statement) : DeclExecutable { + override suspend fun execute(scope: Scope): Obj = delegate.execute(scope) +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/EnumDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/EnumDeclStatement.kt index 45cbf61..9a209ef 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/EnumDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/EnumDeclStatement.kt @@ -19,17 +19,17 @@ package net.sergeych.lyng import net.sergeych.lyng.obj.Obj class EnumDeclStatement( - private val delegate: Statement, + val executable: DeclExecutable, private val startPos: Pos, ) : Statement() { override val pos: Pos = startPos override suspend fun execute(scope: Scope): Obj { - return delegate.execute(scope) + return executable.execute(scope) } override suspend fun callOn(scope: Scope): Obj { val target = scope.parent ?: scope - return delegate.execute(target) + return executable.execute(target) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt index bbf3d08..efe6480 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt @@ -19,17 +19,17 @@ package net.sergeych.lyng import net.sergeych.lyng.obj.Obj class FunctionDeclStatement( - private val delegate: Statement, + val executable: DeclExecutable, private val startPos: Pos, ) : Statement() { override val pos: Pos = startPos override suspend fun execute(scope: Scope): Obj { - return delegate.execute(scope) + return executable.execute(scope) } override suspend fun callOn(scope: Scope): Obj { val target = scope.parent ?: scope - return delegate.execute(target) + return executable.execute(target) } } 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 1276b50..67fd030 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -4077,6 +4077,23 @@ class BytecodeCompiler( return CompiledValue(dst, SlotType.OBJ) } + private fun emitDeclExec(stmt: Statement): CompiledValue { + val executable = when (stmt) { + is net.sergeych.lyng.ClassDeclStatement -> stmt.executable + is net.sergeych.lyng.FunctionDeclStatement -> stmt.executable + is net.sergeych.lyng.EnumDeclStatement -> stmt.executable + else -> throw BytecodeCompileException( + "Bytecode compile error: unsupported declaration ${stmt::class.simpleName}", + stmt.pos + ) + } + val constId = builder.addConst(BytecodeConst.DeclExec(executable)) + val dst = allocSlot() + builder.emit(Opcode.DECL_EXEC, constId, dst) + updateSlotType(dst, SlotType.OBJ) + return CompiledValue(dst, SlotType.OBJ) + } + private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? { val target = if (stmt is BytecodeStatement) stmt.original else stmt setPos(target.pos) @@ -4110,9 +4127,9 @@ class BytecodeCompiler( is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target) is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target) is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target) - is net.sergeych.lyng.ClassDeclStatement -> emitStatementCall(target) - is net.sergeych.lyng.FunctionDeclStatement -> emitStatementCall(target) - is net.sergeych.lyng.EnumDeclStatement -> emitStatementCall(target) + is net.sergeych.lyng.ClassDeclStatement -> emitDeclExec(target) + is net.sergeych.lyng.FunctionDeclStatement -> emitDeclExec(target) + is net.sergeych.lyng.EnumDeclStatement -> emitDeclExec(target) is net.sergeych.lyng.TryStatement -> emitTry(target, true) is net.sergeych.lyng.WhenStatement -> compileWhen(target, true) is net.sergeych.lyng.BreakStatement -> compileBreak(target) @@ -4143,9 +4160,9 @@ class BytecodeCompiler( is VarDeclStatement -> emitVarDecl(target) is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target) is IfStatement -> compileIfStatement(target) - is net.sergeych.lyng.ClassDeclStatement -> emitStatementCall(target) - is net.sergeych.lyng.FunctionDeclStatement -> emitStatementCall(target) - is net.sergeych.lyng.EnumDeclStatement -> emitStatementCall(target) + is net.sergeych.lyng.ClassDeclStatement -> emitDeclExec(target) + is net.sergeych.lyng.FunctionDeclStatement -> emitDeclExec(target) + is net.sergeych.lyng.EnumDeclStatement -> emitDeclExec(target) is net.sergeych.lyng.ForInStatement -> { val resultSlot = emitForIn(target, false) ?: return null CompiledValue(resultSlot, SlotType.OBJ) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt index 07a947b..c51c2ee 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt @@ -34,6 +34,7 @@ sealed class BytecodeConst { data class StatementVal(val statement: net.sergeych.lyng.Statement) : BytecodeConst() data class ListLiteralPlan(val spreads: List) : BytecodeConst() data class ValueFn(val fn: suspend (net.sergeych.lyng.Scope) -> net.sergeych.lyng.obj.ObjRecord) : BytecodeConst() + data class DeclExec(val executable: net.sergeych.lyng.DeclExecutable) : BytecodeConst() data class SlotPlan(val plan: Map, val captures: List = emptyList()) : BytecodeConst() data class ExtensionPropertyDecl( val extTypeName: String, 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 9429f53..e2f446a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -157,6 +157,7 @@ class CmdBuilder { Opcode.PUSH_TRY -> listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP) Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE, + Opcode.DECL_EXEC, Opcode.ASSIGN_DESTRUCTURE -> listOf(OperandKind.CONST, OperandKind.SLOT) Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, @@ -409,6 +410,7 @@ class CmdBuilder { Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1]) Opcode.DECL_DELEGATED -> CmdDeclDelegated(operands[0], operands[1]) Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1]) + Opcode.DECL_EXEC -> CmdDeclExec(operands[0], operands[1]) Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1]) Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3]) Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1]) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt index 025189b..d308be0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt @@ -194,6 +194,7 @@ object CmdDisassembler { is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot) is CmdDeclDelegated -> Opcode.DECL_DELEGATED to intArrayOf(cmd.constId, cmd.slot) is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot) + is CmdDeclExec -> Opcode.DECL_EXEC to intArrayOf(cmd.constId, cmd.slot) is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot) is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst) is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot) @@ -265,6 +266,7 @@ object CmdDisassembler { Opcode.PUSH_TRY -> listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP) Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE, + Opcode.DECL_EXEC, Opcode.ASSIGN_DESTRUCTURE -> listOf(OperandKind.CONST, OperandKind.SLOT) Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt index 47cf67d..7511e2d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -1314,6 +1314,22 @@ class CmdDeclDelegated(internal val constId: Int, internal val slot: Int) : Cmd( } } +class CmdDeclExec(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + if (frame.fn.localSlotNames.isNotEmpty()) { + frame.syncFrameToScope(useRefs = true) + } + val decl = frame.fn.constants[constId] as? BytecodeConst.DeclExec + ?: error("DECL_EXEC expects DeclExec at $constId") + val result = decl.executable.execute(frame.ensureScope()) + if (frame.fn.localSlotNames.isNotEmpty()) { + frame.syncScopeToFrame() + } + frame.storeObjResult(slot, result) + return + } +} + class CmdDeclDestructure(internal val constId: Int, internal val slot: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { val decl = frame.fn.constants[constId] as? BytecodeConst.DestructureDecl 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 8bf8f13..151fe9a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -158,6 +158,7 @@ enum class Opcode(val code: Int) { STORE_BOOL_ADDR(0xB9), THROW(0xBB), RETHROW_PENDING(0xBC), + DECL_EXEC(0xBD), ITER_PUSH(0xBF), ITER_POP(0xC0), ITER_CANCEL(0xC1), diff --git a/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt b/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt index e68094a..b3e3a18 100644 --- a/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt +++ b/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt @@ -24,10 +24,13 @@ import net.sergeych.lyng.ScriptError import net.sergeych.lyng.Source import net.sergeych.lyng.eval import net.sergeych.lyng.toSource +import net.sergeych.lyng.bytecode.CmdDisassembler +import net.sergeych.lyng.bytecode.CmdFunction import net.sergeych.lyng.obj.toInt import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull import kotlin.test.assertTrue class BytecodeRecentOpsTest { @@ -243,6 +246,23 @@ class BytecodeRecentOpsTest { assertTrue(disasm.contains("DECL_DELEGATED"), disasm) } + @Test + fun moduleDeclsAvoidCallableCallSlots() = runTest { + val script = """ + class A {} + fun f() { 1 } + enum E { one } + """.trimIndent() + val compiled = Compiler.compile(script.toSource(), Script.defaultImportManager) + val field = Script::class.java.getDeclaredField("moduleBytecode") + field.isAccessible = true + val moduleFn = field.get(compiled) as? CmdFunction + assertNotNull(moduleFn, "module bytecode missing") + val disasm = CmdDisassembler.disassemble(moduleFn) + assertTrue(!disasm.contains("CALL_SLOT"), disasm) + assertTrue(disasm.contains("DECL_EXEC"), disasm) + } + @Test fun unionMemberDispatchSubtype() = runTest { eval(