Compare commits
No commits in common. "7b70a37e90ea1dd524fe757485de97e8cc8a822c" and "3391da595f554edab236fa02a4822356e9ecb592" have entirely different histories.
7b70a37e90
...
3391da595f
@ -1,32 +0,0 @@
|
||||
# Bytecode Migration Plan
|
||||
|
||||
Goal: migrate :lynglib compiler/runtime so values live in frame slots and bytecode is the default execution path.
|
||||
|
||||
## Step 1: Imports as module slots (done)
|
||||
- [x] Seed module slot plans from import bindings (lazy, unused imports do not allocate).
|
||||
- [x] Avoid mutating scopes during compile-time imports; bind slots at runtime instead.
|
||||
- [x] Make runtime member access honor extensions (methods + properties).
|
||||
- [x] Ensure class members (ObjClass instances) resolve by slot id in bytecode runtime.
|
||||
- [x] Expose `Iterator` in root scope so stdlib externs bind at runtime.
|
||||
|
||||
## Step 2: Class-scope member refs + qualified-this refs (pending)
|
||||
- [ ] Bytecode-compile `ClassScopeMemberRef` (currently forced to AST in `Compiler.containsUnsupportedRef`).
|
||||
- [ ] Bytecode-compile `QualifiedThisFieldSlotRef` / `QualifiedThisMethodSlotCallRef`.
|
||||
- [ ] Ensure slot resolution uses class member ids, not scope lookup; no fallback opcodes.
|
||||
- [ ] Add coverage for class static access + qualified-this access to keep JVM tests green.
|
||||
|
||||
## Step 3: Expand bytecode coverage for control flow + literals (pending)
|
||||
- [ ] Add bytecode support for `TryStatement` (catch/finally) in `Compiler.containsUnsupportedForBytecode` and `BytecodeCompiler`.
|
||||
- [ ] Support `WhenStatement` conditions beyond the current limited set.
|
||||
- [ ] Add map literal spread support (currently throws in `BytecodeCompiler`).
|
||||
- [ ] Remove remaining `BytecodeCompileException` cases for common member access (missing id paths).
|
||||
|
||||
## Known bytecode gaps (from current guards)
|
||||
- [ ] `TryStatement` is always excluded by `Compiler.containsUnsupportedForBytecode`.
|
||||
- [ ] `ClassScopeMemberRef` and qualified-this refs are excluded by `Compiler.containsUnsupportedRef`.
|
||||
- [ ] `BytecodeCompiler` rejects map literal spreads and some argument expressions.
|
||||
- [ ] Member access still fails when compile-time receiver class cannot be resolved.
|
||||
|
||||
## Validation
|
||||
- [ ] `./gradlew :lynglib:jvmTest` (full suite) after each step; if failures pre-exist, run targeted tests tied to the change and record the gap in this file.
|
||||
- [ ] Baseline full suite: currently 46 failures on `:lynglib:jvmTest` (run 2026-02-08); keep targeted tests green until the baseline is addressed.
|
||||
@ -37,14 +37,8 @@ class BlockStatement(
|
||||
?: applyScope.callScope.resolveCaptureRecord(capture.name)
|
||||
} else {
|
||||
scope.resolveCaptureRecord(capture.name)
|
||||
}
|
||||
if (rec == null) {
|
||||
if (scope.getSlotIndexOf(capture.name) == null && scope.getLocalRecordDirect(capture.name) == null) {
|
||||
continue
|
||||
}
|
||||
(applyScope?.callScope ?: scope)
|
||||
.raiseSymbolNotFound("symbol ${capture.name} not found")
|
||||
}
|
||||
} ?: (applyScope?.callScope ?: scope)
|
||||
.raiseSymbolNotFound("symbol ${capture.name} not found")
|
||||
target.updateSlotFor(capture.name, rec)
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,12 +107,6 @@ class Compiler(
|
||||
if (added && localDeclCountStack.isNotEmpty()) {
|
||||
localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1
|
||||
}
|
||||
capturePlanStack.lastOrNull()?.let { plan ->
|
||||
if (plan.captureMap.remove(name) != null) {
|
||||
plan.captureOwners.remove(name)
|
||||
plan.captures.removeAll { it.name == name }
|
||||
}
|
||||
}
|
||||
declareSlotName(name, isMutable, isDelegated)
|
||||
}
|
||||
|
||||
@ -130,20 +124,6 @@ class Compiler(
|
||||
plan.nextIndex += 1
|
||||
}
|
||||
|
||||
private fun declareSlotNameAt(
|
||||
plan: SlotPlan,
|
||||
name: String,
|
||||
slotIndex: Int,
|
||||
isMutable: Boolean,
|
||||
isDelegated: Boolean
|
||||
) {
|
||||
if (plan.slots.containsKey(name)) return
|
||||
plan.slots[name] = SlotEntry(slotIndex, isMutable, isDelegated)
|
||||
if (slotIndex >= plan.nextIndex) {
|
||||
plan.nextIndex = slotIndex + 1
|
||||
}
|
||||
}
|
||||
|
||||
private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull()
|
||||
private val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
|
||||
private val nameObjClass: MutableMap<String, ObjClass> = mutableMapOf()
|
||||
@ -231,20 +211,6 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun seedSlotPlanFromSeedScope(scope: Scope) {
|
||||
val plan = moduleSlotPlan() ?: return
|
||||
for ((name, slotIndex) in scope.slotNameToIndexSnapshot()) {
|
||||
val record = scope.getSlotRecord(slotIndex)
|
||||
declareSlotNameAt(
|
||||
plan,
|
||||
name,
|
||||
slotIndex,
|
||||
record.isMutable,
|
||||
record.type == ObjRecord.Type.Delegated
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun predeclareTopLevelSymbols() {
|
||||
val plan = moduleSlotPlan() ?: return
|
||||
val saved = cc.savePos()
|
||||
@ -482,11 +448,11 @@ class Compiler(
|
||||
val scopeRec = seedScope?.get(name) ?: importManager.rootScope.get(name)
|
||||
val clsFromScope = scopeRec?.value as? ObjClass
|
||||
val clsFromImports = if (clsFromScope == null) {
|
||||
importedModules.asReversed().firstNotNullOfOrNull { it.scope.get(name)?.value as? ObjClass }
|
||||
importedScopes.asReversed().firstNotNullOfOrNull { it.get(name)?.value as? ObjClass }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val cls = clsFromScope ?: clsFromImports ?: resolveClassByName(name) ?: return null
|
||||
val cls = clsFromScope ?: clsFromImports ?: return null
|
||||
val fieldIds = cls.instanceFieldIdMap()
|
||||
val methodIds = cls.instanceMethodIdMap(includeAbstract = true)
|
||||
val baseNames = cls.directParents.map { it.className }
|
||||
@ -870,12 +836,6 @@ class Compiler(
|
||||
}
|
||||
val moduleLoc = if (slotPlanStack.size == 1) lookupSlotLocation(name, includeModule = true) else null
|
||||
if (moduleLoc != null) {
|
||||
val moduleDeclaredNames = localNamesStack.firstOrNull()
|
||||
if (moduleDeclaredNames == null || !moduleDeclaredNames.contains(name)) {
|
||||
resolveImportBinding(name, pos)?.let { resolved ->
|
||||
registerImportBinding(name, resolved.binding, pos)
|
||||
}
|
||||
}
|
||||
val ref = LocalSlotRef(
|
||||
name,
|
||||
moduleLoc.slot,
|
||||
@ -894,83 +854,6 @@ class Compiler(
|
||||
val ids = resolveMemberIds(name, pos, null)
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName())
|
||||
}
|
||||
val implicitTypeFromFunc = implicitReceiverTypeForMember(name)
|
||||
val hasImplicitClassMember = classCtx != null && hasImplicitThisMember(name, classCtx.name)
|
||||
if (implicitTypeFromFunc == null && !hasImplicitClassMember) {
|
||||
val modulePlan = moduleSlotPlan()
|
||||
val moduleEntry = modulePlan?.slots?.get(name)
|
||||
if (moduleEntry != null) {
|
||||
val moduleDeclaredNames = localNamesStack.firstOrNull()
|
||||
if (moduleDeclaredNames == null || !moduleDeclaredNames.contains(name)) {
|
||||
resolveImportBinding(name, pos)?.let { resolved ->
|
||||
registerImportBinding(name, resolved.binding, pos)
|
||||
}
|
||||
}
|
||||
val moduleLoc = SlotLocation(
|
||||
moduleEntry.index,
|
||||
slotPlanStack.size - 1,
|
||||
modulePlan.id,
|
||||
moduleEntry.isMutable,
|
||||
moduleEntry.isDelegated
|
||||
)
|
||||
captureLocalRef(name, moduleLoc, pos)?.let { ref ->
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
val ref = LocalSlotRef(
|
||||
name,
|
||||
moduleLoc.slot,
|
||||
moduleLoc.scopeId,
|
||||
moduleLoc.isMutable,
|
||||
moduleLoc.isDelegated,
|
||||
pos,
|
||||
strictSlotRefs
|
||||
)
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
resolveImportBinding(name, pos)?.let { resolved ->
|
||||
val sourceRecord = resolved.record
|
||||
if (modulePlan != null && !modulePlan.slots.containsKey(name)) {
|
||||
val seedSlotIndex = if (resolved.binding.source is ImportBindingSource.Seed) {
|
||||
seedScope?.getSlotIndexOf(name)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (seedSlotIndex != null) {
|
||||
declareSlotNameAt(
|
||||
modulePlan,
|
||||
name,
|
||||
seedSlotIndex,
|
||||
sourceRecord.isMutable,
|
||||
sourceRecord.type == ObjRecord.Type.Delegated
|
||||
)
|
||||
} else {
|
||||
declareSlotNameIn(
|
||||
modulePlan,
|
||||
name,
|
||||
sourceRecord.isMutable,
|
||||
sourceRecord.type == ObjRecord.Type.Delegated
|
||||
)
|
||||
}
|
||||
}
|
||||
registerImportBinding(name, resolved.binding, pos)
|
||||
val slot = lookupSlotLocation(name)
|
||||
if (slot != null) {
|
||||
val ref = LocalSlotRef(
|
||||
name,
|
||||
slot.slot,
|
||||
slot.scopeId,
|
||||
slot.isMutable,
|
||||
slot.isDelegated,
|
||||
pos,
|
||||
strictSlotRefs
|
||||
)
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
}
|
||||
}
|
||||
if (classCtx != null) {
|
||||
val implicitType = classCtx.name
|
||||
if (hasImplicitThisMember(name, implicitType)) {
|
||||
@ -980,7 +863,7 @@ class Compiler(
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, preferredType)
|
||||
}
|
||||
}
|
||||
val implicitType = implicitTypeFromFunc
|
||||
val implicitType = implicitReceiverTypeForMember(name)
|
||||
if (implicitType != null) {
|
||||
resolutionSink?.referenceMember(name, pos, implicitType)
|
||||
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
|
||||
@ -990,6 +873,52 @@ class Compiler(
|
||||
resolutionSink?.referenceMember(name, pos, classCtx.name)
|
||||
return ClassScopeMemberRef(name, pos, classCtx.name)
|
||||
}
|
||||
val modulePlan = moduleSlotPlan()
|
||||
val moduleEntry = modulePlan?.slots?.get(name)
|
||||
if (moduleEntry != null) {
|
||||
val moduleLoc = SlotLocation(
|
||||
moduleEntry.index,
|
||||
slotPlanStack.size - 1,
|
||||
modulePlan.id,
|
||||
moduleEntry.isMutable,
|
||||
moduleEntry.isDelegated
|
||||
)
|
||||
captureLocalRef(name, moduleLoc, pos)?.let { ref ->
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
val ref = LocalSlotRef(
|
||||
name,
|
||||
moduleLoc.slot,
|
||||
moduleLoc.scopeId,
|
||||
moduleLoc.isMutable,
|
||||
moduleLoc.isDelegated,
|
||||
pos,
|
||||
strictSlotRefs
|
||||
)
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
val rootRecord = importManager.rootScope.objects[name]
|
||||
if (rootRecord != null && rootRecord.visibility.isPublic) {
|
||||
modulePlan?.let { plan ->
|
||||
declareSlotNameIn(plan, name, rootRecord.isMutable, rootRecord.type == ObjRecord.Type.Delegated)
|
||||
}
|
||||
val rootSlot = lookupSlotLocation(name)
|
||||
if (rootSlot != null) {
|
||||
val ref = LocalSlotRef(
|
||||
name,
|
||||
rootSlot.slot,
|
||||
rootSlot.scopeId,
|
||||
rootSlot.isMutable,
|
||||
rootSlot.isDelegated,
|
||||
pos,
|
||||
strictSlotRefs
|
||||
)
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
}
|
||||
val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody }
|
||||
if (classContext && extensionNames.contains(name)) {
|
||||
resolutionSink?.referenceMember(name, pos)
|
||||
@ -1031,10 +960,7 @@ class Compiler(
|
||||
private val seedScope: Scope? = settings.seedScope
|
||||
private var resolutionScriptDepth = 0
|
||||
private val resolutionPredeclared = mutableSetOf<String>()
|
||||
private data class ImportedModule(val scope: ModuleScope, val pos: Pos)
|
||||
private data class ImportBindingResolution(val binding: ImportBinding, val record: ObjRecord)
|
||||
private val importedModules = mutableListOf<ImportedModule>()
|
||||
private val importBindings = mutableMapOf<String, ImportBinding>()
|
||||
private val importedScopes = mutableListOf<Scope>()
|
||||
private val enumEntriesByName = mutableMapOf<String, List<String>>()
|
||||
|
||||
// --- Doc-comment collection state (for immediate preceding declarations) ---
|
||||
@ -1071,118 +997,6 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun seedNameObjClassFromScope(scope: Scope) {
|
||||
var current: Scope? = scope
|
||||
while (current != null) {
|
||||
for ((name, record) in current.objects) {
|
||||
if (!record.visibility.isPublic) continue
|
||||
if (nameObjClass.containsKey(name)) continue
|
||||
when (val value = record.value) {
|
||||
is ObjClass -> nameObjClass[name] = value
|
||||
is ObjInstance -> nameObjClass[name] = value.objClass
|
||||
}
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveImportBinding(name: String, pos: Pos): ImportBindingResolution? {
|
||||
val seedRecord = findSeedScopeRecord(name)?.takeIf { it.visibility.isPublic }
|
||||
val rootRecord = importManager.rootScope.objects[name]?.takeIf { it.visibility.isPublic }
|
||||
val moduleMatches = LinkedHashMap<String, Pair<ImportedModule, ObjRecord>>()
|
||||
for (module in importedModules.asReversed()) {
|
||||
val found = LinkedHashMap<String, Pair<ModuleScope, ObjRecord>>()
|
||||
collectModuleRecordMatches(module.scope, name, mutableSetOf(), found)
|
||||
for ((pkg, pair) in found) {
|
||||
moduleMatches.putIfAbsent(pkg, ImportedModule(pair.first, module.pos) to pair.second)
|
||||
}
|
||||
}
|
||||
if (seedRecord != null) {
|
||||
val value = seedRecord.value
|
||||
if (!nameObjClass.containsKey(name)) {
|
||||
when (value) {
|
||||
is ObjClass -> nameObjClass[name] = value
|
||||
is ObjInstance -> nameObjClass[name] = value.objClass
|
||||
}
|
||||
}
|
||||
return ImportBindingResolution(ImportBinding(name, ImportBindingSource.Seed), seedRecord)
|
||||
}
|
||||
if (rootRecord != null) {
|
||||
val value = rootRecord.value
|
||||
if (!nameObjClass.containsKey(name)) {
|
||||
when (value) {
|
||||
is ObjClass -> nameObjClass[name] = value
|
||||
is ObjInstance -> nameObjClass[name] = value.objClass
|
||||
}
|
||||
}
|
||||
return ImportBindingResolution(ImportBinding(name, ImportBindingSource.Root), rootRecord)
|
||||
}
|
||||
if (moduleMatches.isEmpty()) return null
|
||||
if (moduleMatches.size > 1) {
|
||||
val moduleNames = moduleMatches.keys.toList()
|
||||
throw ScriptError(pos, "symbol $name is ambiguous between imports: ${moduleNames.joinToString(", ")}")
|
||||
}
|
||||
val (module, record) = moduleMatches.values.first()
|
||||
val binding = ImportBinding(name, ImportBindingSource.Module(module.scope.packageName, module.pos))
|
||||
val value = record.value
|
||||
if (!nameObjClass.containsKey(name)) {
|
||||
when (value) {
|
||||
is ObjClass -> nameObjClass[name] = value
|
||||
is ObjInstance -> nameObjClass[name] = value.objClass
|
||||
}
|
||||
}
|
||||
return ImportBindingResolution(binding, record)
|
||||
}
|
||||
|
||||
private fun collectModuleRecordMatches(
|
||||
scope: ModuleScope,
|
||||
name: String,
|
||||
visited: MutableSet<String>,
|
||||
out: MutableMap<String, Pair<ModuleScope, ObjRecord>>
|
||||
) {
|
||||
if (!visited.add(scope.packageName)) return
|
||||
val record = scope.objects[name]
|
||||
if (record != null && record.visibility.isPublic) {
|
||||
out.putIfAbsent(scope.packageName, scope to record)
|
||||
}
|
||||
for (child in scope.importedModules) {
|
||||
collectModuleRecordMatches(child, name, visited, out)
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerImportBinding(name: String, binding: ImportBinding, pos: Pos) {
|
||||
val existing = importBindings[name] ?: run {
|
||||
importBindings[name] = binding
|
||||
return
|
||||
}
|
||||
if (!sameImportBinding(existing, binding)) {
|
||||
throw ScriptError(pos, "symbol $name resolves to multiple imports")
|
||||
}
|
||||
}
|
||||
|
||||
private fun sameImportBinding(left: ImportBinding, right: ImportBinding): Boolean {
|
||||
if (left.symbol != right.symbol) return false
|
||||
val leftSrc = left.source
|
||||
val rightSrc = right.source
|
||||
return when (leftSrc) {
|
||||
is ImportBindingSource.Module -> {
|
||||
rightSrc is ImportBindingSource.Module && leftSrc.name == rightSrc.name
|
||||
}
|
||||
ImportBindingSource.Root -> rightSrc is ImportBindingSource.Root
|
||||
ImportBindingSource.Seed -> rightSrc is ImportBindingSource.Seed
|
||||
}
|
||||
}
|
||||
|
||||
private fun findSeedScopeRecord(name: String): ObjRecord? {
|
||||
var current = seedScope
|
||||
var hops = 0
|
||||
while (current != null && hops++ < 1024) {
|
||||
current.objects[name]?.let { return it }
|
||||
current = current.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun shouldSeedDefaultStdlib(): Boolean {
|
||||
if (seedScope != null) return false
|
||||
if (importManager !== Script.defaultImportManager) return false
|
||||
@ -1402,25 +1216,15 @@ class Compiler(
|
||||
val needsSlotPlan = slotPlanStack.isEmpty()
|
||||
if (needsSlotPlan) {
|
||||
slotPlanStack.add(SlotPlan(mutableMapOf(), 0, nextScopeId++))
|
||||
seedScope?.let { scope ->
|
||||
if (scope !is ModuleScope) {
|
||||
seedSlotPlanFromSeedScope(scope)
|
||||
}
|
||||
}
|
||||
val plan = slotPlanStack.last()
|
||||
if (!plan.slots.containsKey("__PACKAGE__")) {
|
||||
declareSlotNameIn(plan, "__PACKAGE__", isMutable = false, isDelegated = false)
|
||||
}
|
||||
if (!plan.slots.containsKey("$~")) {
|
||||
declareSlotNameIn(plan, "$~", isMutable = true, isDelegated = false)
|
||||
}
|
||||
seedScope?.let { seedNameObjClassFromScope(it) }
|
||||
seedNameObjClassFromScope(importManager.rootScope)
|
||||
declareSlotNameIn(slotPlanStack.last(), "__PACKAGE__", isMutable = false, isDelegated = false)
|
||||
declareSlotNameIn(slotPlanStack.last(), "$~", isMutable = true, isDelegated = false)
|
||||
seedScope?.let { seedSlotPlanFromScope(it, includeParents = true) }
|
||||
seedSlotPlanFromScope(importManager.rootScope)
|
||||
if (shouldSeedDefaultStdlib()) {
|
||||
val stdlib = importManager.prepareImport(start, "lyng.stdlib", null)
|
||||
seedResolutionFromScope(stdlib, start)
|
||||
seedNameObjClassFromScope(stdlib)
|
||||
importedModules.add(ImportedModule(stdlib, start))
|
||||
seedSlotPlanFromScope(stdlib)
|
||||
importedScopes.add(stdlib)
|
||||
}
|
||||
predeclareTopLevelSymbols()
|
||||
}
|
||||
@ -1489,8 +1293,16 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
val module = importManager.prepareImport(pos, name, null)
|
||||
importedModules.add(ImportedModule(module, pos))
|
||||
importedScopes.add(module)
|
||||
seedResolutionFromScope(module, pos)
|
||||
seedSlotPlanFromScope(module)
|
||||
statements += object : Statement() {
|
||||
override val pos: Pos = pos
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
module.importInto(scope, null)
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -1524,8 +1336,7 @@ class Compiler(
|
||||
statements.isNotEmpty() &&
|
||||
codeContexts.lastOrNull() is CodeContext.Module &&
|
||||
resolutionScriptDepth == 1 &&
|
||||
statements.none { containsUnsupportedForBytecode(it) } &&
|
||||
statements.none { containsDelegatedRefs(it) }
|
||||
statements.none { containsUnsupportedForBytecode(it) }
|
||||
val finalStatements = if (wrapScriptBytecode) {
|
||||
val unwrapped = statements.map { unwrapBytecodeDeep(it) }
|
||||
val block = InlineBlockStatement(unwrapped, start)
|
||||
@ -1543,8 +1354,7 @@ class Compiler(
|
||||
} else {
|
||||
statements
|
||||
}
|
||||
val moduleRefs = importedModules.map { ImportBindingSource.Module(it.scope.packageName, it.pos) }
|
||||
Script(start, finalStatements, modulePlan, importBindings.toMap(), moduleRefs)
|
||||
Script(start, finalStatements, modulePlan)
|
||||
}.also {
|
||||
// Best-effort script end notification (use current position)
|
||||
miniSink?.onScriptEnd(
|
||||
@ -1605,23 +1415,8 @@ class Compiler(
|
||||
val candidates = mutableListOf(typeName)
|
||||
cls?.mro?.forEach { candidates.add(it.className) }
|
||||
for (baseName in candidates) {
|
||||
val wrapperNames = listOf(
|
||||
extensionCallableName(baseName, memberName),
|
||||
extensionPropertyGetterName(baseName, memberName),
|
||||
extensionPropertySetterName(baseName, memberName)
|
||||
)
|
||||
for (wrapperName in wrapperNames) {
|
||||
val resolved = resolveImportBinding(wrapperName, Pos.builtIn) ?: continue
|
||||
val plan = moduleSlotPlan()
|
||||
if (plan != null && !plan.slots.containsKey(wrapperName)) {
|
||||
declareSlotNameIn(
|
||||
plan,
|
||||
wrapperName,
|
||||
resolved.record.isMutable,
|
||||
resolved.record.type == ObjRecord.Type.Delegated
|
||||
)
|
||||
}
|
||||
registerImportBinding(wrapperName, resolved.binding, Pos.builtIn)
|
||||
val wrapperName = extensionCallableName(baseName, memberName)
|
||||
if (seedScope?.get(wrapperName) != null || importManager.rootScope.get(wrapperName) != null) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1783,8 +1578,8 @@ class Compiler(
|
||||
}
|
||||
addScope(seedScope)
|
||||
addScope(importManager.rootScope)
|
||||
for (module in importedModules) {
|
||||
addScope(module.scope)
|
||||
for (scope in importedScopes) {
|
||||
addScope(scope)
|
||||
}
|
||||
for (name in compileClassInfos.keys) {
|
||||
val cls = resolveClassByName(name) ?: continue
|
||||
@ -1812,7 +1607,10 @@ class Compiler(
|
||||
}
|
||||
if (stmt is FunctionDeclStatement ||
|
||||
stmt is ClassDeclStatement ||
|
||||
stmt is EnumDeclStatement
|
||||
stmt is EnumDeclStatement ||
|
||||
stmt is BreakStatement ||
|
||||
stmt is ContinueStatement ||
|
||||
stmt is ReturnStatement
|
||||
) {
|
||||
return stmt
|
||||
}
|
||||
@ -1935,11 +1733,8 @@ class Compiler(
|
||||
containsUnsupportedRef(ref.condition) || containsUnsupportedRef(ref.ifTrue) || containsUnsupportedRef(ref.ifFalse)
|
||||
is ElvisRef -> containsUnsupportedRef(ref.left) || containsUnsupportedRef(ref.right)
|
||||
is FieldRef -> {
|
||||
val receiverClass = resolveReceiverClassForMember(ref.target) ?: return true
|
||||
val receiverClass = resolveReceiverClassForMember(ref.target)
|
||||
if (receiverClass == ObjDynamic.type) return true
|
||||
val hasMember = receiverClass.instanceFieldIdMap()[ref.name] != null ||
|
||||
receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] != null
|
||||
if (!hasMember && !hasExtensionFor(receiverClass.className, ref.name)) return true
|
||||
containsUnsupportedRef(ref.target)
|
||||
}
|
||||
is IndexRef -> containsUnsupportedRef(ref.targetRef) || containsUnsupportedRef(ref.indexRef)
|
||||
@ -1955,24 +1750,12 @@ class Compiler(
|
||||
is net.sergeych.lyng.obj.MapLiteralEntry.Spread -> containsUnsupportedRef(it.ref)
|
||||
}
|
||||
}
|
||||
is CallRef -> {
|
||||
val targetName = when (val target = ref.target) {
|
||||
is LocalVarRef -> target.name
|
||||
is LocalSlotRef -> target.name
|
||||
else -> null
|
||||
}
|
||||
if (targetName == "delay") return true
|
||||
containsUnsupportedRef(ref.target) || ref.args.any { containsUnsupportedForBytecode(it.value) }
|
||||
}
|
||||
is CallRef -> containsUnsupportedRef(ref.target) || ref.args.any { containsUnsupportedForBytecode(it.value) }
|
||||
is MethodCallRef -> {
|
||||
if (ref.name == "delay") return true
|
||||
val receiverClass = resolveReceiverClassForMember(ref.receiver) ?: return true
|
||||
val receiverClass = resolveReceiverClassForMember(ref.receiver)
|
||||
if (receiverClass == ObjDynamic.type) return true
|
||||
val hasMember = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] != null
|
||||
if (!hasMember && !hasExtensionFor(receiverClass.className, ref.name)) return true
|
||||
containsUnsupportedRef(ref.receiver) || ref.args.any { containsUnsupportedForBytecode(it.value) }
|
||||
}
|
||||
is ImplicitThisMethodCallRef -> true
|
||||
is QualifiedThisMethodSlotCallRef -> true
|
||||
is QualifiedThisFieldSlotRef -> true
|
||||
is ClassScopeMemberRef -> true
|
||||
@ -1986,7 +1769,7 @@ class Compiler(
|
||||
is ExpressionStatement -> containsDelegatedRefs(target.ref)
|
||||
is BlockStatement -> target.statements().any { containsDelegatedRefs(it) }
|
||||
is VarDeclStatement -> target.initializer?.let { containsDelegatedRefs(it) } ?: false
|
||||
is DelegatedVarDeclStatement -> true
|
||||
is DelegatedVarDeclStatement -> containsDelegatedRefs(target.initializer)
|
||||
is DestructuringVarDeclStatement -> containsDelegatedRefs(target.initializer)
|
||||
is IfStatement -> {
|
||||
containsDelegatedRefs(target.condition) ||
|
||||
@ -2032,18 +1815,6 @@ class Compiler(
|
||||
private fun containsDelegatedRefs(ref: ObjRef): Boolean {
|
||||
return when (ref) {
|
||||
is LocalSlotRef -> ref.isDelegated
|
||||
is ImplicitThisMemberRef -> {
|
||||
val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName()
|
||||
val targetClass = typeName?.let { resolveClassByName(it) }
|
||||
val member = targetClass?.findFirstConcreteMember(ref.name)
|
||||
member?.type == ObjRecord.Type.Delegated
|
||||
}
|
||||
is ImplicitThisMethodCallRef -> {
|
||||
val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName()
|
||||
val targetClass = typeName?.let { resolveClassByName(it) }
|
||||
val member = targetClass?.findFirstConcreteMember(ref.methodName())
|
||||
member?.type == ObjRecord.Type.Delegated
|
||||
}
|
||||
is BinaryOpRef -> containsDelegatedRefs(ref.left) || containsDelegatedRefs(ref.right)
|
||||
is UnaryOpRef -> containsDelegatedRefs(ref.a)
|
||||
is CastRef -> containsDelegatedRefs(ref.castValueRef()) || containsDelegatedRefs(ref.castTypeRef())
|
||||
@ -2058,14 +1829,7 @@ class Compiler(
|
||||
is ConditionalRef ->
|
||||
containsDelegatedRefs(ref.condition) || containsDelegatedRefs(ref.ifTrue) || containsDelegatedRefs(ref.ifFalse)
|
||||
is ElvisRef -> containsDelegatedRefs(ref.left) || containsDelegatedRefs(ref.right)
|
||||
is FieldRef -> {
|
||||
val receiverClass = resolveReceiverClassForMember(ref.target)
|
||||
if (receiverClass != null) {
|
||||
val member = receiverClass.findFirstConcreteMember(ref.name)
|
||||
if (member?.type == ObjRecord.Type.Delegated) return true
|
||||
}
|
||||
containsDelegatedRefs(ref.target)
|
||||
}
|
||||
is FieldRef -> containsDelegatedRefs(ref.target)
|
||||
is IndexRef -> containsDelegatedRefs(ref.targetRef) || containsDelegatedRefs(ref.indexRef)
|
||||
is ListLiteralRef -> ref.entries().any {
|
||||
when (it) {
|
||||
@ -2080,14 +1844,7 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
is CallRef -> containsDelegatedRefs(ref.target) || ref.args.any { containsDelegatedRefs(it.value) }
|
||||
is MethodCallRef -> {
|
||||
val receiverClass = resolveReceiverClassForMember(ref.receiver)
|
||||
if (receiverClass != null) {
|
||||
val member = receiverClass.findFirstConcreteMember(ref.name)
|
||||
if (member?.type == ObjRecord.Type.Delegated) return true
|
||||
}
|
||||
containsDelegatedRefs(ref.receiver) || ref.args.any { containsDelegatedRefs(it.value) }
|
||||
}
|
||||
is MethodCallRef -> containsDelegatedRefs(ref.receiver) || ref.args.any { containsDelegatedRefs(it.value) }
|
||||
is StatementRef -> containsDelegatedRefs(ref.statement)
|
||||
else -> false
|
||||
}
|
||||
@ -4185,15 +3942,6 @@ class Compiler(
|
||||
if (targetClass == ObjInstant.type && (name == "distantFuture" || name == "distantPast")) {
|
||||
return ObjInstant.type
|
||||
}
|
||||
if (targetClass == ObjInstant.type && name in listOf(
|
||||
"truncateToMinute",
|
||||
"truncateToSecond",
|
||||
"truncateToMillisecond",
|
||||
"truncateToMicrosecond"
|
||||
)
|
||||
) {
|
||||
return ObjInstant.type
|
||||
}
|
||||
if (targetClass == ObjString.type && name == "re") {
|
||||
return ObjRegex.type
|
||||
}
|
||||
@ -4275,7 +4023,6 @@ class Compiler(
|
||||
if (isAllowedObjectMember(memberName)) return
|
||||
throw ScriptError(pos, "member access requires compile-time receiver type: $memberName")
|
||||
}
|
||||
registerExtensionWrapperBindings(receiverClass, memberName, pos)
|
||||
if (receiverClass == Obj.rootObjectType) {
|
||||
val allowed = isAllowedObjectMember(memberName)
|
||||
if (!allowed && !hasExtensionFor(receiverClass.className, memberName)) {
|
||||
@ -4284,29 +4031,6 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerExtensionWrapperBindings(receiverClass: ObjClass, memberName: String, pos: Pos) {
|
||||
for (cls in receiverClass.mro) {
|
||||
val wrapperNames = listOf(
|
||||
extensionCallableName(cls.className, memberName),
|
||||
extensionPropertyGetterName(cls.className, memberName),
|
||||
extensionPropertySetterName(cls.className, memberName)
|
||||
)
|
||||
for (wrapperName in wrapperNames) {
|
||||
val resolved = resolveImportBinding(wrapperName, pos) ?: continue
|
||||
val plan = moduleSlotPlan()
|
||||
if (plan != null && !plan.slots.containsKey(wrapperName)) {
|
||||
declareSlotNameIn(
|
||||
plan,
|
||||
wrapperName,
|
||||
resolved.record.isMutable,
|
||||
resolved.record.type == ObjRecord.Type.Delegated
|
||||
)
|
||||
}
|
||||
registerImportBinding(wrapperName, resolved.binding, pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAllowedObjectMember(memberName: String): Boolean {
|
||||
return when (memberName) {
|
||||
"toString",
|
||||
@ -4768,7 +4492,7 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
val implicitThisTypeName = currentImplicitThisTypeName()
|
||||
val result = when (left) {
|
||||
return when (left) {
|
||||
is ImplicitThisMemberRef ->
|
||||
if (left.methodId == null && left.fieldId != null) {
|
||||
CallRef(left, args, detectedBlockArgument, isOptional)
|
||||
@ -4829,7 +4553,6 @@ class Compiler(
|
||||
}
|
||||
else -> CallRef(left, args, detectedBlockArgument, isOptional)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun inferReceiverTypeFromArgs(args: List<ParsedArgument>): String? {
|
||||
@ -6210,14 +5933,6 @@ class Compiler(
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
// the main statement should create custom ObjClass instance with field
|
||||
// accessors, constructor registration, etc.
|
||||
if (isExtern) {
|
||||
val rec = scope[className]
|
||||
val existing = rec?.value as? ObjClass
|
||||
val resolved = existing ?: resolveClassByName(className)
|
||||
val stub = resolved ?: ObjInstanceClass(className).apply { this.isAbstract = true }
|
||||
scope.addItem(declaredName, false, stub)
|
||||
return stub
|
||||
}
|
||||
// Resolve parent classes by name at execution time
|
||||
val parentClasses = baseSpecs.map { baseSpec ->
|
||||
val rec = scope[baseSpec.name]
|
||||
@ -6854,11 +6569,7 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
val fnStatements = rawFnStatements?.let { stmt ->
|
||||
if (useBytecodeStatements &&
|
||||
parentContext !is CodeContext.ClassBody &&
|
||||
!containsUnsupportedForBytecode(stmt) &&
|
||||
!containsDelegatedRefs(stmt)
|
||||
) {
|
||||
if (useBytecodeStatements && !containsUnsupportedForBytecode(stmt)) {
|
||||
val paramKnownClasses = mutableMapOf<String, ObjClass>()
|
||||
for (param in argsDeclaration.params) {
|
||||
val cls = resolveTypeDeclObjClass(param.type) ?: continue
|
||||
@ -6922,19 +6633,6 @@ class Compiler(
|
||||
val fnCreateStatement = object : Statement() {
|
||||
override val pos: Pos = start
|
||||
override suspend fun execute(context: Scope): Obj {
|
||||
if (actualExtern && extTypeName == null && parentContext !is CodeContext.ClassBody) {
|
||||
val existing = context.get(name)
|
||||
if (existing != null) {
|
||||
context.addItem(
|
||||
name,
|
||||
false,
|
||||
existing.value,
|
||||
visibility,
|
||||
callSignature = existing.callSignature
|
||||
)
|
||||
return existing.value
|
||||
}
|
||||
}
|
||||
if (isDelegated) {
|
||||
val accessType = ObjString("Callable")
|
||||
val initValue = delegateExpression!!.execute(context)
|
||||
@ -7063,9 +6761,7 @@ class Compiler(
|
||||
)
|
||||
execScope.currentClassCtx = cls
|
||||
compiledFnBody.execute(execScope)
|
||||
} ?: run {
|
||||
compiledFnBody.execute(thisObj.autoInstanceScope(this))
|
||||
}
|
||||
} ?: compiledFnBody.execute(thisObj.autoInstanceScope(this))
|
||||
} finally {
|
||||
this.currentClassCtx = savedCtx
|
||||
}
|
||||
@ -7403,8 +7099,8 @@ class Compiler(
|
||||
private fun resolveClassByName(name: String): ObjClass? {
|
||||
val rec = seedScope?.get(name) ?: importManager.rootScope.get(name)
|
||||
(rec?.value as? ObjClass)?.let { return it }
|
||||
for (module in importedModules.asReversed()) {
|
||||
val imported = module.scope.get(name)
|
||||
for (scope in importedScopes.asReversed()) {
|
||||
val imported = scope.get(name)
|
||||
(imported?.value as? ObjClass)?.let { return it }
|
||||
}
|
||||
val info = compileClassInfos[name] ?: return null
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
sealed class ImportBindingSource {
|
||||
data class Module(val name: String, val pos: Pos) : ImportBindingSource()
|
||||
object Root : ImportBindingSource()
|
||||
object Seed : ImportBindingSource()
|
||||
}
|
||||
|
||||
data class ImportBinding(
|
||||
val symbol: String,
|
||||
val source: ImportBindingSource,
|
||||
)
|
||||
@ -33,8 +33,6 @@ class ModuleScope(
|
||||
|
||||
constructor(importProvider: ImportProvider, source: Source) : this(importProvider, source.startPos, source.fileName)
|
||||
|
||||
internal var importedModules: List<ModuleScope> = emptyList()
|
||||
|
||||
/**
|
||||
* Import symbols into the scope. It _is called_ after the module is imported by [ImportProvider.prepareImport]
|
||||
* which checks symbol availability and accessibility prior to execution.
|
||||
@ -94,3 +92,4 @@ class ModuleScope(
|
||||
super.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -463,24 +463,6 @@ open class Scope(
|
||||
}
|
||||
}
|
||||
|
||||
internal fun applySlotPlanReset(plan: Map<String, Int>, records: Map<String, ObjRecord>) {
|
||||
if (plan.isEmpty()) return
|
||||
slots.clear()
|
||||
nameToSlot.clear()
|
||||
val maxIndex = plan.values.maxOrNull() ?: return
|
||||
val targetSize = maxIndex + 1
|
||||
repeat(targetSize) {
|
||||
slots.add(ObjRecord(ObjUnset, isMutable = true))
|
||||
}
|
||||
for ((name, idx) in plan) {
|
||||
nameToSlot[name] = idx
|
||||
val record = records[name]
|
||||
if (record != null && record.value !== ObjUnset) {
|
||||
slots[idx] = record
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun applySlotPlanWithSnapshot(plan: Map<String, Int>): Map<String, Int?> {
|
||||
if (plan.isEmpty()) return emptyMap()
|
||||
val maxIndex = plan.values.maxOrNull() ?: return emptyMap()
|
||||
|
||||
@ -33,51 +33,13 @@ class Script(
|
||||
override val pos: Pos,
|
||||
private val statements: List<Statement> = emptyList(),
|
||||
private val moduleSlotPlan: Map<String, Int> = emptyMap(),
|
||||
private val importBindings: Map<String, ImportBinding> = emptyMap(),
|
||||
private val importedModules: List<ImportBindingSource.Module> = emptyList(),
|
||||
// private val catchReturn: Boolean = false,
|
||||
) : Statement() {
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
val isModuleScope = scope is ModuleScope
|
||||
val shouldSeedModule = isModuleScope || scope.thisObj === ObjVoid
|
||||
val moduleTarget = scope
|
||||
if (moduleSlotPlan.isNotEmpty() && shouldSeedModule) {
|
||||
val hasPlanMapping = moduleSlotPlan.keys.any { moduleTarget.getSlotIndexOf(it) != null }
|
||||
val needsReset = moduleTarget is ModuleScope ||
|
||||
moduleTarget.slotCount() == 0 ||
|
||||
moduleTarget.hasSlotPlanConflict(moduleSlotPlan) ||
|
||||
(!hasPlanMapping && moduleTarget.slotCount() > 0)
|
||||
if (needsReset) {
|
||||
val preserved = LinkedHashMap<String, ObjRecord>()
|
||||
for (name in moduleSlotPlan.keys) {
|
||||
moduleTarget.getLocalRecordDirect(name)?.let { preserved[name] = it }
|
||||
}
|
||||
moduleTarget.applySlotPlanReset(moduleSlotPlan, preserved)
|
||||
for (name in moduleSlotPlan.keys) {
|
||||
if (preserved.containsKey(name)) continue
|
||||
val inherited = findSeedRecord(moduleTarget.parent, name)
|
||||
if (inherited != null && inherited.value !== ObjUnset) {
|
||||
moduleTarget.updateSlotFor(name, inherited)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
moduleTarget.applySlotPlan(moduleSlotPlan)
|
||||
for (name in moduleSlotPlan.keys) {
|
||||
val local = moduleTarget.getLocalRecordDirect(name)
|
||||
if (local != null && local.value !== ObjUnset) {
|
||||
moduleTarget.updateSlotFor(name, local)
|
||||
continue
|
||||
}
|
||||
val inherited = findSeedRecord(moduleTarget.parent, name)
|
||||
if (inherited != null && inherited.value !== ObjUnset) {
|
||||
moduleTarget.updateSlotFor(name, inherited)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shouldSeedModule) {
|
||||
seedModuleSlots(moduleTarget)
|
||||
if (moduleSlotPlan.isNotEmpty()) {
|
||||
scope.applySlotPlan(moduleSlotPlan)
|
||||
seedModuleSlots(scope)
|
||||
}
|
||||
var lastResult: Obj = ObjVoid
|
||||
for (s in statements) {
|
||||
@ -86,50 +48,24 @@ class Script(
|
||||
return lastResult
|
||||
}
|
||||
|
||||
private suspend fun seedModuleSlots(scope: Scope) {
|
||||
if (importBindings.isEmpty() && importedModules.isEmpty()) return
|
||||
seedImportBindings(scope)
|
||||
}
|
||||
|
||||
private suspend fun seedImportBindings(scope: 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 ((name, binding) in importBindings) {
|
||||
val record = when (val source = binding.source) {
|
||||
is ImportBindingSource.Module -> {
|
||||
val module = provider.prepareImport(source.pos, source.name, null)
|
||||
importedModules.add(module)
|
||||
module.objects[binding.symbol]?.takeIf { it.visibility.isPublic }
|
||||
?: scope.raiseSymbolNotFound("symbol ${source.name}.${binding.symbol} not found")
|
||||
}
|
||||
ImportBindingSource.Root -> {
|
||||
provider.rootScope.objects[binding.symbol]?.takeIf { it.visibility.isPublic }
|
||||
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
|
||||
}
|
||||
ImportBindingSource.Seed -> {
|
||||
findSeedRecord(scope, binding.symbol)
|
||||
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
|
||||
}
|
||||
private fun seedModuleSlots(scope: Scope) {
|
||||
val parent = scope.parent ?: return
|
||||
for (name in moduleSlotPlan.keys) {
|
||||
if (scope.objects.containsKey(name)) {
|
||||
scope.updateSlotFor(name, scope.objects[name]!!)
|
||||
continue
|
||||
}
|
||||
if (name == "Exception" && record.value !is ObjClass) {
|
||||
val seed = findSeedRecord(parent, name)
|
||||
if (seed != null) {
|
||||
if (name == "Exception" && seed.value !is ObjClass) {
|
||||
scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false))
|
||||
} else {
|
||||
scope.updateSlotFor(name, seed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (name == "Exception") {
|
||||
scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false))
|
||||
} else {
|
||||
scope.updateSlotFor(name, record)
|
||||
}
|
||||
}
|
||||
for (module in importedModules) {
|
||||
for ((cls, map) in module.extensions) {
|
||||
for ((symbol, record) in map) {
|
||||
if (record.visibility.isPublic) {
|
||||
scope.addExtension(cls, symbol, record)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -149,15 +85,6 @@ class Script(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun resolveModuleScope(scope: Scope): ModuleScope? {
|
||||
var current: Scope? = scope
|
||||
while (current != null) {
|
||||
if (current is ModuleScope) return current
|
||||
current = current.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun debugStatements(): List<Statement> = statements
|
||||
|
||||
suspend fun execute() = execute(
|
||||
@ -502,7 +429,6 @@ class Script(
|
||||
// interfaces
|
||||
addConst("Iterable", ObjIterable)
|
||||
addConst("Collection", ObjCollection)
|
||||
addConst("Iterator", ObjIterator)
|
||||
addConst("Array", ObjArray)
|
||||
addConst("RingBuffer", ObjRingBuffer.type)
|
||||
addConst("Class", ObjClassType)
|
||||
|
||||
@ -99,23 +99,7 @@ class BytecodeCompiler(
|
||||
is net.sergeych.lyng.InlineBlockStatement -> compileInlineBlock(name, stmt)
|
||||
is VarDeclStatement -> compileVarDecl(name, stmt)
|
||||
is DelegatedVarDeclStatement -> {
|
||||
val value = emitDelegatedVarDecl(stmt) ?: return null
|
||||
builder.emit(Opcode.RET, value.slot)
|
||||
val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
|
||||
builder.build(
|
||||
name,
|
||||
localCount,
|
||||
addrCount = nextAddrSlot,
|
||||
returnLabels = returnLabels,
|
||||
scopeSlotIndices,
|
||||
scopeSlotNames,
|
||||
scopeSlotIsModule,
|
||||
localSlotNames,
|
||||
localSlotMutables
|
||||
)
|
||||
}
|
||||
is DestructuringVarDeclStatement -> {
|
||||
val value = emitDestructuringVarDecl(stmt) ?: return null
|
||||
val value = emitStatementEval(stmt)
|
||||
builder.emit(Opcode.RET, value.slot)
|
||||
val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
|
||||
builder.build(
|
||||
@ -211,12 +195,6 @@ class BytecodeCompiler(
|
||||
|
||||
private fun allocSlot(): Int = nextSlot++
|
||||
|
||||
private fun encodeMemberId(receiverClass: ObjClass, id: Int?): Int? {
|
||||
if (id == null) return null
|
||||
if (receiverClass == ObjClassType) return -(id + 2)
|
||||
return id
|
||||
}
|
||||
|
||||
private fun compileRef(ref: ObjRef): CompiledValue? {
|
||||
return when (ref) {
|
||||
is ConstRef -> compileConst(ref.constValue)
|
||||
@ -1626,13 +1604,13 @@ class BytecodeCompiler(
|
||||
Pos.builtIn
|
||||
)
|
||||
val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null
|
||||
val fieldId = receiverClass.instanceFieldIdMap()[target.name]
|
||||
val methodId = if (fieldId == null) {
|
||||
receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name]
|
||||
val fieldId = receiverClass.instanceFieldIdMap()[target.name] ?: -1
|
||||
val methodId = if (fieldId < 0) {
|
||||
receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name] ?: -1
|
||||
} else {
|
||||
null
|
||||
-1
|
||||
}
|
||||
if (fieldId == null && methodId == null) {
|
||||
if (fieldId < 0 && methodId < 0) {
|
||||
val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
|
||||
?: throw BytecodeCompileException(
|
||||
"Unknown member ${target.name} on ${receiverClass.className}",
|
||||
@ -1666,10 +1644,8 @@ class BytecodeCompiler(
|
||||
}
|
||||
return value
|
||||
}
|
||||
val encodedFieldId = encodeMemberId(receiverClass, fieldId) ?: -1
|
||||
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: -1
|
||||
if (!target.isOptional) {
|
||||
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, encodedFieldId, encodedMethodId, value.slot)
|
||||
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, value.slot)
|
||||
} else {
|
||||
val nullSlot = allocSlot()
|
||||
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||
@ -1680,7 +1656,7 @@ class BytecodeCompiler(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
|
||||
)
|
||||
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, encodedFieldId, encodedMethodId, value.slot)
|
||||
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, value.slot)
|
||||
builder.mark(endLabel)
|
||||
}
|
||||
return value
|
||||
@ -2056,13 +2032,11 @@ class BytecodeCompiler(
|
||||
}
|
||||
val fieldId = receiverClass.instanceFieldIdMap()[ref.name]
|
||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
||||
val encodedFieldId = encodeMemberId(receiverClass, fieldId)
|
||||
val encodedMethodId = encodeMemberId(receiverClass, methodId)
|
||||
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
|
||||
val dst = allocSlot()
|
||||
if (fieldId != null || methodId != null) {
|
||||
if (!ref.isOptional) {
|
||||
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, encodedFieldId ?: -1, encodedMethodId ?: -1, dst)
|
||||
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, dst)
|
||||
} else {
|
||||
val nullSlot = allocSlot()
|
||||
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||
@ -2074,7 +2048,7 @@ class BytecodeCompiler(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
|
||||
)
|
||||
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, encodedFieldId ?: -1, encodedMethodId ?: -1, dst)
|
||||
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, dst)
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||
builder.mark(nullLabel)
|
||||
builder.emit(Opcode.CONST_NULL, dst)
|
||||
@ -2866,12 +2840,11 @@ class BytecodeCompiler(
|
||||
val dst = allocSlot()
|
||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
||||
if (methodId != null) {
|
||||
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId
|
||||
if (!ref.isOptional) {
|
||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
setPos(callPos)
|
||||
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, encodedMethodId, args.base, encodedCount, dst)
|
||||
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
|
||||
return CompiledValue(dst, SlotType.OBJ)
|
||||
}
|
||||
val nullSlot = allocSlot()
|
||||
@ -2887,7 +2860,7 @@ class BytecodeCompiler(
|
||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
setPos(callPos)
|
||||
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, encodedMethodId, args.base, encodedCount, dst)
|
||||
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||
builder.mark(nullLabel)
|
||||
builder.emit(Opcode.CONST_NULL, dst)
|
||||
@ -3308,8 +3281,8 @@ class BytecodeCompiler(
|
||||
}
|
||||
is BlockStatement -> emitBlock(target, true)
|
||||
is VarDeclStatement -> emitVarDecl(target)
|
||||
is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
|
||||
is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target)
|
||||
is DelegatedVarDeclStatement -> emitStatementEval(target)
|
||||
is DestructuringVarDeclStatement -> emitStatementEval(target)
|
||||
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
|
||||
is net.sergeych.lyng.ClassDeclStatement -> emitStatementEval(target)
|
||||
is net.sergeych.lyng.FunctionDeclStatement -> emitStatementEval(target)
|
||||
@ -3335,7 +3308,7 @@ class BytecodeCompiler(
|
||||
}
|
||||
}
|
||||
is VarDeclStatement -> emitVarDecl(target)
|
||||
is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
|
||||
is DelegatedVarDeclStatement -> emitStatementEval(target)
|
||||
is IfStatement -> compileIfStatement(target)
|
||||
is net.sergeych.lyng.ForInStatement -> {
|
||||
val resultSlot = emitForIn(target, false) ?: return null
|
||||
@ -3356,7 +3329,7 @@ class BytecodeCompiler(
|
||||
}
|
||||
}
|
||||
is BlockStatement -> emitBlock(target, false)
|
||||
is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target)
|
||||
is DestructuringVarDeclStatement -> emitStatementEval(target)
|
||||
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
|
||||
is net.sergeych.lyng.BreakStatement -> compileBreak(target)
|
||||
is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
|
||||
@ -3603,38 +3576,6 @@ class BytecodeCompiler(
|
||||
return value
|
||||
}
|
||||
|
||||
private fun emitDelegatedVarDecl(stmt: DelegatedVarDeclStatement): CompiledValue? {
|
||||
val value = compileStatementValueOrFallback(stmt.initializer) ?: return null
|
||||
val declId = builder.addConst(
|
||||
BytecodeConst.DelegatedDecl(
|
||||
stmt.name,
|
||||
stmt.isMutable,
|
||||
stmt.visibility,
|
||||
stmt.isTransient
|
||||
)
|
||||
)
|
||||
builder.emit(Opcode.DECL_DELEGATED, declId, value.slot)
|
||||
updateSlotType(value.slot, SlotType.OBJ)
|
||||
return CompiledValue(value.slot, SlotType.OBJ)
|
||||
}
|
||||
|
||||
private fun emitDestructuringVarDecl(stmt: DestructuringVarDeclStatement): CompiledValue? {
|
||||
val value = compileStatementValueOrFallback(stmt.initializer) ?: return null
|
||||
val declId = builder.addConst(
|
||||
BytecodeConst.DestructureDecl(
|
||||
stmt.pattern,
|
||||
stmt.names,
|
||||
stmt.isMutable,
|
||||
stmt.visibility,
|
||||
stmt.isTransient,
|
||||
stmt.pos
|
||||
)
|
||||
)
|
||||
builder.emit(Opcode.DECL_DESTRUCTURE, declId, value.slot)
|
||||
updateSlotType(value.slot, SlotType.OBJ)
|
||||
return CompiledValue(value.slot, SlotType.OBJ)
|
||||
}
|
||||
|
||||
private fun updateNameObjClass(name: String, initializer: Statement?, initializerObjClass: ObjClass? = null) {
|
||||
val cls = initializerObjClass ?: objClassForInitializer(initializer)
|
||||
if (cls != null) {
|
||||
@ -4670,17 +4611,43 @@ class BytecodeCompiler(
|
||||
?: resolveReceiverClass(ref.castValueRef())
|
||||
is FieldRef -> {
|
||||
val targetClass = resolveReceiverClass(ref.target) ?: return null
|
||||
inferFieldReturnClass(targetClass, ref.name)
|
||||
if (targetClass == ObjString.type && ref.name == "re") {
|
||||
ObjRegex.type
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
is MethodCallRef -> {
|
||||
val targetClass = resolveReceiverClass(ref.receiver) ?: return null
|
||||
if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) {
|
||||
ObjRegex.type
|
||||
} else {
|
||||
inferMethodCallReturnClass(ref.name)
|
||||
when (ref.name) {
|
||||
"map",
|
||||
"mapNotNull",
|
||||
"filter",
|
||||
"filterNotNull",
|
||||
"drop",
|
||||
"take",
|
||||
"flatMap",
|
||||
"flatten",
|
||||
"sorted",
|
||||
"sortedBy",
|
||||
"sortedWith",
|
||||
"reversed",
|
||||
"toList",
|
||||
"shuffle",
|
||||
"shuffled" -> ObjList.type
|
||||
"dropLast" -> ObjFlow.type
|
||||
"takeLast" -> ObjRingBuffer.type
|
||||
"count" -> ObjInt.type
|
||||
"toSet" -> ObjSet.type
|
||||
"toMap" -> ObjMap.type
|
||||
"joinToString" -> ObjString.type
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
is CallRef -> inferCallReturnClass(ref)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@ -4727,17 +4694,39 @@ class BytecodeCompiler(
|
||||
?: resolveReceiverClassForScopeCollection(ref.castValueRef())
|
||||
is FieldRef -> {
|
||||
val targetClass = resolveReceiverClassForScopeCollection(ref.target) ?: return null
|
||||
inferFieldReturnClass(targetClass, ref.name)
|
||||
if (targetClass == ObjString.type && ref.name == "re") ObjRegex.type else null
|
||||
}
|
||||
is MethodCallRef -> {
|
||||
val targetClass = resolveReceiverClassForScopeCollection(ref.receiver) ?: return null
|
||||
if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) {
|
||||
ObjRegex.type
|
||||
} else {
|
||||
inferMethodCallReturnClass(ref.name)
|
||||
when (ref.name) {
|
||||
"map",
|
||||
"mapNotNull",
|
||||
"filter",
|
||||
"filterNotNull",
|
||||
"drop",
|
||||
"take",
|
||||
"flatMap",
|
||||
"flatten",
|
||||
"sorted",
|
||||
"sortedBy",
|
||||
"sortedWith",
|
||||
"reversed",
|
||||
"toList",
|
||||
"shuffle",
|
||||
"shuffled" -> ObjList.type
|
||||
"dropLast" -> ObjFlow.type
|
||||
"takeLast" -> ObjRingBuffer.type
|
||||
"count" -> ObjInt.type
|
||||
"toSet" -> ObjSet.type
|
||||
"toMap" -> ObjMap.type
|
||||
"joinToString" -> ObjString.type
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
is CallRef -> inferCallReturnClass(ref)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@ -4776,154 +4765,12 @@ class BytecodeCompiler(
|
||||
"Regex" -> ObjRegex.type
|
||||
"RegexMatch" -> ObjRegexMatch.type
|
||||
"MapEntry" -> ObjMapEntry.type
|
||||
"Instant" -> ObjInstant.type
|
||||
"DateTime" -> ObjDateTime.type
|
||||
"Duration" -> ObjDuration.type
|
||||
"Exception" -> ObjException.Root
|
||||
"Class" -> ObjClassType
|
||||
"Callable" -> Statement.type
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun inferCallReturnClass(ref: CallRef): ObjClass? {
|
||||
return when (val target = ref.target) {
|
||||
is LocalSlotRef -> nameObjClass[target.name] ?: resolveTypeNameClass(target.name)
|
||||
is LocalVarRef -> nameObjClass[target.name] ?: resolveTypeNameClass(target.name)
|
||||
is ConstRef -> target.constValue as? ObjClass
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun inferMethodCallReturnClass(name: String): ObjClass? = when (name) {
|
||||
"map",
|
||||
"mapNotNull",
|
||||
"filter",
|
||||
"filterNotNull",
|
||||
"drop",
|
||||
"take",
|
||||
"flatMap",
|
||||
"flatten",
|
||||
"sorted",
|
||||
"sortedBy",
|
||||
"sortedWith",
|
||||
"reversed",
|
||||
"toList",
|
||||
"shuffle",
|
||||
"shuffled" -> ObjList.type
|
||||
"dropLast" -> ObjFlow.type
|
||||
"takeLast" -> ObjRingBuffer.type
|
||||
"iterator" -> ObjIterator
|
||||
"count" -> ObjInt.type
|
||||
"toSet" -> ObjSet.type
|
||||
"toMap" -> ObjMap.type
|
||||
"joinToString" -> ObjString.type
|
||||
"now",
|
||||
"truncateToSecond",
|
||||
"truncateToMinute",
|
||||
"truncateToMillisecond" -> ObjInstant.type
|
||||
"toDateTime",
|
||||
"toTimeZone",
|
||||
"toUTC",
|
||||
"parseRFC3339",
|
||||
"addYears",
|
||||
"addMonths",
|
||||
"addDays",
|
||||
"addHours",
|
||||
"addMinutes",
|
||||
"addSeconds" -> ObjDateTime.type
|
||||
"toInstant" -> ObjInstant.type
|
||||
"toRFC3339",
|
||||
"toSortableString",
|
||||
"toJsonString",
|
||||
"decodeUtf8",
|
||||
"toDump",
|
||||
"toString" -> ObjString.type
|
||||
"startsWith",
|
||||
"matches" -> ObjBool.type
|
||||
"toInt",
|
||||
"toEpochSeconds" -> ObjInt.type
|
||||
"toMutable" -> ObjMutableBuffer.type
|
||||
"seq" -> ObjFlow.type
|
||||
"encode" -> ObjBitBuffer.type
|
||||
"assertThrows" -> ObjException.Root
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun inferFieldReturnClass(targetClass: ObjClass?, name: String): ObjClass? {
|
||||
if (targetClass == null) return null
|
||||
if (targetClass == ObjDynamic.type) return ObjDynamic.type
|
||||
if (targetClass == ObjInstant.type && (name == "distantFuture" || name == "distantPast")) {
|
||||
return ObjInstant.type
|
||||
}
|
||||
if (targetClass == ObjString.type && name == "re") {
|
||||
return ObjRegex.type
|
||||
}
|
||||
if (targetClass == ObjInt.type || targetClass == ObjReal.type) {
|
||||
return when (name) {
|
||||
"day",
|
||||
"days",
|
||||
"hour",
|
||||
"hours",
|
||||
"minute",
|
||||
"minutes",
|
||||
"second",
|
||||
"seconds",
|
||||
"millisecond",
|
||||
"milliseconds",
|
||||
"microsecond",
|
||||
"microseconds" -> ObjDuration.type
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
if (targetClass == ObjDuration.type) {
|
||||
return when (name) {
|
||||
"days",
|
||||
"hours",
|
||||
"minutes",
|
||||
"seconds",
|
||||
"milliseconds",
|
||||
"microseconds" -> ObjReal.type
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
if (targetClass == ObjInstant.type) {
|
||||
return when (name) {
|
||||
"epochSeconds",
|
||||
"epochWholeSeconds" -> ObjInt.type
|
||||
"truncateToSecond",
|
||||
"truncateToMinute",
|
||||
"truncateToMillisecond" -> ObjInstant.type
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
if (targetClass == ObjDateTime.type) {
|
||||
return when (name) {
|
||||
"year",
|
||||
"month",
|
||||
"day",
|
||||
"hour",
|
||||
"minute",
|
||||
"second",
|
||||
"dayOfWeek",
|
||||
"nanosecond" -> ObjInt.type
|
||||
"timeZone" -> ObjString.type
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
if (targetClass == ObjException.Root || targetClass.allParentsSet.contains(ObjException.Root)) {
|
||||
return when (name) {
|
||||
"message" -> ObjString.type
|
||||
"stackTrace" -> ObjList.type
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
if (targetClass == ObjRegex.type && name == "pattern") {
|
||||
return ObjString.type
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun queueExtensionCallableNames(receiverClass: ObjClass, memberName: String) {
|
||||
for (cls in receiverClass.mro) {
|
||||
val name = extensionCallableName(cls.className, memberName)
|
||||
|
||||
@ -18,7 +18,6 @@ package net.sergeych.lyng.bytecode
|
||||
|
||||
import net.sergeych.lyng.Pos
|
||||
import net.sergeych.lyng.Visibility
|
||||
import net.sergeych.lyng.obj.ListLiteralRef
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjProperty
|
||||
|
||||
@ -47,20 +46,6 @@ sealed class BytecodeConst {
|
||||
val visibility: Visibility,
|
||||
val isTransient: Boolean,
|
||||
) : BytecodeConst()
|
||||
data class DelegatedDecl(
|
||||
val name: String,
|
||||
val isMutable: Boolean,
|
||||
val visibility: Visibility,
|
||||
val isTransient: Boolean,
|
||||
) : BytecodeConst()
|
||||
data class DestructureDecl(
|
||||
val pattern: ListLiteralRef,
|
||||
val names: List<String>,
|
||||
val isMutable: Boolean,
|
||||
val visibility: Visibility,
|
||||
val isTransient: Boolean,
|
||||
val pos: Pos,
|
||||
) : BytecodeConst()
|
||||
data class CallArgsPlan(val tailBlock: Boolean, val specs: List<CallArgSpec>) : BytecodeConst()
|
||||
data class CallArgSpec(val name: String?, val isSplat: Boolean)
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ class CmdBuilder {
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||
listOf(OperandKind.CONST)
|
||||
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE ->
|
||||
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY ->
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
||||
Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,
|
||||
@ -377,8 +377,6 @@ class CmdBuilder {
|
||||
Opcode.PUSH_SLOT_PLAN -> CmdPushSlotPlan(operands[0])
|
||||
Opcode.POP_SLOT_PLAN -> CmdPopSlotPlan()
|
||||
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
|
||||
Opcode.DECL_DELEGATED -> CmdDeclDelegated(operands[0], operands[1])
|
||||
Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1])
|
||||
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
|
||||
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
|
||||
Opcode.CALL_MEMBER_SLOT -> CmdCallMemberSlot(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||
|
||||
@ -185,8 +185,6 @@ object CmdDisassembler {
|
||||
is CmdPushSlotPlan -> Opcode.PUSH_SLOT_PLAN to intArrayOf(cmd.planId)
|
||||
is CmdPopSlotPlan -> Opcode.POP_SLOT_PLAN to intArrayOf()
|
||||
is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot)
|
||||
is CmdDeclDelegated -> Opcode.DECL_DELEGATED to intArrayOf(cmd.constId, cmd.slot)
|
||||
is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
||||
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
|
||||
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
||||
is CmdCallMemberSlot -> Opcode.CALL_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
|
||||
@ -242,7 +240,7 @@ object CmdDisassembler {
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||
listOf(OperandKind.CONST)
|
||||
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE ->
|
||||
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY ->
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
||||
Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,
|
||||
|
||||
@ -1187,59 +1187,6 @@ class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
|
||||
}
|
||||
}
|
||||
|
||||
class CmdDeclDelegated(internal val constId: Int, internal val slot: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val decl = frame.fn.constants[constId] as? BytecodeConst.DelegatedDecl
|
||||
?: error("DECL_DELEGATED expects DelegatedDecl at $constId")
|
||||
val initValue = frame.slotToObj(slot)
|
||||
val accessType = ObjString(if (decl.isMutable) "Var" else "Val")
|
||||
val finalDelegate = try {
|
||||
initValue.invokeInstanceMethod(
|
||||
frame.ensureScope(),
|
||||
"bind",
|
||||
Arguments(ObjString(decl.name), accessType, ObjNull)
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
initValue
|
||||
}
|
||||
val rec = frame.ensureScope().addItem(
|
||||
decl.name,
|
||||
decl.isMutable,
|
||||
ObjNull,
|
||||
decl.visibility,
|
||||
recordType = ObjRecord.Type.Delegated,
|
||||
isTransient = decl.isTransient
|
||||
)
|
||||
rec.delegate = finalDelegate
|
||||
frame.storeObjResult(slot, finalDelegate)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdDeclDestructure(internal val constId: Int, internal val slot: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val decl = frame.fn.constants[constId] as? BytecodeConst.DestructureDecl
|
||||
?: error("DECL_DESTRUCTURE expects DestructureDecl at $constId")
|
||||
val value = frame.slotToObj(slot)
|
||||
val scope = frame.ensureScope()
|
||||
for (name in decl.names) {
|
||||
scope.addItem(name, true, ObjVoid, decl.visibility, isTransient = decl.isTransient)
|
||||
}
|
||||
decl.pattern.setAt(decl.pos, scope, value)
|
||||
if (!decl.isMutable) {
|
||||
for (name in decl.names) {
|
||||
val rec = scope.objects[name] ?: continue
|
||||
val immutableRec = rec.copy(isMutable = false)
|
||||
scope.objects[name] = immutableRec
|
||||
scope.localBindings[name] = immutableRec
|
||||
scope.updateSlotFor(name, immutableRec)
|
||||
}
|
||||
}
|
||||
frame.storeObjResult(slot, ObjVoid)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val decl = frame.fn.constants[constId] as? BytecodeConst.ExtensionPropertyDecl
|
||||
@ -1321,8 +1268,7 @@ class CmdCallSlot(
|
||||
frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
|
||||
} else {
|
||||
// Pooling for Statement-based callables (lambdas) can still alter closure semantics; keep safe path for now.
|
||||
val scope = frame.ensureScope()
|
||||
callee.callOn(scope.createChildScope(scope.pos, args = args))
|
||||
callee.callOn(frame.ensureScope().createChildScope(frame.ensureScope().pos, args = args))
|
||||
}
|
||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||
frame.syncScopeToFrame()
|
||||
@ -1361,14 +1307,6 @@ class CmdListLiteral(
|
||||
}
|
||||
}
|
||||
|
||||
private fun decodeMemberId(id: Int): Pair<Int, Boolean> {
|
||||
return if (id <= -2) {
|
||||
Pair(-id - 2, true)
|
||||
} else {
|
||||
Pair(id, false)
|
||||
}
|
||||
}
|
||||
|
||||
class CmdGetMemberSlot(
|
||||
internal val recvSlot: Int,
|
||||
internal val fieldId: Int,
|
||||
@ -1378,42 +1316,22 @@ class CmdGetMemberSlot(
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val receiver = frame.slotToObj(recvSlot)
|
||||
val inst = receiver as? ObjInstance
|
||||
val cls = receiver as? ObjClass
|
||||
val (fieldIdResolved, fieldOnObjClass) = decodeMemberId(fieldId)
|
||||
val (methodIdResolved, methodOnObjClass) = decodeMemberId(methodId)
|
||||
val fieldRec = if (fieldIdResolved >= 0) {
|
||||
when {
|
||||
inst != null -> inst.fieldRecordForId(fieldIdResolved) ?: inst.objClass.fieldRecordForId(fieldIdResolved)
|
||||
cls != null && fieldOnObjClass -> cls.objClass.fieldRecordForId(fieldIdResolved)
|
||||
cls != null -> cls.fieldRecordForId(fieldIdResolved)
|
||||
else -> receiver.objClass.fieldRecordForId(fieldIdResolved)
|
||||
}
|
||||
val fieldRec = if (fieldId >= 0) {
|
||||
inst?.fieldRecordForId(fieldId) ?: receiver.objClass.fieldRecordForId(fieldId)
|
||||
} else null
|
||||
val rec = fieldRec ?: run {
|
||||
if (methodIdResolved >= 0) {
|
||||
when {
|
||||
inst != null -> inst.methodRecordForId(methodIdResolved) ?: inst.objClass.methodRecordForId(methodIdResolved)
|
||||
cls != null && methodOnObjClass -> cls.objClass.methodRecordForId(methodIdResolved)
|
||||
cls != null -> cls.methodRecordForId(methodIdResolved)
|
||||
else -> receiver.objClass.methodRecordForId(methodIdResolved)
|
||||
}
|
||||
if (methodId >= 0) {
|
||||
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
|
||||
} else null
|
||||
} ?: frame.ensureScope().raiseSymbolNotFound("member")
|
||||
val name = rec.memberName ?: "<member>"
|
||||
suspend fun autoCallIfMethod(resolved: ObjRecord, recv: Obj): Obj {
|
||||
return if (resolved.type == ObjRecord.Type.Fun && !resolved.isAbstract) {
|
||||
resolved.value.invoke(frame.ensureScope(), resolved.receiver ?: recv, Arguments.EMPTY, resolved.declaringClass)
|
||||
} else {
|
||||
resolved.value
|
||||
}
|
||||
}
|
||||
if (receiver is ObjQualifiedView) {
|
||||
val resolved = receiver.readField(frame.ensureScope(), name)
|
||||
frame.storeObjResult(dst, autoCallIfMethod(resolved, receiver))
|
||||
frame.storeObjResult(dst, resolved.value)
|
||||
return
|
||||
}
|
||||
val resolved = receiver.resolveRecord(frame.ensureScope(), rec, name, rec.declaringClass)
|
||||
frame.storeObjResult(dst, autoCallIfMethod(resolved, receiver))
|
||||
frame.storeObjResult(dst, resolved.value)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -1427,25 +1345,12 @@ class CmdSetMemberSlot(
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val receiver = frame.slotToObj(recvSlot)
|
||||
val inst = receiver as? ObjInstance
|
||||
val cls = receiver as? ObjClass
|
||||
val (fieldIdResolved, fieldOnObjClass) = decodeMemberId(fieldId)
|
||||
val (methodIdResolved, methodOnObjClass) = decodeMemberId(methodId)
|
||||
val fieldRec = if (fieldIdResolved >= 0) {
|
||||
when {
|
||||
inst != null -> inst.fieldRecordForId(fieldIdResolved) ?: inst.objClass.fieldRecordForId(fieldIdResolved)
|
||||
cls != null && fieldOnObjClass -> cls.objClass.fieldRecordForId(fieldIdResolved)
|
||||
cls != null -> cls.fieldRecordForId(fieldIdResolved)
|
||||
else -> receiver.objClass.fieldRecordForId(fieldIdResolved)
|
||||
}
|
||||
val fieldRec = if (fieldId >= 0) {
|
||||
inst?.fieldRecordForId(fieldId) ?: receiver.objClass.fieldRecordForId(fieldId)
|
||||
} else null
|
||||
val rec = fieldRec ?: run {
|
||||
if (methodIdResolved >= 0) {
|
||||
when {
|
||||
inst != null -> inst.methodRecordForId(methodIdResolved) ?: inst.objClass.methodRecordForId(methodIdResolved)
|
||||
cls != null && methodOnObjClass -> cls.objClass.methodRecordForId(methodIdResolved)
|
||||
cls != null -> cls.methodRecordForId(methodIdResolved)
|
||||
else -> receiver.objClass.methodRecordForId(methodIdResolved)
|
||||
}
|
||||
if (methodId >= 0) {
|
||||
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
|
||||
} else null
|
||||
} ?: frame.ensureScope().raiseSymbolNotFound("member")
|
||||
val name = rec.memberName ?: "<member>"
|
||||
@ -1471,14 +1376,8 @@ class CmdCallMemberSlot(
|
||||
}
|
||||
val receiver = frame.slotToObj(recvSlot)
|
||||
val inst = receiver as? ObjInstance
|
||||
val cls = receiver as? ObjClass
|
||||
val (methodIdResolved, methodOnObjClass) = decodeMemberId(methodId)
|
||||
val rec = inst?.methodRecordForId(methodIdResolved)
|
||||
?: when {
|
||||
cls != null && methodOnObjClass -> cls.objClass.methodRecordForId(methodIdResolved)
|
||||
cls != null -> cls.methodRecordForId(methodIdResolved)
|
||||
else -> receiver.objClass.methodRecordForId(methodIdResolved)
|
||||
}
|
||||
val rec = inst?.methodRecordForId(methodId)
|
||||
?: receiver.objClass.methodRecordForId(methodId)
|
||||
?: frame.ensureScope().raiseError("member id $methodId not found on ${receiver.objClass.className}")
|
||||
val callArgs = frame.buildArguments(argBase, argCount)
|
||||
val name = rec.memberName ?: "<member>"
|
||||
@ -1653,51 +1552,15 @@ class CmdFrame(
|
||||
}
|
||||
|
||||
private fun resolveModuleScope(scope: Scope): Scope {
|
||||
val moduleSlotName = fn.scopeSlotNames.indices
|
||||
.firstOrNull { fn.scopeSlotIsModule.getOrNull(it) == true }
|
||||
?.let { fn.scopeSlotNames[it] }
|
||||
if (moduleSlotName != null) {
|
||||
findScopeWithSlot(scope, moduleSlotName)?.let { return it }
|
||||
}
|
||||
findModuleScope(scope)?.let { return it }
|
||||
return scope
|
||||
}
|
||||
|
||||
private fun findScopeWithSlot(scope: Scope, slotName: String): Scope? {
|
||||
val visited = HashSet<Scope>(16)
|
||||
val queue = ArrayDeque<Scope>()
|
||||
queue.add(scope)
|
||||
while (queue.isNotEmpty()) {
|
||||
val current = queue.removeFirst()
|
||||
if (!visited.add(current)) continue
|
||||
if (current.getSlotIndexOf(slotName) != null) return current
|
||||
current.parent?.let { queue.add(it) }
|
||||
if (current is ClosureScope) {
|
||||
queue.add(current.closureScope)
|
||||
} else if (current is ApplyScope) {
|
||||
queue.add(current.applied)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findModuleScope(scope: Scope): Scope? {
|
||||
val visited = HashSet<Scope>(16)
|
||||
val queue = ArrayDeque<Scope>()
|
||||
queue.add(scope)
|
||||
while (queue.isNotEmpty()) {
|
||||
val current = queue.removeFirst()
|
||||
if (!visited.add(current)) continue
|
||||
var current: Scope? = scope
|
||||
var last: Scope = scope
|
||||
while (current != null) {
|
||||
if (current is ModuleScope) return current
|
||||
if (current.parent is ModuleScope) return current
|
||||
current.parent?.let { queue.add(it) }
|
||||
if (current is ClosureScope) {
|
||||
queue.add(current.closureScope)
|
||||
} else if (current is ApplyScope) {
|
||||
queue.add(current.applied)
|
||||
}
|
||||
last = current
|
||||
current = current.parent
|
||||
}
|
||||
return null
|
||||
return last
|
||||
}
|
||||
|
||||
fun ensureScope(): Scope {
|
||||
@ -1821,7 +1684,7 @@ class CmdFrame(
|
||||
scopeDepth -= 1
|
||||
}
|
||||
|
||||
suspend fun getObj(slot: Int): Obj {
|
||||
fun getObj(slot: Int): Obj {
|
||||
return if (slot < fn.scopeSlotCount) {
|
||||
getScopeSlotValue(slot)
|
||||
} else {
|
||||
@ -1839,7 +1702,7 @@ class CmdFrame(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getInt(slot: Int): Long {
|
||||
fun getInt(slot: Int): Long {
|
||||
return if (slot < fn.scopeSlotCount) {
|
||||
getScopeSlotValue(slot).toLong()
|
||||
} else {
|
||||
@ -1870,7 +1733,7 @@ class CmdFrame(
|
||||
frame.setInt(local, value)
|
||||
}
|
||||
|
||||
suspend fun getReal(slot: Int): Double {
|
||||
fun getReal(slot: Int): Double {
|
||||
return if (slot < fn.scopeSlotCount) {
|
||||
getScopeSlotValue(slot).toDouble()
|
||||
} else {
|
||||
@ -1895,7 +1758,7 @@ class CmdFrame(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getBool(slot: Int): Boolean {
|
||||
fun getBool(slot: Int): Boolean {
|
||||
return if (slot < fn.scopeSlotCount) {
|
||||
getScopeSlotValue(slot).toBool()
|
||||
} else {
|
||||
@ -1934,7 +1797,7 @@ class CmdFrame(
|
||||
addrScopeSlots[addrSlot] = scopeSlot
|
||||
}
|
||||
|
||||
suspend fun getAddrObj(addrSlot: Int): Obj {
|
||||
fun getAddrObj(addrSlot: Int): Obj {
|
||||
return getScopeSlotValueAtAddr(addrSlot)
|
||||
}
|
||||
|
||||
@ -1942,7 +1805,7 @@ class CmdFrame(
|
||||
setScopeSlotValueAtAddr(addrSlot, value)
|
||||
}
|
||||
|
||||
suspend fun getAddrInt(addrSlot: Int): Long {
|
||||
fun getAddrInt(addrSlot: Int): Long {
|
||||
return getScopeSlotValueAtAddr(addrSlot).toLong()
|
||||
}
|
||||
|
||||
@ -1950,7 +1813,7 @@ class CmdFrame(
|
||||
setScopeSlotValueAtAddr(addrSlot, ObjInt.of(value))
|
||||
}
|
||||
|
||||
suspend fun getAddrReal(addrSlot: Int): Double {
|
||||
fun getAddrReal(addrSlot: Int): Double {
|
||||
return getScopeSlotValueAtAddr(addrSlot).toDouble()
|
||||
}
|
||||
|
||||
@ -1958,7 +1821,7 @@ class CmdFrame(
|
||||
setScopeSlotValueAtAddr(addrSlot, ObjReal.of(value))
|
||||
}
|
||||
|
||||
suspend fun getAddrBool(addrSlot: Int): Boolean {
|
||||
fun getAddrBool(addrSlot: Int): Boolean {
|
||||
return getScopeSlotValueAtAddr(addrSlot).toBool()
|
||||
}
|
||||
|
||||
@ -1966,18 +1829,11 @@ class CmdFrame(
|
||||
setScopeSlotValueAtAddr(addrSlot, if (value) ObjTrue else ObjFalse)
|
||||
}
|
||||
|
||||
suspend fun slotToObj(slot: Int): Obj {
|
||||
fun slotToObj(slot: Int): Obj {
|
||||
if (slot < fn.scopeSlotCount) {
|
||||
return getScopeSlotValue(slot)
|
||||
}
|
||||
val local = slot - fn.scopeSlotCount
|
||||
val localName = fn.localSlotNames.getOrNull(local)
|
||||
if (localName != null) {
|
||||
val rec = scope.getLocalRecordDirect(localName) ?: scope.localBindings[localName]
|
||||
if (rec != null && (rec.type == ObjRecord.Type.Delegated || rec.type == ObjRecord.Type.Property || rec.value is ObjProperty)) {
|
||||
return scope.resolve(rec, localName)
|
||||
}
|
||||
}
|
||||
return when (frame.getSlotTypeCode(local)) {
|
||||
SlotType.INT.code -> ObjInt.of(frame.getInt(local))
|
||||
SlotType.REAL.code -> ObjReal.of(frame.getReal(local))
|
||||
@ -2158,20 +2014,14 @@ class CmdFrame(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getScopeSlotValue(slot: Int): Obj {
|
||||
private fun getScopeSlotValue(slot: Int): Obj {
|
||||
val target = scopeTarget(slot)
|
||||
val index = ensureScopeSlot(target, slot)
|
||||
val record = target.getSlotRecord(index)
|
||||
val direct = record.value
|
||||
if (direct is FrameSlotRef) return direct.read()
|
||||
val name = fn.scopeSlotNames[slot]
|
||||
if (direct !== ObjUnset) {
|
||||
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
|
||||
return target.resolve(record, name)
|
||||
}
|
||||
return direct
|
||||
}
|
||||
if (name == null) return record.value
|
||||
if (direct !== ObjUnset) return direct
|
||||
val name = fn.scopeSlotNames[slot] ?: return record.value
|
||||
val resolved = target.get(name) ?: return record.value
|
||||
if (resolved.value !== ObjUnset) {
|
||||
target.updateSlotFor(name, resolved)
|
||||
@ -2179,21 +2029,15 @@ class CmdFrame(
|
||||
return resolved.value
|
||||
}
|
||||
|
||||
private suspend fun getScopeSlotValueAtAddr(addrSlot: Int): Obj {
|
||||
private fun getScopeSlotValueAtAddr(addrSlot: Int): Obj {
|
||||
val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved")
|
||||
val index = addrIndices[addrSlot]
|
||||
val record = target.getSlotRecord(index)
|
||||
val direct = record.value
|
||||
if (direct is FrameSlotRef) return direct.read()
|
||||
if (direct !== ObjUnset) return direct
|
||||
val slotId = addrScopeSlots[addrSlot]
|
||||
val name = fn.scopeSlotNames.getOrNull(slotId)
|
||||
if (direct !== ObjUnset) {
|
||||
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
|
||||
return target.resolve(record, name)
|
||||
}
|
||||
return direct
|
||||
}
|
||||
if (name == null) return record.value
|
||||
val name = fn.scopeSlotNames[slotId] ?: return record.value
|
||||
val resolved = target.get(name) ?: return record.value
|
||||
if (resolved.value !== ObjUnset) {
|
||||
target.updateSlotFor(name, resolved)
|
||||
|
||||
@ -125,8 +125,6 @@ enum class Opcode(val code: Int) {
|
||||
POP_SLOT_PLAN(0x88),
|
||||
DECL_LOCAL(0x89),
|
||||
DECL_EXT_PROPERTY(0x8A),
|
||||
DECL_DELEGATED(0x8B),
|
||||
DECL_DESTRUCTURE(0x8C),
|
||||
|
||||
CALL_DIRECT(0x90),
|
||||
CALL_MEMBER_SLOT(0x92),
|
||||
|
||||
@ -65,8 +65,7 @@ open class Obj {
|
||||
fun isInstanceOf(someClass: Obj) = someClass === objClass ||
|
||||
objClass.allParentsSet.contains(someClass) ||
|
||||
someClass == rootObjectType ||
|
||||
(someClass is ObjClass && (objClass.allImplementingNames.contains(someClass.className) ||
|
||||
objClass.className == someClass.className))
|
||||
(someClass is ObjClass && objClass.allImplementingNames.contains(someClass.className))
|
||||
|
||||
fun isInstanceOf(className: String) =
|
||||
objClass.mro.any { it.className == className } ||
|
||||
@ -142,15 +141,6 @@ open class Obj {
|
||||
}
|
||||
}
|
||||
}
|
||||
scope.findExtension(objClass, name)?.let { ext ->
|
||||
if (ext.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) {
|
||||
return (ext.value as ObjProperty).callGetter(scope, this, ext.declaringClass)
|
||||
}
|
||||
} else if (ext.type != ObjRecord.Type.Delegated) {
|
||||
return ext.value.invoke(scope, this, args, ext.declaringClass)
|
||||
}
|
||||
}
|
||||
|
||||
return onNotFoundResult?.invoke()
|
||||
?: scope.raiseError(
|
||||
@ -482,14 +472,6 @@ open class Obj {
|
||||
}
|
||||
}
|
||||
}
|
||||
scope.findExtension(objClass, name)?.let { ext ->
|
||||
return if (ext.type == ObjRecord.Type.Property) {
|
||||
val prop = ext.value as ObjProperty
|
||||
ObjRecord(prop.callGetter(scope, this, ext.declaringClass), isMutable = false)
|
||||
} else {
|
||||
ext.copy(value = ext.value.invoke(scope, this, Arguments.EMPTY, ext.declaringClass))
|
||||
}
|
||||
}
|
||||
|
||||
scope.raiseError(
|
||||
"no such field: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}"
|
||||
@ -504,7 +486,6 @@ open class Obj {
|
||||
if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") {
|
||||
val wrapper = object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
override suspend fun execute(s: Scope): Obj {
|
||||
val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj
|
||||
val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray()
|
||||
@ -606,19 +587,16 @@ open class Obj {
|
||||
scope.raiseNotImplemented()
|
||||
}
|
||||
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj {
|
||||
val usePool = PerfFlags.SCOPE_POOL && this !is Statement
|
||||
return if (usePool) {
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj =
|
||||
if (PerfFlags.SCOPE_POOL)
|
||||
scope.withChildFrame(args, newThisObj = thisObj) { child ->
|
||||
if (declaringClass != null) child.currentClassCtx = declaringClass
|
||||
callOn(child)
|
||||
}
|
||||
} else {
|
||||
else
|
||||
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
|
||||
if (declaringClass != null) it.currentClassCtx = declaringClass
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
||||
callOn(
|
||||
|
||||
@ -92,48 +92,34 @@ open class ObjException(
|
||||
|
||||
|
||||
companion object {
|
||||
private var stackTraceCaptureDepth = 0
|
||||
|
||||
suspend fun captureStackTrace(scope: Scope): ObjList {
|
||||
val result = ObjList()
|
||||
val nestedCapture = stackTraceCaptureDepth > 0
|
||||
stackTraceCaptureDepth += 1
|
||||
val maybeCls = if (nestedCapture) null else scope.get("StackTraceEntry")?.value as? ObjClass
|
||||
val maybeCls = scope.get("StackTraceEntry")?.value as? ObjClass
|
||||
var s: Scope? = scope
|
||||
var lastPos: Pos? = null
|
||||
try {
|
||||
while (s != null) {
|
||||
val pos = s.pos
|
||||
if (pos != lastPos && !pos.currentLine.isEmpty()) {
|
||||
if (lastPos == null || (lastPos.source != pos.source || lastPos.line != pos.line)) {
|
||||
val fallback =
|
||||
ObjString("#${pos.source.objSourceName}:${pos.line+1}:${pos.column+1}: ${pos.currentLine}")
|
||||
if (maybeCls != null) {
|
||||
try {
|
||||
result.list += maybeCls.callWithArgs(
|
||||
scope,
|
||||
pos.source.objSourceName,
|
||||
ObjInt(pos.line.toLong()),
|
||||
ObjInt(pos.column.toLong()),
|
||||
ObjString(pos.currentLine)
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
// Fallback textual entry if StackTraceEntry fails to instantiate
|
||||
result.list += fallback
|
||||
}
|
||||
} else {
|
||||
// Fallback textual entry if StackTraceEntry class is not available in this scope
|
||||
result.list += fallback
|
||||
}
|
||||
lastPos = pos
|
||||
while (s != null) {
|
||||
val pos = s.pos
|
||||
if (pos != lastPos && !pos.currentLine.isEmpty()) {
|
||||
if( (lastPos == null || (lastPos.source != pos.source || lastPos.line != pos.line)) ) {
|
||||
if (maybeCls != null) {
|
||||
result.list += maybeCls.callWithArgs(
|
||||
scope,
|
||||
pos.source.objSourceName,
|
||||
ObjInt(pos.line.toLong()),
|
||||
ObjInt(pos.column.toLong()),
|
||||
ObjString(pos.currentLine)
|
||||
)
|
||||
} else {
|
||||
// Fallback textual entry if StackTraceEntry class is not available in this scope
|
||||
result.list += ObjString("#${pos.source.objSourceName}:${pos.line+1}:${pos.column+1}: ${pos.currentLine}")
|
||||
}
|
||||
lastPos = pos
|
||||
}
|
||||
s = s.parent
|
||||
}
|
||||
return result
|
||||
} finally {
|
||||
stackTraceCaptureDepth -= 1
|
||||
s = s.parent
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
|
||||
|
||||
@ -174,7 +174,6 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") {
|
||||
val wrapper = object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
override suspend fun execute(s: Scope): Obj {
|
||||
val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj
|
||||
val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray()
|
||||
@ -365,16 +364,6 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
|
||||
// Fast path for public members when outside any class context
|
||||
if (caller == null) {
|
||||
objClass.members[name]?.let { rec ->
|
||||
if (rec.visibility == Visibility.Public && !rec.isAbstract) {
|
||||
val decl = rec.declaringClass
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(instanceScope, this, args, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
objClass.publicMemberResolution[name]?.let { key ->
|
||||
methodRecordForKey(key)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Public && !rec.isAbstract) {
|
||||
|
||||
@ -286,3 +286,5 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -445,64 +445,38 @@ class CastRef(
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val v0 = valueRef.evalValue(scope)
|
||||
val t = typeRef.evalValue(scope)
|
||||
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${t} is not the class instance")
|
||||
// unwrap qualified views
|
||||
val v = when (v0) {
|
||||
is ObjQualifiedView -> v0.instance
|
||||
else -> v0
|
||||
}
|
||||
return when (t) {
|
||||
is ObjClass -> {
|
||||
if (v.isInstanceOf(t)) {
|
||||
// For instances, return a qualified view to enforce ancestor-start dispatch
|
||||
if (v is ObjInstance) ObjQualifiedView(v, t).asReadonly else v.asReadonly
|
||||
} else {
|
||||
if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError(
|
||||
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${t.className}"
|
||||
)
|
||||
}
|
||||
}
|
||||
is ObjTypeExpr -> {
|
||||
if (matchesTypeDecl(scope, v, t.typeDecl)) {
|
||||
v.asReadonly
|
||||
} else {
|
||||
if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError(
|
||||
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${t.typeDecl}"
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> scope.raiseClassCastError("${t} is not the class instance")
|
||||
return if (v.isInstanceOf(target)) {
|
||||
// For instances, return a qualified view to enforce ancestor-start dispatch
|
||||
if (v is ObjInstance) ObjQualifiedView(v, target).asReadonly else v.asReadonly
|
||||
} else {
|
||||
if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError(
|
||||
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${target.className}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun evalValue(scope: Scope): Obj {
|
||||
val v0 = valueRef.evalValue(scope)
|
||||
val t = typeRef.evalValue(scope)
|
||||
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${t} is not the class instance")
|
||||
// unwrap qualified views
|
||||
val v = when (v0) {
|
||||
is ObjQualifiedView -> v0.instance
|
||||
else -> v0
|
||||
}
|
||||
return when (t) {
|
||||
is ObjClass -> {
|
||||
if (v.isInstanceOf(t)) {
|
||||
// For instances, return a qualified view to enforce ancestor-start dispatch
|
||||
if (v is ObjInstance) ObjQualifiedView(v, t) else v
|
||||
} else {
|
||||
if (isNullable) ObjNull else scope.raiseClassCastError(
|
||||
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${t.className}"
|
||||
)
|
||||
}
|
||||
}
|
||||
is ObjTypeExpr -> {
|
||||
if (matchesTypeDecl(scope, v, t.typeDecl)) {
|
||||
v
|
||||
} else {
|
||||
if (isNullable) ObjNull else scope.raiseClassCastError(
|
||||
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${t.typeDecl}"
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> scope.raiseClassCastError("${t} is not the class instance")
|
||||
return if (v.isInstanceOf(target)) {
|
||||
// For instances, return a qualified view to enforce ancestor-start dispatch
|
||||
if (v is ObjInstance) ObjQualifiedView(v, target) else v
|
||||
} else {
|
||||
if (isNullable) ObjNull else scope.raiseClassCastError(
|
||||
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${target.className}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1201,10 +1175,6 @@ class FieldRef(
|
||||
if (rec.receiver != null && rec.declaringClass != null) {
|
||||
return rec.receiver!!.resolveRecord(scope, rec, name, rec.declaringClass).value
|
||||
}
|
||||
if (rec.type == ObjRecord.Type.Fun && !rec.isAbstract) {
|
||||
val receiver = rec.receiver ?: base
|
||||
return rec.value.invoke(scope, receiver, Arguments.EMPTY, rec.declaringClass)
|
||||
}
|
||||
return rec.value
|
||||
}
|
||||
|
||||
@ -2330,11 +2300,6 @@ class LocalSlotRef(
|
||||
scope.raiseError("slot index out of range for $name")
|
||||
}
|
||||
val rec = owner.getSlotRecord(slotIndex)
|
||||
val direct = owner.getLocalRecordDirect(name)
|
||||
if (direct != null && direct !== rec) {
|
||||
owner.updateSlotFor(name, direct)
|
||||
return direct
|
||||
}
|
||||
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
|
||||
}
|
||||
@ -2359,11 +2324,6 @@ class LocalSlotRef(
|
||||
scope.raiseError("slot index out of range for $name")
|
||||
}
|
||||
val rec = owner.getSlotRecord(slotIndex)
|
||||
val direct = owner.getLocalRecordDirect(name)
|
||||
if (direct != null && direct !== rec) {
|
||||
owner.updateSlotFor(name, direct)
|
||||
return scope.resolve(direct, name)
|
||||
}
|
||||
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
|
||||
}
|
||||
|
||||
@ -68,17 +68,11 @@ abstract class ImportProvider(
|
||||
|
||||
suspend fun newStdScope(pos: Pos = Pos.builtIn): Scope =
|
||||
cachedStdScope.get {
|
||||
val module = newModuleAt(pos)
|
||||
val stdlib = prepareImport(pos, "lyng.stdlib", null)
|
||||
val plan = LinkedHashMap<String, Int>()
|
||||
for ((name, record) in stdlib.objects) {
|
||||
if (!record.visibility.isPublic) continue
|
||||
plan[name] = plan.size
|
||||
newModuleAt(pos).also {
|
||||
it.eval("import lyng.stdlib\n")
|
||||
}
|
||||
if (plan.isNotEmpty()) module.applySlotPlan(plan)
|
||||
stdlib.importInto(module, null)
|
||||
module
|
||||
}.createChildScope()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user