diff --git a/bytecode_migration_plan.md b/bytecode_migration_plan.md index 2e0a9e5..684f3e1 100644 --- a/bytecode_migration_plan.md +++ b/bytecode_migration_plan.md @@ -114,9 +114,11 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te - [ ] Step 25: Replace Statement-based declaration calls in bytecode. - [x] Add bytecode const/op for enum declarations (no `Statement` objects in constants). - - [ ] Add bytecode const/op for class/function declarations (no `Statement` objects in constants). + - [ ] Add bytecode const/op for class declarations (no `Statement` objects in constants). + - [x] Add bytecode const/op for function declarations (no `Statement` objects in constants). - [x] Replace `emitStatementCall` usage for `EnumDeclStatement`. - - [ ] Replace `emitStatementCall` usage for `ClassDeclStatement` and `FunctionDeclStatement`. + - [ ] Replace `emitStatementCall` usage for `ClassDeclStatement`. + - [x] Replace `emitStatementCall` usage for `FunctionDeclStatement`. - [ ] Add JVM disasm coverage to ensure module init has no `CALL_SLOT` to `Callable@...` for declarations. - [ ] Step 26: Bytecode-backed lambdas (remove `ValueFnRef` runtime execution). - [ ] Compile lambda bodies to bytecode and emit an opcode to create a callable from bytecode + capture plan. diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 5ab9ddc..6a9d784 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -7000,8 +7000,7 @@ class Compiler( // Capture and pop the local declarations count for this function val fnLocalDecls = localDeclCountStack.removeLastOrNull() ?: 0 - var closure: Scope? = null - var captureContext: Scope? = null + val closureBox = FunctionClosureBox() val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan) val captureSlots = capturePlan.captures.toList() @@ -7014,14 +7013,14 @@ class Compiler( // restore closure where the function was defined, and making a copy of it // for local space. If there is no closure, we are in, say, class context where // the closure is in the class initialization and we needn't more: - val context = closure?.let { ClosureScope(callerContext, it) } + val context = closureBox.closure?.let { ClosureScope(callerContext, it) } ?: callerContext // Capacity hint: parameters + declared locals + small overhead val capacityHint = paramNames.size + fnLocalDecls + 4 context.hintLocalCapacity(capacityHint) if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot) - val captureBase = captureContext ?: closure + val captureBase = closureBox.captureContext ?: closureBox.closure if (captureBase != null && captureSlots.isNotEmpty()) { for (capture in captureSlots) { // Interpreter-only capture resolution; bytecode functions do not use resolveCaptureRecord. @@ -7047,172 +7046,29 @@ class Compiler( } cc.labels.remove(name) outerLabel?.let { cc.labels.remove(it) } -// parentContext - val fnCreateStatement = object : Statement() { - override val pos: Pos = start - override suspend fun execute(context: Scope): Obj { - if (actualExtern && extTypeName == null && parentContext !is CodeContext.ClassBody) { - val existing = context.get(name) - if (existing != null) { - context.addItem( - name, - false, - existing.value, - visibility, - callSignature = existing.callSignature - ) - return existing.value - } - } - if (isDelegated) { - val accessType = ObjString("Callable") - val initValue = delegateExpression!!.execute(context) - val finalDelegate = try { - initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, context.thisObj)) - } catch (e: Exception) { - initValue - } - - if (extTypeName != null) { - val type = context[extTypeName]?.value ?: context.raiseSymbolNotFound("class $extTypeName not found") - if (type !is ObjClass) context.raiseClassCastError("$extTypeName is not the class instance") - context.addExtension(type, name, ObjRecord(ObjUnset, isMutable = false, visibility = visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply { - delegate = finalDelegate - }) - return ObjVoid - } - - val th = context.thisObj - if (isStatic) { - (th as ObjClass).createClassField(name, ObjUnset, false, visibility, null, start, isTransient = isTransient, type = ObjRecord.Type.Delegated).apply { - delegate = finalDelegate - } - context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply { - delegate = finalDelegate - } - } else if (th is ObjClass) { - val cls: ObjClass = th - val storageName = "${cls.className}::$name" - cls.createField( - name, - ObjUnset, - false, - visibility, - null, - start, - declaringClass = cls, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - isTransient = isTransient, - type = ObjRecord.Type.Delegated, - methodId = memberMethodId - ) - cls.instanceInitializers += object : Statement() { - override val pos: Pos = start - override suspend fun execute(scp: Scope): Obj { - val accessType2 = ObjString("Callable") - val initValue2 = delegateExpression.execute(scp) - val finalDelegate2 = try { - initValue2.invokeInstanceMethod(scp, "bind", Arguments(ObjString(name), accessType2, scp.thisObj)) - } catch (e: Exception) { - initValue2 - } - scp.addItem(storageName, false, ObjUnset, visibility, null, recordType = ObjRecord.Type.Delegated, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient).apply { - delegate = finalDelegate2 - } - return ObjVoid - } - } - } else { - context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply { - delegate = finalDelegate - } - } - return ObjVoid - } - - // we added fn in the context. now we must save closure - // for the function, unless we're in the class scope: - if (isStatic || parentContext !is CodeContext.ClassBody) - closure = context - if (parentContext is CodeContext.ClassBody && captureSlots.isNotEmpty()) - captureContext = context - - val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody) - ?: fnBody - val compiledFnBody = annotatedFnBody - - extTypeName?.let { typeName -> - // class extension method - val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found") - if (type !is ObjClass) context.raiseClassCastError("$typeName is not the class instance") - val stmt = object : Statement() { - override val pos: Pos = start - override suspend fun execute(scope: Scope): Obj { - // ObjInstance has a fixed instance scope, so we need to build a closure - val result = (scope.thisObj as? ObjInstance)?.let { i -> - compiledFnBody.execute(ClosureScope(scope, i.instanceScope)) - } - // other classes can create one-time scope for this rare case: - ?: compiledFnBody.execute(scope.thisObj.autoInstanceScope(scope)) - return result - } - } - context.addExtension(type, name, ObjRecord(stmt, isMutable = false, visibility = visibility, declaringClass = null)) - val wrapperName = extensionWrapperName ?: extensionCallableName(typeName, name) - val wrapper = ObjExtensionMethodCallable(name, stmt) - context.addItem(wrapperName, false, wrapper, visibility, recordType = ObjRecord.Type.Fun) - } - // regular function/method - ?: run { - val th = context.thisObj - if (!isStatic && th is ObjClass) { - // Instance method declared inside a class body: register on the class - val cls: ObjClass = th - cls.addFn( - name, - isMutable = true, - visibility = visibility, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - pos = start, - methodId = memberMethodId - ) { - // Execute with the instance as receiver; set caller lexical class for visibility - val savedCtx = this.currentClassCtx - this.currentClassCtx = cls - try { - (thisObj as? ObjInstance)?.let { i -> - val execScope = i.instanceScope.createChildScope( - pos = this.pos, - args = this.args, - newThisObj = i - ) - execScope.currentClassCtx = cls - compiledFnBody.execute(execScope) - } ?: run { - compiledFnBody.execute(thisObj.autoInstanceScope(this)) - } - } finally { - this.currentClassCtx = savedCtx - } - } - // also expose the symbol in the class scope for possible references - context.addItem(name, false, compiledFnBody, visibility, callSignature = externCallSignature) - compiledFnBody - } else { - // top-level or nested function - context.addItem(name, false, compiledFnBody, visibility, callSignature = externCallSignature) - } - } - // as the function can be called from anywhere, we have - // saved the proper context in the closure - return annotatedFnBody - } - } - val declaredFn = FunctionDeclStatement(StatementDeclExecutable(fnCreateStatement), start) + val spec = FunctionDeclSpec( + name = name, + visibility = visibility, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isStatic = isStatic, + isTransient = isTransient, + isDelegated = isDelegated, + delegateExpression = delegateExpression, + extTypeName = extTypeName, + extensionWrapperName = extensionWrapperName, + memberMethodId = memberMethodId, + actualExtern = actualExtern, + parentIsClassBody = parentContext is CodeContext.ClassBody, + externCallSignature = externCallSignature, + annotation = annotation, + fnBody = fnBody, + closureBox = closureBox, + captureSlots = captureSlots, + startPos = start + ) + val declaredFn = FunctionDeclStatement(spec) if (isStatic) { currentInitScope += declaredFn NopStatement diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt index efe6480..646b29e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt @@ -17,19 +17,240 @@ package net.sergeych.lyng import net.sergeych.lyng.obj.Obj +import net.sergeych.lyng.obj.ObjClass +import net.sergeych.lyng.obj.ObjExtensionMethodCallable +import net.sergeych.lyng.obj.ObjInstance +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 FunctionClosureBox( + var closure: Scope? = null, + var captureContext: Scope? = null, +) + +data class FunctionDeclSpec( + val name: String, + val visibility: Visibility, + val isAbstract: Boolean, + val isClosed: Boolean, + val isOverride: Boolean, + val isStatic: Boolean, + val isTransient: Boolean, + val isDelegated: Boolean, + val delegateExpression: Statement?, + val extTypeName: String?, + val extensionWrapperName: String?, + val memberMethodId: Int?, + val actualExtern: Boolean, + val parentIsClassBody: Boolean, + val externCallSignature: CallSignature?, + val annotation: (suspend (Scope, ObjString, Statement) -> Statement)?, + val fnBody: Statement, + val closureBox: FunctionClosureBox, + val captureSlots: List, + val startPos: Pos, +) + +internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec): Obj { + if (spec.actualExtern && spec.extTypeName == null && !spec.parentIsClassBody) { + val existing = scope.get(spec.name) + if (existing != null) { + scope.addItem( + spec.name, + false, + existing.value, + spec.visibility, + callSignature = existing.callSignature + ) + return existing.value + } + } + + if (spec.isDelegated) { + val delegateExpr = spec.delegateExpression ?: scope.raiseError("delegated function missing delegate") + val accessType = ObjString("Callable") + val initValue = delegateExpr.execute(scope) + val finalDelegate = try { + initValue.invokeInstanceMethod(scope, "bind", Arguments(ObjString(spec.name), accessType, scope.thisObj)) + } catch (e: Exception) { + initValue + } + + if (spec.extTypeName != null) { + val type = scope[spec.extTypeName]?.value ?: scope.raiseSymbolNotFound("class ${spec.extTypeName} not found") + if (type !is ObjClass) scope.raiseClassCastError("${spec.extTypeName} is not the class instance") + scope.addExtension( + type, + spec.name, + ObjRecord(ObjUnset, isMutable = false, visibility = spec.visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply { + delegate = finalDelegate + } + ) + return ObjVoid + } + + val th = scope.thisObj + if (spec.isStatic) { + (th as ObjClass).createClassField( + spec.name, + ObjUnset, + false, + spec.visibility, + null, + spec.startPos, + isTransient = spec.isTransient, + type = ObjRecord.Type.Delegated + ).apply { + delegate = finalDelegate + } + scope.addItem( + spec.name, + false, + ObjUnset, + spec.visibility, + recordType = ObjRecord.Type.Delegated, + isTransient = spec.isTransient + ).apply { + delegate = finalDelegate + } + } else if (th is ObjClass) { + val cls: ObjClass = th + val storageName = "${cls.className}::${spec.name}" + cls.createField( + spec.name, + ObjUnset, + false, + spec.visibility, + null, + spec.startPos, + declaringClass = cls, + isAbstract = spec.isAbstract, + isClosed = spec.isClosed, + isOverride = spec.isOverride, + isTransient = spec.isTransient, + type = ObjRecord.Type.Delegated, + methodId = spec.memberMethodId + ) + cls.instanceInitializers += object : Statement() { + override val pos: Pos = spec.startPos + override suspend fun execute(scp: Scope): Obj { + val accessType2 = ObjString("Callable") + val initValue2 = delegateExpr.execute(scp) + val finalDelegate2 = try { + initValue2.invokeInstanceMethod(scp, "bind", Arguments(ObjString(spec.name), accessType2, scp.thisObj)) + } catch (e: Exception) { + initValue2 + } + scp.addItem( + storageName, + false, + ObjUnset, + spec.visibility, + null, + recordType = ObjRecord.Type.Delegated, + isAbstract = spec.isAbstract, + isClosed = spec.isClosed, + isOverride = spec.isOverride, + isTransient = spec.isTransient + ).apply { + delegate = finalDelegate2 + } + return ObjVoid + } + } + } else { + scope.addItem( + spec.name, + false, + ObjUnset, + spec.visibility, + recordType = ObjRecord.Type.Delegated, + isTransient = spec.isTransient + ).apply { + delegate = finalDelegate + } + } + return ObjVoid + } + + if (spec.isStatic || !spec.parentIsClassBody) { + spec.closureBox.closure = scope + } + if (spec.parentIsClassBody && spec.captureSlots.isNotEmpty()) { + spec.closureBox.captureContext = scope + } + + val annotatedFnBody = spec.annotation?.invoke(scope, ObjString(spec.name), spec.fnBody) ?: spec.fnBody + val compiledFnBody = annotatedFnBody + + spec.extTypeName?.let { typeName -> + val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found") + if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance") + val stmt = object : Statement() { + override val pos: Pos = spec.startPos + override suspend fun execute(scope: Scope): Obj { + val result = (scope.thisObj as? ObjInstance)?.let { i -> + compiledFnBody.execute(ClosureScope(scope, i.instanceScope)) + } ?: compiledFnBody.execute(scope.thisObj.autoInstanceScope(scope)) + return result + } + } + scope.addExtension(type, spec.name, ObjRecord(stmt, isMutable = false, visibility = spec.visibility, declaringClass = null)) + val wrapperName = spec.extensionWrapperName ?: extensionCallableName(typeName, spec.name) + val wrapper = ObjExtensionMethodCallable(spec.name, stmt) + scope.addItem(wrapperName, false, wrapper, spec.visibility, recordType = ObjRecord.Type.Fun) + } ?: run { + val th = scope.thisObj + if (!spec.isStatic && th is ObjClass) { + val cls: ObjClass = th + cls.addFn( + spec.name, + isMutable = true, + visibility = spec.visibility, + isAbstract = spec.isAbstract, + isClosed = spec.isClosed, + isOverride = spec.isOverride, + pos = spec.startPos, + methodId = spec.memberMethodId + ) { + val savedCtx = this.currentClassCtx + this.currentClassCtx = cls + try { + (thisObj as? ObjInstance)?.let { i -> + val execScope = i.instanceScope.createChildScope( + pos = this.pos, + args = this.args, + newThisObj = i + ) + execScope.currentClassCtx = cls + compiledFnBody.execute(execScope) + } ?: compiledFnBody.execute(thisObj.autoInstanceScope(this)) + } finally { + this.currentClassCtx = savedCtx + } + } + scope.addItem(spec.name, false, compiledFnBody, spec.visibility, callSignature = spec.externCallSignature) + compiledFnBody + } else { + scope.addItem(spec.name, false, compiledFnBody, spec.visibility, callSignature = spec.externCallSignature) + } + } + return annotatedFnBody +} class FunctionDeclStatement( - val executable: DeclExecutable, - private val startPos: Pos, + val spec: FunctionDeclSpec, ) : Statement() { - override val pos: Pos = startPos + override val pos: Pos = spec.startPos override suspend fun execute(scope: Scope): Obj { - return executable.execute(scope) + return executeFunctionDecl(scope, spec) } override suspend fun callOn(scope: Scope): Obj { val target = scope.parent ?: scope - return executable.execute(target) + return executeFunctionDecl(target, spec) } } 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 2378917..c5a3345 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -178,7 +178,7 @@ class BytecodeCompiler( ) } is net.sergeych.lyng.FunctionDeclStatement -> { - val value = emitStatementCall(stmt) + val value = emitDeclFunction(stmt) builder.emit(Opcode.RET, value.slot) val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount builder.build( @@ -4109,7 +4109,6 @@ class BytecodeCompiler( private fun emitDeclExec(stmt: Statement): CompiledValue { val executable = when (stmt) { is net.sergeych.lyng.ClassDeclStatement -> stmt.executable - is net.sergeych.lyng.FunctionDeclStatement -> stmt.executable else -> throw BytecodeCompileException( "Bytecode compile error: unsupported declaration ${stmt::class.simpleName}", stmt.pos @@ -4137,6 +4136,14 @@ class BytecodeCompiler( return CompiledValue(dst, SlotType.OBJ) } + private fun emitDeclFunction(stmt: net.sergeych.lyng.FunctionDeclStatement): CompiledValue { + val constId = builder.addConst(BytecodeConst.FunctionDecl(stmt.spec)) + val dst = allocSlot() + builder.emit(Opcode.DECL_FUNCTION, 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) @@ -4171,7 +4178,7 @@ class BytecodeCompiler( is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target) is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target) is net.sergeych.lyng.ClassDeclStatement -> emitDeclExec(target) - is net.sergeych.lyng.FunctionDeclStatement -> emitDeclExec(target) + is net.sergeych.lyng.FunctionDeclStatement -> emitDeclFunction(target) is net.sergeych.lyng.EnumDeclStatement -> emitDeclEnum(target) is net.sergeych.lyng.TryStatement -> emitTry(target, true) is net.sergeych.lyng.WhenStatement -> compileWhen(target, true) @@ -4204,7 +4211,7 @@ class BytecodeCompiler( is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target) is IfStatement -> compileIfStatement(target) is net.sergeych.lyng.ClassDeclStatement -> emitDeclExec(target) - is net.sergeych.lyng.FunctionDeclStatement -> emitDeclExec(target) + is net.sergeych.lyng.FunctionDeclStatement -> emitDeclFunction(target) is net.sergeych.lyng.EnumDeclStatement -> emitDeclEnum(target) is net.sergeych.lyng.ForInStatement -> { val resultSlot = emitForIn(target, false) ?: return null 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 40aa5f6..1c216c9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt @@ -44,6 +44,9 @@ sealed class BytecodeConst { val entries: List, val lifted: Boolean, ) : BytecodeConst() + data class FunctionDecl( + val spec: net.sergeych.lyng.FunctionDeclSpec, + ) : BytecodeConst() data class SlotPlan(val plan: Map, val captures: List = emptyList()) : BytecodeConst() data class CaptureTable(val entries: List) : BytecodeConst() data class ExtensionPropertyDecl( 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 c46e7ba..0fe0391 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -157,7 +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.DECL_ENUM, + Opcode.DECL_EXEC, Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.ASSIGN_DESTRUCTURE -> listOf(OperandKind.CONST, OperandKind.SLOT) Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, @@ -412,6 +412,7 @@ class CmdBuilder { Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1]) Opcode.DECL_EXEC -> CmdDeclExec(operands[0], operands[1]) Opcode.DECL_ENUM -> CmdDeclEnum(operands[0], operands[1]) + Opcode.DECL_FUNCTION -> CmdDeclFunction(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 f91d3d5..bd82f0b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt @@ -213,6 +213,7 @@ object CmdDisassembler { is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot) is CmdDeclExec -> Opcode.DECL_EXEC 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 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) @@ -284,7 +285,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.DECL_ENUM, + Opcode.DECL_EXEC, Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, 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 92ee8b3..1aec0f5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -1344,6 +1344,16 @@ class CmdDeclEnum(internal val constId: Int, internal val slot: Int) : Cmd() { } } +class CmdDeclFunction(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val decl = frame.fn.constants[constId] as? BytecodeConst.FunctionDecl + ?: error("DECL_FUNCTION expects FunctionDecl at $constId") + val result = executeFunctionDecl(frame.ensureScope(), decl.spec) + frame.storeObjResult(slot, result) + 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 44e54a7..371321f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -166,6 +166,7 @@ enum class Opcode(val code: Int) { DELEGATED_GET_LOCAL(0xC2), DELEGATED_SET_LOCAL(0xC3), BIND_DELEGATE_LOCAL(0xC4), + DECL_FUNCTION(0xC5), ; companion object {