From 9771b40c987c8e4cd6f5dca8df098bd4ef3b2487 Mon Sep 17 00:00:00 2001 From: sergeych Date: Thu, 10 Jul 2025 12:41:10 +0300 Subject: [PATCH] fixed double imports bug added clean import scope Scope.new() --- .../kotlin/net/sergeych/lyng/Compiler.kt | 13 ++++-- .../kotlin/net/sergeych/lyng/ModuleScope.kt | 16 ++++++-- .../kotlin/net/sergeych/lyng/Obj.kt | 9 ++++- .../kotlin/net/sergeych/lyng/Scope.kt | 26 ++++++++---- .../net/sergeych/lyng/pacman/ImportManager.kt | 14 +++++++ .../sergeych/lyng/pacman/ImportProvider.kt | 3 ++ lynglib/src/commonTest/kotlin/ScriptTest.kt | 40 ++++++++++++++++++- 7 files changed, 102 insertions(+), 19 deletions(-) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index d06f304..c99eaf8 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -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("", code)) + suspend fun compile(code: String): Script = compile(Source("", code), Script.defaultImportManager) /** * The keywords that stop processing of expression term diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ModuleScope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ModuleScope.kt index ef756bf..a106da6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ModuleScope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ModuleScope.kt @@ -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()) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt index 9377136..85cca83 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt @@ -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, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index 56bb037..dcff4be 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -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) + } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportManager.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportManager.kt index a347c9f..3271065 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportManager.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportManager.kt @@ -19,6 +19,8 @@ class ImportManager( securityManager: SecurityManager = SecurityManager.allowAll ) : ImportProvider(rootScope, securityManager) { + val packageNames: List 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) + } + } + } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportProvider.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportProvider.kt index b7ae739..8b6ad47 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportProvider.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportProvider.kt @@ -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. diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index d237cc1..24fd5b8 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -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()) + + } + } \ No newline at end of file