- fixed CLI -x mode

- fixed a wrong error message when global symbol is not found
This commit is contained in:
Sergey Chernov 2026-03-31 20:52:31 +03:00
parent 6e9333844e
commit c097464750
13 changed files with 426 additions and 77 deletions

View File

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

View File

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

View File

@ -187,6 +187,8 @@ class Compiler(
private val externCallableNames: MutableSet<String> = mutableSetOf()
private val externBindingNames: MutableSet<String> = mutableSetOf()
private val moduleDeclaredNames: MutableSet<String> = mutableSetOf()
private val predeclaredTopLevelValueNames: MutableSet<String> = mutableSetOf()
private val moduleReferencePosByName: MutableMap<String, Pos> = mutableMapOf()
private var seedingSlotPlan: Boolean = false
private fun moduleForcedLocalSlotInfo(): Map<String, ForcedLocalSlotInfo> {
@ -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
)
}

View File

@ -43,6 +43,8 @@ class BytecodeCompiler(
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
private val externCallableNames: Set<String> = emptySet(),
private val externBindingNames: Set<String> = emptySet(),
private val preparedModuleBindingNames: Set<String> = emptySet(),
private val scopeRefPosByName: Map<String, Pos> = emptyMap(),
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
) {
private val useScopeSlots: Boolean = allowedScopeNames != null || scopeSlotNameSet != null
@ -53,12 +55,15 @@ class BytecodeCompiler(
private var scopeSlotIndices = IntArray(0)
private var scopeSlotNames = emptyArray<String?>()
private var scopeSlotIsModule = BooleanArray(0)
private var scopeSlotRequiresPreparedBinding = BooleanArray(0)
private var scopeSlotRefPos = emptyArray<Pos?>()
private var scopeSlotMutables = BooleanArray(0)
private var scopeKeyByIndex = emptyArray<ScopeSlotKey?>()
private val scopeSlotMap = LinkedHashMap<ScopeSlotKey, Int>()
private val scopeSlotNameMap = LinkedHashMap<ScopeSlotKey, String>()
private val scopeSlotMutableMap = LinkedHashMap<ScopeSlotKey, Boolean>()
private val scopeSlotIndexByName = LinkedHashMap<String, Int>()
private val scopeSlotRefPosByKey = LinkedHashMap<ScopeSlotKey, Pos>()
private val pendingScopeNameRefs = LinkedHashSet<String>()
private val addrSlotByScopeSlot = LinkedHashMap<Int, Int>()
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
}

View File

@ -88,6 +88,8 @@ class BytecodeStatement private constructor(
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
externCallableNames: Set<String> = emptySet(),
externBindingNames: Set<String> = emptySet(),
preparedModuleBindingNames: Set<String> = emptySet(),
scopeRefPosByName: Map<String, Pos> = emptyMap(),
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = 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)

View File

@ -67,6 +67,8 @@ class CmdBuilder {
scopeSlotIndices: IntArray = IntArray(0),
scopeSlotNames: Array<String?> = emptyArray(),
scopeSlotIsModule: BooleanArray = BooleanArray(0),
scopeSlotRequiresPreparedBinding: BooleanArray = BooleanArray(0),
scopeSlotRefPos: Array<net.sergeych.lyng.Pos?> = emptyArray(),
localSlotNames: Array<String?> = 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,

View File

@ -26,6 +26,8 @@ data class CmdFunction(
val scopeSlotIndices: IntArray,
val scopeSlotNames: Array<String?>,
val scopeSlotIsModule: BooleanArray,
val scopeSlotRequiresPreparedBinding: BooleanArray,
val scopeSlotRefPos: Array<net.sergeych.lyng.Pos?>,
val localSlotNames: Array<String?>,
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" }

View File

@ -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<String, Int>, captures: List<String>) {
if (scope.skipScopeCreation) {
val snapshot = emptyMap<String, Int?>()
@ -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"
)
}

View File

@ -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<ScriptError> {
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")
}
}

View File

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

View File

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

View File

@ -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<ScriptError> {
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")
}
}