Compare commits
No commits in common. "ab39110834fd3a41919827a1cf73433095fc7369" and "a1ea09440d5837f2ea0b27cc10dcffda4d8eb2a6" have entirely different histories.
ab39110834
...
a1ea09440d
@ -1,137 +0,0 @@
|
||||
package net.sergeych
|
||||
|
||||
import net.sergeych.lyng.EvalSession
|
||||
import net.sergeych.lyng.Source
|
||||
import net.sergeych.lyng.obj.ObjString
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.writeText
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CliLocalModuleImportRegressionJvmTest {
|
||||
|
||||
private fun writeTransitiveImportTree(root: java.nio.file.Path) {
|
||||
val packageDir = Files.createDirectories(root.resolve("package1"))
|
||||
val nestedDir = Files.createDirectories(packageDir.resolve("nested"))
|
||||
|
||||
packageDir.resolve("alpha.lyng").writeText(
|
||||
"""
|
||||
package package1.alpha
|
||||
|
||||
import lyng.stdlib
|
||||
import lyng.io.net
|
||||
|
||||
class Alpha {
|
||||
val headers = Map<String, String>()
|
||||
|
||||
fun makeTask(port: Int, host: String): Deferred = launch {
|
||||
host + ":" + port
|
||||
}
|
||||
|
||||
fun netModule() = Net
|
||||
}
|
||||
|
||||
fun alphaValue() = "alpha"
|
||||
""".trimIndent()
|
||||
)
|
||||
packageDir.resolve("beta.lyng").writeText(
|
||||
"""
|
||||
package package1.beta
|
||||
|
||||
import lyng.stdlib
|
||||
import package1.alpha
|
||||
|
||||
fun betaValue() = alphaValue() + "|beta"
|
||||
""".trimIndent()
|
||||
)
|
||||
nestedDir.resolve("gamma.lyng").writeText(
|
||||
"""
|
||||
package package1.nested.gamma
|
||||
|
||||
import lyng.io.net
|
||||
import package1.alpha
|
||||
import package1.beta
|
||||
|
||||
val String.gammaTag get() = this + "|gamma"
|
||||
|
||||
fun gammaValue() = betaValue().gammaTag
|
||||
fun netModule() = Net
|
||||
""".trimIndent()
|
||||
)
|
||||
packageDir.resolve("entry.lyng").writeText(
|
||||
"""
|
||||
package package1.entry
|
||||
|
||||
import lyng.stdlib
|
||||
import lyng.io.net
|
||||
import package1.alpha
|
||||
import package1.beta
|
||||
import package1.nested.gamma
|
||||
|
||||
fun report() = gammaValue() + "|entry"
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun localModuleUsingLaunchAndNetImportsWithoutStdlibRedefinition() = runBlocking {
|
||||
val root = Files.createTempDirectory("lyng-cli-import-regression")
|
||||
try {
|
||||
val mainFile = root.resolve("main.lyng")
|
||||
writeTransitiveImportTree(root)
|
||||
mainFile.writeText(
|
||||
"""
|
||||
import package1.entry
|
||||
import package1.beta
|
||||
import package1.nested.gamma
|
||||
|
||||
println(report())
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
executeFile(mainFile.toString(), emptyList())
|
||||
} finally {
|
||||
root.toFile().deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun localModuleImportsAreNoOpsWhenEvaldRepeatedlyOnSameCliContext() = runBlocking {
|
||||
val root = Files.createTempDirectory("lyng-cli-import-regression-repeat")
|
||||
try {
|
||||
val mainFile = root.resolve("main.lyng")
|
||||
writeTransitiveImportTree(root)
|
||||
mainFile.writeText("println(\"bootstrap\")")
|
||||
|
||||
val session = EvalSession(newCliScope(emptyList(), mainFile.toString()))
|
||||
try {
|
||||
repeat(5) { index ->
|
||||
val result = evalOnCliDispatcher(
|
||||
session,
|
||||
Source(
|
||||
"<repeat-local-import-$index>",
|
||||
"""
|
||||
import package1.entry
|
||||
import package1.nested.gamma
|
||||
import package1.beta
|
||||
import package1.alpha
|
||||
|
||||
report()
|
||||
""".trimIndent()
|
||||
)
|
||||
) as ObjString
|
||||
|
||||
assertEquals(
|
||||
"alpha|beta|gamma|entry",
|
||||
result.value
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
session.cancelAndJoin()
|
||||
}
|
||||
} finally {
|
||||
root.toFile().deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -64,73 +64,6 @@ class CliLocalImportsJvmTest {
|
||||
return CliResult(outBuf.toString("UTF-8"), errBuf.toString("UTF-8"), exitCode)
|
||||
}
|
||||
|
||||
private fun writeTransitiveImportTree(root: java.nio.file.Path) {
|
||||
val packageDir = Files.createDirectories(root.resolve("package1"))
|
||||
val nestedDir = Files.createDirectories(packageDir.resolve("nested"))
|
||||
|
||||
Files.writeString(
|
||||
packageDir.resolve("alpha.lyng"),
|
||||
"""
|
||||
package package1.alpha
|
||||
|
||||
import lyng.stdlib
|
||||
import lyng.io.net
|
||||
|
||||
class Alpha {
|
||||
val headers = Map<String, String>()
|
||||
|
||||
fun makeTask(port: Int, host: String): Deferred = launch {
|
||||
host + ":" + port
|
||||
}
|
||||
|
||||
fun netModule() = Net
|
||||
}
|
||||
|
||||
fun alphaValue() = "alpha"
|
||||
""".trimIndent()
|
||||
)
|
||||
Files.writeString(
|
||||
packageDir.resolve("beta.lyng"),
|
||||
"""
|
||||
package package1.beta
|
||||
|
||||
import lyng.stdlib
|
||||
import package1.alpha
|
||||
|
||||
fun betaValue() = alphaValue() + "|beta"
|
||||
""".trimIndent()
|
||||
)
|
||||
Files.writeString(
|
||||
nestedDir.resolve("gamma.lyng"),
|
||||
"""
|
||||
package package1.nested.gamma
|
||||
|
||||
import lyng.io.net
|
||||
import package1.alpha
|
||||
import package1.beta
|
||||
|
||||
val String.gammaTag get() = this + "|gamma"
|
||||
|
||||
fun gammaValue() = betaValue().gammaTag
|
||||
fun netModule() = Net
|
||||
""".trimIndent()
|
||||
)
|
||||
Files.writeString(
|
||||
packageDir.resolve("entry.lyng"),
|
||||
"""
|
||||
package package1.entry
|
||||
|
||||
import lyng.stdlib
|
||||
import lyng.io.net
|
||||
import package1.alpha
|
||||
import package1.beta
|
||||
import package1.nested.gamma
|
||||
|
||||
fun report() = gammaValue() + "|entry"
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cliDiscoversSiblingAndNestedLocalImportsFromEntryRoot() {
|
||||
val dir = Files.createTempDirectory("lyng_cli_local_imports_")
|
||||
@ -201,37 +134,4 @@ class CliLocalImportsJvmTest {
|
||||
dir.toFile().deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cliHandlesOverlappingDirectoryImportsWithTransitiveStdlibAndNetSymbols() {
|
||||
val dir = Files.createTempDirectory("lyng_cli_local_imports_transitive_")
|
||||
try {
|
||||
val mainFile = dir.resolve("main.lyng")
|
||||
writeTransitiveImportTree(dir)
|
||||
Files.writeString(
|
||||
mainFile,
|
||||
"""
|
||||
import package1.entry
|
||||
import package1.beta
|
||||
import package1.nested.gamma
|
||||
|
||||
println(report())
|
||||
println(gammaValue())
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val result = runCli(mainFile.toString())
|
||||
assertTrue(result.err, result.err.isBlank())
|
||||
assertTrue(
|
||||
result.out,
|
||||
result.out.contains("alpha|beta|gamma|entry")
|
||||
)
|
||||
assertTrue(
|
||||
result.out,
|
||||
result.out.contains("alpha|beta|gamma")
|
||||
)
|
||||
} finally {
|
||||
dir.toFile().deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3436,12 +3436,12 @@ class Compiler(
|
||||
val resolvedRecords = ArrayList<ObjRecord>(captureSlots.size)
|
||||
val resolvedNames = ArrayList<String>(captureSlots.size)
|
||||
for (capture in captureSlots) {
|
||||
val rec = resolveStableCaptureRecord(
|
||||
closureScope,
|
||||
val rec = closureScope.chainLookupIgnoreClosure(
|
||||
capture.name,
|
||||
context.currentClassCtx
|
||||
followClosure = true,
|
||||
caller = context.currentClassCtx
|
||||
) ?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found")
|
||||
resolvedRecords.add(freezeImmutableCaptureRecord(rec))
|
||||
resolvedRecords.add(rec)
|
||||
resolvedNames.add(capture.name)
|
||||
}
|
||||
context.captureRecords = resolvedRecords
|
||||
@ -8766,46 +8766,6 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun freezeImmutableCaptureRecord(record: ObjRecord): ObjRecord {
|
||||
val value = record.value as Obj?
|
||||
if (record.isMutable || record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || value is ObjProperty) {
|
||||
return record
|
||||
}
|
||||
return when (value) {
|
||||
is FrameSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
|
||||
is RecordSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
|
||||
is ScopeSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
|
||||
null -> record
|
||||
else -> record.copy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTransientCapturePlaceholder(value: Obj?): Boolean {
|
||||
return when (value) {
|
||||
null, ObjVoid -> true
|
||||
is FrameSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
|
||||
is RecordSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
|
||||
is ScopeSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveStableCaptureRecord(scope: Scope, name: String, caller: ObjClass?): ObjRecord? {
|
||||
val direct = scope.chainLookupIgnoreClosure(name, followClosure = true, caller = caller) ?: scope.get(name)
|
||||
if (direct != null && !isTransientCapturePlaceholder(direct.value as Obj?)) {
|
||||
return direct
|
||||
}
|
||||
var parent = scope.parent
|
||||
while (parent != null) {
|
||||
val candidate = parent.chainLookupIgnoreClosure(name, followClosure = true, caller = caller) ?: parent.get(name)
|
||||
if (candidate != null && !isTransientCapturePlaceholder(candidate.value as Obj?)) {
|
||||
return candidate
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
return direct
|
||||
}
|
||||
|
||||
private suspend fun parseFunctionDeclaration(
|
||||
visibility: Visibility = Visibility.Public,
|
||||
isAbstract: Boolean = false,
|
||||
@ -9187,12 +9147,12 @@ class Compiler(
|
||||
} else if (captureBase != null && captureNames.isNotEmpty()) {
|
||||
val resolvedRecords = ArrayList<ObjRecord>(captureNames.size)
|
||||
for (name in captureNames) {
|
||||
val rec = resolveStableCaptureRecord(
|
||||
captureBase,
|
||||
val rec = captureBase.chainLookupIgnoreClosure(
|
||||
name,
|
||||
context.currentClassCtx
|
||||
followClosure = true,
|
||||
caller = context.currentClassCtx
|
||||
) ?: captureBase.raiseSymbolNotFound("symbol $name not found")
|
||||
resolvedRecords.add(freezeImmutableCaptureRecord(rec))
|
||||
resolvedRecords.add(rec)
|
||||
}
|
||||
context.captureRecords = resolvedRecords
|
||||
context.captureNames = captureNames
|
||||
@ -9233,12 +9193,7 @@ class Compiler(
|
||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
||||
context.resolve(record, localName)
|
||||
} else {
|
||||
when (val direct = record.value) {
|
||||
is FrameSlotRef -> direct.read()
|
||||
is RecordSlotRef -> direct.read(context, localName)
|
||||
is ScopeSlotRef -> direct.read()
|
||||
else -> direct
|
||||
}
|
||||
record.value
|
||||
}
|
||||
frame.frame.setObj(i, value)
|
||||
}
|
||||
|
||||
@ -79,13 +79,6 @@ class FrameSlotRef(
|
||||
}
|
||||
}
|
||||
|
||||
internal fun resolvedCaptureValueOrNull(): Obj? {
|
||||
return when (frame.getSlotTypeCode(slot)) {
|
||||
SlotType.INT.code, SlotType.REAL.code, SlotType.BOOL.code -> read()
|
||||
else -> peekValue()?.let { read() }
|
||||
}
|
||||
}
|
||||
|
||||
fun write(value: Obj) {
|
||||
when (value) {
|
||||
is ObjInt -> frame.setInt(slot, value.value)
|
||||
@ -139,16 +132,6 @@ class ScopeSlotRef(
|
||||
}
|
||||
}
|
||||
|
||||
internal fun resolvedCaptureValueOrNull(): Obj? {
|
||||
val record = scope.getSlotRecord(slot)
|
||||
return when (val direct = record.value as Obj?) {
|
||||
is FrameSlotRef -> direct.resolvedCaptureValueOrNull()
|
||||
is RecordSlotRef -> direct.resolvedCaptureValueOrNull()
|
||||
is ScopeSlotRef -> direct.resolvedCaptureValueOrNull()
|
||||
else -> direct
|
||||
}
|
||||
}
|
||||
|
||||
fun write(value: Obj) {
|
||||
scope.setSlotValue(slot, value)
|
||||
}
|
||||
@ -215,15 +198,6 @@ class RecordSlotRef(
|
||||
}
|
||||
}
|
||||
|
||||
internal fun resolvedCaptureValueOrNull(): Obj? {
|
||||
return when (val direct = record.value as Obj?) {
|
||||
is FrameSlotRef -> direct.resolvedCaptureValueOrNull()
|
||||
is RecordSlotRef -> direct.resolvedCaptureValueOrNull()
|
||||
is ScopeSlotRef -> direct.resolvedCaptureValueOrNull()
|
||||
else -> direct
|
||||
}
|
||||
}
|
||||
|
||||
fun write(value: Obj) {
|
||||
val direct = record.value
|
||||
if (direct is ScopeSlotRef) {
|
||||
|
||||
@ -32,7 +32,6 @@ class ModuleScope(
|
||||
pos: Pos = Pos.builtIn,
|
||||
override val packageName: String
|
||||
) : Scope(importProvider.rootScope, Arguments.EMPTY, pos) {
|
||||
private fun ObjRecord.importedCopy(source: Scope): ObjRecord = copy(importedFrom = source)
|
||||
|
||||
constructor(importProvider: ImportProvider, source: Source) : this(importProvider, source.startPos, source.fileName)
|
||||
|
||||
@ -85,7 +84,7 @@ class ModuleScope(
|
||||
override suspend fun importInto(scope: Scope, symbols: Map<String, String>?) {
|
||||
val symbolsToImport = symbols?.keys?.toMutableSet()
|
||||
for ((symbol, record) in this.objects) {
|
||||
if (record.visibility.isPublic && record.importedFrom == null) {
|
||||
if (record.visibility.isPublic) {
|
||||
val newName = symbols?.let { ss: Map<String, String> ->
|
||||
ss[symbol]
|
||||
?.also { symbolsToImport!!.remove(symbol) }
|
||||
@ -95,21 +94,21 @@ class ModuleScope(
|
||||
if (newName != null) {
|
||||
val existing = scope.objects[newName]
|
||||
if (existing != null) {
|
||||
val sameBinding = existing.importedFrom == this
|
||||
if (!sameBinding)
|
||||
if (existing.importedFrom != record.importedFrom)
|
||||
scope.raiseError("symbol ${existing.importedFrom?.packageName}.$newName already exists, redefinition on import is not allowed")
|
||||
// already imported
|
||||
} else {
|
||||
val imported = record.importedCopy(this)
|
||||
scope.objects[newName] = imported
|
||||
scope.updateSlotFor(newName, imported)
|
||||
// when importing records, we keep track of its package (not otherwise needed)
|
||||
if (record.importedFrom == null) record.importedFrom = this
|
||||
scope.objects[newName] = record
|
||||
scope.updateSlotFor(newName, record)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for ((cls, map) in this.extensions) {
|
||||
for ((symbol, record) in map) {
|
||||
if (record.visibility.isPublic && record.importedFrom == null) {
|
||||
if (record.visibility.isPublic) {
|
||||
val newName = symbols?.let { ss: Map<String, String> ->
|
||||
ss[symbol]
|
||||
?.also { symbolsToImport!!.remove(symbol) }
|
||||
@ -117,7 +116,7 @@ class ModuleScope(
|
||||
} ?: if (symbols == null) symbol else null
|
||||
|
||||
if (newName != null) {
|
||||
scope.addExtension(cls, newName, record.importedCopy(this))
|
||||
scope.addExtension(cls, newName, record)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,42 +181,37 @@ class Script(
|
||||
}
|
||||
}
|
||||
|
||||
private fun importedBindingRecord(record: ObjRecord, source: Scope): ObjRecord =
|
||||
record.copy(importedFrom = source)
|
||||
|
||||
private suspend fun seedImportBindings(scope: Scope, seedScope: Scope) {
|
||||
val provider = scope.currentImportProvider
|
||||
val importedModules = LinkedHashSet<ModuleScope>()
|
||||
for (moduleRef in this.importedModules) {
|
||||
importedModules.add(provider.prepareImport(moduleRef.pos, moduleRef.name, null))
|
||||
}
|
||||
if (scope is ModuleScope) {
|
||||
scope.importedModules = importedModules.toList()
|
||||
}
|
||||
for (module in importedModules) {
|
||||
module.importInto(scope, null)
|
||||
}
|
||||
for ((name, binding) in importBindings) {
|
||||
val sourceScope: Scope
|
||||
val baseRecord = when (val source = binding.source) {
|
||||
val record = when (val source = binding.source) {
|
||||
is ImportBindingSource.Module -> {
|
||||
val module = provider.prepareImport(source.pos, source.name, null)
|
||||
importedModules.add(module)
|
||||
sourceScope = module
|
||||
module.objects[binding.symbol]?.takeIf { it.visibility.isPublic }
|
||||
?: scope.raiseSymbolNotFound("symbol ${source.name}.${binding.symbol} not found")
|
||||
}
|
||||
ImportBindingSource.Root -> {
|
||||
sourceScope = provider.rootScope
|
||||
provider.rootScope.objects[binding.symbol]?.takeIf { it.visibility.isPublic }
|
||||
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
|
||||
}
|
||||
ImportBindingSource.Seed -> {
|
||||
sourceScope = seedScope
|
||||
findSeedRecord(seedScope, binding.symbol)
|
||||
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
|
||||
}
|
||||
}
|
||||
val record = importedBindingRecord(baseRecord, sourceScope)
|
||||
if (name == "Exception" && record.value !is ObjClass) {
|
||||
scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false, importedFrom = sourceScope))
|
||||
scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false))
|
||||
} else {
|
||||
scope.updateSlotFor(name, record)
|
||||
}
|
||||
@ -224,15 +219,12 @@ class Script(
|
||||
for (module in importedModules) {
|
||||
for ((cls, map) in module.extensions) {
|
||||
for ((symbol, record) in map) {
|
||||
if (record.visibility.isPublic && record.importedFrom == null) {
|
||||
scope.addExtension(cls, symbol, importedBindingRecord(record, module))
|
||||
if (record.visibility.isPublic) {
|
||||
scope.addExtension(cls, symbol, record)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scope is ModuleScope) {
|
||||
scope.importedModules = importedModules.toList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun findSeedRecord(scope: Scope?, name: String): ObjRecord? {
|
||||
|
||||
@ -53,12 +53,7 @@ class BytecodeStatement private constructor(
|
||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) {
|
||||
scope.resolve(record, name)
|
||||
} else {
|
||||
when (val direct = record.value) {
|
||||
is FrameSlotRef -> direct.read()
|
||||
is RecordSlotRef -> direct.read(scope, name)
|
||||
is ScopeSlotRef -> direct.read()
|
||||
else -> direct
|
||||
}
|
||||
record.value
|
||||
}
|
||||
frame.frame.setObj(i, value)
|
||||
}
|
||||
|
||||
@ -2560,46 +2560,6 @@ private fun captureNamesForStatement(stmt: Statement?): List<String> {
|
||||
return ordered.toList()
|
||||
}
|
||||
|
||||
private fun freezeImmutableCaptureRecord(record: ObjRecord): ObjRecord {
|
||||
val value = record.value as Obj?
|
||||
if (record.isMutable || record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || value is ObjProperty) {
|
||||
return record
|
||||
}
|
||||
return when (value) {
|
||||
is FrameSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
|
||||
is RecordSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
|
||||
is ScopeSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
|
||||
null -> record
|
||||
else -> record.copy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTransientCapturePlaceholder(value: Obj?): Boolean {
|
||||
return when (value) {
|
||||
null, ObjVoid -> true
|
||||
is FrameSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
|
||||
is RecordSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
|
||||
is ScopeSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveStableCaptureRecord(scope: Scope, name: String): ObjRecord? {
|
||||
val direct = scope.chainLookupIgnoreClosure(name, followClosure = true) ?: scope.get(name)
|
||||
if (direct != null && !isTransientCapturePlaceholder(direct.value as Obj?)) {
|
||||
return direct
|
||||
}
|
||||
var parent = scope.parent
|
||||
while (parent != null) {
|
||||
val candidate = parent.chainLookupIgnoreClosure(name, followClosure = true) ?: parent.get(name)
|
||||
if (candidate != null && !isTransientCapturePlaceholder(candidate.value as Obj?)) {
|
||||
return candidate
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
return direct
|
||||
}
|
||||
|
||||
private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<String>): List<ObjRecord>? {
|
||||
if (captureNames.isEmpty()) return null
|
||||
val records = ArrayList<ObjRecord>(captureNames.size)
|
||||
@ -2615,16 +2575,12 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
|
||||
}
|
||||
} else {
|
||||
val raw = frame.frame.getRawObj(localIndex)
|
||||
val captureRecord = if (isTransientCapturePlaceholder(raw)) {
|
||||
resolveStableCaptureRecord(frame.scope.parent ?: frame.scope, name)
|
||||
} else {
|
||||
resolveStableCaptureRecord(frame.scope, name)
|
||||
}
|
||||
if (captureRecord != null) {
|
||||
records += freezeImmutableCaptureRecord(captureRecord)
|
||||
val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name)
|
||||
if (scoped != null && scoped.value !== ObjUnset) {
|
||||
records += scoped
|
||||
continue
|
||||
}
|
||||
records += freezeImmutableCaptureRecord(ObjRecord(FrameSlotRef(frame.frame, localIndex), isMutable))
|
||||
records += ObjRecord(FrameSlotRef(frame.frame, localIndex), isMutable)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -2632,7 +2588,7 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
|
||||
if (scopeSlot >= 0) {
|
||||
val target = frame.scopeTarget(scopeSlot)
|
||||
val index = frame.fn.scopeSlotIndices[scopeSlot]
|
||||
records += freezeImmutableCaptureRecord(target.getSlotRecord(index))
|
||||
records += target.getSlotRecord(index)
|
||||
continue
|
||||
}
|
||||
val scopeCaptures = frame.scope.captureRecords
|
||||
@ -2649,7 +2605,7 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
|
||||
}
|
||||
val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name)
|
||||
if (scoped != null) {
|
||||
records += freezeImmutableCaptureRecord(scoped)
|
||||
records += scoped
|
||||
continue
|
||||
}
|
||||
frame.ensureScope().raiseSymbolNotFound("capture $name not found")
|
||||
@ -3421,12 +3377,7 @@ class CmdGetMemberSlot(
|
||||
resolved.declaringClass
|
||||
)
|
||||
} else {
|
||||
when (val value = resolved.value) {
|
||||
is FrameSlotRef -> value.read()
|
||||
is RecordSlotRef -> value.read(frame.ensureScope(), name)
|
||||
is ScopeSlotRef -> value.read()
|
||||
else -> value
|
||||
}
|
||||
resolved.value
|
||||
}
|
||||
}
|
||||
if (receiver is ObjQualifiedView) {
|
||||
@ -3833,15 +3784,13 @@ class BytecodeLambdaCallable(
|
||||
override val pos: Pos,
|
||||
) : Statement(), BytecodeCallable {
|
||||
private fun freezeRecord(record: ObjRecord): ObjRecord {
|
||||
if (record.isMutable) return record
|
||||
val raw = record.value as Obj?
|
||||
return when (raw) {
|
||||
is net.sergeych.lyng.FrameSlotRef -> raw.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
|
||||
is net.sergeych.lyng.RecordSlotRef -> raw.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
|
||||
is net.sergeych.lyng.ScopeSlotRef -> raw.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
|
||||
null -> record
|
||||
else -> record.copy()
|
||||
val frozenValue = when (val raw = record.value) {
|
||||
is net.sergeych.lyng.FrameSlotRef -> raw.read()
|
||||
is net.sergeych.lyng.RecordSlotRef -> raw.read()
|
||||
is net.sergeych.lyng.ScopeSlotRef -> raw.read()
|
||||
else -> raw
|
||||
}
|
||||
return record.copy(value = frozenValue)
|
||||
}
|
||||
|
||||
private fun resolveCaptureRecords(base: Scope): List<ObjRecord>? {
|
||||
@ -3859,7 +3808,7 @@ class BytecodeLambdaCallable(
|
||||
return BytecodeLambdaCallable(
|
||||
fn = fn,
|
||||
closureScope = newClosureScope,
|
||||
captureRecords = captureRecords ?: resolveCaptureRecords(newClosureScope),
|
||||
captureRecords = resolveCaptureRecords(newClosureScope) ?: captureRecords,
|
||||
captureNames = captureNames,
|
||||
paramSlotPlan = paramSlotPlan,
|
||||
argsDeclaration = argsDeclaration,
|
||||
@ -3974,12 +3923,7 @@ class BytecodeLambdaCallable(
|
||||
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
||||
context.resolve(record, name)
|
||||
} else {
|
||||
when (val direct = record.value) {
|
||||
is FrameSlotRef -> direct.read()
|
||||
is RecordSlotRef -> direct.read(context, name)
|
||||
is ScopeSlotRef -> direct.read()
|
||||
else -> direct
|
||||
}
|
||||
record.value
|
||||
}
|
||||
frame.frame.setObj(i, value)
|
||||
}
|
||||
@ -4146,31 +4090,7 @@ class CmdFrame(
|
||||
frame.setObj(localIndex, record.delegate ?: ObjNull)
|
||||
} else {
|
||||
val value = record.value
|
||||
if (!record.isMutable && value is FrameSlotRef) {
|
||||
val resolved = value.peekValue()
|
||||
if (resolved != null) {
|
||||
if (value.refersTo(frame, localIndex)) continue
|
||||
frame.setObj(localIndex, value.read())
|
||||
} else {
|
||||
frame.setObj(localIndex, value)
|
||||
}
|
||||
} else if (!record.isMutable && value is RecordSlotRef) {
|
||||
val resolved = value.peekValue()
|
||||
if (resolved != null) {
|
||||
frame.setObj(localIndex, value.read())
|
||||
} else {
|
||||
frame.setObj(localIndex, value)
|
||||
}
|
||||
} else if (!record.isMutable && value is ScopeSlotRef) {
|
||||
val resolved = value.peekValue()
|
||||
if (resolved != null) {
|
||||
frame.setObj(localIndex, value.read())
|
||||
} else {
|
||||
frame.setObj(localIndex, value)
|
||||
}
|
||||
} else if (!record.isMutable) {
|
||||
frame.setObj(localIndex, value)
|
||||
} else if (value is FrameSlotRef) {
|
||||
if (value is FrameSlotRef) {
|
||||
if (value.refersTo(frame, localIndex)) continue
|
||||
frame.setObj(localIndex, value)
|
||||
} else {
|
||||
@ -4185,30 +4105,7 @@ class CmdFrame(
|
||||
frame.setObj(idx, record.delegate ?: ObjNull)
|
||||
} else {
|
||||
val value = record.value
|
||||
if (!record.isMutable && value is FrameSlotRef) {
|
||||
val resolved = value.peekValue()
|
||||
if (resolved != null) {
|
||||
frame.setObj(idx, value.read())
|
||||
} else {
|
||||
frame.setObj(idx, value)
|
||||
}
|
||||
} else if (!record.isMutable && value is RecordSlotRef) {
|
||||
val resolved = value.peekValue()
|
||||
if (resolved != null) {
|
||||
frame.setObj(idx, value.read())
|
||||
} else {
|
||||
frame.setObj(idx, value)
|
||||
}
|
||||
} else if (!record.isMutable && value is ScopeSlotRef) {
|
||||
val resolved = value.peekValue()
|
||||
if (resolved != null) {
|
||||
frame.setObj(idx, value.read())
|
||||
} else {
|
||||
frame.setObj(idx, value)
|
||||
}
|
||||
} else if (!record.isMutable) {
|
||||
frame.setObj(idx, value)
|
||||
} else if (value is FrameSlotRef) {
|
||||
if (value is FrameSlotRef) {
|
||||
frame.setObj(idx, value)
|
||||
} else {
|
||||
frame.setObj(idx, RecordSlotRef(record))
|
||||
@ -5187,7 +5084,6 @@ class CmdFrame(
|
||||
when (obj) {
|
||||
is FrameSlotRef -> obj.read()
|
||||
is RecordSlotRef -> obj.read(scope, localName)
|
||||
is ScopeSlotRef -> obj.read()
|
||||
is ObjProperty -> resolvePropertyLikeLocal(localName, obj)
|
||||
ObjUnset -> resolveUnsetLocal(localName)
|
||||
else -> obj
|
||||
@ -5199,7 +5095,6 @@ class CmdFrame(
|
||||
when (obj) {
|
||||
is FrameSlotRef -> obj.read()
|
||||
is RecordSlotRef -> obj.read(scope, localName)
|
||||
is ScopeSlotRef -> obj.read()
|
||||
is ObjProperty -> resolvePropertyLikeLocal(localName, obj)
|
||||
ObjUnset -> resolveUnsetLocal(localName)
|
||||
else -> obj
|
||||
@ -5224,24 +5119,7 @@ class CmdFrame(
|
||||
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
||||
return scope.resolve(record, localName)
|
||||
}
|
||||
return when (val value = record.value) {
|
||||
is FrameSlotRef -> value.read()
|
||||
is RecordSlotRef -> value.read(scope, localName)
|
||||
is ScopeSlotRef -> value.read()
|
||||
else -> value
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun readResolvedScopeRecord(target: Scope, name: String, record: ObjRecord): Obj {
|
||||
val value = record.value
|
||||
return when {
|
||||
record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || value is ObjProperty ->
|
||||
target.resolve(record, name)
|
||||
value is FrameSlotRef -> value.read()
|
||||
value is RecordSlotRef -> value.read(target, name)
|
||||
value is ScopeSlotRef -> value.read()
|
||||
else -> value
|
||||
}
|
||||
return record.value
|
||||
}
|
||||
|
||||
private suspend fun getScopeSlotValue(slot: Int): Obj {
|
||||
@ -5256,7 +5134,10 @@ class CmdFrame(
|
||||
if (name != null && record.memberName != null && record.memberName != name) {
|
||||
val resolved = target.get(name)
|
||||
if (resolved != null) {
|
||||
val resolvedValue = readResolvedScopeRecord(target, name, resolved)
|
||||
val resolvedValue = resolved.value
|
||||
if (resolved.type == ObjRecord.Type.Delegated || resolved.type == ObjRecord.Type.Property || resolvedValue is ObjProperty) {
|
||||
return target.resolve(resolved, name)
|
||||
}
|
||||
if (resolvedValue !== ObjUnset) {
|
||||
target.updateSlotFor(name, resolved)
|
||||
}
|
||||
@ -5275,13 +5156,12 @@ class CmdFrame(
|
||||
failMissingPreparedModuleBinding(slot, name, hadNamedBinding, record)
|
||||
return record.value
|
||||
}
|
||||
val resolvedValue = readResolvedScopeRecord(target, name, resolved)
|
||||
if (resolvedValue !== ObjUnset) {
|
||||
if (resolved.value !== ObjUnset) {
|
||||
target.updateSlotFor(name, resolved)
|
||||
} else {
|
||||
failMissingPreparedModuleBinding(slot, name, hadNamedBinding, resolved)
|
||||
}
|
||||
return resolvedValue
|
||||
return resolved.value
|
||||
}
|
||||
|
||||
private suspend fun getScopeSlotValueAtAddr(addrSlot: Int): Obj {
|
||||
@ -5297,7 +5177,10 @@ class CmdFrame(
|
||||
if (name != null && record.memberName != null && record.memberName != name) {
|
||||
val resolved = target.get(name)
|
||||
if (resolved != null) {
|
||||
val resolvedValue = readResolvedScopeRecord(target, name, resolved)
|
||||
val resolvedValue = resolved.value
|
||||
if (resolved.type == ObjRecord.Type.Delegated || resolved.type == ObjRecord.Type.Property || resolvedValue is ObjProperty) {
|
||||
return target.resolve(resolved, name)
|
||||
}
|
||||
if (resolvedValue !== ObjUnset) {
|
||||
target.updateSlotFor(name, resolved)
|
||||
}
|
||||
@ -5316,13 +5199,12 @@ class CmdFrame(
|
||||
failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, record)
|
||||
return record.value
|
||||
}
|
||||
val resolvedValue = readResolvedScopeRecord(target, name, resolved)
|
||||
if (resolvedValue !== ObjUnset) {
|
||||
if (resolved.value !== ObjUnset) {
|
||||
target.updateSlotFor(name, resolved)
|
||||
} else {
|
||||
failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, resolved)
|
||||
}
|
||||
return resolvedValue
|
||||
return resolved.value
|
||||
}
|
||||
|
||||
private suspend fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {
|
||||
|
||||
@ -34,12 +34,7 @@ internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
|
||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) {
|
||||
scope.resolve(record, name)
|
||||
} else {
|
||||
when (val direct = record.value) {
|
||||
is net.sergeych.lyng.FrameSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
|
||||
is net.sergeych.lyng.RecordSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
|
||||
is net.sergeych.lyng.ScopeSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
|
||||
else -> direct
|
||||
}
|
||||
record.value
|
||||
}
|
||||
if (value is net.sergeych.lyng.FrameSlotRef && value.refersTo(frame.frame, i)) {
|
||||
continue
|
||||
|
||||
@ -16,60 +16,17 @@
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.Compiler
|
||||
import net.sergeych.lyng.EvalSession
|
||||
import net.sergeych.lyng.Script
|
||||
import net.sergeych.lyng.Source
|
||||
import net.sergeych.lyng.obj.ObjString
|
||||
import net.sergeych.lyng.obj.toInt
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertSame
|
||||
|
||||
class ScriptImportPreparationTest {
|
||||
|
||||
private fun nestedImportSources(prefix: String): Array<String> =
|
||||
arrayOf(
|
||||
"""
|
||||
package $prefix.alpha
|
||||
|
||||
class Alpha {
|
||||
val headers = Map<String, String>()
|
||||
|
||||
fun tagged(port: Int, host: String): String {
|
||||
val task: Deferred = launch {
|
||||
host + ":" + port + ":" + headers.size
|
||||
}
|
||||
return task.await()
|
||||
}
|
||||
}
|
||||
|
||||
fun alphaValue() = Alpha().tagged(7, "alpha")
|
||||
""".trimIndent(),
|
||||
"""
|
||||
package $prefix.beta
|
||||
import $prefix.alpha
|
||||
|
||||
fun betaValue() = alphaValue() + "|" + Alpha().tagged(8, "beta")
|
||||
""".trimIndent(),
|
||||
"""
|
||||
package $prefix.gamma
|
||||
import $prefix.alpha
|
||||
import $prefix.beta
|
||||
|
||||
val String.gammaTag get() = this + ":gamma"
|
||||
|
||||
fun gammaValue() = betaValue() + "|" + "done".gammaTag
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
private fun nestedImportManager(prefix: String = "tree"): ImportManager =
|
||||
Script.defaultImportManager.copy().apply {
|
||||
addTextPackages(*nestedImportSources(prefix))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun scriptImportIntoExplicitlyPreparesExistingScope() = runTest {
|
||||
val manager = ImportManager()
|
||||
@ -127,67 +84,4 @@ class ScriptImportPreparationTest {
|
||||
val record = assertNotNull(module["answer"])
|
||||
assertEquals(42, module.resolve(record, "answer").toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun repeatedImportIntoOnSameScopeIsIdempotentForNestedPackageGraph() = runTest {
|
||||
val manager = nestedImportManager()
|
||||
val script = Compiler.compile(
|
||||
Source(
|
||||
"<repeat-import-into>",
|
||||
"""
|
||||
import tree.gamma
|
||||
import tree.beta
|
||||
import tree.alpha
|
||||
|
||||
gammaValue()
|
||||
""".trimIndent()
|
||||
),
|
||||
manager
|
||||
)
|
||||
val scope = manager.newStdScope()
|
||||
|
||||
script.importInto(scope)
|
||||
val importedGammaValue = assertNotNull(scope["gammaValue"])
|
||||
val importedAlpha = assertNotNull(scope["Alpha"])
|
||||
|
||||
repeat(5) {
|
||||
script.importInto(scope)
|
||||
}
|
||||
|
||||
assertSame(importedGammaValue, scope["gammaValue"])
|
||||
assertSame(importedAlpha, scope["Alpha"])
|
||||
assertEquals(
|
||||
"alpha:7:0|beta:8:0|done:gamma",
|
||||
(script.execute(scope) as ObjString).value
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun repeatedEvalOnSameSessionCanReimportNestedPackageGraph() = runTest {
|
||||
val prefix = "repeattree"
|
||||
val manager = nestedImportManager(prefix)
|
||||
val scope = manager.newModule()
|
||||
val session = EvalSession(scope)
|
||||
|
||||
try {
|
||||
repeat(5) { index ->
|
||||
val result = session.eval(
|
||||
Source(
|
||||
"<repeat-eval-$index>",
|
||||
"""
|
||||
import $prefix.gamma
|
||||
import $prefix.beta
|
||||
import $prefix.alpha
|
||||
|
||||
gammaValue()
|
||||
""".trimIndent()
|
||||
)
|
||||
) as ObjString
|
||||
|
||||
assertEquals("alpha:7:0|beta:8:0|done:gamma", result.value)
|
||||
}
|
||||
} finally {
|
||||
session.cancelAndJoin()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user