diff --git a/lynglib/src/androidMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugAndroid.kt b/lynglib/src/androidMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugAndroid.kt index ed5080b..e0e0a63 100644 --- a/lynglib/src/androidMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugAndroid.kt +++ b/lynglib/src/androidMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugAndroid.kt @@ -17,6 +17,8 @@ package net.sergeych.lyng.bytecode -internal actual fun vmIterDebug(message: String, error: Throwable?) { +internal actual val vmIterDebugEnabled: Boolean = false + +internal actual fun vmIterDebugWrite(message: String, error: Throwable?) { // no-op on Android } 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 18cb3bb..b4e1d0c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -34,28 +34,24 @@ class CmdVm { frame.applyCaptureRecords() binder?.invoke(frame, args) val cmds = fn.cmds - try { - while (result == null) { - try { - while (result == null) { - val cmd = cmds[frame.ip] - frame.ip += 1 - if (cmd.isFast) { - cmd.performFast(frame) - } else { - cmd.perform(frame) - } - } - } catch (e: Throwable) { - if (!frame.handleException(e)) { - frame.cancelIterators() - throw e + while (true) { + try { + while (result == null) { + val cmd = cmds[frame.ip] + frame.ip += 1 + if (cmd.isFast) { + cmd.performFast(frame) + } else { + cmd.perform(frame) } } + break + } catch (e: Throwable) { + if (!frame.handleException(e)) { + frame.cancelIterators() + throw e + } } - } catch (e: Throwable) { - frame.cancelIterators() - throw e } frame.cancelIterators() return result ?: ObjVoid @@ -71,6 +67,7 @@ sealed class Cmd { open fun performFast(frame: CmdFrame) { error("fast command not supported: ${this::class.simpleName}") } + open suspend fun perform(frame: CmdFrame) { error("slow command not supported: ${this::class.simpleName}") } @@ -179,6 +176,7 @@ class CmdConstObj(internal val constId: Int, internal val dst: Int) : Cmd() { else -> frame.setObj(dst, obj) } } + is BytecodeConst.StringVal -> frame.setObj(dst, ObjString(c.value)) else -> error("CONST_OBJ expects ObjRef/StringVal at $constId") } @@ -255,7 +253,10 @@ class CmdLoadThisVariant( if (candidate.isInstanceOf(typeName)) return@run candidate if (typeClass != null) { val inst = candidate as? net.sergeych.lyng.obj.ObjInstance - if (inst != null && (inst.objClass === typeClass || inst.objClass.allParentsSet.contains(typeClass))) { + if (inst != null && (inst.objClass === typeClass || inst.objClass.allParentsSet.contains( + typeClass + )) + ) { return@run inst } } @@ -284,7 +285,10 @@ class CmdMakeRange( val descending = frame.slotToObj(descendingSlot).toBool() val stepObj = frame.slotToObj(stepSlot) val step = if (stepObj.isNull) null else stepObj - frame.storeObjResult(dst, ObjRange(start, end, isEndInclusive = inclusive, isDescending = descending, step = step)) + frame.storeObjResult( + dst, + ObjRange(start, end, isEndInclusive = inclusive, isDescending = descending, step = step) + ) return } } @@ -372,6 +376,7 @@ class CmdCheckIs(internal val objSlot: Int, internal val typeSlot: Int, internal val rightDecl = typeDeclFromObj(frame.ensureScope(), typeObj) ?: return frame.setBool(dst, false) typeDeclIsSubtype(frame.ensureScope(), leftDecl, rightDecl) } + typeObj is ObjTypeExpr -> matchesTypeDecl(frame.ensureScope(), obj, typeObj.typeDecl) typeObj is ObjClass -> obj.isInstanceOf(typeObj) else -> false @@ -393,6 +398,7 @@ class CmdAssertIs(internal val objSlot: Int, internal val typeSlot: Int) : Cmd() ) } } + is ObjTypeExpr -> { if (!matchesTypeDecl(frame.ensureScope(), obj, typeObj.typeDecl)) { frame.ensureScope().raiseClassCastError( @@ -400,6 +406,7 @@ class CmdAssertIs(internal val objSlot: Int, internal val typeSlot: Int) : Cmd() ) } } + else -> frame.ensureScope().raiseClassCastError( "${typeObj.inspect(frame.ensureScope())} is not the class instance" ) @@ -428,6 +435,7 @@ class CmdMakeQualifiedView( base } } + is ObjTypeExpr -> base else -> frame.ensureScope().raiseClassCastError( "${typeObj.inspect(frame.ensureScope())} is not the class instance" @@ -459,11 +467,13 @@ class CmdRangeIntBounds( val start = (range.start as ObjInt).value val end = (range.end as ObjInt).value frame.setInt(startSlot, start) - frame.setInt(endSlot, if (range.isDescending) { - if (range.isEndInclusive) end - 1 else end - } else { - if (range.isEndInclusive) end + 1 else end - }) + frame.setInt( + endSlot, if (range.isDescending) { + if (range.isEndInclusive) end - 1 else end + } else { + if (range.isEndInclusive) end + 1 else end + } + ) frame.setBool(descendingSlot, range.isDescending) frame.setBool(okSlot, true) return @@ -974,6 +984,7 @@ class CmdInvIntLocal(internal val src: Int, internal val dst: Int) : Cmd() { return } } + class CmdCmpEqInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { frame.setBool(dst, frame.getInt(a) == frame.getInt(b)) @@ -3118,6 +3129,7 @@ private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value: assignDestructurePattern(frame, ref, value, pos) return } + is LocalSlotRef -> { val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = ref.captureOwnerScopeId != null) if (index != null) { @@ -3132,6 +3144,7 @@ private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value: return } } + is LocalVarRef -> { val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = false) if (index != null) { @@ -3146,6 +3159,7 @@ private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value: return } } + is FastLocalVarRef -> { val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = false) if (index != null) { @@ -3160,6 +3174,7 @@ private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value: return } } + else -> {} } ref.setAt(pos, frame.ensureScope(), value) @@ -3213,7 +3228,8 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm if (decl.property.setter != null) { val setterName = extensionPropertySetterName(decl.extTypeName, decl.property.name) val setterWrapper = ObjExtensionPropertySetterCallable(decl.property.name, decl.property) - frame.ensureScope().addItem(setterName, false, setterWrapper, decl.visibility, recordType = ObjRecord.Type.Fun) + frame.ensureScope() + .addItem(setterName, false, setterWrapper, decl.visibility, recordType = ObjRecord.Type.Fun) val setterLocal = resolveLocalSlotIndex(frame.fn, setterName, preferCapture = false) if (setterLocal != null) { frame.setObjUnchecked(frame.fn.scopeSlotCount + setterLocal, setterWrapper) @@ -3341,6 +3357,7 @@ class CmdListLiteral( list.ensureCapacity(list.size + value.list.size) list.addAll(value.list) } + else -> frame.ensureScope().raiseError("Spread element must be list") } } else { @@ -3415,7 +3432,9 @@ class CmdGetMemberSlot( val (methodIdResolved, methodOnObjClass) = decodeMemberId(methodId) val fieldRec = if (fieldIdResolved >= 0) { when { - inst != null -> inst.fieldRecordForId(fieldIdResolved) ?: inst.objClass.fieldRecordForId(fieldIdResolved) + inst != null -> inst.fieldRecordForId(fieldIdResolved) + ?: inst.objClass.fieldRecordForId(fieldIdResolved) + cls != null && fieldOnObjClass -> cls.objClass.fieldRecordForId(fieldIdResolved) cls != null -> cls.fieldRecordForId(fieldIdResolved) else -> receiver.objClass.fieldRecordForId(fieldIdResolved) @@ -3424,7 +3443,10 @@ class CmdGetMemberSlot( val rec = fieldRec ?: run { if (methodIdResolved >= 0) { when { - inst != null -> inst.methodRecordForId(methodIdResolved) ?: inst.objClass.methodRecordForId(methodIdResolved) + inst != null -> inst.methodRecordForId(methodIdResolved) ?: inst.objClass.methodRecordForId( + methodIdResolved + ) + cls != null && methodOnObjClass -> cls.objClass.methodRecordForId(methodIdResolved) cls != null -> cls.methodRecordForId(methodIdResolved) else -> receiver.objClass.methodRecordForId(methodIdResolved) @@ -3456,9 +3478,15 @@ class CmdGetMemberSlot( } else { rawName } + suspend fun autoCallIfMethod(resolved: ObjRecord, recv: Obj): Obj { return if (resolved.type == ObjRecord.Type.Fun && !resolved.isAbstract) { - resolved.value.invoke(frame.ensureScope(), resolved.receiver ?: recv, Arguments.EMPTY, resolved.declaringClass) + resolved.value.invoke( + frame.ensureScope(), + resolved.receiver ?: recv, + Arguments.EMPTY, + resolved.declaringClass + ) } else { resolved.value } @@ -3489,7 +3517,9 @@ class CmdSetMemberSlot( val (methodIdResolved, methodOnObjClass) = decodeMemberId(methodId) val fieldRec = if (fieldIdResolved >= 0) { when { - inst != null -> inst.fieldRecordForId(fieldIdResolved) ?: inst.objClass.fieldRecordForId(fieldIdResolved) + inst != null -> inst.fieldRecordForId(fieldIdResolved) + ?: inst.objClass.fieldRecordForId(fieldIdResolved) + cls != null && fieldOnObjClass -> cls.objClass.fieldRecordForId(fieldIdResolved) cls != null -> cls.fieldRecordForId(fieldIdResolved) else -> receiver.objClass.fieldRecordForId(fieldIdResolved) @@ -3498,7 +3528,10 @@ class CmdSetMemberSlot( val rec = fieldRec ?: run { if (methodIdResolved >= 0) { when { - inst != null -> inst.methodRecordForId(methodIdResolved) ?: inst.objClass.methodRecordForId(methodIdResolved) + inst != null -> inst.methodRecordForId(methodIdResolved) ?: inst.objClass.methodRecordForId( + methodIdResolved + ) + cls != null && methodOnObjClass -> cls.objClass.methodRecordForId(methodIdResolved) cls != null -> cls.methodRecordForId(methodIdResolved) else -> receiver.objClass.methodRecordForId(methodIdResolved) @@ -3692,10 +3725,12 @@ class CmdCallMemberSlot( if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, receiver, decl) else scope.raiseError("property $name cannot be called with arguments") } + ObjRecord.Type.Fun -> { val callScope = inst?.instanceScope ?: scope rec.value.invoke(callScope, receiver, callArgs, decl) } + ObjRecord.Type.Delegated -> { val delegate = when (receiver) { is ObjInstance -> { @@ -3707,9 +3742,13 @@ class CmdCallMemberSlot( if (del != null) break } } - del ?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)") + del + ?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)") } - is ObjClass -> rec.delegate ?: scope.raiseError("Internal error: delegated member $name has no delegate") + + is ObjClass -> rec.delegate + ?: scope.raiseError("Internal error: delegated member $name has no delegate") + else -> rec.delegate ?: scope.raiseError("Internal error: delegated member $name has no delegate") } val allArgs = (listOf(receiver, ObjString(name)) + callArgs.list).toTypedArray() @@ -3718,6 +3757,7 @@ class CmdCallMemberSlot( propVal.invoke(scope, receiver, callArgs, decl) }) } + else -> frame.ensureScope().raiseError("member $name is not callable") } frame.storeObjResult(dst, result) @@ -3939,11 +3979,12 @@ class BytecodeLambdaCallable( ?: context.parent?.get(name) ?: context.get(name) ?: continue - val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) { - context.resolve(record, name) - } else { - record.value - } + val value = + if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) { + context.resolve(record, name) + } else { + record.value + } frame.frame.setObj(i, value) } } @@ -4000,6 +4041,7 @@ class CmdFrame( private var scopeDepth = 0 private var virtualDepth = 0 private val iterStack = ArrayDeque() + internal data class TryHandler( val exceptionSlot: Int, val catchIp: Int, @@ -4007,6 +4049,7 @@ class CmdFrame( val iterDepthAtPush: Int, var inCatch: Boolean = false ) + internal val tryStack = ArrayDeque() private var pendingThrowable: Throwable? = null @@ -4045,6 +4088,7 @@ class CmdFrame( else -> obj } } + else -> { val obj = frame.getObj(localIndex) when (obj) { @@ -4055,6 +4099,7 @@ class CmdFrame( } } } + internal fun isFastLocalSlot(slot: Int): Boolean { if (slot < fn.scopeSlotCount) return false val localIndex = slot - fn.scopeSlotCount @@ -4195,6 +4240,7 @@ class CmdFrame( } } } + CaptureOwnerFrameKind.MODULE -> { val slotId = entry.slotIndex val target = moduleScope @@ -4325,9 +4371,9 @@ class CmdFrame( suspend fun handleException(t: Throwable): Boolean { val handler = tryStack.lastOrNull() ?: return false - vmIterDebug( + vmIterDebug { "handleException fn=${fn.name} throwable=${t::class.simpleName} message=${t.message} catchIp=${handler.catchIp} finallyIp=${handler.finallyIp} iterDepth=${iterStack.size}" - ) + } val finallyIp = handler.finallyIp if (t is ReturnException || t is LoopBreakContinueException) { if (finallyIp >= 0) { @@ -4453,42 +4499,50 @@ class CmdFrame( fun pushIterator(iter: Obj) { iterStack.addLast(iter) if (iter.objClass.className == "FlowIterator") { - vmIterDebug("pushIterator fn=${fn.name} depth=${iterStack.size} iterClass=${iter.objClass.className}") + vmIterDebug { "pushIterator fn=${fn.name} depth=${iterStack.size} iterClass=${iter.objClass.className}" } } } fun popIterator() { val iter = iterStack.lastOrNull() if (iter != null && iter.objClass.className == "FlowIterator") { - vmIterDebug("popIterator fn=${fn.name} depth=${iterStack.size} iterClass=${iter.objClass.className}") + vmIterDebug { "popIterator fn=${fn.name} depth=${iterStack.size} iterClass=${iter.objClass.className}" } } iterStack.removeLastOrNull() } suspend fun cancelTopIterator() { val iter = iterStack.removeLastOrNull() ?: return - vmIterDebug("cancelTopIterator fn=${fn.name} depthAfter=${iterStack.size} iterClass=${iter.objClass.className}") + vmIterDebug { "cancelTopIterator fn=${fn.name} depthAfter=${iterStack.size} iterClass=${iter.objClass.className}" } iter.invokeInstanceMethod(ensureScope(), "cancelIteration") { ObjVoid } } suspend fun cancelIterators() { while (iterStack.isNotEmpty()) { val iter = iterStack.removeLast() - vmIterDebug("cancelIterators fn=${fn.name} depthAfter=${iterStack.size} iterClass=${iter.objClass.className}") - iter.invokeInstanceMethod(ensureScope(), "cancelIteration") { ObjVoid } + vmIterDebug { "cancelIterators fn=${fn.name} depthAfter=${iterStack.size} iterClass=${iter.objClass.className}" } + try { + iter.invokeInstanceMethod(ensureScope(), "cancelIteration") { ObjVoid } + } catch (e: Throwable) { + vmIterDebug(e) { + "cancelIterators: cancelIteration failed fn=${fn.name} depthAfter=${iterStack.size} iterClass=${iter.objClass.className}" + } + } } } private suspend fun cancelIteratorsToDepth(depth: Int, reason: String) { while (iterStack.size > depth) { val iter = iterStack.removeLast() - vmIterDebug( + vmIterDebug { "cancelIteratorsToDepth fn=${fn.name} reason=$reason targetDepth=$depth depthAfter=${iterStack.size} iterClass=${iter.objClass.className}" - ) + } try { iter.invokeInstanceMethod(ensureScope(), "cancelIteration") { ObjVoid } } catch (e: Throwable) { - vmIterDebug("cancelIteratorsToDepth: cancelIteration failed fn=${fn.name} reason=$reason", e) + vmIterDebug(e) { + "cancelIteratorsToDepth: cancelIteration failed fn=${fn.name} reason=$reason" + } } } } @@ -4549,10 +4603,12 @@ class CmdFrame( existing.write(value) return } + is RecordSlotRef -> { existing.write(value) return } + else -> {} } } @@ -4629,10 +4685,12 @@ class CmdFrame( existing.write(ObjInt.of(value)) return } + is RecordSlotRef -> { existing.write(ObjInt.of(value)) return } + else -> {} } } @@ -4659,10 +4717,12 @@ class CmdFrame( existing.write(ObjInt.of(value)) return } + is RecordSlotRef -> { existing.write(ObjInt.of(value)) return } + else -> {} } } @@ -4712,10 +4772,12 @@ class CmdFrame( existing.write(ObjReal.of(value)) return } + is RecordSlotRef -> { existing.write(ObjReal.of(value)) return } + else -> {} } } @@ -4736,10 +4798,12 @@ class CmdFrame( existing.write(ObjReal.of(value)) return } + is RecordSlotRef -> { existing.write(ObjReal.of(value)) return } + else -> {} } } @@ -4783,10 +4847,12 @@ class CmdFrame( existing.write(if (value) ObjTrue else ObjFalse) return } + is RecordSlotRef -> { existing.write(if (value) ObjTrue else ObjFalse) return } + else -> {} } } @@ -4807,10 +4873,12 @@ class CmdFrame( existing.write(if (value) ObjTrue else ObjFalse) return } + is RecordSlotRef -> { existing.write(if (value) ObjTrue else ObjFalse) return } + else -> {} } } @@ -4903,6 +4971,7 @@ class CmdFrame( null -> ObjNull else -> raw } + else -> frame.getRawObj(local) ?: ObjNull } } @@ -4929,10 +4998,12 @@ class CmdFrame( existing.write(value) return } + is RecordSlotRef -> { existing.write(value) return } + else -> {} } } @@ -5018,15 +5089,18 @@ class CmdFrame( } namedSeen = true } + value is ObjList -> { if (namedSeen) scope.raiseIllegalArgument("positional splat cannot follow named arguments") positional.addAll(value.list) } + value.isInstanceOf(ObjIterable) -> { if (namedSeen) scope.raiseIllegalArgument("positional splat cannot follow named arguments") val list = (value.invokeInstanceMethod(scope, "toList") as ObjList).list positional.addAll(list) } + else -> scope.raiseClassCastError("expected list of objects for splat argument") } } else { @@ -5075,6 +5149,7 @@ class CmdFrame( else -> obj } } + else -> { val obj = frame.getObj(localIndex) when (obj) { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebug.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebug.kt index 5053e18..4876acd 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebug.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebug.kt @@ -17,4 +17,18 @@ package net.sergeych.lyng.bytecode -internal expect fun vmIterDebug(message: String, error: Throwable? = null) +internal expect val vmIterDebugEnabled: Boolean + +internal expect fun vmIterDebugWrite(message: String, error: Throwable? = null) + +internal inline fun vmIterDebug(message: () -> String) { + if (vmIterDebugEnabled) { + vmIterDebugWrite(message()) + } +} + +internal inline fun vmIterDebug(error: Throwable, message: () -> String) { + if (vmIterDebugEnabled) { + vmIterDebugWrite(message(), error) + } +} diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index d29b161..c7ed52a 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -131,7 +131,7 @@ class ScriptTest { } // --- Helpers to test iterator cancellation semantics --- - class ObjTestIterable : Obj() { + class ObjTestIterable(private val throwOnCancel: Boolean = false) : Obj() { var cancelCount: Int = 0 @@ -145,6 +145,13 @@ class ScriptTest { addFn("cancelCount") { thisAs().cancelCount.toObj() } } } + + internal fun onCancel() { + cancelCount += 1 + if (throwOnCancel) { + throw IllegalStateException("cancel failed") + } + } } class ObjTestIterator(private val owner: ObjTestIterable) : Obj() { @@ -154,7 +161,7 @@ class ScriptTest { private fun hasNext(): Boolean = i < 5 private fun next(): Obj = ObjInt((++i).toLong()) private fun cancelIteration() { - owner.cancelCount += 1 + owner.onCancel() } companion object { @@ -221,6 +228,33 @@ class ScriptTest { assertEquals(1, ti.cancelCount) } + @Test + fun testVmCancelsAllIteratorsWhenOneCancelFails() = runTest { + val scope = Script.newScope() + val outer = ObjTestIterable() + val inner = ObjTestIterable(throwOnCancel = true) + scope.addConst("outer", outer) + scope.addConst("inner", inner) + + try { + scope.eval( + """ + for (o in outer) { + for (i in inner) { + throw "boom" + } + } + """.trimIndent() + ) + fail("Exception expected") + } catch (_: Exception) { + // ignore + } + + assertEquals(1, inner.cancelCount) + assertEquals(1, outer.cancelCount) + } + @Test fun parseNewlines() { fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) { diff --git a/lynglib/src/jsMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugJs.kt b/lynglib/src/jsMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugJs.kt index 328384a..875780b 100644 --- a/lynglib/src/jsMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugJs.kt +++ b/lynglib/src/jsMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugJs.kt @@ -17,6 +17,8 @@ package net.sergeych.lyng.bytecode -internal actual fun vmIterDebug(message: String, error: Throwable?) { +internal actual val vmIterDebugEnabled: Boolean = false + +internal actual fun vmIterDebugWrite(message: String, error: Throwable?) { // no-op on JS } diff --git a/lynglib/src/jvmMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugJvm.kt b/lynglib/src/jvmMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugJvm.kt index 7653a1f..d707cba 100644 --- a/lynglib/src/jvmMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugJvm.kt +++ b/lynglib/src/jvmMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugJvm.kt @@ -20,6 +20,18 @@ package net.sergeych.lyng.bytecode import java.io.File import java.time.Instant +private fun parseEnabledFlag(value: String?): Boolean { + return when (value?.lowercase()) { + "1", "true", "yes", "on" -> true + else -> false + } +} + +internal actual val vmIterDebugEnabled: Boolean = run { + parseEnabledFlag(System.getProperty("LYNG_VM_ITER_DEBUG")) + || parseEnabledFlag(System.getenv("LYNG_VM_ITER_DEBUG")) +} + private val vmIterLogFilePath: String = System.getenv("LYNG_VM_DEBUG_LOG") ?.takeIf { it.isNotBlank() } @@ -27,7 +39,7 @@ private val vmIterLogFilePath: String = private val vmIterLogLock = Any() -internal actual fun vmIterDebug(message: String, error: Throwable?) { +internal actual fun vmIterDebugWrite(message: String, error: Throwable?) { runCatching { val line = buildString { append(Instant.now().toString()) diff --git a/lynglib/src/nativeMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugNative.kt b/lynglib/src/nativeMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugNative.kt index 324db03..958c93a 100644 --- a/lynglib/src/nativeMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugNative.kt +++ b/lynglib/src/nativeMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugNative.kt @@ -17,6 +17,8 @@ package net.sergeych.lyng.bytecode -internal actual fun vmIterDebug(message: String, error: Throwable?) { +internal actual val vmIterDebugEnabled: Boolean = false + +internal actual fun vmIterDebugWrite(message: String, error: Throwable?) { // no-op on Native } diff --git a/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugWasmJs.kt b/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugWasmJs.kt index 91d5a37..1897b53 100644 --- a/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugWasmJs.kt +++ b/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/bytecode/VmIterDebugWasmJs.kt @@ -17,6 +17,8 @@ package net.sergeych.lyng.bytecode -internal actual fun vmIterDebug(message: String, error: Throwable?) { +internal actual val vmIterDebugEnabled: Boolean = false + +internal actual fun vmIterDebugWrite(message: String, error: Throwable?) { // no-op on wasmJs }