From 490faea2ba6dc1e5acb012a7473882620ef23836 Mon Sep 17 00:00:00 2001 From: sergeych Date: Wed, 28 Jan 2026 18:55:41 +0300 Subject: [PATCH] Bytecode compile is/not is and contains --- .../lyng/bytecode/BytecodeCompiler.kt | 25 +++++++++++++------ .../net/sergeych/lyng/bytecode/CmdBuilder.kt | 3 ++- .../sergeych/lyng/bytecode/CmdDisassembler.kt | 3 ++- .../net/sergeych/lyng/bytecode/CmdRuntime.kt | 7 ++++++ .../net/sergeych/lyng/bytecode/Opcode.kt | 1 + lynglib/src/commonTest/kotlin/ScriptTest.kt | 14 +++++++++++ 6 files changed, 43 insertions(+), 10 deletions(-) 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 5fcc7d2..23308e5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -209,15 +209,8 @@ class BytecodeCompiler( val rightValue = compileRefWithFallback(binaryRight(ref), null, refPos(ref)) ?: return null val leftObj = ensureObjSlot(leftValue) val rightObj = ensureObjSlot(rightValue) - val methodId = builder.addConst(BytecodeConst.StringVal("contains")) - if (methodId > 0xFFFF) return null - val argSlot = allocSlot() - builder.emit(Opcode.BOX_OBJ, leftObj.slot, argSlot) - updateSlotType(argSlot, SlotType.OBJ) - val callSlot = allocSlot() - builder.emit(Opcode.CALL_VIRTUAL, rightObj.slot, methodId, argSlot, 1, callSlot) val boolSlot = allocSlot() - builder.emit(Opcode.OBJ_TO_BOOL, callSlot, boolSlot) + builder.emit(Opcode.CONTAINS_OBJ, rightObj.slot, leftObj.slot, boolSlot) updateSlotType(boolSlot, SlotType.BOOL) if (op == BinOp.NOTIN) { val outSlot = allocSlot() @@ -227,6 +220,22 @@ class BytecodeCompiler( } return CompiledValue(boolSlot, SlotType.BOOL) } + if (op == BinOp.IS || op == BinOp.NOTIS) { + val objValue = compileRefWithFallback(binaryLeft(ref), null, refPos(ref)) ?: return null + val typeValue = compileRefWithFallback(binaryRight(ref), null, refPos(ref)) ?: return null + val objSlot = ensureObjSlot(objValue) + val typeSlot = ensureObjSlot(typeValue) + val checkSlot = allocSlot() + builder.emit(Opcode.CHECK_IS, objSlot.slot, typeSlot.slot, checkSlot) + updateSlotType(checkSlot, SlotType.BOOL) + if (op == BinOp.NOTIS) { + val outSlot = allocSlot() + builder.emit(Opcode.NOT_BOOL, checkSlot, outSlot) + updateSlotType(outSlot, SlotType.BOOL) + return CompiledValue(outSlot, SlotType.BOOL) + } + return CompiledValue(checkSlot, SlotType.BOOL) + } val leftRef = binaryLeft(ref) val rightRef = binaryRight(ref) var a = compileRef(leftRef) ?: return null 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 226d41c..e87c00e 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.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT, Opcode.CMP_EQ_OBJ, Opcode.CMP_NEQ_OBJ, Opcode.CMP_REF_EQ_OBJ, Opcode.CMP_REF_NEQ_OBJ, Opcode.CMP_LT_OBJ, Opcode.CMP_LTE_OBJ, Opcode.CMP_GT_OBJ, Opcode.CMP_GTE_OBJ, - Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, + Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, Opcode.CONTAINS_OBJ, Opcode.AND_BOOL, Opcode.OR_BOOL -> listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET -> @@ -348,6 +348,7 @@ class CmdBuilder { Opcode.MUL_OBJ -> CmdMulObj(operands[0], operands[1], operands[2]) Opcode.DIV_OBJ -> CmdDivObj(operands[0], operands[1], operands[2]) Opcode.MOD_OBJ -> CmdModObj(operands[0], operands[1], operands[2]) + Opcode.CONTAINS_OBJ -> CmdContainsObj(operands[0], operands[1], operands[2]) Opcode.JMP -> CmdJmp(operands[0]) Opcode.JMP_IF_TRUE -> CmdJmpIfTrue(operands[0], operands[1]) Opcode.JMP_IF_FALSE -> CmdJmpIfFalse(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 7860974..c7d33d7 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt @@ -160,6 +160,7 @@ object CmdDisassembler { is CmdMulObj -> Opcode.MUL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) is CmdDivObj -> Opcode.DIV_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) is CmdModObj -> Opcode.MOD_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdContainsObj -> Opcode.CONTAINS_OBJ to intArrayOf(cmd.target, cmd.value, cmd.dst) is CmdJmp -> Opcode.JMP to intArrayOf(cmd.target) is CmdJmpIfTrue -> Opcode.JMP_IF_TRUE to intArrayOf(cmd.cond, cmd.target) is CmdJmpIfFalse -> Opcode.JMP_IF_FALSE to intArrayOf(cmd.cond, cmd.target) @@ -235,7 +236,7 @@ object CmdDisassembler { Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT, Opcode.CMP_EQ_OBJ, Opcode.CMP_NEQ_OBJ, Opcode.CMP_REF_EQ_OBJ, Opcode.CMP_REF_NEQ_OBJ, Opcode.CMP_LT_OBJ, Opcode.CMP_LTE_OBJ, Opcode.CMP_GT_OBJ, Opcode.CMP_GTE_OBJ, - Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, + Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, Opcode.CONTAINS_OBJ, Opcode.AND_BOOL, Opcode.OR_BOOL -> listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET -> 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 31de03d..0ab9620 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -913,6 +913,13 @@ class CmdModObj(internal val a: Int, internal val b: Int, internal val dst: Int) } } +class CmdContainsObj(internal val target: Int, internal val value: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.slotToObj(target).contains(frame.scope, frame.slotToObj(value))) + return + } +} + class CmdJmp(internal val target: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { frame.ip = target 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 8d8d290..e2d9489 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -105,6 +105,7 @@ enum class Opcode(val code: Int) { MUL_OBJ(0x79), DIV_OBJ(0x7A), MOD_OBJ(0x7B), + CONTAINS_OBJ(0x7C), JMP(0x80), JMP_IF_TRUE(0x81), diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index b8e3f3c..783f57f 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -5215,6 +5215,20 @@ class ScriptTest { assertEquals(ObjFalse, scope.eval("inList(5, [1,2,3])")) } + @Test + fun testIsOperatorBytecode() = runTest { + val scope = Script.newScope() + scope.eval( + """ + fun isInt(x) { x is Int } + """.trimIndent() + ) + val disasm = scope.disassembleSymbol("isInt") + assertFalse(disasm.contains("not a compiled body")) + assertEquals(ObjTrue, scope.eval("isInt(42)")) + assertEquals(ObjFalse, scope.eval("isInt(\"42\")")) + } + @Test fun testFilterBug() = runTest { eval(