diff --git a/examples/free_fall.lyng b/examples/free_fall.lyng index d7ad556..145e27d 100644 --- a/examples/free_fall.lyng +++ b/examples/free_fall.lyng @@ -1,28 +1,28 @@ /* - Рассчитывает глубину провала по времени падения камня и прихода звука. + Рассчитывает глубину провала по времени падения камня и прихода звука. - @param T измеренное полное время (с) - @param m масса камня (кг) - @param d диаметр камня (м) (предполагается сферическая форма) - @param rho плотность воздуха (кг/м³), по умолчанию 1.2 - @param c скорость звука (м/с), по умолчанию 340.0 - @param g ускорение свободного падения (м/с²), по умолчанию 9.81 - @param Cd коэффициент лобового сопротивления, по умолчанию 0.5 - @param epsilon относительная точность (м), по умолчанию 1e-3 - @param maxIter максимальное число итераций, по умолчанию 20 - @return глубина h (м), или null если расчёт не сошёлся - */ + @param T измеренное полное время (с) + @param m масса камня (кг) + @param d диаметр камня (м) (предполагается сферическая форма) + @param rho плотность воздуха (кг/м³), по умолчанию 1.2 + @param c скорость звука (м/с), по умолчанию 340.0 + @param g ускорение свободного падения (м/с²), по умолчанию 9.81 + @param Cd коэффициент лобового сопротивления, по умолчанию 0.5 + @param epsilon относительная точность (м), по умолчанию 1e-3 + @param maxIter максимальное число итераций, по умолчанию 20 + @return глубина h (м), или null если расчёт не сошёлся +*/ fun calculateDepth( - T: Real, - m: Real, - d: Real, - rho: Real = 1.2, - c: Real = 340.0, - g: Real = 9.81, - Cd: Real = 0.5, - epsilon: Real = 1e-3, - maxIter: Int = 20 + T: Real, + m: Real, + d: Real, + rho: Real = 1.2, + c: Real = 340.0, + g: Real = 9.81, + Cd: Real = 0.5, + epsilon: Real = 1e-3, + maxIter: Int = 20 ): Real? { // Площадь миделя val r = d / 2.0 @@ -90,14 +90,14 @@ fun calculateDepth( return null } - // Пример использования - val T = 6.0 // секунды - val m = 1.0 // кг - val d = 0.1 // м (10 см) +// Пример использования +val T = 6.0 // секунды +val m = 1.0 // кг +val d = 0.1 // м (10 см) - val depth = calculateDepth(T, m, d) - if (depth != null) { - println("Глубина: %.2f м".format(depth)) - } else { - println("Расчёт не сошёлся") - } +val depth = calculateDepth(T, m, d) +if (depth != null) { + println("Глубина: %.2f м".format(depth)) +} else { + println("Расчёт не сошёлся") +} diff --git a/lyng/src/commonMain/kotlin/Common.kt b/lyng/src/commonMain/kotlin/Common.kt index e413554..08c392a 100644 --- a/lyng/src/commonMain/kotlin/Common.kt +++ b/lyng/src/commonMain/kotlin/Common.kt @@ -158,6 +158,7 @@ private class Fmt : CliktCommand(name = "fmt") { private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() { + override val invokeWithoutSubcommand = true override val printHelpOnEmptyArgs = true val version by option("-v", "--version", help = "Print version and exit").flag() diff --git a/lyng/src/jvmTest/kotlin/net/sergeych/lyng_cli/CliFmtJvmTest.kt b/lyng/src/jvmTest/kotlin/net/sergeych/lyng_cli/CliFmtJvmTest.kt index e0ac983..28a7c6d 100644 --- a/lyng/src/jvmTest/kotlin/net/sergeych/lyng_cli/CliFmtJvmTest.kt +++ b/lyng/src/jvmTest/kotlin/net/sergeych/lyng_cli/CliFmtJvmTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -131,4 +131,18 @@ class CliFmtJvmTest { Files.deleteIfExists(tmp) } } + + @Test + fun inlineExecuteWithDashXStillWorksAndPassesArgv() { + val r = runCli( + "-x", + """println("INLINE"); println(ARGV[0]); println(ARGV[1])""", + "one", + "two" + ) + assertTrue("Expected inline execution output", r.out.contains("INLINE")) + assertTrue("Expected ARGV to include first trailing arg", r.out.contains("one")) + assertTrue("Expected ARGV to include second trailing arg", r.out.contains("two")) + assertTrue("Did not expect CLI exit()", r.exitCode == null) + } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index b8ef0f8..e5a113b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -187,6 +187,8 @@ class Compiler( private val externCallableNames: MutableSet = mutableSetOf() private val externBindingNames: MutableSet = mutableSetOf() private val moduleDeclaredNames: MutableSet = mutableSetOf() + private val predeclaredTopLevelValueNames: MutableSet = mutableSetOf() + private val moduleReferencePosByName: MutableMap = mutableMapOf() private var seedingSlotPlan: Boolean = false private fun moduleForcedLocalSlotInfo(): Map { @@ -393,6 +395,7 @@ class Compiler( } declareSlotNameIn(plan, nameToken.value, isMutable = t.value == "var", isDelegated = false) moduleDeclaredNames.add(nameToken.value) + predeclaredTopLevelValueNames.add(nameToken.value) } "class", "object" -> { val nameToken = nextNonWs() @@ -933,6 +936,7 @@ class Compiler( } } captureLocalRef(name, slotLoc, pos)?.let { ref -> + moduleReferencePosByName.putIfAbsent(name, pos) resolutionSink?.reference(name, pos) return ref } @@ -978,8 +982,11 @@ class Compiler( if (moduleLoc != null) { val moduleDeclaredNames = localNamesStack.firstOrNull() if (moduleDeclaredNames == null || !moduleDeclaredNames.contains(name)) { - resolveImportBinding(name, pos)?.let { resolved -> - registerImportBinding(name, resolved.binding, pos) + val resolvedImport = resolveImportBinding(name, pos) + if (resolvedImport != null) { + registerImportBinding(name, resolvedImport.binding, pos) + } else if (predeclaredTopLevelValueNames.contains(name)) { + throw ScriptError(pos, "symbol '$name' is not defined") } } val ref = LocalSlotRef( @@ -1008,8 +1015,11 @@ class Compiler( if (moduleEntry != null) { val moduleDeclaredNames = localNamesStack.firstOrNull() if (moduleDeclaredNames == null || !moduleDeclaredNames.contains(name)) { - resolveImportBinding(name, pos)?.let { resolved -> - registerImportBinding(name, resolved.binding, pos) + val resolvedImport = resolveImportBinding(name, pos) + if (resolvedImport != null) { + registerImportBinding(name, resolvedImport.binding, pos) + } else if (predeclaredTopLevelValueNames.contains(name)) { + throw ScriptError(pos, "symbol '$name' is not defined") } } val moduleLoc = SlotLocation( @@ -1020,6 +1030,7 @@ class Compiler( moduleEntry.isDelegated ) captureLocalRef(name, moduleLoc, pos)?.let { ref -> + moduleReferencePosByName.putIfAbsent(name, pos) resolutionSink?.reference(name, pos) return ref } @@ -1046,6 +1057,7 @@ class Compiler( strictSlotRefs ) } + moduleReferencePosByName.putIfAbsent(name, pos) resolutionSink?.reference(name, pos) return ref } @@ -1080,6 +1092,7 @@ class Compiler( val slot = lookupSlotLocation(name) if (slot != null) { captureLocalRef(name, slot, pos)?.let { ref -> + moduleReferencePosByName.putIfAbsent(name, pos) resolutionSink?.reference(name, pos) return ref } @@ -1106,6 +1119,7 @@ class Compiler( strictSlotRefs ) } + moduleReferencePosByName.putIfAbsent(name, pos) resolutionSink?.reference(name, pos) return ref } @@ -1757,6 +1771,8 @@ class Compiler( callableReturnTypeByScopeId = callableReturnTypeByScopeId, callableReturnTypeByName = callableReturnTypeByName, externBindingNames = externBindingNames, + preparedModuleBindingNames = importBindings.keys, + scopeRefPosByName = moduleReferencePosByName, lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef ) as BytecodeStatement unwrapped to bytecodeStmt.bytecodeFunction() @@ -2085,6 +2101,8 @@ class Compiler( callableReturnTypeByName = callableReturnTypeByName, externCallableNames = externCallableNames, externBindingNames = externBindingNames, + preparedModuleBindingNames = importBindings.keys, + scopeRefPosByName = moduleReferencePosByName, lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef ) } @@ -2116,6 +2134,8 @@ class Compiler( callableReturnTypeByName = callableReturnTypeByName, externCallableNames = externCallableNames, externBindingNames = externBindingNames, + preparedModuleBindingNames = importBindings.keys, + scopeRefPosByName = moduleReferencePosByName, lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef ) } @@ -2172,6 +2192,8 @@ class Compiler( callableReturnTypeByName = callableReturnTypeByName, externCallableNames = externCallableNames, externBindingNames = externBindingNames, + preparedModuleBindingNames = importBindings.keys, + scopeRefPosByName = moduleReferencePosByName, lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef ) } 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 44e6cf8..8d79aa9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -43,6 +43,8 @@ class BytecodeCompiler( private val callableReturnTypeByName: Map = emptyMap(), private val externCallableNames: Set = emptySet(), private val externBindingNames: Set = emptySet(), + private val preparedModuleBindingNames: Set = emptySet(), + private val scopeRefPosByName: Map = emptyMap(), private val lambdaCaptureEntriesByRef: Map> = emptyMap(), ) { private val useScopeSlots: Boolean = allowedScopeNames != null || scopeSlotNameSet != null @@ -53,12 +55,15 @@ class BytecodeCompiler( private var scopeSlotIndices = IntArray(0) private var scopeSlotNames = emptyArray() private var scopeSlotIsModule = BooleanArray(0) + private var scopeSlotRequiresPreparedBinding = BooleanArray(0) + private var scopeSlotRefPos = emptyArray() private var scopeSlotMutables = BooleanArray(0) private var scopeKeyByIndex = emptyArray() private val scopeSlotMap = LinkedHashMap() private val scopeSlotNameMap = LinkedHashMap() private val scopeSlotMutableMap = LinkedHashMap() private val scopeSlotIndexByName = LinkedHashMap() + private val scopeSlotRefPosByKey = LinkedHashMap() private val pendingScopeNameRefs = LinkedHashSet() private val addrSlotByScopeSlot = LinkedHashMap() private data class LocalSlotInfo(val name: String, val isMutable: Boolean, val isDelegated: Boolean) @@ -119,6 +124,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -140,6 +147,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -158,6 +167,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -176,6 +187,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -194,6 +207,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -212,6 +227,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -230,6 +247,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -248,6 +267,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -266,6 +287,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -284,6 +307,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -302,6 +327,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -322,6 +349,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -340,6 +369,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -358,6 +389,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -376,6 +409,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -395,6 +430,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -416,6 +453,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -439,6 +478,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -459,6 +500,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -534,6 +577,7 @@ class BytecodeCompiler( resolved = SlotType.INT } if (mapped < scopeSlotCount && resolved != SlotType.UNKNOWN) { + noteScopeSlotRef(mapped, ref.pos()) val addrSlot = ensureScopeAddr(mapped) val local = allocSlot() emitLoadFromAddr(addrSlot, local, resolved) @@ -544,6 +588,7 @@ class BytecodeCompiler( return CompiledValue(local, resolved) } if (mapped < scopeSlotCount && resolved == SlotType.UNKNOWN) { + noteScopeSlotRef(mapped, ref.pos()) val addrSlot = ensureScopeAddr(mapped) val local = allocSlot() emitLoadFromAddr(addrSlot, local, SlotType.OBJ) @@ -563,6 +608,7 @@ class BytecodeCompiler( } if (allowLocalSlots) { scopeSlotIndexByName[ref.name]?.let { slot -> + noteScopeSlotRef(slot, callSitePos()) val resolved = slotTypes[slot] ?: SlotType.UNKNOWN return CompiledValue(slot, resolved) } @@ -585,6 +631,7 @@ class BytecodeCompiler( return CompiledValue(slot, resolved) } scopeSlotIndexByName[ref.name]?.let { slot -> + noteScopeSlotRef(slot, callSitePos()) val resolved = slotTypes[slot] ?: SlotType.UNKNOWN return CompiledValue(slot, resolved) } @@ -596,6 +643,7 @@ class BytecodeCompiler( val slot = ref.slotIndex() val resolved = slotTypes[slot] ?: SlotType.UNKNOWN if (slot < scopeSlotCount && resolved != SlotType.UNKNOWN) { + noteScopeSlotRef(slot, callSitePos()) val addrSlot = ensureScopeAddr(slot) val local = allocSlot() emitLoadFromAddr(addrSlot, local, resolved) @@ -5002,6 +5050,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -5021,6 +5071,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -5041,6 +5093,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -5061,6 +5115,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -5084,6 +5140,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -5103,6 +5161,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -5727,6 +5787,8 @@ class BytecodeCompiler( scopeSlotIndices, scopeSlotNames, scopeSlotIsModule, + scopeSlotRequiresPreparedBinding, + scopeSlotRefPos, localSlotNames, localSlotMutables, localSlotDelegated, @@ -7599,6 +7661,12 @@ class BytecodeCompiler( private fun assignValue(ref: AssignRef): ObjRef = ref.value private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn + private fun noteScopeSlotRef(slot: Int, pos: Pos) { + if (slot >= scopeSlotCount) return + val key = scopeKeyByIndex.getOrNull(slot) ?: return + scopeSlotRefPosByKey.putIfAbsent(key, pos) + } + private fun resolveSlot(ref: LocalSlotRef): Int? { loopSlotOverrides[ref.name]?.let { return it } val scopeId = refScopeId(ref) @@ -7606,8 +7674,14 @@ class BytecodeCompiler( val key = ScopeSlotKey(scopeId, refSlot(ref)) val localIndex = localSlotIndexByKey[key] if (localIndex != null) return scopeSlotCount + localIndex - scopeSlotMap[key]?.let { return it } - scopeSlotIndexByName[ref.name]?.let { return it } + scopeSlotMap[key]?.let { + scopeSlotRefPosByKey.putIfAbsent(key, ref.pos()) + return it + } + scopeSlotIndexByName[ref.name]?.let { + scopeSlotRefPosByKey.putIfAbsent(key, ref.pos()) + return it + } } if (ref.captureOwnerScopeId != null) { val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref)) @@ -7617,7 +7691,11 @@ class BytecodeCompiler( return scopeSlotCount + localIndex } } - return scopeSlotMap[scopeKey] + val resolved = scopeSlotMap[scopeKey] + if (resolved != null) { + scopeSlotRefPosByKey.putIfAbsent(scopeKey, ref.pos()) + } + return resolved } if (ref.isDelegated) { val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref)) @@ -7628,7 +7706,11 @@ class BytecodeCompiler( val localIndex = localSlotIndexByKey[localKey] if (localIndex != null) return scopeSlotCount + localIndex val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref)) - return scopeSlotMap[scopeKey] + val resolved = scopeSlotMap[scopeKey] + if (resolved != null) { + scopeSlotRefPosByKey.putIfAbsent(scopeKey, ref.pos()) + } + return resolved } private fun resolveCapturedOwnerScopeSlot(ref: LocalSlotRef): Int? { @@ -7858,6 +7940,8 @@ class BytecodeCompiler( scopeSlotIndices = IntArray(scopeSlotCount) scopeSlotNames = arrayOfNulls(scopeSlotCount) scopeSlotIsModule = BooleanArray(scopeSlotCount) + scopeSlotRequiresPreparedBinding = BooleanArray(scopeSlotCount) + scopeSlotRefPos = arrayOfNulls(scopeSlotCount) scopeSlotMutables = BooleanArray(scopeSlotCount) { true } scopeKeyByIndex = arrayOfNulls(scopeSlotCount) for ((key, index) in scopeSlotMap) { @@ -7865,6 +7949,8 @@ class BytecodeCompiler( scopeSlotIndices[index] = key.slot scopeSlotNames[index] = name scopeSlotIsModule[index] = moduleScopeId != null && key.scopeId == moduleScopeId + scopeSlotRequiresPreparedBinding[index] = name != null && preparedModuleBindingNames.contains(name) + scopeSlotRefPos[index] = name?.let { scopeRefPosByName[it] } ?: scopeSlotRefPosByKey[key] scopeSlotMutableMap[key]?.let { scopeSlotMutables[index] = it } scopeKeyByIndex[index] = key } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt index ee3d852..b6b5b3e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt @@ -88,6 +88,8 @@ class BytecodeStatement private constructor( callableReturnTypeByName: Map = emptyMap(), externCallableNames: Set = emptySet(), externBindingNames: Set = emptySet(), + preparedModuleBindingNames: Set = emptySet(), + scopeRefPosByName: Map = emptyMap(), lambdaCaptureEntriesByRef: Map> = emptyMap(), slotTypeDeclByScopeId: Map> = emptyMap(), ): Statement { @@ -124,6 +126,8 @@ class BytecodeStatement private constructor( callableReturnTypeByName = callableReturnTypeByName, externCallableNames = externCallableNames, externBindingNames = externBindingNames, + preparedModuleBindingNames = preparedModuleBindingNames, + scopeRefPosByName = scopeRefPosByName, lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef ) val compiled = compiler.compileStatement(nameHint, statement) 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 6d60eb1..3c78487 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -67,6 +67,8 @@ class CmdBuilder { scopeSlotIndices: IntArray = IntArray(0), scopeSlotNames: Array = emptyArray(), scopeSlotIsModule: BooleanArray = BooleanArray(0), + scopeSlotRequiresPreparedBinding: BooleanArray = BooleanArray(0), + scopeSlotRefPos: Array = emptyArray(), localSlotNames: Array = emptyArray(), localSlotMutables: BooleanArray = BooleanArray(0), localSlotDelegated: BooleanArray = BooleanArray(0), @@ -79,6 +81,12 @@ class CmdBuilder { require(scopeSlotIsModule.isEmpty() || scopeSlotIsModule.size == scopeSlotCount) { "scope slot module mapping size mismatch" } + require(scopeSlotRequiresPreparedBinding.isEmpty() || scopeSlotRequiresPreparedBinding.size == scopeSlotCount) { + "scope slot prepared-binding mapping size mismatch" + } + require(scopeSlotRefPos.isEmpty() || scopeSlotRefPos.size == scopeSlotCount) { + "scope slot position mapping size mismatch" + } require(localSlotNames.size == localSlotMutables.size) { "local slot metadata size mismatch" } require(localSlotNames.size == localSlotDelegated.size) { "local slot delegation size mismatch" } require(localSlotNames.size == localSlotCaptures.size) { "local slot capture size mismatch" } @@ -113,6 +121,8 @@ class CmdBuilder { scopeSlotIndices = scopeSlotIndices, scopeSlotNames = if (scopeSlotNames.isEmpty()) Array(scopeSlotCount) { null } else scopeSlotNames, scopeSlotIsModule = if (scopeSlotIsModule.isEmpty()) BooleanArray(scopeSlotCount) else scopeSlotIsModule, + scopeSlotRequiresPreparedBinding = if (scopeSlotRequiresPreparedBinding.isEmpty()) BooleanArray(scopeSlotCount) else scopeSlotRequiresPreparedBinding, + scopeSlotRefPos = if (scopeSlotRefPos.isEmpty()) Array(scopeSlotCount) { null } else scopeSlotRefPos, localSlotNames = localSlotNames, localSlotMutables = localSlotMutables, localSlotDelegated = localSlotDelegated, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt index fcd047e..7955446 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt @@ -26,6 +26,8 @@ data class CmdFunction( val scopeSlotIndices: IntArray, val scopeSlotNames: Array, val scopeSlotIsModule: BooleanArray, + val scopeSlotRequiresPreparedBinding: BooleanArray, + val scopeSlotRefPos: Array, val localSlotNames: Array, val localSlotMutables: BooleanArray, val localSlotDelegated: BooleanArray, @@ -38,6 +40,8 @@ data class CmdFunction( require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" } require(scopeSlotNames.size == scopeSlotCount) { "scopeSlotNames size mismatch" } require(scopeSlotIsModule.size == scopeSlotCount) { "scopeSlotIsModule size mismatch" } + require(scopeSlotRequiresPreparedBinding.size == scopeSlotCount) { "scopeSlotRequiresPreparedBinding size mismatch" } + require(scopeSlotRefPos.size == scopeSlotCount) { "scopeSlotRefPos size mismatch" } require(localSlotNames.size == localSlotMutables.size) { "localSlot metadata size mismatch" } require(localSlotNames.size == localSlotDelegated.size) { "localSlot delegation size mismatch" } require(localSlotNames.size == localSlotCaptures.size) { "localSlot capture size mismatch" } 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 e82cf94..58e612c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -4112,7 +4112,7 @@ class CmdFrame( } fun ensureScope(): Scope { - val pos = posForIp(ip - 1) + val pos = currentErrorPos() if (pos != null && lastScopePosIp != ip) { scope.pos = pos lastScopePosIp = ip @@ -4189,6 +4189,33 @@ class CmdFrame( return fn.posByIp.getOrNull(ip) } + private fun currentErrorPos(): Pos? { + val center = ip - 1 + if (center < 0) return null + var fallback: Pos? = null + val maxRadius = maxOf(center, fn.posByIp.size - 1 - center) + for (radius in 0..maxRadius) { + val before = center - radius + if (before >= 0) { + val pos = posForIp(before) + if (pos != null) { + if (pos.source !== Source.builtIn && pos.source !== Source.UNKNOWN) return pos + if (fallback == null) fallback = pos + } + } + if (radius == 0) continue + val after = center + radius + if (after < fn.posByIp.size) { + val pos = posForIp(after) + if (pos != null) { + if (pos.source !== Source.builtIn && pos.source !== Source.UNKNOWN) return pos + if (fallback == null) fallback = pos + } + } + } + return fallback + } + fun pushScope(plan: Map, captures: List) { if (scope.skipScopeCreation) { val snapshot = emptyMap() @@ -5045,8 +5072,12 @@ class CmdFrame( if (hadNamedBinding) return if (record.value !== ObjUnset) return if (fn.scopeSlotIsModule.getOrNull(slot) != true) return + val pos = fn.scopeSlotRefPos.getOrNull(slot) ?: currentErrorPos() ?: ensureScope().pos + if (fn.scopeSlotRequiresPreparedBinding.getOrNull(slot) != true) { + throw ScriptError(pos, "symbol '$name' is not defined") + } throw ScriptError( - ensureScope().pos, + pos, "module binding '$name' is not available in the execution scope; prepare the script imports/module bindings explicitly" ) } diff --git a/lynglib/src/commonTest/kotlin/CalculusDiagnosticRegressionTest.kt b/lynglib/src/commonTest/kotlin/CalculusDiagnosticRegressionTest.kt new file mode 100644 index 0000000..b5f837f --- /dev/null +++ b/lynglib/src/commonTest/kotlin/CalculusDiagnosticRegressionTest.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com + * + * 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. + * + */ + +import kotlinx.coroutines.test.runTest +import net.sergeych.lyng.ScriptError +import net.sergeych.lyng.eval +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CalculusDiagnosticRegressionTest { + + @Test + fun undefinedHInCalculusSnippetReportsPreciseDiagnostic() = runTest { + val ex = assertFailsWith { + eval( + """ + var x = 7.0 + // глубина по звуку падения + val m = 1 // kg + val d = 0.06 // 6 cm + val c = 340 // скор. звука + val g = 9.82 + var cnt = 0 + var t = x + var message = "" + val hinv = 1/h + var h = c*c/g*(1 + g*t/c -sqrt(1+2*g*t/c)) + assert(h is Real) + assert(!h.isNaN()) + """.trimIndent() + ) + } + + assertEquals(9, ex.pos.line) + assertEquals(13, ex.pos.column) + assertContains(ex.errorMessage, "symbol 'h' is not defined") + assertContains(ex.message ?: "", "val hinv = 1/h") + } +} diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 9e735db..d5fd142 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -5474,33 +5474,112 @@ class ScriptTest { ) } -// @Test -// fun testFromCalcrus1() = runTest { -// eval($$""" -// import lyng.decimal -// var x = 7.0.d -// // глубина по звуку падения -// val m = 1 // kg -// val d = 0.06 // 6 cm -// val c = 340 // скор. звука -// val g = 9.82 -// var cnt = 0 -// var h = 0.0 -// var t = x -// var message = "" -// -// while(true){ -// val h0 = 0 -// h = c*c/h*(1 + g*t/c -sqrt(1+2*g*t/c)) -// message = "iter ${cnt++}" -// if( cnt > 100 ) { -// message= "ошибка" -// break 0 -// } -// x = h -// if( abs(h-h0)/h > 0.08 ) break h -// } -// println(x) -// """.trimIndent()) -// } + @Test + fun testFromCalcurus1() = runTest { + eval($$""" + + /* + Рассчитывает глубину провала по времени падения камня и прихода звука. + + @param T измеренное полное время (с) + @param m масса камня (кг) + @param d диаметр камня (м) (предполагается сферическая форма) + @param rho плотность воздуха (кг/м³), по умолчанию 1.2 + @param c скорость звука (м/с), по умолчанию 340.0 + @param g ускорение свободного падения (м/с²), по умолчанию 9.81 + @param Cd коэффициент лобового сопротивления, по умолчанию 0.5 + @param epsilon относительная точность (м), по умолчанию 1e-3 + @param maxIter максимальное число итераций, по умолчанию 20 + @return глубина h (м), или null если расчёт не сошёлся + */ + fun calculateDepth( + T: Real, + m: Real, + d: Real, + rho: Real = 1.2, + c: Real = 340.0, + g: Real = 9.81, + Cd: Real = 0.5, + epsilon: Real = 1e-3, + maxIter: Int = 20 + ): Real? { + // Площадь миделя + val r = d / 2.0 + val A = π * r * r + + // Коэффициент сопротивления + val k = 0.5 * Cd * rho * A + + // Предельная скорость + val vTerm = sqrt(m * g / k) + + // Функция времени падения с высоты h + fun tFall(h: Real): Real { + val arg = exp(g * h / (vTerm * vTerm)) + // arcosh(x) = ln(x + sqrt(x^2 - 1)) + val arcosh = ln(arg + sqrt(arg * arg - 1.0)) + return vTerm / g * arcosh + } + + // Производная времени падения по h + fun dtFall_dh(h: Real): Real { + val expArg = exp(2.0 * g * h / (vTerm * vTerm)) + return 1.0 / (vTerm * sqrt(expArg - 1.0)) + } + + // Полное расчётное время T_calc(h) = tFall(h) + h/c + fun Tcalc(h: Real): Real = tFall(h) + h / c + + // Производная T_calc по h + fun dTcalc_dh(h: Real): Real = dtFall_dh(h) + 1.0 / c + + // Начальное приближение (без сопротивления) + val term = 1.0 + g * T / c + val sqrtTerm = sqrt(1.0 + 2.0 * g * T / c) + var h = (c * c / g) * (term - sqrtTerm) + + // Проверка на валидность начального приближения + if (h.isNaN() || h <= 0.0) { + // Если формула дала некорректный результат, используем оценку по свободному падению + h = 0.5 * g * T * T // грубая оценка, всё равно будет уточняться + if (h.isNaN() || h <= 0.0) return null + } + + // Итерации Ньютона + var iter = 0 + while (iter < maxIter) { + val f = Tcalc(h) - T + val df = dTcalc_dh(h) + + // Если производная близка к нулю, выходим + if (abs(df) < 1e-12) return null + + val hNew = h - f / df + + // Проверка сходимости + if (abs(hNew - h) < epsilon) { + return hNew + } + + h = hNew + iter++ + } + + // Не сошлось за maxIter + return null + } + + // Пример использования + val T = 6.0 // секунды + val m = 1.0 // кг + val d = 0.1 // м (10 см) + + val depth = calculateDepth(T, m, d) + if (depth != null) { + println("Глубина: %.2f м".format(depth)) + } else { + println("Расчёт не сошёлся") + } + """.trimIndent()) + } } diff --git a/lynglib/src/commonTest/kotlin/SeedLocalsRegressionTest.kt b/lynglib/src/commonTest/kotlin/SeedLocalsRegressionTest.kt index 24964c5..ab4cfbf 100644 --- a/lynglib/src/commonTest/kotlin/SeedLocalsRegressionTest.kt +++ b/lynglib/src/commonTest/kotlin/SeedLocalsRegressionTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2026 Sergey S. Chernov + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,17 +12,14 @@ * 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. + * */ import kotlinx.coroutines.test.runTest import net.sergeych.lyng.FrameSlotRef import net.sergeych.lyng.Pos import net.sergeych.lyng.Scope -import net.sergeych.lyng.bytecode.BytecodeConst -import net.sergeych.lyng.bytecode.CmdFrame -import net.sergeych.lyng.bytecode.CmdFunction -import net.sergeych.lyng.bytecode.CmdVm -import net.sergeych.lyng.bytecode.seedFrameLocalsFromScope +import net.sergeych.lyng.bytecode.* import kotlin.test.Test import kotlin.test.assertNull @@ -39,6 +36,8 @@ class SeedLocalsRegressionTest { scopeSlotIndices = intArrayOf(0), scopeSlotNames = arrayOf(null), scopeSlotIsModule = booleanArrayOf(false), + scopeSlotRequiresPreparedBinding = booleanArrayOf(false), + scopeSlotRefPos = arrayOfNulls(1), localSlotNames = arrayOf("x"), localSlotMutables = booleanArrayOf(true), localSlotDelegated = booleanArrayOf(false), diff --git a/lynglib/src/commonTest/kotlin/UndefinedSymbolDiagnosticTest.kt b/lynglib/src/commonTest/kotlin/UndefinedSymbolDiagnosticTest.kt new file mode 100644 index 0000000..3267d18 --- /dev/null +++ b/lynglib/src/commonTest/kotlin/UndefinedSymbolDiagnosticTest.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com + * + * 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. + * + */ + +import kotlinx.coroutines.test.runTest +import net.sergeych.lyng.ScriptError +import net.sergeych.lyng.eval +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class UndefinedSymbolDiagnosticTest { + + @Test + fun laterDeclaredTopLevelSymbolReportsUndefinedNameAtUseSite() = runTest { + val ex = assertFailsWith { + eval( + """ + var x = 7.0 + val hinv = 1/h + var h = x + """.trimIndent() + ) + } + + assertEquals(1, ex.pos.line) + assertEquals(13, ex.pos.column) + assertContains(ex.errorMessage, "symbol 'h' is not defined") + } +}