fixed double imports bug

added clean import scope Scope.new()
This commit is contained in:
Sergey Chernov 2025-07-10 12:41:10 +03:00
parent 230cb0a067
commit 9771b40c98
7 changed files with 102 additions and 19 deletions

View File

@ -7,10 +7,15 @@ import net.sergeych.lyng.pacman.ImportProvider
*/
class Compiler(
val cc: CompilerContext,
val importManager: ImportProvider = Script.defaultImportManager,
val importManager: ImportProvider,
@Suppress("UNUSED_PARAMETER")
settings: Settings = Settings()
) {
init {
println("Compiler initialized: $importManager")
}
var packageName: String? = null
class Settings
@ -1618,8 +1623,8 @@ class Compiler(
companion object {
suspend fun compile(source: Source, importProvider: ImportProvider = Script.defaultImportManager): Script {
return Compiler(CompilerContext(parseLyng(source)),importProvider).parseScript()
suspend fun compile(source: Source,importManager: ImportProvider): Script {
return Compiler(CompilerContext(parseLyng(source)),importManager).parseScript()
}
private var lastPriority = 0
@ -1742,7 +1747,7 @@ class Compiler(
allOps.filter { it.priority == l }.associateBy { it.tokenType }
}
suspend fun compile(code: String): Script = compile(Source("<eval>", code))
suspend fun compile(code: String): Script = compile(Source("<eval>", code), Script.defaultImportManager)
/**
* The keywords that stop processing of expression term

View File

@ -7,7 +7,7 @@ import net.sergeych.lyng.pacman.ImportProvider
* used in [Compiler];
*/
class ModuleScope(
val importProvider: ImportProvider,
var importProvider: ImportProvider,
pos: Pos = Pos.builtIn,
override val packageName: String
) : Scope(importProvider.rootScope, Arguments.EMPTY, pos) {
@ -29,9 +29,17 @@ class ModuleScope(
?.also { symbolsToImport!!.remove(it) }
?: scope.raiseError("internal error: symbol $symbol not found though the module is cached")
} ?: symbol
if (newName in scope.objects)
scope.raiseError("symbol $newName already exists, redefinition on import is not allowed")
scope.objects[newName] = record
val existing = scope.objects[newName]
if (existing != null ) {
if (existing.importedFrom != record.importedFrom)
scope.raiseError("symbol ${existing.importedFrom?.packageName}.$newName already exists, redefinition on import is not allowed")
// already imported
}
else {
// when importing records, we keep track of its package (not otherwise needed)
if (record.importedFrom == null) record.importedFrom = this
scope.objects[newName] = record
}
}
}
if (!symbolsToImport.isNullOrEmpty())

View File

@ -15,8 +15,13 @@ import kotlin.contracts.ExperimentalContracts
data class ObjRecord(
var value: Obj,
val isMutable: Boolean,
val visibility: Visibility = Visibility.Public
)
val visibility: Visibility = Visibility.Public,
var importedFrom: Scope? = null
) {
@Suppress("unused")
fun qualifiedName(name: String): String =
"${importedFrom?.packageName ?: "anonymous"}.$name"
}
/**
* When we need read-write access to an object in some abstract storage, we need Accessor,

View File

@ -1,15 +1,16 @@
package net.sergeych.lyng
import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.pacman.ImportProvider
/**
* Scope is where local variables and methods are stored. Scope is also a parent scope for other scopes.
* Each block usually creates a scope. Accessing Lyng closures usually is done via a scope.
*
* To create default scope, use default `Scope()` constructor, it will create a scope with a parent
* module scope with default [ImportManager], you can access with [importManager] as needed.
* module scope with default [ImportManager], you can access with [currentImportProvider] as needed.
*
* If you want to create [ModuleScope] by hand, try [importManager] and [ImportManager.newModule],
* If you want to create [ModuleScope] by hand, try [currentImportProvider] and [ImportManager.newModule],
* or [ImportManager.newModuleAt].
*
* There are special types of scopes:
@ -141,12 +142,12 @@ open class Scope(
fun addConst(name: String, value: Obj) = addItem(name, false, value)
suspend fun eval(code: String): Obj =
Compiler.compile(code.toSource()).execute(this)
Compiler.compile(code.toSource(), currentImportProvider).execute(this)
suspend fun eval(source: Source): Obj =
Compiler.compile(
source,
(this as? ModuleScope)?.importProvider ?: Script.defaultImportManager
currentImportProvider
).execute(this)
fun containsLocal(name: String): Boolean = name in objects
@ -168,10 +169,19 @@ open class Scope(
* @throws IllegalStateException if there is no such manager (if you create some specific scope with no manager,
* then you knew what you did)
*/
val importManager: ImportManager by lazy {
if( this is ModuleScope )
(importProvider as? ImportManager)?.let { return@lazy it }
parent?.importManager ?: throw IllegalStateException("this scope has no manager in the chain")
val currentImportProvider: ImportProvider by lazy {
if (this is ModuleScope)
importProvider.getActualProvider()
else
parent?.currentImportProvider ?: throw IllegalStateException("this scope has no manager in the chain")
}
val importManager by lazy { (currentImportProvider as? ImportManager)
?: throw IllegalStateException("this scope has no manager in the chain (provided $currentImportProvider") }
companion object {
fun new(): Scope =
Script.defaultImportManager.copy().newModuleAt(Pos.builtIn)
}
}

View File

@ -19,6 +19,8 @@ class ImportManager(
securityManager: SecurityManager = SecurityManager.allowAll
) : ImportProvider(rootScope, securityManager) {
val packageNames: List<String> get() = imports.keys.toList()
private inner class Entry(
val packageName: String,
val builder: suspend (ModuleScope) -> Unit,
@ -43,6 +45,10 @@ class ImportManager(
override suspend fun createModuleScope(pos: Pos, packageName: String): ModuleScope {
return doImport(packageName, pos)
}
override fun getActualProvider(): ImportProvider {
return this@ImportManager
}
}
/**
@ -95,6 +101,7 @@ class ImportManager(
*/
private suspend fun doImport(packageName: String, pos: Pos): ModuleScope {
val entry = imports[packageName] ?: throw ImportException(pos, "package not found: $packageName")
println("import enrty found: $packageName")
return entry.getScope(pos)
}
@ -125,4 +132,11 @@ class ImportManager(
}
}
fun copy(): ImportManager =
op.withLock {
ImportManager(rootScope, securityManager).apply {
imports.putAll(this@ImportManager.imports)
}
}
}

View File

@ -14,6 +14,9 @@ abstract class ImportProvider(
val rootScope: Scope,
val securityManager: SecurityManager = SecurityManager.allowAll
) {
open fun getActualProvider() = this
/**
* Find an import and create a scope for it. This method must implement caching so repeated
* imports are not repeatedly loaded and parsed and should be cheap.

View File

@ -2443,7 +2443,7 @@ class ScriptTest {
@Test
fun testDefaultImportManager() = runTest {
val scope = Scope()
val scope = Scope.new()
assertFails {
scope.eval("""
import foo
@ -2574,4 +2574,42 @@ class ScriptTest {
)
}
@Test
fun testDoubleImports() = runTest {
val s = Scope.new()
println(Script.defaultImportManager.packageNames)
println(s.importManager.packageNames)
s.importManager.addTextPackages("""
package foo
import lyng.time
fun foo() {
println("foo: %s"(Instant()))
}
""".trimIndent())
s.importManager.addTextPackages("""
package bar
import lyng.time
fun bar() {
println("bar: %s"(Instant()))
}
""".trimIndent())
println(s.importManager.packageNames)
s.eval("""
import foo
import bar
foo()
bar()
""".trimIndent())
}
}