Refactor scope handling to improve slot synchronization, dynamic access, and module frame interactions; bump version to 1.5.1-SNAPSHOT.
This commit is contained in:
parent
51b0d1fdfc
commit
274abaaf03
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "1.5.0-SNAPSHOT"
|
version = "1.5.1-SNAPSHOT"
|
||||||
|
|
||||||
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||||
|
|
||||||
|
|||||||
@ -36,10 +36,19 @@ class BytecodeClosureScope(
|
|||||||
val desired = preferredThisType?.let { typeName ->
|
val desired = preferredThisType?.let { typeName ->
|
||||||
callScope.thisVariants.firstOrNull { it.objClass.className == typeName }
|
callScope.thisVariants.firstOrNull { it.objClass.className == typeName }
|
||||||
}
|
}
|
||||||
val primaryThis = closureScope.thisObj
|
val primaryThis = when {
|
||||||
val merged = ArrayList<Obj>(callScope.thisVariants.size + closureScope.thisVariants.size + 1)
|
callScope is ApplyScope -> callScope.thisObj
|
||||||
|
desired != null -> desired
|
||||||
|
else -> closureScope.thisObj
|
||||||
|
}
|
||||||
|
val merged = ArrayList<Obj>(callScope.thisVariants.size + closureScope.thisVariants.size + 3)
|
||||||
desired?.let { merged.add(it) }
|
desired?.let { merged.add(it) }
|
||||||
|
merged.add(callScope.thisObj)
|
||||||
merged.addAll(callScope.thisVariants)
|
merged.addAll(callScope.thisVariants)
|
||||||
|
if (callScope is ApplyScope) {
|
||||||
|
merged.add(callScope.applied.thisObj)
|
||||||
|
merged.addAll(callScope.applied.thisVariants)
|
||||||
|
}
|
||||||
merged.addAll(closureScope.thisVariants)
|
merged.addAll(closureScope.thisVariants)
|
||||||
setThisVariants(primaryThis, merged)
|
setThisVariants(primaryThis, merged)
|
||||||
this.currentClassCtx = closureScope.currentClassCtx ?: callScope.currentClassCtx
|
this.currentClassCtx = closureScope.currentClassCtx ?: callScope.currentClassCtx
|
||||||
@ -47,10 +56,20 @@ class BytecodeClosureScope(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ApplyScope(val callScope: Scope, val applied: Scope) :
|
class ApplyScope(val callScope: Scope, val applied: Scope) :
|
||||||
Scope(callScope, thisObj = applied.thisObj) {
|
Scope(applied, callScope.args, callScope.pos, callScope.thisObj) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Merge applied receiver variants with the caller variants so qualified this@Type
|
||||||
|
// can see both the applied receiver and outer receivers.
|
||||||
|
val merged = ArrayList<Obj>(applied.thisVariants.size + callScope.thisVariants.size + 1)
|
||||||
|
merged.addAll(applied.thisVariants)
|
||||||
|
merged.addAll(callScope.thisVariants)
|
||||||
|
setThisVariants(callScope.thisObj, merged)
|
||||||
|
this.currentClassCtx = applied.currentClassCtx ?: callScope.currentClassCtx
|
||||||
|
}
|
||||||
|
|
||||||
override fun get(name: String): ObjRecord? {
|
override fun get(name: String): ObjRecord? {
|
||||||
return applied.get(name) ?: super.get(name)
|
return applied.get(name) ?: callScope.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun applyClosure(closure: Scope, preferredThisType: String?): Scope {
|
override fun applyClosure(closure: Scope, preferredThisType: String?): Scope {
|
||||||
|
|||||||
@ -294,6 +294,25 @@ class Compiler(
|
|||||||
if (record.typeDecl != null) {
|
if (record.typeDecl != null) {
|
||||||
slotTypeDeclByScopeId.getOrPut(plan.id) { mutableMapOf() }[slotIndex] = record.typeDecl
|
slotTypeDeclByScopeId.getOrPut(plan.id) { mutableMapOf() }[slotIndex] = record.typeDecl
|
||||||
}
|
}
|
||||||
|
val resolved = when (val raw = record.value) {
|
||||||
|
is FrameSlotRef -> raw.peekValue() ?: raw.read()
|
||||||
|
is RecordSlotRef -> raw.peekValue() ?: raw.read()
|
||||||
|
else -> raw
|
||||||
|
}
|
||||||
|
when (resolved) {
|
||||||
|
is ObjClass -> {
|
||||||
|
slotTypeByScopeId.getOrPut(plan.id) { mutableMapOf() }[slotIndex] = resolved
|
||||||
|
if (nameObjClass[name] == null) nameObjClass[name] = resolved
|
||||||
|
}
|
||||||
|
is ObjInstance -> {
|
||||||
|
slotTypeByScopeId.getOrPut(plan.id) { mutableMapOf() }[slotIndex] = resolved.objClass
|
||||||
|
if (nameObjClass[name] == null) nameObjClass[name] = resolved.objClass
|
||||||
|
}
|
||||||
|
is ObjDynamic -> {
|
||||||
|
slotTypeByScopeId.getOrPut(plan.id) { mutableMapOf() }[slotIndex] = resolved.objClass
|
||||||
|
if (nameObjClass[name] == null) nameObjClass[name] = resolved.objClass
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,10 +353,12 @@ class Compiler(
|
|||||||
extensionNames.add(actual.value)
|
extensionNames.add(actual.value)
|
||||||
registerExtensionName(nameToken.value, actual.value)
|
registerExtensionName(nameToken.value, actual.value)
|
||||||
declareSlotNameIn(plan, extensionCallableName(nameToken.value, actual.value), isMutable = false, isDelegated = false)
|
declareSlotNameIn(plan, extensionCallableName(nameToken.value, actual.value), isMutable = false, isDelegated = false)
|
||||||
|
moduleDeclaredNames.add(extensionCallableName(nameToken.value, actual.value))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
|
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
|
||||||
|
moduleDeclaredNames.add(nameToken.value)
|
||||||
}
|
}
|
||||||
"val", "var" -> {
|
"val", "var" -> {
|
||||||
val nameToken = nextNonWs()
|
val nameToken = nextNonWs()
|
||||||
@ -350,19 +371,23 @@ class Compiler(
|
|||||||
extensionNames.add(actual.value)
|
extensionNames.add(actual.value)
|
||||||
registerExtensionName(nameToken.value, actual.value)
|
registerExtensionName(nameToken.value, actual.value)
|
||||||
declareSlotNameIn(plan, extensionPropertyGetterName(nameToken.value, actual.value), isMutable = false, isDelegated = false)
|
declareSlotNameIn(plan, extensionPropertyGetterName(nameToken.value, actual.value), isMutable = false, isDelegated = false)
|
||||||
|
moduleDeclaredNames.add(extensionPropertyGetterName(nameToken.value, actual.value))
|
||||||
if (t.value == "var") {
|
if (t.value == "var") {
|
||||||
declareSlotNameIn(plan, extensionPropertySetterName(nameToken.value, actual.value), isMutable = false, isDelegated = false)
|
declareSlotNameIn(plan, extensionPropertySetterName(nameToken.value, actual.value), isMutable = false, isDelegated = false)
|
||||||
|
moduleDeclaredNames.add(extensionPropertySetterName(nameToken.value, actual.value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
declareSlotNameIn(plan, nameToken.value, isMutable = t.value == "var", isDelegated = false)
|
declareSlotNameIn(plan, nameToken.value, isMutable = t.value == "var", isDelegated = false)
|
||||||
|
moduleDeclaredNames.add(nameToken.value)
|
||||||
}
|
}
|
||||||
"class", "object" -> {
|
"class", "object" -> {
|
||||||
val nameToken = nextNonWs()
|
val nameToken = nextNonWs()
|
||||||
if (nameToken.type == Token.Type.ID) {
|
if (nameToken.type == Token.Type.ID) {
|
||||||
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
|
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
|
||||||
scopeSeedNames.add(nameToken.value)
|
scopeSeedNames.add(nameToken.value)
|
||||||
|
moduleDeclaredNames.add(nameToken.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"enum" -> {
|
"enum" -> {
|
||||||
@ -371,6 +396,7 @@ class Compiler(
|
|||||||
if (nameToken.type == Token.Type.ID) {
|
if (nameToken.type == Token.Type.ID) {
|
||||||
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
|
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
|
||||||
scopeSeedNames.add(nameToken.value)
|
scopeSeedNames.add(nameToken.value)
|
||||||
|
moduleDeclaredNames.add(nameToken.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -984,7 +1010,7 @@ class Compiler(
|
|||||||
resolutionSink?.reference(name, pos)
|
resolutionSink?.reference(name, pos)
|
||||||
return ref
|
return ref
|
||||||
}
|
}
|
||||||
val ref = if (capturePlanStack.isEmpty() && moduleLoc.depth > 0) {
|
val ref = if (!useScopeSlots && capturePlanStack.isEmpty() && moduleLoc.depth > 0) {
|
||||||
LocalSlotRef(
|
LocalSlotRef(
|
||||||
name,
|
name,
|
||||||
moduleLoc.slot,
|
moduleLoc.slot,
|
||||||
@ -1044,7 +1070,7 @@ class Compiler(
|
|||||||
resolutionSink?.reference(name, pos)
|
resolutionSink?.reference(name, pos)
|
||||||
return ref
|
return ref
|
||||||
}
|
}
|
||||||
val ref = if (capturePlanStack.isEmpty() && slot.depth > 0) {
|
val ref = if (!useScopeSlots && capturePlanStack.isEmpty() && slot.depth > 0) {
|
||||||
LocalSlotRef(
|
LocalSlotRef(
|
||||||
name,
|
name,
|
||||||
slot.slot,
|
slot.slot,
|
||||||
@ -1182,8 +1208,8 @@ class Compiler(
|
|||||||
if (!record.visibility.isPublic) continue
|
if (!record.visibility.isPublic) continue
|
||||||
if (nameObjClass.containsKey(name)) continue
|
if (nameObjClass.containsKey(name)) continue
|
||||||
val resolved = when (val raw = record.value) {
|
val resolved = when (val raw = record.value) {
|
||||||
is FrameSlotRef -> raw.peekValue()
|
is FrameSlotRef -> raw.peekValue() ?: raw.read()
|
||||||
is RecordSlotRef -> raw.peekValue()
|
is RecordSlotRef -> raw.peekValue() ?: raw.read()
|
||||||
else -> raw
|
else -> raw
|
||||||
} ?: continue
|
} ?: continue
|
||||||
when (resolved) {
|
when (resolved) {
|
||||||
@ -1250,6 +1276,25 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
if (moduleMatches.isEmpty()) return null
|
if (moduleMatches.isEmpty()) return null
|
||||||
if (moduleMatches.size > 1) {
|
if (moduleMatches.size > 1) {
|
||||||
|
val byOrigin = LinkedHashMap<String, MutableList<Pair<ImportedModule, ObjRecord>>>()
|
||||||
|
for ((_, pair) in moduleMatches) {
|
||||||
|
val origin = pair.second.importedFrom?.packageName ?: pair.first.scope.packageName
|
||||||
|
byOrigin.getOrPut(origin) { mutableListOf() }.add(pair)
|
||||||
|
}
|
||||||
|
if (byOrigin.size == 1) {
|
||||||
|
val origin = byOrigin.keys.first()
|
||||||
|
val candidates = byOrigin[origin] ?: mutableListOf()
|
||||||
|
val preferred = candidates.firstOrNull { it.first.scope.packageName == origin } ?: candidates.first()
|
||||||
|
val binding = ImportBinding(name, ImportBindingSource.Module(origin, preferred.first.pos))
|
||||||
|
val value = preferred.second.value
|
||||||
|
if (!nameObjClass.containsKey(name)) {
|
||||||
|
when (value) {
|
||||||
|
is ObjClass -> nameObjClass[name] = value
|
||||||
|
is ObjInstance -> nameObjClass[name] = value.objClass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ImportBindingResolution(binding, preferred.second)
|
||||||
|
}
|
||||||
val moduleNames = moduleMatches.keys.toList()
|
val moduleNames = moduleMatches.keys.toList()
|
||||||
throw ScriptError(pos, "symbol $name is ambiguous between imports: ${moduleNames.joinToString(", ")}")
|
throw ScriptError(pos, "symbol $name is ambiguous between imports: ${moduleNames.joinToString(", ")}")
|
||||||
}
|
}
|
||||||
@ -1539,16 +1584,14 @@ class Compiler(
|
|||||||
if (needsSlotPlan) {
|
if (needsSlotPlan) {
|
||||||
slotPlanStack.add(SlotPlan(mutableMapOf(), 0, nextScopeId++))
|
slotPlanStack.add(SlotPlan(mutableMapOf(), 0, nextScopeId++))
|
||||||
seedScope?.let { scope ->
|
seedScope?.let { scope ->
|
||||||
if (scope !is ModuleScope) {
|
|
||||||
seedSlotPlanFromSeedScope(scope)
|
seedSlotPlanFromSeedScope(scope)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
val plan = slotPlanStack.last()
|
val plan = slotPlanStack.last()
|
||||||
if (!plan.slots.containsKey("__PACKAGE__")) {
|
seedScope?.getSlotIndexOf("__PACKAGE__")?.let { slotIndex ->
|
||||||
declareSlotNameIn(plan, "__PACKAGE__", isMutable = false, isDelegated = false)
|
declareSlotNameAt(plan, "__PACKAGE__", slotIndex, isMutable = false, isDelegated = false)
|
||||||
}
|
}
|
||||||
if (!plan.slots.containsKey("$~")) {
|
seedScope?.getSlotIndexOf("$~")?.let { slotIndex ->
|
||||||
declareSlotNameIn(plan, "$~", isMutable = true, isDelegated = false)
|
declareSlotNameAt(plan, "$~", slotIndex, isMutable = true, isDelegated = false)
|
||||||
}
|
}
|
||||||
seedScope?.let { seedNameObjClassFromScope(it) }
|
seedScope?.let { seedNameObjClassFromScope(it) }
|
||||||
seedScope?.let { seedNameTypeDeclFromScope(it) }
|
seedScope?.let { seedNameTypeDeclFromScope(it) }
|
||||||
@ -1557,6 +1600,7 @@ class Compiler(
|
|||||||
val stdlib = importManager.prepareImport(start, "lyng.stdlib", null)
|
val stdlib = importManager.prepareImport(start, "lyng.stdlib", null)
|
||||||
seedResolutionFromScope(stdlib, start)
|
seedResolutionFromScope(stdlib, start)
|
||||||
seedNameObjClassFromScope(stdlib)
|
seedNameObjClassFromScope(stdlib)
|
||||||
|
seedSlotPlanFromScope(stdlib)
|
||||||
importedModules.add(ImportedModule(stdlib, start))
|
importedModules.add(ImportedModule(stdlib, start))
|
||||||
}
|
}
|
||||||
predeclareTopLevelSymbols()
|
predeclareTopLevelSymbols()
|
||||||
@ -1660,7 +1704,7 @@ class Compiler(
|
|||||||
val forcedLocalScopeId = if (useScopeSlots) null else moduleSlotPlan()?.id
|
val forcedLocalScopeId = if (useScopeSlots) null else moduleSlotPlan()?.id
|
||||||
val allowedScopeNames = if (useScopeSlots) modulePlan.keys else null
|
val allowedScopeNames = if (useScopeSlots) modulePlan.keys else null
|
||||||
val scopeSlotNameSet = if (useScopeSlots) scopeSeedNames else null
|
val scopeSlotNameSet = if (useScopeSlots) scopeSeedNames else null
|
||||||
val moduleScopeId = if (useScopeSlots) null else moduleSlotPlan()?.id
|
val moduleScopeId = moduleSlotPlan()?.id
|
||||||
val isModuleScript = codeContexts.lastOrNull() is CodeContext.Module && resolutionScriptDepth == 1
|
val isModuleScript = codeContexts.lastOrNull() is CodeContext.Module && resolutionScriptDepth == 1
|
||||||
val wrapScriptBytecode = compileBytecode && isModuleScript
|
val wrapScriptBytecode = compileBytecode && isModuleScript
|
||||||
val (finalStatements, moduleBytecode) = if (wrapScriptBytecode) {
|
val (finalStatements, moduleBytecode) = if (wrapScriptBytecode) {
|
||||||
@ -1678,6 +1722,7 @@ class Compiler(
|
|||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
|
knownClassNames = knownClassNamesForBytecode(),
|
||||||
knownObjectNames = objectDeclNames,
|
knownObjectNames = objectDeclNames,
|
||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
@ -1690,11 +1735,16 @@ class Compiler(
|
|||||||
statements to null
|
statements to null
|
||||||
}
|
}
|
||||||
val moduleRefs = importedModules.map { ImportBindingSource.Module(it.scope.packageName, it.pos) }
|
val moduleRefs = importedModules.map { ImportBindingSource.Module(it.scope.packageName, it.pos) }
|
||||||
|
val declaredNames = if (importBindings.isEmpty()) {
|
||||||
|
moduleDeclaredNames.toSet()
|
||||||
|
} else {
|
||||||
|
moduleDeclaredNames.subtract(importBindings.keys)
|
||||||
|
}
|
||||||
Script(
|
Script(
|
||||||
start,
|
start,
|
||||||
finalStatements,
|
finalStatements,
|
||||||
modulePlan,
|
modulePlan,
|
||||||
moduleDeclaredNames.toSet(),
|
declaredNames,
|
||||||
importBindings.toMap(),
|
importBindings.toMap(),
|
||||||
moduleRefs,
|
moduleRefs,
|
||||||
moduleBytecode
|
moduleBytecode
|
||||||
@ -1741,7 +1791,7 @@ class Compiler(
|
|||||||
private val rangeParamNamesStack = mutableListOf<Set<String>>()
|
private val rangeParamNamesStack = mutableListOf<Set<String>>()
|
||||||
private val extensionNames = mutableSetOf<String>()
|
private val extensionNames = mutableSetOf<String>()
|
||||||
private val extensionNamesByType = mutableMapOf<String, MutableSet<String>>()
|
private val extensionNamesByType = mutableMapOf<String, MutableSet<String>>()
|
||||||
private val useScopeSlots: Boolean = seedScope != null && seedScope !is ModuleScope
|
private val useScopeSlots: Boolean = seedScope == null
|
||||||
|
|
||||||
private fun registerExtensionName(typeName: String, memberName: String) {
|
private fun registerExtensionName(typeName: String, memberName: String) {
|
||||||
extensionNamesByType.getOrPut(typeName) { mutableSetOf() }.add(memberName)
|
extensionNamesByType.getOrPut(typeName) { mutableSetOf() }.add(memberName)
|
||||||
@ -1884,6 +1934,9 @@ class Compiler(
|
|||||||
val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId }
|
val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId }
|
||||||
if (functionIndex >= 0 && scopeIndex >= functionIndex) return null
|
if (functionIndex >= 0 && scopeIndex >= functionIndex) return null
|
||||||
val modulePlan = moduleSlotPlan()
|
val modulePlan = moduleSlotPlan()
|
||||||
|
if (useScopeSlots && modulePlan != null && slotLoc.scopeId == modulePlan.id) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
if (scopeSeedNames.contains(name)) {
|
if (scopeSeedNames.contains(name)) {
|
||||||
val isModuleSlot = modulePlan != null && slotLoc.scopeId == modulePlan.id
|
val isModuleSlot = modulePlan != null && slotLoc.scopeId == modulePlan.id
|
||||||
if (!isModuleSlot || useScopeSlots) return null
|
if (!isModuleSlot || useScopeSlots) return null
|
||||||
@ -1949,6 +2002,23 @@ class Compiler(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun knownClassNamesForBytecode(): Set<String> {
|
||||||
|
val result = LinkedHashSet<String>()
|
||||||
|
fun addScope(scope: Scope?) {
|
||||||
|
if (scope == null) return
|
||||||
|
for ((name, rec) in scope.objects) {
|
||||||
|
if (rec.value is ObjClass) result.add(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addScope(seedScope)
|
||||||
|
addScope(importManager.rootScope)
|
||||||
|
for (module in importedModules) {
|
||||||
|
addScope(module.scope)
|
||||||
|
}
|
||||||
|
result.addAll(compileClassInfos.keys)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
private fun wrapBytecode(stmt: Statement): Statement {
|
private fun wrapBytecode(stmt: Statement): Statement {
|
||||||
if (codeContexts.lastOrNull() is CodeContext.Module) return stmt
|
if (codeContexts.lastOrNull() is CodeContext.Module) return stmt
|
||||||
if (codeContexts.lastOrNull() is CodeContext.ClassBody) return stmt
|
if (codeContexts.lastOrNull() is CodeContext.ClassBody) return stmt
|
||||||
@ -1975,6 +2045,7 @@ class Compiler(
|
|||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
|
knownClassNames = knownClassNamesForBytecode(),
|
||||||
knownObjectNames = objectDeclNames,
|
knownObjectNames = objectDeclNames,
|
||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
@ -2004,6 +2075,7 @@ class Compiler(
|
|||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
|
knownClassNames = knownClassNamesForBytecode(),
|
||||||
knownObjectNames = objectDeclNames,
|
knownObjectNames = objectDeclNames,
|
||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
@ -2058,6 +2130,7 @@ class Compiler(
|
|||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownNames,
|
knownNameObjClass = knownNames,
|
||||||
|
knownClassNames = knownClassNamesForBytecode(),
|
||||||
knownObjectNames = objectDeclNames,
|
knownObjectNames = objectDeclNames,
|
||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
@ -3000,11 +3073,18 @@ class Compiler(
|
|||||||
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
|
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
|
||||||
val captureSlots = capturePlan.captures.toList()
|
val captureSlots = capturePlan.captures.toList()
|
||||||
val captureEntries = if (captureSlots.isNotEmpty()) {
|
val captureEntries = if (captureSlots.isNotEmpty()) {
|
||||||
|
val modulePlan = moduleSlotPlan()
|
||||||
captureSlots.map { capture ->
|
captureSlots.map { capture ->
|
||||||
val owner = capturePlan.captureOwners[capture.name]
|
val owner = capturePlan.captureOwners[capture.name]
|
||||||
?: error("Missing capture owner for ${capture.name}")
|
?: error("Missing capture owner for ${capture.name}")
|
||||||
|
val isModuleSlot = modulePlan != null && owner.scopeId == modulePlan.id
|
||||||
|
val ownerKind = if (isModuleSlot) {
|
||||||
|
net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.MODULE
|
||||||
|
} else {
|
||||||
|
net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.LOCAL
|
||||||
|
}
|
||||||
net.sergeych.lyng.bytecode.LambdaCaptureEntry(
|
net.sergeych.lyng.bytecode.LambdaCaptureEntry(
|
||||||
ownerKind = net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.LOCAL,
|
ownerKind = ownerKind,
|
||||||
ownerScopeId = owner.scopeId,
|
ownerScopeId = owner.scopeId,
|
||||||
ownerSlotId = owner.slot,
|
ownerSlotId = owner.slot,
|
||||||
ownerName = capture.name,
|
ownerName = capture.name,
|
||||||
@ -4269,15 +4349,24 @@ class Compiler(
|
|||||||
is LocalSlotRef -> {
|
is LocalSlotRef -> {
|
||||||
val ownerScopeId = ref.captureOwnerScopeId ?: ref.scopeId
|
val ownerScopeId = ref.captureOwnerScopeId ?: ref.scopeId
|
||||||
val ownerSlot = ref.captureOwnerSlot ?: ref.slot
|
val ownerSlot = ref.captureOwnerSlot ?: ref.slot
|
||||||
|
val knownClass = nameObjClass[ref.name]
|
||||||
|
if (knownClass == ObjDynamic.type) {
|
||||||
|
knownClass
|
||||||
|
} else {
|
||||||
slotTypeByScopeId[ownerScopeId]?.get(ownerSlot)
|
slotTypeByScopeId[ownerScopeId]?.get(ownerSlot)
|
||||||
?: slotTypeDeclByScopeId[ownerScopeId]?.get(ownerSlot)?.let { resolveTypeDeclObjClass(it) }
|
?: slotTypeDeclByScopeId[ownerScopeId]?.get(ownerSlot)?.let { resolveTypeDeclObjClass(it) }
|
||||||
?: nameObjClass[ref.name]
|
?: knownClass
|
||||||
|
}
|
||||||
?: resolveClassByName(ref.name)
|
?: resolveClassByName(ref.name)
|
||||||
}
|
}
|
||||||
is LocalVarRef -> nameObjClass[ref.name]
|
is LocalVarRef -> nameObjClass[ref.name]
|
||||||
|
?.takeIf { it == ObjDynamic.type }
|
||||||
|
?: nameObjClass[ref.name]
|
||||||
?: nameTypeDecl[ref.name]?.let { resolveTypeDeclObjClass(it) }
|
?: nameTypeDecl[ref.name]?.let { resolveTypeDeclObjClass(it) }
|
||||||
?: resolveClassByName(ref.name)
|
?: resolveClassByName(ref.name)
|
||||||
is FastLocalVarRef -> nameObjClass[ref.name]
|
is FastLocalVarRef -> nameObjClass[ref.name]
|
||||||
|
?.takeIf { it == ObjDynamic.type }
|
||||||
|
?: nameObjClass[ref.name]
|
||||||
?: nameTypeDecl[ref.name]?.let { resolveTypeDeclObjClass(it) }
|
?: nameTypeDecl[ref.name]?.let { resolveTypeDeclObjClass(it) }
|
||||||
?: resolveClassByName(ref.name)
|
?: resolveClassByName(ref.name)
|
||||||
is ClassScopeMemberRef -> {
|
is ClassScopeMemberRef -> {
|
||||||
@ -7485,6 +7574,9 @@ class Compiler(
|
|||||||
val bytecodeBody = (fnStatements as? BytecodeStatement)
|
val bytecodeBody = (fnStatements as? BytecodeStatement)
|
||||||
?: context.raiseIllegalState("non-bytecode function body encountered")
|
?: context.raiseIllegalState("non-bytecode function body encountered")
|
||||||
val bytecodeFn = bytecodeBody.bytecodeFunction()
|
val bytecodeFn = bytecodeBody.bytecodeFunction()
|
||||||
|
val declaredNames = bytecodeFn.constants
|
||||||
|
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
||||||
|
.mapTo(mutableSetOf()) { it.name }
|
||||||
val captureNames = if (captureSlots.isNotEmpty()) {
|
val captureNames = if (captureSlots.isNotEmpty()) {
|
||||||
captureSlots.map { it.name }
|
captureSlots.map { it.name }
|
||||||
} else {
|
} else {
|
||||||
@ -7534,6 +7626,28 @@ class Compiler(
|
|||||||
if (extTypeName != null) {
|
if (extTypeName != null) {
|
||||||
context.thisObj = scope.thisObj
|
context.thisObj = scope.thisObj
|
||||||
}
|
}
|
||||||
|
val localNames = frame.fn.localSlotNames
|
||||||
|
for (i in localNames.indices) {
|
||||||
|
val localName = localNames[i] ?: continue
|
||||||
|
if (declaredNames.contains(localName)) continue
|
||||||
|
val slotType = frame.getLocalSlotTypeCode(i)
|
||||||
|
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (slotType == SlotType.OBJ.code && frame.frame.getRawObj(i) != null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val record = context.getLocalRecordDirect(localName)
|
||||||
|
?: context.parent?.get(localName)
|
||||||
|
?: context.get(localName)
|
||||||
|
?: continue
|
||||||
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
|
context.resolve(record, localName)
|
||||||
|
} else {
|
||||||
|
record.value
|
||||||
|
}
|
||||||
|
frame.frame.setObj(i, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return try {
|
return try {
|
||||||
net.sergeych.lyng.bytecode.CmdVm().execute(bytecodeFn, context, scope.args, binder)
|
net.sergeych.lyng.bytecode.CmdVm().execute(bytecodeFn, context, scope.args, binder)
|
||||||
@ -8795,7 +8909,8 @@ class Compiler(
|
|||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
suspend fun compile(source: Source, importManager: ImportProvider): Script {
|
suspend fun compile(source: Source, importManager: ImportProvider): Script {
|
||||||
return Compiler(CompilerContext(parseLyng(source)), importManager).parseScript()
|
val script = Compiler(CompilerContext(parseLyng(source)), importManager).parseScript()
|
||||||
|
return script
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun dryRun(source: Source, importManager: ImportProvider): ResolutionReport {
|
suspend fun dryRun(source: Source, importManager: ImportProvider): ResolutionReport {
|
||||||
|
|||||||
@ -56,6 +56,14 @@ class FrameSlotRef(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
val resolved = read()
|
||||||
|
if (resolved === this) {
|
||||||
|
scope.raiseNotImplemented("call on unresolved frame slot")
|
||||||
|
}
|
||||||
|
return resolved.callOn(scope)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun refersTo(frame: FrameAccess, slot: Int): Boolean {
|
internal fun refersTo(frame: FrameAccess, slot: Int): Boolean {
|
||||||
return this.frame === frame && this.slot == slot
|
return this.frame === frame && this.slot == slot
|
||||||
}
|
}
|
||||||
@ -81,6 +89,62 @@ class FrameSlotRef(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ScopeSlotRef(
|
||||||
|
private val scope: Scope,
|
||||||
|
private val slot: Int,
|
||||||
|
private val name: String? = null,
|
||||||
|
) : net.sergeych.lyng.obj.Obj() {
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
val resolvedOther = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
is ScopeSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
return read().compareTo(scope, resolvedOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(): Obj {
|
||||||
|
val record = scope.getSlotRecord(slot)
|
||||||
|
val direct = record.value
|
||||||
|
if (direct is FrameSlotRef) return direct.read()
|
||||||
|
if (direct is RecordSlotRef) return direct.read()
|
||||||
|
if (direct is ScopeSlotRef) return direct.read()
|
||||||
|
if (direct !== ObjUnset) {
|
||||||
|
return direct
|
||||||
|
}
|
||||||
|
if (name == null) return record.value
|
||||||
|
val resolved = scope.get(name) ?: return record.value
|
||||||
|
if (resolved.value !== ObjUnset) {
|
||||||
|
scope.updateSlotFor(name, resolved)
|
||||||
|
}
|
||||||
|
return resolved.value
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun peekValue(): Obj? {
|
||||||
|
val record = scope.getSlotRecord(slot)
|
||||||
|
val direct = record.value
|
||||||
|
return when (direct) {
|
||||||
|
is FrameSlotRef -> direct.peekValue()
|
||||||
|
is RecordSlotRef -> direct.peekValue()
|
||||||
|
is ScopeSlotRef -> direct.peekValue()
|
||||||
|
else -> direct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(value: Obj) {
|
||||||
|
scope.setSlotValue(slot, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
val resolved = read()
|
||||||
|
if (resolved === this) {
|
||||||
|
scope.raiseNotImplemented("call on unresolved scope slot")
|
||||||
|
}
|
||||||
|
return resolved.callOn(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class RecordSlotRef(
|
class RecordSlotRef(
|
||||||
private val record: ObjRecord,
|
private val record: ObjRecord,
|
||||||
) : net.sergeych.lyng.obj.Obj() {
|
) : net.sergeych.lyng.obj.Obj() {
|
||||||
@ -88,6 +152,7 @@ class RecordSlotRef(
|
|||||||
val resolvedOther = when (other) {
|
val resolvedOther = when (other) {
|
||||||
is FrameSlotRef -> other.read()
|
is FrameSlotRef -> other.read()
|
||||||
is RecordSlotRef -> other.read()
|
is RecordSlotRef -> other.read()
|
||||||
|
is ScopeSlotRef -> other.read()
|
||||||
else -> other
|
else -> other
|
||||||
}
|
}
|
||||||
return read().compareTo(scope, resolvedOther)
|
return read().compareTo(scope, resolvedOther)
|
||||||
@ -95,7 +160,19 @@ class RecordSlotRef(
|
|||||||
|
|
||||||
fun read(): Obj {
|
fun read(): Obj {
|
||||||
val direct = record.value
|
val direct = record.value
|
||||||
return if (direct is FrameSlotRef) direct.read() else direct
|
return when (direct) {
|
||||||
|
is FrameSlotRef -> direct.read()
|
||||||
|
is ScopeSlotRef -> direct.read()
|
||||||
|
else -> direct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
val resolved = read()
|
||||||
|
if (resolved === this) {
|
||||||
|
scope.raiseNotImplemented("call on unresolved record slot")
|
||||||
|
}
|
||||||
|
return resolved.callOn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun peekValue(): Obj? {
|
internal fun peekValue(): Obj? {
|
||||||
@ -103,11 +180,17 @@ class RecordSlotRef(
|
|||||||
return when (direct) {
|
return when (direct) {
|
||||||
is FrameSlotRef -> direct.peekValue()
|
is FrameSlotRef -> direct.peekValue()
|
||||||
is RecordSlotRef -> direct.peekValue()
|
is RecordSlotRef -> direct.peekValue()
|
||||||
|
is ScopeSlotRef -> direct.peekValue()
|
||||||
else -> direct
|
else -> direct
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun write(value: Obj) {
|
fun write(value: Obj) {
|
||||||
|
val direct = record.value
|
||||||
|
if (direct is ScopeSlotRef) {
|
||||||
|
direct.write(value)
|
||||||
|
} else {
|
||||||
record.value = value
|
record.value = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -44,11 +44,28 @@ class ModuleScope(
|
|||||||
|
|
||||||
internal fun ensureModuleFrame(fn: CmdFunction): BytecodeFrame {
|
internal fun ensureModuleFrame(fn: CmdFunction): BytecodeFrame {
|
||||||
val current = moduleFrame
|
val current = moduleFrame
|
||||||
val frame = if (current == null || moduleFrameLocalCount != fn.localCount) {
|
val frame = if (current == null) {
|
||||||
BytecodeFrame(fn.localCount, 0).also {
|
BytecodeFrame(fn.localCount, 0).also {
|
||||||
moduleFrame = it
|
moduleFrame = it
|
||||||
moduleFrameLocalCount = fn.localCount
|
moduleFrameLocalCount = fn.localCount
|
||||||
}
|
}
|
||||||
|
} else if (fn.localCount > moduleFrameLocalCount) {
|
||||||
|
val next = BytecodeFrame(fn.localCount, 0)
|
||||||
|
current.copyTo(next)
|
||||||
|
moduleFrame = next
|
||||||
|
moduleFrameLocalCount = fn.localCount
|
||||||
|
// Retarget frame-based locals to the new frame instance.
|
||||||
|
val localNames = fn.localSlotNames
|
||||||
|
for (i in localNames.indices) {
|
||||||
|
val name = localNames[i] ?: continue
|
||||||
|
val record = objects[name] ?: localBindings[name] ?: continue
|
||||||
|
val value = record.value
|
||||||
|
if (value is FrameSlotRef && value.refersTo(current, i)) {
|
||||||
|
record.value = FrameSlotRef(next, i)
|
||||||
|
updateSlotFor(name, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next
|
||||||
} else {
|
} else {
|
||||||
current
|
current
|
||||||
}
|
}
|
||||||
|
|||||||
@ -635,10 +635,16 @@ open class Scope(
|
|||||||
objects[name]?.let {
|
objects[name]?.let {
|
||||||
if( !it.isMutable )
|
if( !it.isMutable )
|
||||||
raiseIllegalAssignment("symbol is readonly: $name")
|
raiseIllegalAssignment("symbol is readonly: $name")
|
||||||
it.value = value
|
when (val current = it.value) {
|
||||||
|
is FrameSlotRef -> current.write(value)
|
||||||
|
is RecordSlotRef -> current.write(value)
|
||||||
|
else -> it.value = value
|
||||||
|
}
|
||||||
// keep local binding index consistent within the frame
|
// keep local binding index consistent within the frame
|
||||||
localBindings[name] = it
|
localBindings[name] = it
|
||||||
bumpClassLayoutIfNeeded(name, value, recordType)
|
bumpClassLayoutIfNeeded(name, value, recordType)
|
||||||
|
updateSlotFor(name, it)
|
||||||
|
syncModuleFrameSlot(name, value)
|
||||||
it
|
it
|
||||||
} ?: addItem(name, true, value, visibility, writeVisibility, recordType, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride)
|
} ?: addItem(name, true, value, visibility, writeVisibility, recordType, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride)
|
||||||
|
|
||||||
@ -705,6 +711,7 @@ open class Scope(
|
|||||||
slots[idx] = rec
|
slots[idx] = rec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
syncModuleFrameSlot(name, value)
|
||||||
return rec
|
return rec
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -752,7 +759,48 @@ open class Scope(
|
|||||||
|
|
||||||
// --- removed doc-aware overloads to keep runtime lean ---
|
// --- removed doc-aware overloads to keep runtime lean ---
|
||||||
|
|
||||||
fun addConst(name: String, value: Obj) = addItem(name, false, value)
|
fun addConst(name: String, value: Obj): ObjRecord {
|
||||||
|
val existing = objects[name]
|
||||||
|
if (existing != null) {
|
||||||
|
when (val current = existing.value) {
|
||||||
|
is FrameSlotRef -> current.write(value)
|
||||||
|
is RecordSlotRef -> current.write(value)
|
||||||
|
else -> existing.value = value
|
||||||
|
}
|
||||||
|
bumpClassLayoutIfNeeded(name, value, existing.type)
|
||||||
|
updateSlotFor(name, existing)
|
||||||
|
syncModuleFrameSlot(name, value)
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
val slotIndex = getSlotIndexOf(name)
|
||||||
|
if (slotIndex != null) {
|
||||||
|
val record = getSlotRecord(slotIndex)
|
||||||
|
when (val current = record.value) {
|
||||||
|
is FrameSlotRef -> current.write(value)
|
||||||
|
is RecordSlotRef -> current.write(value)
|
||||||
|
else -> record.value = value
|
||||||
|
}
|
||||||
|
bumpClassLayoutIfNeeded(name, value, record.type)
|
||||||
|
updateSlotFor(name, record)
|
||||||
|
syncModuleFrameSlot(name, value)
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
val record = addItem(name, false, value)
|
||||||
|
syncModuleFrameSlot(name, value)
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun syncModuleFrameSlot(name: String, value: Obj) {
|
||||||
|
val module = this as? ModuleScope ?: return
|
||||||
|
val frame = module.moduleFrame ?: return
|
||||||
|
val localNames = module.moduleFrameLocalSlotNames
|
||||||
|
if (localNames.isEmpty()) return
|
||||||
|
for (i in localNames.indices) {
|
||||||
|
if (localNames[i] == name) {
|
||||||
|
frame.setObj(i, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun eval(code: String): Obj =
|
suspend fun eval(code: String): Obj =
|
||||||
|
|||||||
@ -42,13 +42,12 @@ class Script(
|
|||||||
// private val catchReturn: Boolean = false,
|
// private val catchReturn: Boolean = false,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
fun statements(): List<Statement> = statements
|
fun statements(): List<Statement> = statements
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
scope.pos = pos
|
scope.pos = pos
|
||||||
val execScope = resolveModuleScope(scope) ?: scope
|
val execScope = resolveModuleScope(scope) ?: scope
|
||||||
val isModuleScope = execScope is ModuleScope
|
val isModuleScope = execScope is ModuleScope
|
||||||
val shouldSeedModule = isModuleScope || execScope.thisObj === ObjVoid
|
val shouldSeedModule = isModuleScope || execScope.thisObj === ObjVoid
|
||||||
val moduleTarget = execScope
|
val moduleTarget = (execScope as? ModuleScope) ?: execScope.parent as? ModuleScope ?: execScope
|
||||||
if (shouldSeedModule) {
|
if (shouldSeedModule) {
|
||||||
seedModuleSlots(moduleTarget, scope)
|
seedModuleSlots(moduleTarget, scope)
|
||||||
}
|
}
|
||||||
@ -56,9 +55,15 @@ class Script(
|
|||||||
if (execScope is ModuleScope) {
|
if (execScope is ModuleScope) {
|
||||||
execScope.ensureModuleFrame(fn)
|
execScope.ensureModuleFrame(fn)
|
||||||
}
|
}
|
||||||
return CmdVm().execute(fn, execScope, scope.args) { frame, _ ->
|
var execFrame: net.sergeych.lyng.bytecode.CmdFrame? = null
|
||||||
|
val result = CmdVm().execute(fn, execScope, scope.args) { frame, _ ->
|
||||||
|
execFrame = frame
|
||||||
seedModuleLocals(frame, moduleTarget, scope)
|
seedModuleLocals(frame, moduleTarget, scope)
|
||||||
}
|
}
|
||||||
|
if (execScope !is ModuleScope) {
|
||||||
|
execFrame?.let { syncFrameLocalsToScope(it, execScope) }
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
if (statements.isNotEmpty()) {
|
if (statements.isNotEmpty()) {
|
||||||
scope.raiseIllegalState("bytecode-only execution is required; missing module bytecode")
|
scope.raiseIllegalState("bytecode-only execution is required; missing module bytecode")
|
||||||
@ -69,6 +74,13 @@ class Script(
|
|||||||
private suspend fun seedModuleSlots(scope: Scope, seedScope: Scope) {
|
private suspend fun seedModuleSlots(scope: Scope, seedScope: Scope) {
|
||||||
if (importBindings.isEmpty() && importedModules.isEmpty()) return
|
if (importBindings.isEmpty() && importedModules.isEmpty()) return
|
||||||
seedImportBindings(scope, seedScope)
|
seedImportBindings(scope, seedScope)
|
||||||
|
if (moduleSlotPlan.isNotEmpty()) {
|
||||||
|
scope.applySlotPlan(moduleSlotPlan)
|
||||||
|
for (name in moduleSlotPlan.keys) {
|
||||||
|
val record = scope.objects[name] ?: scope.localBindings[name] ?: continue
|
||||||
|
scope.updateSlotFor(name, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun seedModuleLocals(
|
private suspend fun seedModuleLocals(
|
||||||
@ -87,12 +99,43 @@ class Script(
|
|||||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
scope.resolve(record, name)
|
scope.resolve(record, name)
|
||||||
} else {
|
} else {
|
||||||
record.value
|
val raw = record.value
|
||||||
|
when (raw) {
|
||||||
|
is FrameSlotRef -> {
|
||||||
|
if (raw.refersTo(frame.frame, i)) {
|
||||||
|
raw.peekValue() ?: continue
|
||||||
|
} else if (seedScope !is ModuleScope) {
|
||||||
|
raw
|
||||||
|
} else {
|
||||||
|
raw.read()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RecordSlotRef -> {
|
||||||
|
if (seedScope !is ModuleScope) raw else raw.read()
|
||||||
|
}
|
||||||
|
else -> raw
|
||||||
|
}
|
||||||
}
|
}
|
||||||
frame.setObjUnchecked(base + i, value)
|
frame.setObjUnchecked(base + i, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun syncFrameLocalsToScope(frame: net.sergeych.lyng.bytecode.CmdFrame, scope: Scope) {
|
||||||
|
val localNames = frame.fn.localSlotNames
|
||||||
|
if (localNames.isEmpty()) return
|
||||||
|
for (i in localNames.indices) {
|
||||||
|
val name = localNames[i] ?: continue
|
||||||
|
val record = scope.getLocalRecordDirect(name) ?: scope.localBindings[name] ?: scope.objects[name] ?: continue
|
||||||
|
val value = frame.readLocalObj(i)
|
||||||
|
when (val current = record.value) {
|
||||||
|
is FrameSlotRef -> current.write(value)
|
||||||
|
is RecordSlotRef -> current.write(value)
|
||||||
|
else -> record.value = value
|
||||||
|
}
|
||||||
|
scope.updateSlotFor(name, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun seedImportBindings(scope: Scope, seedScope: Scope) {
|
private suspend fun seedImportBindings(scope: Scope, seedScope: Scope) {
|
||||||
val provider = scope.currentImportProvider
|
val provider = scope.currentImportProvider
|
||||||
val importedModules = LinkedHashSet<ModuleScope>()
|
val importedModules = LinkedHashSet<ModuleScope>()
|
||||||
@ -102,6 +145,9 @@ class Script(
|
|||||||
if (scope is ModuleScope) {
|
if (scope is ModuleScope) {
|
||||||
scope.importedModules = importedModules.toList()
|
scope.importedModules = importedModules.toList()
|
||||||
}
|
}
|
||||||
|
for (module in importedModules) {
|
||||||
|
module.importInto(scope, null)
|
||||||
|
}
|
||||||
for ((name, binding) in importBindings) {
|
for ((name, binding) in importBindings) {
|
||||||
val record = when (val source = binding.source) {
|
val record = when (val source = binding.source) {
|
||||||
is ImportBindingSource.Module -> {
|
is ImportBindingSource.Module -> {
|
||||||
|
|||||||
@ -35,6 +35,7 @@ class BytecodeCompiler(
|
|||||||
private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
private val slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(),
|
private val slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(),
|
||||||
private val knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
private val knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
||||||
|
private val knownClassNames: Set<String> = emptySet(),
|
||||||
private val knownObjectNames: Set<String> = emptySet(),
|
private val knownObjectNames: Set<String> = emptySet(),
|
||||||
private val classFieldTypesByName: Map<String, Map<String, ObjClass>> = emptyMap(),
|
private val classFieldTypesByName: Map<String, Map<String, ObjClass>> = emptyMap(),
|
||||||
private val enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
private val enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||||
@ -78,7 +79,6 @@ class BytecodeCompiler(
|
|||||||
private val stableObjSlots = mutableSetOf<Int>()
|
private val stableObjSlots = mutableSetOf<Int>()
|
||||||
private val nameObjClass = knownNameObjClass.toMutableMap()
|
private val nameObjClass = knownNameObjClass.toMutableMap()
|
||||||
private val listElementClassBySlot = mutableMapOf<Int, ObjClass>()
|
private val listElementClassBySlot = mutableMapOf<Int, ObjClass>()
|
||||||
private val knownClassNames = knownNameObjClass.keys.toSet()
|
|
||||||
private val slotInitClassByKey = mutableMapOf<ScopeSlotKey, ObjClass>()
|
private val slotInitClassByKey = mutableMapOf<ScopeSlotKey, ObjClass>()
|
||||||
private val intLoopVarNames = LinkedHashSet<String>()
|
private val intLoopVarNames = LinkedHashSet<String>()
|
||||||
private val valueFnRefs = LinkedHashSet<ValueFnRef>()
|
private val valueFnRefs = LinkedHashSet<ValueFnRef>()
|
||||||
@ -522,7 +522,7 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (resolved == SlotType.UNKNOWN) {
|
if (resolved == SlotType.UNKNOWN) {
|
||||||
val inferred = slotTypeFromClass(nameObjClass[ref.name])
|
val inferred = if (knownClassNames.contains(ref.name)) null else slotTypeFromClass(nameObjClass[ref.name])
|
||||||
if (inferred != null) {
|
if (inferred != null) {
|
||||||
updateSlotType(mapped, inferred)
|
updateSlotType(mapped, inferred)
|
||||||
resolved = inferred
|
resolved = inferred
|
||||||
@ -7695,7 +7695,7 @@ class BytecodeCompiler(
|
|||||||
for (ref in valueFnRefs) {
|
for (ref in valueFnRefs) {
|
||||||
val entries = lambdaCaptureEntriesByRef[ref] ?: continue
|
val entries = lambdaCaptureEntriesByRef[ref] ?: continue
|
||||||
for (entry in entries) {
|
for (entry in entries) {
|
||||||
if (entry.ownerKind != CaptureOwnerFrameKind.LOCAL) continue
|
if (entry.ownerKind == CaptureOwnerFrameKind.LOCAL) {
|
||||||
val key = ScopeSlotKey(entry.ownerScopeId, entry.ownerSlotId)
|
val key = ScopeSlotKey(entry.ownerScopeId, entry.ownerSlotId)
|
||||||
if (!localSlotInfoMap.containsKey(key)) {
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
localSlotInfoMap[key] = LocalSlotInfo(
|
localSlotInfoMap[key] = LocalSlotInfo(
|
||||||
@ -7704,6 +7704,20 @@ class BytecodeCompiler(
|
|||||||
entry.ownerIsDelegated
|
entry.ownerIsDelegated
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (entry.ownerKind == CaptureOwnerFrameKind.MODULE) {
|
||||||
|
val key = ScopeSlotKey(entry.ownerScopeId, entry.ownerSlotId)
|
||||||
|
if (!scopeSlotMap.containsKey(key)) {
|
||||||
|
scopeSlotMap[key] = scopeSlotMap.size
|
||||||
|
}
|
||||||
|
if (!scopeSlotNameMap.containsKey(key)) {
|
||||||
|
scopeSlotNameMap[key] = entry.ownerName
|
||||||
|
}
|
||||||
|
if (!scopeSlotMutableMap.containsKey(key)) {
|
||||||
|
scopeSlotMutableMap[key] = entry.ownerIsMutable
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8171,7 +8185,8 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
|
private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
|
||||||
val scopeNames = scopeSlotNameSet ?: allowedScopeNames
|
if (moduleScopeId != null && scopeId != moduleScopeId) return false
|
||||||
|
val scopeNames = allowedScopeNames ?: scopeSlotNameSet
|
||||||
if (scopeNames == null || name == null) return false
|
if (scopeNames == null || name == null) return false
|
||||||
return scopeNames.contains(name)
|
return scopeNames.contains(name)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,17 @@ class BytecodeFrame(
|
|||||||
private val realSlots: DoubleArray = DoubleArray(slotCount)
|
private val realSlots: DoubleArray = DoubleArray(slotCount)
|
||||||
private val boolSlots: BooleanArray = BooleanArray(slotCount)
|
private val boolSlots: BooleanArray = BooleanArray(slotCount)
|
||||||
|
|
||||||
|
internal fun copyTo(target: BytecodeFrame) {
|
||||||
|
val limit = minOf(slotCount, target.slotCount)
|
||||||
|
for (i in 0 until limit) {
|
||||||
|
target.slotTypes[i] = slotTypes[i]
|
||||||
|
target.objSlots[i] = objSlots[i]
|
||||||
|
target.intSlots[i] = intSlots[i]
|
||||||
|
target.realSlots[i] = realSlots[i]
|
||||||
|
target.boolSlots[i] = boolSlots[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getSlotType(slot: Int): SlotType = SlotType.values().first { it.code == slotTypes[slot] }
|
fun getSlotType(slot: Int): SlotType = SlotType.values().first { it.code == slotTypes[slot] }
|
||||||
override fun getSlotTypeCode(slot: Int): Byte = slotTypes[slot]
|
override fun getSlotTypeCode(slot: Int): Byte = slotTypes[slot]
|
||||||
fun setSlotType(slot: Int, type: SlotType) {
|
fun setSlotType(slot: Int, type: SlotType) {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ package net.sergeych.lyng.bytecode
|
|||||||
import net.sergeych.lyng.*
|
import net.sergeych.lyng.*
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjClass
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
import net.sergeych.lyng.obj.ValueFnRef
|
import net.sergeych.lyng.obj.ValueFnRef
|
||||||
|
|
||||||
class BytecodeStatement private constructor(
|
class BytecodeStatement private constructor(
|
||||||
@ -30,7 +31,34 @@ class BytecodeStatement private constructor(
|
|||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
scope.pos = pos
|
scope.pos = pos
|
||||||
return CmdVm().execute(function, scope, scope.args)
|
val declaredNames = function.constants
|
||||||
|
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
||||||
|
.mapTo(mutableSetOf()) { it.name }
|
||||||
|
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ ->
|
||||||
|
val localNames = frame.fn.localSlotNames
|
||||||
|
for (i in localNames.indices) {
|
||||||
|
val name = localNames[i] ?: continue
|
||||||
|
if (declaredNames.contains(name)) continue
|
||||||
|
val slotType = frame.getLocalSlotTypeCode(i)
|
||||||
|
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (slotType == SlotType.OBJ.code && frame.frame.getRawObj(i) != null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val record = scope.getLocalRecordDirect(name)
|
||||||
|
?: scope.parent?.get(name)
|
||||||
|
?: scope.get(name)
|
||||||
|
?: continue
|
||||||
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
|
scope.resolve(record, name)
|
||||||
|
} else {
|
||||||
|
record.value
|
||||||
|
}
|
||||||
|
frame.frame.setObj(i, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CmdVm().execute(function, scope, scope.args, binder)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun bytecodeFunction(): CmdFunction = function
|
internal fun bytecodeFunction(): CmdFunction = function
|
||||||
@ -52,6 +80,7 @@ class BytecodeStatement private constructor(
|
|||||||
globalSlotScopeId: Int? = null,
|
globalSlotScopeId: Int? = null,
|
||||||
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
||||||
|
knownClassNames: Set<String> = emptySet(),
|
||||||
knownObjectNames: Set<String> = emptySet(),
|
knownObjectNames: Set<String> = emptySet(),
|
||||||
classFieldTypesByName: Map<String, Map<String, ObjClass>> = emptyMap(),
|
classFieldTypesByName: Map<String, Map<String, ObjClass>> = emptyMap(),
|
||||||
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||||
@ -86,6 +115,7 @@ class BytecodeStatement private constructor(
|
|||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownNameObjClass,
|
knownNameObjClass = knownNameObjClass,
|
||||||
|
knownClassNames = knownClassNames,
|
||||||
knownObjectNames = knownObjectNames,
|
knownObjectNames = knownObjectNames,
|
||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
|
|||||||
@ -229,8 +229,29 @@ class CmdLoadThisVariant(
|
|||||||
val typeConst = frame.fn.constants.getOrNull(typeId) as? BytecodeConst.StringVal
|
val typeConst = frame.fn.constants.getOrNull(typeId) as? BytecodeConst.StringVal
|
||||||
?: error("LOAD_THIS_VARIANT expects StringVal at $typeId")
|
?: error("LOAD_THIS_VARIANT expects StringVal at $typeId")
|
||||||
val typeName = typeConst.value
|
val typeName = typeConst.value
|
||||||
val receiver = frame.ensureScope().thisVariants.firstOrNull { it.isInstanceOf(typeName) }
|
val scope = frame.ensureScope()
|
||||||
?: frame.ensureScope().raiseClassCastError("Cannot cast ${frame.ensureScope().thisObj.objClass.className} to $typeName")
|
if (scope.thisVariants.isEmpty() || scope.thisVariants.firstOrNull() !== scope.thisObj) {
|
||||||
|
scope.setThisVariants(scope.thisObj, scope.thisVariants)
|
||||||
|
}
|
||||||
|
val receiver = scope.thisVariants.firstOrNull { it.isInstanceOf(typeName) }
|
||||||
|
?: run {
|
||||||
|
if (scope.thisObj.isInstanceOf(typeName)) return@run scope.thisObj
|
||||||
|
val typeClass = scope[typeName]?.value as? net.sergeych.lyng.obj.ObjClass
|
||||||
|
var s: Scope? = scope
|
||||||
|
while (s != null) {
|
||||||
|
val candidate = s.thisObj
|
||||||
|
if (candidate.isInstanceOf(typeName)) return@run candidate
|
||||||
|
if (typeClass != null) {
|
||||||
|
val inst = candidate as? net.sergeych.lyng.obj.ObjInstance
|
||||||
|
if (inst != null && (inst.objClass === typeClass || inst.objClass.allParentsSet.contains(typeClass))) {
|
||||||
|
return@run inst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = s.parent
|
||||||
|
}
|
||||||
|
val variants = scope.thisVariants.joinToString { it.objClass.className }
|
||||||
|
scope.raiseClassCastError("Cannot cast ${scope.thisObj.objClass.className} to $typeName (variants: $variants)")
|
||||||
|
}
|
||||||
frame.setObj(dst, receiver)
|
frame.setObj(dst, receiver)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2398,12 +2419,10 @@ class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
|
|||||||
)
|
)
|
||||||
val moduleScope = frame.scope as? ModuleScope
|
val moduleScope = frame.scope as? ModuleScope
|
||||||
if (moduleScope != null) {
|
if (moduleScope != null) {
|
||||||
moduleScope.updateSlotFor(decl.name, record)
|
|
||||||
moduleScope.objects[decl.name] = record
|
moduleScope.objects[decl.name] = record
|
||||||
moduleScope.localBindings[decl.name] = record
|
moduleScope.localBindings[decl.name] = record
|
||||||
} else if (frame.fn.name == "<script>") {
|
} else if (frame.fn.name == "<script>") {
|
||||||
val target = frame.ensureScope()
|
val target = frame.ensureScope()
|
||||||
target.updateSlotFor(decl.name, record)
|
|
||||||
target.objects[decl.name] = record
|
target.objects[decl.name] = record
|
||||||
target.localBindings[decl.name] = record
|
target.localBindings[decl.name] = record
|
||||||
}
|
}
|
||||||
@ -2540,6 +2559,12 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
|
|||||||
it.delegate = delegate
|
it.delegate = delegate
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val raw = frame.frame.getRawObj(localIndex)
|
||||||
|
val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name)
|
||||||
|
if (scoped != null && scoped.value !== ObjUnset) {
|
||||||
|
records += scoped
|
||||||
|
continue
|
||||||
|
}
|
||||||
records += ObjRecord(FrameSlotRef(frame.frame, localIndex), isMutable)
|
records += ObjRecord(FrameSlotRef(frame.frame, localIndex), isMutable)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@ -2563,6 +2588,11 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name)
|
||||||
|
if (scoped != null) {
|
||||||
|
records += ObjRecord(RecordSlotRef(scoped), isMutable = scoped.isMutable)
|
||||||
|
continue
|
||||||
|
}
|
||||||
frame.ensureScope().raiseSymbolNotFound("capture $name not found")
|
frame.ensureScope().raiseSymbolNotFound("capture $name not found")
|
||||||
}
|
}
|
||||||
return records
|
return records
|
||||||
@ -2579,6 +2609,17 @@ class CmdDeclClass(internal val constId: Int, internal val slot: Int) : Cmd() {
|
|||||||
val bodyCaptureRecords = buildFunctionCaptureRecords(frame, bodyCaptureNames)
|
val bodyCaptureRecords = buildFunctionCaptureRecords(frame, bodyCaptureNames)
|
||||||
val result = executeClassDecl(frame.ensureScope(), decl.spec, bodyCaptureRecords, bodyCaptureNames)
|
val result = executeClassDecl(frame.ensureScope(), decl.spec, bodyCaptureRecords, bodyCaptureNames)
|
||||||
frame.setObjUnchecked(slot, result)
|
frame.setObjUnchecked(slot, result)
|
||||||
|
val name = decl.spec.declaredName ?: return
|
||||||
|
val moduleScope = frame.scope as? ModuleScope ?: return
|
||||||
|
val record = ObjRecord(
|
||||||
|
result,
|
||||||
|
isMutable = false,
|
||||||
|
visibility = net.sergeych.lyng.Visibility.Public,
|
||||||
|
type = ObjRecord.Type.Other
|
||||||
|
)
|
||||||
|
moduleScope.updateSlotFor(name, record)
|
||||||
|
moduleScope.objects[name] = record
|
||||||
|
moduleScope.localBindings[name] = record
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3769,6 +3810,7 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun getLocalSlotTypeCode(localIndex: Int): Byte = frame.getSlotTypeCode(localIndex)
|
internal fun getLocalSlotTypeCode(localIndex: Int): Byte = frame.getSlotTypeCode(localIndex)
|
||||||
|
internal fun readLocalObj(localIndex: Int): Obj = localSlotToObj(localIndex)
|
||||||
internal fun isFastLocalSlot(slot: Int): Boolean {
|
internal fun isFastLocalSlot(slot: Int): Boolean {
|
||||||
if (slot < fn.scopeSlotCount) return false
|
if (slot < fn.scopeSlotCount) return false
|
||||||
val localIndex = slot - fn.scopeSlotCount
|
val localIndex = slot - fn.scopeSlotCount
|
||||||
@ -3904,10 +3946,26 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
CaptureOwnerFrameKind.MODULE -> {
|
CaptureOwnerFrameKind.MODULE -> {
|
||||||
val slot = entry.slotIndex
|
val slotId = entry.slotIndex
|
||||||
val target = scopeTarget(slot)
|
val target = moduleScope
|
||||||
val index = fn.scopeSlotIndices[slot]
|
val name = captureNames?.getOrNull(index)
|
||||||
target.getSlotRecord(index)
|
if (name != null) {
|
||||||
|
target.tryGetLocalRecord(target, name, target.currentClassCtx)?.let { return@mapIndexed it }
|
||||||
|
target.getSlotIndexOf(name)?.let { return@mapIndexed target.getSlotRecord(it) }
|
||||||
|
target.get(name)?.let { return@mapIndexed it }
|
||||||
|
// Fallback to current scope in case the module scope isn't in the parent chain
|
||||||
|
// or doesn't carry the imported symbol yet.
|
||||||
|
scope.tryGetLocalRecord(scope, name, scope.currentClassCtx)?.let { return@mapIndexed it }
|
||||||
|
scope.get(name)?.let { return@mapIndexed it }
|
||||||
|
}
|
||||||
|
if (slotId < target.slotCount) {
|
||||||
|
return@mapIndexed target.getSlotRecord(slotId)
|
||||||
|
}
|
||||||
|
if (name != null) {
|
||||||
|
target.applySlotPlan(mapOf(name to slotId))
|
||||||
|
return@mapIndexed target.getSlotRecord(slotId)
|
||||||
|
}
|
||||||
|
error("Missing module capture slot $slotId")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3931,10 +3989,15 @@ class CmdFrame(
|
|||||||
.firstOrNull { fn.scopeSlotIsModule.getOrNull(it) == true }
|
.firstOrNull { fn.scopeSlotIsModule.getOrNull(it) == true }
|
||||||
?.let { fn.scopeSlotNames[it] }
|
?.let { fn.scopeSlotNames[it] }
|
||||||
if (moduleSlotName != null) {
|
if (moduleSlotName != null) {
|
||||||
|
findModuleScope(scope)?.let { return it }
|
||||||
val bySlot = findScopeWithSlot(scope, moduleSlotName)
|
val bySlot = findScopeWithSlot(scope, moduleSlotName)
|
||||||
bySlot?.let { return it }
|
if (bySlot is ModuleScope) return bySlot
|
||||||
|
val bySlotParent = bySlot?.parent
|
||||||
|
if (bySlotParent is ModuleScope) return bySlotParent
|
||||||
val byRecord = findScopeWithRecord(scope, moduleSlotName)
|
val byRecord = findScopeWithRecord(scope, moduleSlotName)
|
||||||
byRecord?.let { return it }
|
if (byRecord is ModuleScope) return byRecord
|
||||||
|
val byRecordParent = byRecord?.parent
|
||||||
|
if (byRecordParent is ModuleScope) return byRecordParent
|
||||||
return scope
|
return scope
|
||||||
}
|
}
|
||||||
findModuleScope(scope)?.let { return it }
|
findModuleScope(scope)?.let { return it }
|
||||||
@ -3985,7 +4048,7 @@ class CmdFrame(
|
|||||||
val current = queue.removeFirst()
|
val current = queue.removeFirst()
|
||||||
if (!visited.add(current)) continue
|
if (!visited.add(current)) continue
|
||||||
if (current is ModuleScope) return current
|
if (current is ModuleScope) return current
|
||||||
if (current.parent is ModuleScope) return current
|
if (current.parent is ModuleScope) return current.parent
|
||||||
current.parent?.let { queue.add(it) }
|
current.parent?.let { queue.add(it) }
|
||||||
if (current is BytecodeClosureScope) {
|
if (current is BytecodeClosureScope) {
|
||||||
queue.add(current.closureScope)
|
queue.add(current.closureScope)
|
||||||
@ -4168,6 +4231,7 @@ class CmdFrame(
|
|||||||
} else {
|
} else {
|
||||||
val localIndex = slot - fn.scopeSlotCount
|
val localIndex = slot - fn.scopeSlotCount
|
||||||
ensureLocalMutable(localIndex)
|
ensureLocalMutable(localIndex)
|
||||||
|
if (shouldWriteThroughLocal(localIndex)) {
|
||||||
when (val existing = frame.getRawObj(localIndex)) {
|
when (val existing = frame.getRawObj(localIndex)) {
|
||||||
is FrameSlotRef -> {
|
is FrameSlotRef -> {
|
||||||
existing.write(value)
|
existing.write(value)
|
||||||
@ -4179,6 +4243,7 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
frame.setObj(localIndex, value)
|
frame.setObj(localIndex, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4224,6 +4289,7 @@ class CmdFrame(
|
|||||||
target.setSlotValue(index, ObjInt.of(value))
|
target.setSlotValue(index, ObjInt.of(value))
|
||||||
} else {
|
} else {
|
||||||
val localIndex = slot - fn.scopeSlotCount
|
val localIndex = slot - fn.scopeSlotCount
|
||||||
|
if (shouldWriteThroughLocal(localIndex)) {
|
||||||
when (val existing = frame.getRawObj(localIndex)) {
|
when (val existing = frame.getRawObj(localIndex)) {
|
||||||
is FrameSlotRef -> {
|
is FrameSlotRef -> {
|
||||||
existing.write(ObjInt.of(value))
|
existing.write(ObjInt.of(value))
|
||||||
@ -4235,6 +4301,7 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
frame.setInt(localIndex, value)
|
frame.setInt(localIndex, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4252,6 +4319,7 @@ class CmdFrame(
|
|||||||
} else {
|
} else {
|
||||||
val localIndex = slot - fn.scopeSlotCount
|
val localIndex = slot - fn.scopeSlotCount
|
||||||
ensureLocalMutable(localIndex)
|
ensureLocalMutable(localIndex)
|
||||||
|
if (shouldWriteThroughLocal(localIndex)) {
|
||||||
when (val existing = frame.getRawObj(localIndex)) {
|
when (val existing = frame.getRawObj(localIndex)) {
|
||||||
is FrameSlotRef -> {
|
is FrameSlotRef -> {
|
||||||
existing.write(ObjInt.of(value))
|
existing.write(ObjInt.of(value))
|
||||||
@ -4263,6 +4331,7 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
frame.setInt(localIndex, value)
|
frame.setInt(localIndex, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4310,6 +4379,7 @@ class CmdFrame(
|
|||||||
} else {
|
} else {
|
||||||
val localIndex = slot - fn.scopeSlotCount
|
val localIndex = slot - fn.scopeSlotCount
|
||||||
ensureLocalMutable(localIndex)
|
ensureLocalMutable(localIndex)
|
||||||
|
if (shouldWriteThroughLocal(localIndex)) {
|
||||||
when (val existing = frame.getRawObj(localIndex)) {
|
when (val existing = frame.getRawObj(localIndex)) {
|
||||||
is FrameSlotRef -> {
|
is FrameSlotRef -> {
|
||||||
existing.write(ObjReal.of(value))
|
existing.write(ObjReal.of(value))
|
||||||
@ -4321,6 +4391,7 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
frame.setReal(localIndex, value)
|
frame.setReal(localIndex, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4332,6 +4403,7 @@ class CmdFrame(
|
|||||||
target.setSlotValue(index, ObjReal.of(value))
|
target.setSlotValue(index, ObjReal.of(value))
|
||||||
} else {
|
} else {
|
||||||
val localIndex = slot - fn.scopeSlotCount
|
val localIndex = slot - fn.scopeSlotCount
|
||||||
|
if (shouldWriteThroughLocal(localIndex)) {
|
||||||
when (val existing = frame.getRawObj(localIndex)) {
|
when (val existing = frame.getRawObj(localIndex)) {
|
||||||
is FrameSlotRef -> {
|
is FrameSlotRef -> {
|
||||||
existing.write(ObjReal.of(value))
|
existing.write(ObjReal.of(value))
|
||||||
@ -4343,6 +4415,7 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
frame.setReal(localIndex, value)
|
frame.setReal(localIndex, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4384,6 +4457,7 @@ class CmdFrame(
|
|||||||
} else {
|
} else {
|
||||||
val localIndex = slot - fn.scopeSlotCount
|
val localIndex = slot - fn.scopeSlotCount
|
||||||
ensureLocalMutable(localIndex)
|
ensureLocalMutable(localIndex)
|
||||||
|
if (shouldWriteThroughLocal(localIndex)) {
|
||||||
when (val existing = frame.getRawObj(localIndex)) {
|
when (val existing = frame.getRawObj(localIndex)) {
|
||||||
is FrameSlotRef -> {
|
is FrameSlotRef -> {
|
||||||
existing.write(if (value) ObjTrue else ObjFalse)
|
existing.write(if (value) ObjTrue else ObjFalse)
|
||||||
@ -4395,6 +4469,7 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
frame.setBool(localIndex, value)
|
frame.setBool(localIndex, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4406,6 +4481,7 @@ class CmdFrame(
|
|||||||
target.setSlotValue(index, if (value) ObjTrue else ObjFalse)
|
target.setSlotValue(index, if (value) ObjTrue else ObjFalse)
|
||||||
} else {
|
} else {
|
||||||
val localIndex = slot - fn.scopeSlotCount
|
val localIndex = slot - fn.scopeSlotCount
|
||||||
|
if (shouldWriteThroughLocal(localIndex)) {
|
||||||
when (val existing = frame.getRawObj(localIndex)) {
|
when (val existing = frame.getRawObj(localIndex)) {
|
||||||
is FrameSlotRef -> {
|
is FrameSlotRef -> {
|
||||||
existing.write(if (value) ObjTrue else ObjFalse)
|
existing.write(if (value) ObjTrue else ObjFalse)
|
||||||
@ -4417,6 +4493,7 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
frame.setBool(localIndex, value)
|
frame.setBool(localIndex, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4504,7 +4581,30 @@ class CmdFrame(
|
|||||||
val index = ensureScopeSlot(target, slot)
|
val index = ensureScopeSlot(target, slot)
|
||||||
target.setSlotValue(index, value)
|
target.setSlotValue(index, value)
|
||||||
} else {
|
} else {
|
||||||
frame.setObj(slot - fn.scopeSlotCount, value)
|
val localIndex = slot - fn.scopeSlotCount
|
||||||
|
if (shouldWriteThroughLocal(localIndex)) {
|
||||||
|
when (val existing = frame.getRawObj(localIndex)) {
|
||||||
|
is FrameSlotRef -> {
|
||||||
|
existing.write(value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
is RecordSlotRef -> {
|
||||||
|
existing.write(value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frame.setObj(localIndex, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldWriteThroughLocal(localIndex: Int): Boolean {
|
||||||
|
if (localIndex < fn.localSlotCaptures.size && fn.localSlotCaptures[localIndex]) return true
|
||||||
|
if (localIndex < fn.localSlotDelegated.size && fn.localSlotDelegated[localIndex]) return true
|
||||||
|
return when (frame.getRawObj(localIndex)) {
|
||||||
|
is FrameSlotRef, is RecordSlotRef -> true
|
||||||
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4645,6 +4745,19 @@ class CmdFrame(
|
|||||||
if (direct is FrameSlotRef) return direct.read()
|
if (direct is FrameSlotRef) return direct.read()
|
||||||
if (direct is RecordSlotRef) return direct.read()
|
if (direct is RecordSlotRef) return direct.read()
|
||||||
val name = fn.scopeSlotNames[slot]
|
val name = fn.scopeSlotNames[slot]
|
||||||
|
if (name != null && record.memberName != null && record.memberName != name) {
|
||||||
|
val resolved = target.get(name)
|
||||||
|
if (resolved != null) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return resolvedValue
|
||||||
|
}
|
||||||
|
}
|
||||||
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
|
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
|
||||||
return target.resolve(record, name)
|
return target.resolve(record, name)
|
||||||
}
|
}
|
||||||
@ -4668,6 +4781,19 @@ class CmdFrame(
|
|||||||
if (direct is RecordSlotRef) return direct.read()
|
if (direct is RecordSlotRef) return direct.read()
|
||||||
val slotId = addrScopeSlots[addrSlot]
|
val slotId = addrScopeSlots[addrSlot]
|
||||||
val name = fn.scopeSlotNames.getOrNull(slotId)
|
val name = fn.scopeSlotNames.getOrNull(slotId)
|
||||||
|
if (name != null && record.memberName != null && record.memberName != name) {
|
||||||
|
val resolved = target.get(name)
|
||||||
|
if (resolved != null) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return resolvedValue
|
||||||
|
}
|
||||||
|
}
|
||||||
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
|
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
|
||||||
return target.resolve(record, name)
|
return target.resolve(record, name)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1196,7 +1196,22 @@ open class ObjClass(
|
|||||||
return JsonObject(result)
|
return JsonObject(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
open suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||||
scope.raiseNotImplemented()
|
val meta = constructorMeta
|
||||||
|
?: scope.raiseError("can't deserialize non-serializable object (no constructor meta)")
|
||||||
|
val params = meta.params.filter { !it.isTransient }
|
||||||
|
val values = decoder.decodeAnyList(scope)
|
||||||
|
if (values.size > params.size) {
|
||||||
|
scope.raiseIllegalArgument(
|
||||||
|
"serialized params has bigger size ${values.size} than constructor params (${params.size}): " +
|
||||||
|
values.joinToString(",")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val instance = callWithArgs(scope, *values.toTypedArray())
|
||||||
|
if (instance is ObjInstance) {
|
||||||
|
instance.deserializeStateVars(scope, decoder)
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -121,8 +121,9 @@ open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = n
|
|||||||
// Capture the function's lexical scope (scope) so callbacks can see outer locals like parameters.
|
// Capture the function's lexical scope (scope) so callbacks can see outer locals like parameters.
|
||||||
// Build the dynamic in a child scope purely to set `this` to context, but keep captured closure at parent.
|
// Build the dynamic in a child scope purely to set `this` to context, but keep captured closure at parent.
|
||||||
val buildScope = scope.createChildScope(newThisObj = context)
|
val buildScope = scope.createChildScope(newThisObj = context)
|
||||||
// Snapshot the caller scope to capture locals/args even if the runtime pools/reuses frames
|
// Snapshot the caller scope to capture locals/args even if the runtime pools/reuses frames.
|
||||||
delegate.builderScope = scope.snapshotForClosure()
|
// Module scope should stay late-bound to allow extern class rebinding and similar updates.
|
||||||
|
delegate.builderScope = if (scope is net.sergeych.lyng.ModuleScope) null else scope.snapshotForClosure()
|
||||||
builder.callOn(buildScope)
|
builder.callOn(buildScope)
|
||||||
return delegate
|
return delegate
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,14 +17,12 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.FrameSlotRef
|
|
||||||
import net.sergeych.lyng.PerfFlags
|
import net.sergeych.lyng.PerfFlags
|
||||||
import net.sergeych.lyng.Pos
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.RecordSlotRef
|
|
||||||
import net.sergeych.lyng.RegexCache
|
import net.sergeych.lyng.RegexCache
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.requireScope
|
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
|
import net.sergeych.lyng.requireScope
|
||||||
|
|
||||||
class ObjRegex(val regex: Regex) : Obj() {
|
class ObjRegex(val regex: Regex) : Obj() {
|
||||||
override val objClass get() = type
|
override val objClass get() = type
|
||||||
@ -33,18 +31,10 @@ class ObjRegex(val regex: Regex) : Obj() {
|
|||||||
return regex.find(other.cast<ObjString>(scope).value)?.let {
|
return regex.find(other.cast<ObjString>(scope).value)?.let {
|
||||||
val match = ObjRegexMatch(it)
|
val match = ObjRegexMatch(it)
|
||||||
val record = scope.chainLookupIgnoreClosure("$~", followClosure = true)
|
val record = scope.chainLookupIgnoreClosure("$~", followClosure = true)
|
||||||
if (record != null) {
|
if (record != null && !record.isMutable) {
|
||||||
if (!record.isMutable) {
|
|
||||||
scope.raiseIllegalAssignment("symbol is readonly: $~")
|
scope.raiseIllegalAssignment("symbol is readonly: $~")
|
||||||
}
|
}
|
||||||
when (val value = record.value) {
|
|
||||||
is FrameSlotRef -> value.write(match)
|
|
||||||
is RecordSlotRef -> value.write(match)
|
|
||||||
else -> record.value = match
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
scope.addOrUpdateItem("$~", match)
|
scope.addOrUpdateItem("$~", match)
|
||||||
}
|
|
||||||
ObjTrue
|
ObjTrue
|
||||||
} ?: ObjFalse
|
} ?: ObjFalse
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,7 +84,8 @@ abstract class ImportProvider(
|
|||||||
val module = newModuleAt(pos)
|
val module = newModuleAt(pos)
|
||||||
if (seed.plan.isNotEmpty()) module.applySlotPlan(seed.plan)
|
if (seed.plan.isNotEmpty()) module.applySlotPlan(seed.plan)
|
||||||
seed.stdlib.importInto(module, null)
|
seed.stdlib.importInto(module, null)
|
||||||
|
// Predeclare regex match result slot ($~) in every module scope.
|
||||||
|
module.addOrUpdateItem("$~", net.sergeych.lyng.obj.ObjNull)
|
||||||
return module
|
return module
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,8 @@ import kotlinx.coroutines.test.runTest
|
|||||||
import net.sergeych.lyng.bridge.LyngClassBridge
|
import net.sergeych.lyng.bridge.LyngClassBridge
|
||||||
import net.sergeych.lyng.bridge.bindObject
|
import net.sergeych.lyng.bridge.bindObject
|
||||||
import net.sergeych.lyng.bridge.data
|
import net.sergeych.lyng.bridge.data
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
import net.sergeych.lyng.obj.ObjInt
|
import net.sergeych.lyng.obj.ObjInt
|
||||||
import net.sergeych.lyng.obj.ObjString
|
import net.sergeych.lyng.obj.ObjString
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
@ -214,17 +216,35 @@ class BridgeBindingTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
class ObjA: Obj() {
|
||||||
// fun testGlobalBindingsProperty() = runTest {
|
companion object {
|
||||||
// eval("""
|
val type = ObjClass("A").apply {
|
||||||
// val D: ()->Void = dynamic {
|
addProperty("field",{
|
||||||
// get { name ->
|
ObjInt.of(42)
|
||||||
// {
|
})
|
||||||
// args -> "name: "+name+" args="+args
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
// assertEquals("name: foo args=[42,bar]", D.foo(42, "bar"))
|
@Test
|
||||||
// """.trimIndent())
|
fun testBindExternClass() = runTest {
|
||||||
// }
|
val ms = Script.newScope()
|
||||||
|
ms.eval("""
|
||||||
|
extern class A {
|
||||||
|
val field: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test(a: A) = a.field
|
||||||
|
|
||||||
|
val prop = dynamic {
|
||||||
|
get { name ->
|
||||||
|
name + "=" + A().field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
""".trimIndent())
|
||||||
|
ms.addConst("A", ObjA.type)
|
||||||
|
ms.eval("assertEquals(42, test(A()))")
|
||||||
|
ms.eval("assertEquals(\"test=42\", prop.test)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user