Step 25: emit DECL_EXEC for declarations

This commit is contained in:
Sergey Chernov 2026-02-09 22:00:46 +03:00
parent 8314127fdb
commit 0caa9849cf
12 changed files with 105 additions and 19 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,6 +34,7 @@ sealed class BytecodeConst {
data class StatementVal(val statement: net.sergeych.lyng.Statement) : BytecodeConst()
data class ListLiteralPlan(val spreads: List<Boolean>) : 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<String, Int>, val captures: List<String> = emptyList()) : BytecodeConst()
data class ExtensionPropertyDecl(
val extTypeName: String,

View File

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

View File

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

View File

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

View File

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

View File

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