Remove interpreter fallbacks and enforce bytecode execution
This commit is contained in:
parent
26564438e2
commit
e73fe0d3c5
@ -29,7 +29,6 @@ import com.github.ajalt.clikt.parameters.options.option
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import net.sergeych.lyng.Compiler
|
import net.sergeych.lyng.Compiler
|
||||||
import net.sergeych.lyng.LyngVersion
|
import net.sergeych.lyng.LyngVersion
|
||||||
import net.sergeych.lyng.Pos
|
|
||||||
import net.sergeych.lyng.Script
|
import net.sergeych.lyng.Script
|
||||||
import net.sergeych.lyng.ScriptError
|
import net.sergeych.lyng.ScriptError
|
||||||
import net.sergeych.lyng.Source
|
import net.sergeych.lyng.Source
|
||||||
@ -158,7 +157,6 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
|||||||
|
|
||||||
val version by option("-v", "--version", help = "Print version and exit").flag()
|
val version by option("-v", "--version", help = "Print version and exit").flag()
|
||||||
val benchmark by option("--benchmark", help = "Run JVM microbenchmarks and exit").flag()
|
val benchmark by option("--benchmark", help = "Run JVM microbenchmarks and exit").flag()
|
||||||
val bytecodeFallbacks by option("--bytecode-fallbacks", help = "Report lambdas that fall back to interpreter").flag()
|
|
||||||
val script by argument(help = "one or more scripts to execute").optional()
|
val script by argument(help = "one or more scripts to execute").optional()
|
||||||
val execute: String? by option(
|
val execute: String? by option(
|
||||||
"-x", "--execute", help = """
|
"-x", "--execute", help = """
|
||||||
@ -170,7 +168,7 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
|||||||
|
|
||||||
override fun help(context: Context): String =
|
override fun help(context: Context): String =
|
||||||
"""
|
"""
|
||||||
The Lyng script language interpreter, language version is $LyngVersion.
|
The Lyng script language runtime, language version is $LyngVersion.
|
||||||
|
|
||||||
Please refer form more information to the project site:
|
Please refer form more information to the project site:
|
||||||
https://gitea.sergeych.net/SergeychWorks/lyng
|
https://gitea.sergeych.net/SergeychWorks/lyng
|
||||||
@ -201,15 +199,12 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
|||||||
launcher {
|
launcher {
|
||||||
// there is no script name, it is a first argument instead:
|
// there is no script name, it is a first argument instead:
|
||||||
processErrors {
|
processErrors {
|
||||||
val reporter = bytecodeFallbackReporter(bytecodeFallbacks)
|
|
||||||
val script = Compiler.compileWithResolution(
|
val script = Compiler.compileWithResolution(
|
||||||
Source("<eval>", execute!!),
|
Source("<eval>", execute!!),
|
||||||
baseScope.currentImportProvider,
|
baseScope.currentImportProvider,
|
||||||
seedScope = baseScope,
|
seedScope = baseScope
|
||||||
bytecodeFallbackReporter = reporter
|
|
||||||
)
|
)
|
||||||
script.execute(baseScope)
|
script.execute(baseScope)
|
||||||
flushBytecodeFallbacks(reporter)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,7 +215,7 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
|||||||
echoFormattedHelp()
|
echoFormattedHelp()
|
||||||
} else {
|
} else {
|
||||||
baseScope.addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
|
baseScope.addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
|
||||||
launcher { executeFile(script!!, bytecodeFallbacks) }
|
launcher { executeFile(script!!) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,14 +223,14 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun executeFileWithArgs(fileName: String, args: List<String>, reportBytecodeFallbacks: Boolean = false) {
|
fun executeFileWithArgs(fileName: String, args: List<String>) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
baseScopeDefer.await().addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
|
baseScopeDefer.await().addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
|
||||||
executeFile(fileName, reportBytecodeFallbacks)
|
executeFile(fileName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun executeFile(fileName: String, reportBytecodeFallbacks: Boolean = false) {
|
suspend fun executeFile(fileName: String) {
|
||||||
var text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource ->
|
var text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource ->
|
||||||
fileSource.buffer().use { bs ->
|
fileSource.buffer().use { bs ->
|
||||||
bs.readUtf8()
|
bs.readUtf8()
|
||||||
@ -248,15 +243,12 @@ suspend fun executeFile(fileName: String, reportBytecodeFallbacks: Boolean = fal
|
|||||||
}
|
}
|
||||||
processErrors {
|
processErrors {
|
||||||
val scope = baseScopeDefer.await()
|
val scope = baseScopeDefer.await()
|
||||||
val reporter = bytecodeFallbackReporter(reportBytecodeFallbacks)
|
|
||||||
val script = Compiler.compileWithResolution(
|
val script = Compiler.compileWithResolution(
|
||||||
Source(fileName, text),
|
Source(fileName, text),
|
||||||
scope.currentImportProvider,
|
scope.currentImportProvider,
|
||||||
seedScope = scope,
|
seedScope = scope
|
||||||
bytecodeFallbackReporter = reporter
|
|
||||||
)
|
)
|
||||||
script.execute(scope)
|
script.execute(scope)
|
||||||
flushBytecodeFallbacks(reporter)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,22 +260,3 @@ suspend fun processErrors(block: suspend () -> Unit) {
|
|||||||
println("\nError executing the script:\n$e\n")
|
println("\nError executing the script:\n$e\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bytecodeFallbackReporter(enabled: Boolean): ((Pos, String) -> Unit)? {
|
|
||||||
if (!enabled) return null
|
|
||||||
val reports = ArrayList<String>()
|
|
||||||
val reporter: (Pos, String) -> Unit = { pos, msg ->
|
|
||||||
reports.add("$pos: $msg")
|
|
||||||
}
|
|
||||||
return object : (Pos, String) -> Unit by reporter {
|
|
||||||
override fun invoke(pos: Pos, msg: String) = reporter(pos, msg)
|
|
||||||
override fun toString(): String = reports.joinToString("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun flushBytecodeFallbacks(reporter: ((Pos, String) -> Unit)?) {
|
|
||||||
val text = reporter?.toString().orEmpty()
|
|
||||||
if (text.isBlank()) return
|
|
||||||
println("Bytecode lambda fallbacks:")
|
|
||||||
println(text)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import net.sergeych.lyng.Scope
|
|||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
import net.sergeych.lyng.statement
|
|
||||||
import net.sergeych.lyngio.process.*
|
import net.sergeych.lyngio.process.*
|
||||||
import net.sergeych.lyngio.process.security.ProcessAccessDeniedException
|
import net.sergeych.lyngio.process.security.ProcessAccessDeniedException
|
||||||
import net.sergeych.lyngio.process.security.ProcessAccessPolicy
|
import net.sergeych.lyngio.process.security.ProcessAccessPolicy
|
||||||
@ -216,7 +215,7 @@ private suspend inline fun Scope.processGuard(crossinline block: suspend () -> O
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow {
|
private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow {
|
||||||
val producer = statement {
|
val producer = ObjNativeCallable {
|
||||||
val builder = (this as? net.sergeych.lyng.BytecodeClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
|
val builder = (this as? net.sergeych.lyng.BytecodeClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
|
||||||
?: this.thisObj as? ObjFlowBuilder
|
?: this.thisObj as? ObjFlowBuilder
|
||||||
|
|
||||||
|
|||||||
@ -112,6 +112,9 @@ class Compiler(
|
|||||||
if (!added) {
|
if (!added) {
|
||||||
currentShadowedLocalNames?.add(name)
|
currentShadowedLocalNames?.add(name)
|
||||||
}
|
}
|
||||||
|
if (added) {
|
||||||
|
scopeSeedNames.remove(name)
|
||||||
|
}
|
||||||
if (added && localDeclCountStack.isNotEmpty()) {
|
if (added && localDeclCountStack.isNotEmpty()) {
|
||||||
localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1
|
localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1
|
||||||
}
|
}
|
||||||
@ -136,6 +139,9 @@ class Compiler(
|
|||||||
if (plan.slots.containsKey(name)) return
|
if (plan.slots.containsKey(name)) return
|
||||||
plan.slots[name] = SlotEntry(plan.nextIndex, isMutable, isDelegated)
|
plan.slots[name] = SlotEntry(plan.nextIndex, isMutable, isDelegated)
|
||||||
plan.nextIndex += 1
|
plan.nextIndex += 1
|
||||||
|
if (!seedingSlotPlan && plan == moduleSlotPlan()) {
|
||||||
|
moduleDeclaredNames.add(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun declareSlotNameAt(
|
private fun declareSlotNameAt(
|
||||||
@ -155,6 +161,7 @@ class Compiler(
|
|||||||
private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull()
|
private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull()
|
||||||
private val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
|
private val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
|
||||||
private val nameObjClass: MutableMap<String, ObjClass> = mutableMapOf()
|
private val nameObjClass: MutableMap<String, ObjClass> = mutableMapOf()
|
||||||
|
private val scopeSeedNames: MutableSet<String> = mutableSetOf()
|
||||||
private val slotTypeDeclByScopeId: MutableMap<Int, MutableMap<Int, TypeDecl>> = mutableMapOf()
|
private val slotTypeDeclByScopeId: MutableMap<Int, MutableMap<Int, TypeDecl>> = mutableMapOf()
|
||||||
private val nameTypeDecl: MutableMap<String, TypeDecl> = mutableMapOf()
|
private val nameTypeDecl: MutableMap<String, TypeDecl> = mutableMapOf()
|
||||||
private data class TypeAliasDecl(
|
private data class TypeAliasDecl(
|
||||||
@ -177,15 +184,20 @@ class Compiler(
|
|||||||
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
||||||
private val objectDeclNames: MutableSet<String> = mutableSetOf()
|
private val objectDeclNames: MutableSet<String> = mutableSetOf()
|
||||||
private val externCallableNames: MutableSet<String> = mutableSetOf()
|
private val externCallableNames: MutableSet<String> = mutableSetOf()
|
||||||
|
private val moduleDeclaredNames: MutableSet<String> = mutableSetOf()
|
||||||
|
private var seedingSlotPlan: Boolean = false
|
||||||
|
|
||||||
private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
|
private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
|
||||||
val plan = moduleSlotPlan() ?: return
|
val plan = moduleSlotPlan() ?: return
|
||||||
|
seedingSlotPlan = true
|
||||||
|
try {
|
||||||
var current: Scope? = scope
|
var current: Scope? = scope
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
for ((name, record) in current.objects) {
|
for ((name, record) in current.objects) {
|
||||||
if (!record.visibility.isPublic) continue
|
if (!record.visibility.isPublic) continue
|
||||||
if (plan.slots.containsKey(name)) continue
|
if (plan.slots.containsKey(name)) continue
|
||||||
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
||||||
|
scopeSeedNames.add(name)
|
||||||
val instance = record.value as? ObjInstance
|
val instance = record.value as? ObjInstance
|
||||||
if (instance != null && nameObjClass[name] == null) {
|
if (instance != null && nameObjClass[name] == null) {
|
||||||
nameObjClass[name] = instance.objClass
|
nameObjClass[name] = instance.objClass
|
||||||
@ -204,6 +216,7 @@ class Compiler(
|
|||||||
isMutable = false,
|
isMutable = false,
|
||||||
isDelegated = false
|
isDelegated = false
|
||||||
)
|
)
|
||||||
|
scopeSeedNames.add(getterName)
|
||||||
}
|
}
|
||||||
val prop = record.value as? ObjProperty
|
val prop = record.value as? ObjProperty
|
||||||
if (prop?.setter != null) {
|
if (prop?.setter != null) {
|
||||||
@ -215,6 +228,7 @@ class Compiler(
|
|||||||
isMutable = false,
|
isMutable = false,
|
||||||
isDelegated = false
|
isDelegated = false
|
||||||
)
|
)
|
||||||
|
scopeSeedNames.add(setterName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,6 +241,7 @@ class Compiler(
|
|||||||
isMutable = false,
|
isMutable = false,
|
||||||
isDelegated = false
|
isDelegated = false
|
||||||
)
|
)
|
||||||
|
scopeSeedNames.add(callableName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,10 +252,14 @@ class Compiler(
|
|||||||
if (!record.visibility.isPublic) continue
|
if (!record.visibility.isPublic) continue
|
||||||
if (plan.slots.containsKey(name)) continue
|
if (plan.slots.containsKey(name)) continue
|
||||||
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
||||||
|
scopeSeedNames.add(name)
|
||||||
}
|
}
|
||||||
if (!includeParents) return
|
if (!includeParents) return
|
||||||
current = current.parent
|
current = current.parent
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
seedingSlotPlan = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun seedSlotPlanFromSeedScope(scope: Scope) {
|
private fun seedSlotPlanFromSeedScope(scope: Scope) {
|
||||||
@ -254,6 +273,7 @@ class Compiler(
|
|||||||
record.isMutable,
|
record.isMutable,
|
||||||
record.type == ObjRecord.Type.Delegated
|
record.type == ObjRecord.Type.Delegated
|
||||||
)
|
)
|
||||||
|
scopeSeedNames.add(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,6 +342,7 @@ class Compiler(
|
|||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"enum" -> {
|
"enum" -> {
|
||||||
@ -329,6 +350,7 @@ class Compiler(
|
|||||||
val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next
|
val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1557,6 +1579,7 @@ class Compiler(
|
|||||||
"<script>",
|
"<script>",
|
||||||
allowLocalSlots = true,
|
allowLocalSlots = true,
|
||||||
allowedScopeNames = modulePlan.keys,
|
allowedScopeNames = modulePlan.keys,
|
||||||
|
scopeSlotNameSet = scopeSeedNames,
|
||||||
moduleScopeId = moduleSlotPlan()?.id,
|
moduleScopeId = moduleSlotPlan()?.id,
|
||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
@ -1765,8 +1788,11 @@ class Compiler(
|
|||||||
} ?: -1
|
} ?: -1
|
||||||
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 moduleId = moduleSlotPlan()?.id
|
if (scopeSeedNames.contains(name)) return null
|
||||||
if (moduleId != null && slotLoc.scopeId == moduleId) return null
|
val modulePlan = moduleSlotPlan()
|
||||||
|
if (modulePlan != null && slotLoc.scopeId == modulePlan.id) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
recordCaptureSlot(name, slotLoc)
|
recordCaptureSlot(name, slotLoc)
|
||||||
val plan = capturePlanStack.lastOrNull() ?: return null
|
val plan = capturePlanStack.lastOrNull() ?: return null
|
||||||
val entry = plan.slotPlan.slots[name] ?: return null
|
val entry = plan.slotPlan.slots[name] ?: return null
|
||||||
@ -1846,6 +1872,7 @@ class Compiler(
|
|||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
rangeLocalNames = currentRangeParamNames,
|
rangeLocalNames = currentRangeParamNames,
|
||||||
allowedScopeNames = allowedScopeNames,
|
allowedScopeNames = allowedScopeNames,
|
||||||
|
scopeSlotNameSet = scopeSeedNames,
|
||||||
moduleScopeId = moduleSlotPlan()?.id,
|
moduleScopeId = moduleSlotPlan()?.id,
|
||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
@ -1870,6 +1897,7 @@ class Compiler(
|
|||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
rangeLocalNames = currentRangeParamNames,
|
rangeLocalNames = currentRangeParamNames,
|
||||||
allowedScopeNames = allowedScopeNames,
|
allowedScopeNames = allowedScopeNames,
|
||||||
|
scopeSlotNameSet = scopeSeedNames,
|
||||||
moduleScopeId = moduleSlotPlan()?.id,
|
moduleScopeId = moduleSlotPlan()?.id,
|
||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
@ -1913,6 +1941,7 @@ class Compiler(
|
|||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
rangeLocalNames = currentRangeParamNames,
|
rangeLocalNames = currentRangeParamNames,
|
||||||
allowedScopeNames = allowedScopeNames,
|
allowedScopeNames = allowedScopeNames,
|
||||||
|
scopeSlotNameSet = scopeSeedNames,
|
||||||
moduleScopeId = moduleSlotPlan()?.id,
|
moduleScopeId = moduleSlotPlan()?.id,
|
||||||
forcedLocalSlots = forcedLocalSlots,
|
forcedLocalSlots = forcedLocalSlots,
|
||||||
forcedLocalScopeId = forcedLocalScopeId,
|
forcedLocalScopeId = forcedLocalScopeId,
|
||||||
@ -2390,8 +2419,14 @@ class Compiler(
|
|||||||
val args = parsed.first
|
val args = parsed.first
|
||||||
val tailBlock = parsed.second
|
val tailBlock = parsed.second
|
||||||
isCall = true
|
isCall = true
|
||||||
|
val receiverClass = resolveReceiverClassForMember(left)
|
||||||
|
val nestedClass = receiverClass?.let { resolveClassByName("${it.className}.${next.value}") }
|
||||||
|
if (nestedClass != null) {
|
||||||
val field = FieldRef(left, next.value, isOptional)
|
val field = FieldRef(left, next.value, isOptional)
|
||||||
operand = CallRef(field, args, tailBlock, isOptional)
|
operand = CallRef(field, args, tailBlock, isOptional)
|
||||||
|
} else {
|
||||||
|
operand = MethodCallRef(left, next.value, args, tailBlock, isOptional)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// instance method call
|
// instance method call
|
||||||
val receiverType = if (next.value == "apply" || next.value == "run") {
|
val receiverType = if (next.value == "apply" || next.value == "run") {
|
||||||
@ -2967,17 +3002,11 @@ class Compiler(
|
|||||||
lambdaReturnTypeByRef[ref] = returnClass
|
lambdaReturnTypeByRef[ref] = returnClass
|
||||||
}
|
}
|
||||||
if (captureSlots.isNotEmpty()) {
|
if (captureSlots.isNotEmpty()) {
|
||||||
val moduleScopeId = moduleSlotPlan()?.id
|
|
||||||
val captureEntries = captureSlots.map { capture ->
|
val captureEntries = 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 kind = if (moduleScopeId != null && owner.scopeId == moduleScopeId) {
|
|
||||||
net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.MODULE
|
|
||||||
} else {
|
|
||||||
net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.LOCAL
|
|
||||||
}
|
|
||||||
net.sergeych.lyng.bytecode.LambdaCaptureEntry(
|
net.sergeych.lyng.bytecode.LambdaCaptureEntry(
|
||||||
ownerKind = kind,
|
ownerKind = net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.LOCAL,
|
||||||
ownerScopeId = owner.scopeId,
|
ownerScopeId = owner.scopeId,
|
||||||
ownerSlotId = owner.slot,
|
ownerSlotId = owner.slot,
|
||||||
ownerName = capture.name,
|
ownerName = capture.name,
|
||||||
@ -5725,6 +5754,9 @@ class Compiler(
|
|||||||
val outerClassName = currentEnclosingClassName()
|
val outerClassName = currentEnclosingClassName()
|
||||||
val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
|
val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
|
||||||
resolutionSink?.declareSymbol(declaredName, SymbolKind.ENUM, isMutable = false, pos = nameToken.pos)
|
resolutionSink?.declareSymbol(declaredName, SymbolKind.ENUM, isMutable = false, pos = nameToken.pos)
|
||||||
|
if (codeContexts.lastOrNull() is CodeContext.Module) {
|
||||||
|
scopeSeedNames.add(declaredName)
|
||||||
|
}
|
||||||
if (outerClassName != null) {
|
if (outerClassName != null) {
|
||||||
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
||||||
outerCtx?.classScopeMembers?.add(declaredName)
|
outerCtx?.classScopeMembers?.add(declaredName)
|
||||||
@ -6024,6 +6056,9 @@ class Compiler(
|
|||||||
val outerClassName = currentEnclosingClassName()
|
val outerClassName = currentEnclosingClassName()
|
||||||
val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
|
val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
|
||||||
resolutionSink?.declareSymbol(declaredName, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
|
resolutionSink?.declareSymbol(declaredName, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
|
||||||
|
if (codeContexts.lastOrNull() is CodeContext.Module) {
|
||||||
|
scopeSeedNames.add(declaredName)
|
||||||
|
}
|
||||||
if (outerClassName != null) {
|
if (outerClassName != null) {
|
||||||
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
||||||
outerCtx?.classScopeMembers?.add(declaredName)
|
outerCtx?.classScopeMembers?.add(declaredName)
|
||||||
@ -7909,29 +7944,7 @@ class Compiler(
|
|||||||
pendingDeclDoc = null
|
pendingDeclDoc = null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (declaringClassNameCaptured == null &&
|
fun buildVarDecl(initialExpression: Statement?): VarDeclStatement {
|
||||||
extTypeName == null &&
|
|
||||||
!isStatic &&
|
|
||||||
!isProperty &&
|
|
||||||
!actualExtern &&
|
|
||||||
!isAbstract
|
|
||||||
) {
|
|
||||||
if (isDelegate) {
|
|
||||||
val initExpr = initialExpression ?: throw ScriptError(start, "Delegate must be initialized")
|
|
||||||
val slotPlan = slotPlanStack.lastOrNull()
|
|
||||||
val slotIndex = slotPlan?.slots?.get(name)?.index
|
|
||||||
val scopeId = slotPlan?.id
|
|
||||||
return DelegatedVarDeclStatement(
|
|
||||||
name,
|
|
||||||
isMutable,
|
|
||||||
visibility,
|
|
||||||
initExpr,
|
|
||||||
isTransient,
|
|
||||||
slotIndex,
|
|
||||||
scopeId,
|
|
||||||
start
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val slotPlan = slotPlanStack.lastOrNull()
|
val slotPlan = slotPlanStack.lastOrNull()
|
||||||
val slotIndex = slotPlan?.slots?.get(name)?.index
|
val slotIndex = slotPlan?.slots?.get(name)?.index
|
||||||
val scopeId = slotPlan?.id
|
val scopeId = slotPlan?.id
|
||||||
@ -7983,6 +7996,30 @@ class Compiler(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (declaringClassNameCaptured == null &&
|
||||||
|
extTypeName == null &&
|
||||||
|
!isStatic &&
|
||||||
|
!isProperty
|
||||||
|
) {
|
||||||
|
if (isDelegate) {
|
||||||
|
val initExpr = initialExpression ?: throw ScriptError(start, "Delegate must be initialized")
|
||||||
|
val slotPlan = slotPlanStack.lastOrNull()
|
||||||
|
val slotIndex = slotPlan?.slots?.get(name)?.index
|
||||||
|
val scopeId = slotPlan?.id
|
||||||
|
return DelegatedVarDeclStatement(
|
||||||
|
name,
|
||||||
|
isMutable,
|
||||||
|
visibility,
|
||||||
|
initExpr,
|
||||||
|
isTransient,
|
||||||
|
slotIndex,
|
||||||
|
scopeId,
|
||||||
|
start
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return buildVarDecl(initialExpression)
|
||||||
|
}
|
||||||
|
|
||||||
if (isStatic) {
|
if (isStatic) {
|
||||||
if (declaringClassNameCaptured != null) {
|
if (declaringClassNameCaptured != null) {
|
||||||
val directRef = unwrapDirectRef(initialExpression)
|
val directRef = unwrapDirectRef(initialExpression)
|
||||||
@ -8288,56 +8325,7 @@ class Compiler(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return object : Statement() {
|
return buildVarDecl(initialExpression)
|
||||||
override val pos: Pos = start
|
|
||||||
override suspend fun execute(context: Scope): Obj {
|
|
||||||
if (context.containsLocal(name)) {
|
|
||||||
throw ScriptError(start, "Variable $name is already defined")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDelegate) {
|
|
||||||
val initValue = initialExpression!!.execute(context)
|
|
||||||
val accessTypeStr = if (isMutable) "Var" else "Val"
|
|
||||||
val accessType = ObjString(accessTypeStr)
|
|
||||||
val finalDelegate = try {
|
|
||||||
initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, ObjNull))
|
|
||||||
} catch (_: Exception) {
|
|
||||||
initValue
|
|
||||||
}
|
|
||||||
val rec = context.addItem(
|
|
||||||
name, isMutable, ObjUnset, visibility, setterVisibility,
|
|
||||||
recordType = ObjRecord.Type.Delegated,
|
|
||||||
isAbstract = isAbstract,
|
|
||||||
isClosed = isClosed,
|
|
||||||
isOverride = isOverride,
|
|
||||||
isTransient = isTransient
|
|
||||||
)
|
|
||||||
rec.delegate = finalDelegate
|
|
||||||
return finalDelegate
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getter != null || setter != null) {
|
|
||||||
val prop = ObjProperty(name, getter, setter)
|
|
||||||
context.addItem(
|
|
||||||
name, isMutable, prop, visibility, setterVisibility,
|
|
||||||
recordType = ObjRecord.Type.Property,
|
|
||||||
isAbstract = isAbstract,
|
|
||||||
isClosed = isClosed,
|
|
||||||
isOverride = isOverride,
|
|
||||||
isTransient = isTransient
|
|
||||||
)
|
|
||||||
return prop
|
|
||||||
}
|
|
||||||
|
|
||||||
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
|
||||||
context.addItem(
|
|
||||||
name, isMutable, initValue, visibility,
|
|
||||||
recordType = ObjRecord.Type.Other,
|
|
||||||
isTransient = isTransient
|
|
||||||
)
|
|
||||||
return initValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
if (implicitTypeParams.isNotEmpty()) pendingTypeParamStack.removeLast()
|
if (implicitTypeParams.isNotEmpty()) pendingTypeParamStack.removeLast()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -174,54 +174,49 @@ internal suspend fun executeFunctionDecl(
|
|||||||
spec.extTypeName?.let { typeName ->
|
spec.extTypeName?.let { typeName ->
|
||||||
val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found")
|
val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found")
|
||||||
if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance")
|
if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance")
|
||||||
val callable = net.sergeych.lyng.obj.ObjNativeCallable {
|
scope.addExtension(
|
||||||
val result = (thisObj as? ObjInstance)?.let { i ->
|
type,
|
||||||
val execScope = applyClosureForBytecode(i.instanceScope).also {
|
spec.name,
|
||||||
it.args = args
|
ObjRecord(
|
||||||
}
|
compiledFnBody,
|
||||||
compiledFnBody.execute(execScope)
|
isMutable = false,
|
||||||
} ?: compiledFnBody.execute(thisObj.autoInstanceScope(this))
|
visibility = spec.visibility,
|
||||||
result
|
declaringClass = null,
|
||||||
}
|
type = ObjRecord.Type.Fun
|
||||||
scope.addExtension(type, spec.name, ObjRecord(callable, isMutable = false, visibility = spec.visibility, declaringClass = null))
|
)
|
||||||
|
)
|
||||||
val wrapperName = spec.extensionWrapperName ?: extensionCallableName(typeName, spec.name)
|
val wrapperName = spec.extensionWrapperName ?: extensionCallableName(typeName, spec.name)
|
||||||
val wrapper = ObjExtensionMethodCallable(spec.name, callable)
|
val wrapper = ObjExtensionMethodCallable(spec.name, compiledFnBody)
|
||||||
scope.addItem(wrapperName, false, wrapper, spec.visibility, recordType = ObjRecord.Type.Fun)
|
scope.addItem(wrapperName, false, wrapper, spec.visibility, recordType = ObjRecord.Type.Fun)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
val th = scope.thisObj
|
val th = scope.thisObj
|
||||||
if (!spec.isStatic && th is ObjClass) {
|
if (!spec.isStatic && th is ObjClass) {
|
||||||
val cls: ObjClass = th
|
val cls: ObjClass = th
|
||||||
cls.addFn(
|
cls.createField(
|
||||||
spec.name,
|
spec.name,
|
||||||
|
compiledFnBody,
|
||||||
isMutable = true,
|
isMutable = true,
|
||||||
visibility = spec.visibility,
|
visibility = spec.visibility,
|
||||||
|
pos = spec.startPos,
|
||||||
|
declaringClass = cls,
|
||||||
isAbstract = spec.isAbstract,
|
isAbstract = spec.isAbstract,
|
||||||
isClosed = spec.isClosed,
|
isClosed = spec.isClosed,
|
||||||
isOverride = spec.isOverride,
|
isOverride = spec.isOverride,
|
||||||
pos = spec.startPos,
|
type = ObjRecord.Type.Fun,
|
||||||
methodId = spec.memberMethodId
|
methodId = spec.memberMethodId
|
||||||
) {
|
|
||||||
val savedCtx = this.currentClassCtx
|
|
||||||
this.currentClassCtx = cls
|
|
||||||
try {
|
|
||||||
(thisObj as? ObjInstance)?.let { i ->
|
|
||||||
val execScope = i.instanceScope.createChildScope(
|
|
||||||
pos = this.pos,
|
|
||||||
args = this.args,
|
|
||||||
newThisObj = i
|
|
||||||
)
|
)
|
||||||
execScope.currentClassCtx = cls
|
|
||||||
compiledFnBody.execute(execScope)
|
|
||||||
} ?: compiledFnBody.execute(thisObj.autoInstanceScope(this))
|
|
||||||
} finally {
|
|
||||||
this.currentClassCtx = savedCtx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val memberValue = cls.members[spec.name]?.value ?: compiledFnBody
|
val memberValue = cls.members[spec.name]?.value ?: compiledFnBody
|
||||||
scope.addItem(spec.name, false, memberValue, spec.visibility, callSignature = spec.externCallSignature)
|
scope.addItem(spec.name, false, memberValue, spec.visibility, callSignature = spec.externCallSignature)
|
||||||
compiledFnBody
|
compiledFnBody
|
||||||
} else {
|
} else {
|
||||||
scope.addItem(spec.name, false, compiledFnBody, spec.visibility, callSignature = spec.externCallSignature)
|
scope.addItem(
|
||||||
|
spec.name,
|
||||||
|
false,
|
||||||
|
compiledFnBody,
|
||||||
|
spec.visibility,
|
||||||
|
recordType = ObjRecord.Type.Fun,
|
||||||
|
callSignature = spec.externCallSignature
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return annotatedFnBody
|
return annotatedFnBody
|
||||||
|
|||||||
@ -78,8 +78,17 @@ open class Scope(
|
|||||||
thisObj = primary
|
thisObj = primary
|
||||||
val extrasSnapshot = when {
|
val extrasSnapshot = when {
|
||||||
extras.isEmpty() -> emptyList()
|
extras.isEmpty() -> emptyList()
|
||||||
extras is MutableList<*> -> synchronized(extras) { extras.toList() }
|
else -> {
|
||||||
else -> extras.toList()
|
try {
|
||||||
|
if (extras is MutableList<*>) {
|
||||||
|
synchronized(extras) { extras.toList() }
|
||||||
|
} else {
|
||||||
|
extras.toList()
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
synchronized(thisVariants) {
|
synchronized(thisVariants) {
|
||||||
thisVariants.clear()
|
thisVariants.clear()
|
||||||
@ -818,34 +827,27 @@ open class Scope(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve and evaluate a qualified identifier exactly as compiled code would.
|
* Resolve and evaluate a qualified identifier (e.g. `A.B.C`) using the same name/field
|
||||||
* For input like `A.B.C`, it builds the same ObjRef chain the compiler emits:
|
* resolution rules as compiled code, without invoking the compiler or ObjRef evaluation.
|
||||||
* `LocalVarRef("A", Pos.builtIn)` followed by `FieldRef` for each segment, then evaluates it.
|
|
||||||
* This mirrors `eval("A.B.C")` resolution semantics without invoking the compiler.
|
|
||||||
*/
|
*/
|
||||||
suspend fun resolveQualifiedIdentifier(qualifiedName: String): Obj {
|
suspend fun resolveQualifiedIdentifier(qualifiedName: String): Obj {
|
||||||
val trimmed = qualifiedName.trim()
|
val trimmed = qualifiedName.trim()
|
||||||
if (trimmed.isEmpty()) raiseSymbolNotFound("empty identifier")
|
if (trimmed.isEmpty()) raiseSymbolNotFound("empty identifier")
|
||||||
val parts = trimmed.split('.')
|
val parts = trimmed.split('.')
|
||||||
val first = parts[0]
|
val first = parts[0]
|
||||||
val ref: ObjRef = if (first == "this") {
|
val base: Obj = if (first == "this") {
|
||||||
ConstRef(thisObj.asReadonly)
|
thisObj
|
||||||
} else {
|
} else {
|
||||||
var s: Scope? = this
|
val rec = get(first) ?: raiseSymbolNotFound(first)
|
||||||
var slot: Int? = null
|
resolve(rec, first)
|
||||||
var guard = 0
|
|
||||||
while (s != null && guard++ < 1024 && slot == null) {
|
|
||||||
slot = s.getSlotIndexOf(first)
|
|
||||||
s = s.parent
|
|
||||||
}
|
}
|
||||||
if (slot == null) raiseSymbolNotFound(first)
|
var current = base
|
||||||
LocalSlotRef(first, slot, 0, isMutable = false, isDelegated = false, Pos.builtIn, strict = true)
|
|
||||||
}
|
|
||||||
var ref0: ObjRef = ref
|
|
||||||
for (i in 1 until parts.size) {
|
for (i in 1 until parts.size) {
|
||||||
ref0 = FieldRef(ref0, parts[i], false)
|
val name = parts[i]
|
||||||
|
val rec = current.readField(this, name)
|
||||||
|
current = resolve(rec, name)
|
||||||
}
|
}
|
||||||
return ref0.evalValue(this)
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun resolve(rec: ObjRecord, name: String): Obj {
|
suspend fun resolve(rec: ObjRecord, name: String): Obj {
|
||||||
|
|||||||
@ -85,7 +85,9 @@ class Script(
|
|||||||
seedModuleSlots(moduleTarget)
|
seedModuleSlots(moduleTarget)
|
||||||
}
|
}
|
||||||
moduleBytecode?.let { fn ->
|
moduleBytecode?.let { fn ->
|
||||||
return CmdVm().execute(fn, scope, scope.args)
|
return CmdVm().execute(fn, scope, scope.args) { frame, _ ->
|
||||||
|
seedModuleLocals(frame, moduleTarget)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (statements.isNotEmpty()) {
|
if (statements.isNotEmpty()) {
|
||||||
scope.raiseIllegalState("interpreter execution is not supported; missing module bytecode")
|
scope.raiseIllegalState("interpreter execution is not supported; missing module bytecode")
|
||||||
@ -98,6 +100,31 @@ class Script(
|
|||||||
seedImportBindings(scope)
|
seedImportBindings(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun seedModuleLocals(frame: net.sergeych.lyng.bytecode.CmdFrame, scope: Scope) {
|
||||||
|
if (importBindings.isEmpty()) return
|
||||||
|
val localNames = frame.fn.localSlotNames
|
||||||
|
if (localNames.isEmpty()) return
|
||||||
|
val values = HashMap<String, Obj>(importBindings.size)
|
||||||
|
for (name in importBindings.keys) {
|
||||||
|
val record = scope.getLocalRecordDirect(name) ?: continue
|
||||||
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
|
scope.resolve(record, name)
|
||||||
|
} else {
|
||||||
|
record.value
|
||||||
|
}
|
||||||
|
if (value !== ObjUnset) {
|
||||||
|
values[name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (values.isEmpty()) return
|
||||||
|
val base = frame.fn.scopeSlotCount
|
||||||
|
for (i in localNames.indices) {
|
||||||
|
val name = localNames[i] ?: continue
|
||||||
|
val value = values[name] ?: continue
|
||||||
|
frame.setObjUnchecked(base + i, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun seedImportBindings(scope: Scope) {
|
private suspend fun seedImportBindings(scope: Scope) {
|
||||||
val provider = scope.currentImportProvider
|
val provider = scope.currentImportProvider
|
||||||
val importedModules = LinkedHashSet<ModuleScope>()
|
val importedModules = LinkedHashSet<ModuleScope>()
|
||||||
|
|||||||
@ -19,6 +19,10 @@ package net.sergeych.lyng
|
|||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
sealed class WhenCondition(open val expr: Statement, open val pos: Pos) {
|
sealed class WhenCondition(open val expr: Statement, open val pos: Pos) {
|
||||||
|
protected fun interpreterDisabled(scope: Scope): Nothing {
|
||||||
|
return scope.raiseIllegalState("interpreter execution is not supported; when condition requires bytecode")
|
||||||
|
}
|
||||||
|
|
||||||
abstract suspend fun matches(scope: Scope, value: Obj): Boolean
|
abstract suspend fun matches(scope: Scope, value: Obj): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +31,7 @@ class WhenEqualsCondition(
|
|||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
) : WhenCondition(expr, pos) {
|
) : WhenCondition(expr, pos) {
|
||||||
override suspend fun matches(scope: Scope, value: Obj): Boolean {
|
override suspend fun matches(scope: Scope, value: Obj): Boolean {
|
||||||
return expr.execute(scope).compareTo(scope, value) == 0
|
return interpreterDisabled(scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,8 +41,7 @@ class WhenInCondition(
|
|||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
) : WhenCondition(expr, pos) {
|
) : WhenCondition(expr, pos) {
|
||||||
override suspend fun matches(scope: Scope, value: Obj): Boolean {
|
override suspend fun matches(scope: Scope, value: Obj): Boolean {
|
||||||
val result = expr.execute(scope).contains(scope, value)
|
return interpreterDisabled(scope)
|
||||||
return if (negated) !result else result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,12 +51,7 @@ class WhenIsCondition(
|
|||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
) : WhenCondition(expr, pos) {
|
) : WhenCondition(expr, pos) {
|
||||||
override suspend fun matches(scope: Scope, value: Obj): Boolean {
|
override suspend fun matches(scope: Scope, value: Obj): Boolean {
|
||||||
val typeExpr = expr.execute(scope)
|
return interpreterDisabled(scope)
|
||||||
val result = when (typeExpr) {
|
|
||||||
is net.sergeych.lyng.obj.ObjTypeExpr -> net.sergeych.lyng.obj.matchesTypeDecl(scope, value, typeExpr.typeDecl)
|
|
||||||
else -> value.isInstanceOf(typeExpr)
|
|
||||||
}
|
|
||||||
return if (negated) !result else result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,7 @@ class BytecodeCompiler(
|
|||||||
private val returnLabels: Set<String> = emptySet(),
|
private val returnLabels: Set<String> = emptySet(),
|
||||||
private val rangeLocalNames: Set<String> = emptySet(),
|
private val rangeLocalNames: Set<String> = emptySet(),
|
||||||
private val allowedScopeNames: Set<String>? = null,
|
private val allowedScopeNames: Set<String>? = null,
|
||||||
|
private val scopeSlotNameSet: Set<String>? = null,
|
||||||
private val moduleScopeId: Int? = null,
|
private val moduleScopeId: Int? = null,
|
||||||
private val forcedLocalSlots: Map<String, Int> = emptyMap(),
|
private val forcedLocalSlots: Map<String, Int> = emptyMap(),
|
||||||
private val forcedLocalScopeId: Int? = null,
|
private val forcedLocalScopeId: Int? = null,
|
||||||
@ -498,13 +499,6 @@ class BytecodeCompiler(
|
|||||||
return CompiledValue(local, SlotType.OBJ)
|
return CompiledValue(local, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
if (ref.name.isEmpty()) return null
|
if (ref.name.isEmpty()) return null
|
||||||
if (ref.captureOwnerScopeId == null && refScopeId(ref) == 0) {
|
|
||||||
val byName = scopeSlotIndexByName[ref.name]
|
|
||||||
if (byName != null) {
|
|
||||||
val resolved = slotTypes[byName] ?: SlotType.UNKNOWN
|
|
||||||
return CompiledValue(byName, resolved)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val mapped = resolveSlot(ref) ?: return null
|
val mapped = resolveSlot(ref) ?: return null
|
||||||
var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
|
var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
|
||||||
if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) {
|
if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) {
|
||||||
@ -518,6 +512,13 @@ class BytecodeCompiler(
|
|||||||
updateSlotType(local, resolved)
|
updateSlotType(local, resolved)
|
||||||
return CompiledValue(local, resolved)
|
return CompiledValue(local, resolved)
|
||||||
}
|
}
|
||||||
|
if (mapped < scopeSlotCount && resolved == SlotType.UNKNOWN) {
|
||||||
|
val addrSlot = ensureScopeAddr(mapped)
|
||||||
|
val local = allocSlot()
|
||||||
|
emitLoadFromAddr(addrSlot, local, SlotType.OBJ)
|
||||||
|
updateSlotType(local, SlotType.OBJ)
|
||||||
|
return CompiledValue(local, SlotType.OBJ)
|
||||||
|
}
|
||||||
CompiledValue(mapped, resolved)
|
CompiledValue(mapped, resolved)
|
||||||
}
|
}
|
||||||
is LocalVarRef -> {
|
is LocalVarRef -> {
|
||||||
@ -529,12 +530,6 @@ class BytecodeCompiler(
|
|||||||
return CompiledValue(slot, resolved)
|
return CompiledValue(slot, resolved)
|
||||||
}
|
}
|
||||||
if (allowLocalSlots) {
|
if (allowLocalSlots) {
|
||||||
val localIndex = localSlotIndexByName[ref.name]
|
|
||||||
if (localIndex != null) {
|
|
||||||
val slot = scopeSlotCount + localIndex
|
|
||||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
|
||||||
return CompiledValue(slot, resolved)
|
|
||||||
}
|
|
||||||
scopeSlotIndexByName[ref.name]?.let { slot ->
|
scopeSlotIndexByName[ref.name]?.let { slot ->
|
||||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||||
return CompiledValue(slot, resolved)
|
return CompiledValue(slot, resolved)
|
||||||
@ -1292,6 +1287,23 @@ class BytecodeCompiler(
|
|||||||
val rightRef = binaryRight(ref)
|
val rightRef = binaryRight(ref)
|
||||||
var a = compileRefWithFallback(leftRef, null, refPos(ref)) ?: return null
|
var a = compileRefWithFallback(leftRef, null, refPos(ref)) ?: return null
|
||||||
var b = compileRefWithFallback(rightRef, null, refPos(ref)) ?: return null
|
var b = compileRefWithFallback(rightRef, null, refPos(ref)) ?: return null
|
||||||
|
if (op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT)) {
|
||||||
|
val leftNeedsObj = a.type == SlotType.INT && b.type == SlotType.REAL
|
||||||
|
val rightNeedsObj = b.type == SlotType.INT && a.type == SlotType.REAL
|
||||||
|
if (leftNeedsObj || rightNeedsObj) {
|
||||||
|
val leftObj = if (leftNeedsObj) {
|
||||||
|
compileScopeSlotObj(leftRef) ?: a
|
||||||
|
} else {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
val rightObj = if (rightNeedsObj) {
|
||||||
|
compileScopeSlotObj(rightRef) ?: b
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
return compileObjBinaryOp(leftRef, leftObj, rightObj, op, refPos(ref))
|
||||||
|
}
|
||||||
|
}
|
||||||
val intOps = setOf(
|
val intOps = setOf(
|
||||||
BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT,
|
BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT,
|
||||||
BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR
|
BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR
|
||||||
@ -1917,34 +1929,15 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.THROW, posId, msgSlot)
|
builder.emit(Opcode.THROW, posId, msgSlot)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
val resolvedSlot = resolveSlot(localTarget)
|
val slot = resolveSlot(localTarget)
|
||||||
?: resolveAssignableSlotByName(localTarget.name)?.first
|
?: resolveAssignableSlotByName(localTarget.name)?.first
|
||||||
?: return null
|
?: return null
|
||||||
val slot = if (resolvedSlot < scopeSlotCount && localTarget.captureOwnerScopeId == null) {
|
|
||||||
localSlotIndexByName[localTarget.name]?.let { scopeSlotCount + it } ?: resolvedSlot
|
|
||||||
} else {
|
|
||||||
resolvedSlot
|
|
||||||
}
|
|
||||||
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
|
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
|
||||||
val addrSlot = ensureScopeAddr(slot)
|
val addrSlot = ensureScopeAddr(slot)
|
||||||
emitStoreToAddr(value.slot, addrSlot, value.type)
|
emitStoreToAddr(value.slot, addrSlot, value.type)
|
||||||
if (localTarget.captureOwnerScopeId == null) {
|
|
||||||
localSlotIndexByName[localTarget.name]?.let { mirror ->
|
|
||||||
val mirrorSlot = scopeSlotCount + mirror
|
|
||||||
emitMove(value, mirrorSlot)
|
|
||||||
updateSlotType(mirrorSlot, value.type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (slot < scopeSlotCount) {
|
} else if (slot < scopeSlotCount) {
|
||||||
val addrSlot = ensureScopeAddr(slot)
|
val addrSlot = ensureScopeAddr(slot)
|
||||||
emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ)
|
emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ)
|
||||||
if (localTarget.captureOwnerScopeId == null) {
|
|
||||||
localSlotIndexByName[localTarget.name]?.let { mirror ->
|
|
||||||
val mirrorSlot = scopeSlotCount + mirror
|
|
||||||
emitMove(value, mirrorSlot)
|
|
||||||
updateSlotType(mirrorSlot, value.type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
when (value.type) {
|
when (value.type) {
|
||||||
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
|
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
|
||||||
@ -1980,19 +1973,9 @@ class BytecodeCompiler(
|
|||||||
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
|
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
|
||||||
val addrSlot = ensureScopeAddr(slot)
|
val addrSlot = ensureScopeAddr(slot)
|
||||||
emitStoreToAddr(value.slot, addrSlot, value.type)
|
emitStoreToAddr(value.slot, addrSlot, value.type)
|
||||||
localSlotIndexByName[nameTarget]?.let { mirror ->
|
|
||||||
val mirrorSlot = scopeSlotCount + mirror
|
|
||||||
emitMove(value, mirrorSlot)
|
|
||||||
updateSlotType(mirrorSlot, value.type)
|
|
||||||
}
|
|
||||||
} else if (slot < scopeSlotCount) {
|
} else if (slot < scopeSlotCount) {
|
||||||
val addrSlot = ensureScopeAddr(slot)
|
val addrSlot = ensureScopeAddr(slot)
|
||||||
emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ)
|
emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ)
|
||||||
localSlotIndexByName[nameTarget]?.let { mirror ->
|
|
||||||
val mirrorSlot = scopeSlotCount + mirror
|
|
||||||
emitMove(value, mirrorSlot)
|
|
||||||
updateSlotType(mirrorSlot, value.type)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
when (value.type) {
|
when (value.type) {
|
||||||
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
|
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
|
||||||
@ -2337,8 +2320,65 @@ class BytecodeCompiler(
|
|||||||
updateSlotType(result, SlotType.OBJ)
|
updateSlotType(result, SlotType.OBJ)
|
||||||
return CompiledValue(result, SlotType.OBJ)
|
return CompiledValue(result, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
val fieldId = receiverClass.instanceFieldIdMap()[fieldTarget.name]
|
if (receiverClass is ObjInstanceClass && !isThisReceiver(fieldTarget.target)) {
|
||||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[fieldTarget.name]
|
val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
|
||||||
|
if (!fieldTarget.isOptional) {
|
||||||
|
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
|
||||||
|
builder.emit(objOp, current, rhs.slot, result)
|
||||||
|
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
|
||||||
|
} else {
|
||||||
|
val nullSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||||
|
val cmpSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
|
||||||
|
val nullLabel = builder.label()
|
||||||
|
val endLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
|
||||||
|
builder.emit(objOp, current, rhs.slot, result)
|
||||||
|
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
|
||||||
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||||
|
builder.mark(nullLabel)
|
||||||
|
builder.emit(Opcode.CONST_NULL, result)
|
||||||
|
builder.mark(endLabel)
|
||||||
|
}
|
||||||
|
updateSlotType(result, SlotType.OBJ)
|
||||||
|
return CompiledValue(result, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
val resolvedMember = receiverClass.resolveInstanceMember(fieldTarget.name)
|
||||||
|
if (resolvedMember?.declaringClass?.className == "Obj") {
|
||||||
|
val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
|
||||||
|
if (!fieldTarget.isOptional) {
|
||||||
|
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
|
||||||
|
builder.emit(objOp, current, rhs.slot, result)
|
||||||
|
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
|
||||||
|
} else {
|
||||||
|
val nullSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||||
|
val cmpSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
|
||||||
|
val nullLabel = builder.label()
|
||||||
|
val endLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
|
||||||
|
builder.emit(objOp, current, rhs.slot, result)
|
||||||
|
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
|
||||||
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||||
|
builder.mark(nullLabel)
|
||||||
|
builder.emit(Opcode.CONST_NULL, result)
|
||||||
|
builder.mark(endLabel)
|
||||||
|
}
|
||||||
|
updateSlotType(result, SlotType.OBJ)
|
||||||
|
return CompiledValue(result, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[fieldTarget.name] else null
|
||||||
|
val methodId = if (resolvedMember != null) receiverClass.instanceMethodIdMap(includeAbstract = true)[fieldTarget.name] else null
|
||||||
if (fieldId == null && methodId == null && isKnownClassReceiver(fieldTarget.target)) {
|
if (fieldId == null && methodId == null && isKnownClassReceiver(fieldTarget.target)) {
|
||||||
val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
|
val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
|
||||||
if (!fieldTarget.isOptional) {
|
if (!fieldTarget.isOptional) {
|
||||||
@ -2608,9 +2648,45 @@ class BytecodeCompiler(
|
|||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
updateSlotType(resultSlot, SlotType.OBJ)
|
updateSlotType(resultSlot, SlotType.OBJ)
|
||||||
return CompiledValue(resultSlot, SlotType.OBJ)
|
return CompiledValue(resultSlot, SlotType.OBJ)
|
||||||
|
} else if (receiverClass is ObjInstanceClass && !isThisReceiver(target.target)) {
|
||||||
|
val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
|
||||||
|
if (!target.isOptional) {
|
||||||
|
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
|
||||||
} else {
|
} else {
|
||||||
val fieldId = receiverClass.instanceFieldIdMap()[target.name]
|
val recvNull = allocSlot()
|
||||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name]
|
builder.emit(Opcode.CONST_NULL, recvNull)
|
||||||
|
val recvCmp = allocSlot()
|
||||||
|
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
|
||||||
|
val skipLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
|
||||||
|
builder.mark(skipLabel)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val resolvedMember = receiverClass.resolveInstanceMember(target.name)
|
||||||
|
if (resolvedMember?.declaringClass?.className == "Obj") {
|
||||||
|
val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
|
||||||
|
if (!target.isOptional) {
|
||||||
|
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
|
||||||
|
} else {
|
||||||
|
val recvNull = allocSlot()
|
||||||
|
builder.emit(Opcode.CONST_NULL, recvNull)
|
||||||
|
val recvCmp = allocSlot()
|
||||||
|
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
|
||||||
|
val skipLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
|
||||||
|
builder.mark(skipLabel)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[target.name] else null
|
||||||
|
val methodId = if (resolvedMember != null) receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name] else null
|
||||||
if (fieldId != null || methodId != null) {
|
if (fieldId != null || methodId != null) {
|
||||||
if (!target.isOptional) {
|
if (!target.isOptional) {
|
||||||
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, newValue.slot)
|
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, newValue.slot)
|
||||||
@ -2661,6 +2737,7 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is IndexRef -> {
|
is IndexRef -> {
|
||||||
val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null
|
val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null
|
||||||
if (!target.optionalRef) {
|
if (!target.optionalRef) {
|
||||||
@ -2718,8 +2795,61 @@ class BytecodeCompiler(
|
|||||||
updateSlotType(dst, SlotType.OBJ)
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
val fieldId = receiverClass.instanceFieldIdMap()[ref.name]
|
if (receiverClass is ObjInstanceClass && !isThisReceiver(ref.target)) {
|
||||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
|
||||||
|
val dst = allocSlot()
|
||||||
|
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
|
||||||
|
if (!ref.isOptional) {
|
||||||
|
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
|
||||||
|
} else {
|
||||||
|
val nullSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||||
|
val cmpSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
|
||||||
|
val nullLabel = builder.label()
|
||||||
|
val endLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
|
||||||
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||||
|
builder.mark(nullLabel)
|
||||||
|
builder.emit(Opcode.CONST_NULL, dst)
|
||||||
|
builder.mark(endLabel)
|
||||||
|
}
|
||||||
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
val resolvedMember = receiverClass.resolveInstanceMember(ref.name)
|
||||||
|
if (resolvedMember?.declaringClass?.className == "Obj") {
|
||||||
|
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
|
||||||
|
val dst = allocSlot()
|
||||||
|
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
|
||||||
|
if (!ref.isOptional) {
|
||||||
|
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
|
||||||
|
} else {
|
||||||
|
val nullSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||||
|
val cmpSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
|
||||||
|
val nullLabel = builder.label()
|
||||||
|
val endLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
|
||||||
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||||
|
builder.mark(nullLabel)
|
||||||
|
builder.emit(Opcode.CONST_NULL, dst)
|
||||||
|
builder.mark(endLabel)
|
||||||
|
}
|
||||||
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[ref.name] else null
|
||||||
|
val methodId = if (resolvedMember != null) receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] else null
|
||||||
val encodedFieldId = encodeMemberId(receiverClass, fieldId)
|
val encodedFieldId = encodeMemberId(receiverClass, fieldId)
|
||||||
val encodedMethodId = encodeMemberId(receiverClass, methodId)
|
val encodedMethodId = encodeMemberId(receiverClass, methodId)
|
||||||
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
|
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
|
||||||
@ -3688,6 +3818,44 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun compileCall(ref: CallRef): CompiledValue? {
|
private fun compileCall(ref: CallRef): CompiledValue? {
|
||||||
val callPos = callSitePos()
|
val callPos = callSitePos()
|
||||||
|
val fieldTarget = ref.target as? FieldRef
|
||||||
|
if (fieldTarget != null && isKnownClassReceiver(fieldTarget.target)) {
|
||||||
|
val receiverClass = resolveReceiverClass(fieldTarget.target)
|
||||||
|
val methodId = receiverClass?.instanceMethodIdMap(includeAbstract = true)?.get(fieldTarget.name)
|
||||||
|
if (methodId != null) {
|
||||||
|
val receiver = compileRefWithFallback(fieldTarget.target, null, refPosOrCurrent(fieldTarget.target))
|
||||||
|
?: return null
|
||||||
|
val dst = allocSlot()
|
||||||
|
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId
|
||||||
|
if (!ref.isOptionalInvoke) {
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
val nullSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||||
|
val cmpSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
|
||||||
|
val nullLabel = builder.label()
|
||||||
|
val endLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
|
||||||
|
)
|
||||||
|
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.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||||
|
builder.mark(nullLabel)
|
||||||
|
builder.emit(Opcode.CONST_NULL, dst)
|
||||||
|
builder.mark(endLabel)
|
||||||
|
}
|
||||||
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
}
|
||||||
val localTarget = ref.target as? LocalVarRef
|
val localTarget = ref.target as? LocalVarRef
|
||||||
val isExternCall = localTarget != null && externCallableNames.contains(localTarget.name)
|
val isExternCall = localTarget != null && externCallableNames.contains(localTarget.name)
|
||||||
if (localTarget != null) {
|
if (localTarget != null) {
|
||||||
@ -3791,7 +3959,7 @@ class BytecodeCompiler(
|
|||||||
val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type
|
val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type
|
||||||
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
|
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
|
||||||
val dst = allocSlot()
|
val dst = allocSlot()
|
||||||
if (receiverClass == ObjDynamic.type) {
|
fun emitDynamicCall(): CompiledValue? {
|
||||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||||
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
|
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
|
||||||
@ -3818,8 +3986,25 @@ class BytecodeCompiler(
|
|||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
|
if (receiverClass == ObjDynamic.type) {
|
||||||
|
return emitDynamicCall()
|
||||||
|
}
|
||||||
|
if (receiverClass is ObjInstanceClass && !isThisReceiver(ref.receiver)) {
|
||||||
|
return emitDynamicCall()
|
||||||
|
}
|
||||||
|
val resolvedMember = receiverClass.resolveInstanceMember(ref.name)
|
||||||
|
if (resolvedMember?.declaringClass?.className == "Obj") {
|
||||||
|
return emitDynamicCall()
|
||||||
|
}
|
||||||
|
val abstractRecord = receiverClass.members[ref.name] ?: receiverClass.classScope?.objects?.get(ref.name)
|
||||||
|
if (abstractRecord?.isAbstract == true) {
|
||||||
|
return emitDynamicCall()
|
||||||
|
}
|
||||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
||||||
if (methodId != null) {
|
if (methodId != null && resolvedMember == null) {
|
||||||
|
return emitDynamicCall()
|
||||||
|
}
|
||||||
|
if (methodId != null && resolvedMember?.declaringClass?.className != "Obj") {
|
||||||
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId
|
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId
|
||||||
if (!ref.isOptional) {
|
if (!ref.isOptional) {
|
||||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||||
@ -3848,7 +4033,7 @@ class BytecodeCompiler(
|
|||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
val fieldId = receiverClass.instanceFieldIdMap()[ref.name]
|
val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[ref.name] else null
|
||||||
if (fieldId != null) {
|
if (fieldId != null) {
|
||||||
val encodedFieldId = encodeMemberId(receiverClass, fieldId) ?: fieldId
|
val encodedFieldId = encodeMemberId(receiverClass, fieldId) ?: fieldId
|
||||||
val calleeSlot = allocSlot()
|
val calleeSlot = allocSlot()
|
||||||
@ -4045,7 +4230,12 @@ class BytecodeCompiler(
|
|||||||
private fun resolveExtensionCallableSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
|
private fun resolveExtensionCallableSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
|
||||||
for (cls in receiverClass.mro) {
|
for (cls in receiverClass.mro) {
|
||||||
val candidate = extensionCallableName(cls.className, memberName)
|
val candidate = extensionCallableName(cls.className, memberName)
|
||||||
if (allowedScopeNames != null && !allowedScopeNames.contains(candidate)) continue
|
if (allowedScopeNames != null &&
|
||||||
|
!allowedScopeNames.contains(candidate) &&
|
||||||
|
!localSlotIndexByName.containsKey(candidate)
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
resolveDirectNameSlot(candidate)?.let { return it }
|
resolveDirectNameSlot(candidate)?.let { return it }
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@ -4054,7 +4244,12 @@ class BytecodeCompiler(
|
|||||||
private fun resolveExtensionGetterSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
|
private fun resolveExtensionGetterSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
|
||||||
for (cls in receiverClass.mro) {
|
for (cls in receiverClass.mro) {
|
||||||
val candidate = extensionPropertyGetterName(cls.className, memberName)
|
val candidate = extensionPropertyGetterName(cls.className, memberName)
|
||||||
if (allowedScopeNames != null && !allowedScopeNames.contains(candidate)) continue
|
if (allowedScopeNames != null &&
|
||||||
|
!allowedScopeNames.contains(candidate) &&
|
||||||
|
!localSlotIndexByName.containsKey(candidate)
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
resolveDirectNameSlot(candidate)?.let { return it }
|
resolveDirectNameSlot(candidate)?.let { return it }
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@ -4063,7 +4258,12 @@ class BytecodeCompiler(
|
|||||||
private fun resolveExtensionSetterSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
|
private fun resolveExtensionSetterSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
|
||||||
for (cls in receiverClass.mro) {
|
for (cls in receiverClass.mro) {
|
||||||
val candidate = extensionPropertySetterName(cls.className, memberName)
|
val candidate = extensionPropertySetterName(cls.className, memberName)
|
||||||
if (allowedScopeNames != null && !allowedScopeNames.contains(candidate)) continue
|
if (allowedScopeNames != null &&
|
||||||
|
!allowedScopeNames.contains(candidate) &&
|
||||||
|
!localSlotIndexByName.containsKey(candidate)
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
resolveDirectNameSlot(candidate)?.let { return it }
|
resolveDirectNameSlot(candidate)?.let { return it }
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@ -4339,9 +4539,12 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun emitDeclClass(stmt: net.sergeych.lyng.ClassDeclStatement): CompiledValue {
|
private fun emitDeclClass(stmt: net.sergeych.lyng.ClassDeclStatement): CompiledValue {
|
||||||
val constId = builder.addConst(BytecodeConst.ClassDecl(stmt.spec))
|
val constId = builder.addConst(BytecodeConst.ClassDecl(stmt.spec))
|
||||||
val dst = allocSlot()
|
val dst = stmt.spec.declaredName?.let { name ->
|
||||||
|
resolveDirectNameSlot(name)?.slot
|
||||||
|
} ?: allocSlot()
|
||||||
builder.emit(Opcode.DECL_CLASS, constId, dst)
|
builder.emit(Opcode.DECL_CLASS, constId, dst)
|
||||||
updateSlotType(dst, SlotType.OBJ)
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
stmt.spec.declaredName?.let { updateNameObjClassFromSlot(it, dst) }
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4658,9 +4861,6 @@ class BytecodeCompiler(
|
|||||||
if (shouldInlineBlock(stmt)) {
|
if (shouldInlineBlock(stmt)) {
|
||||||
return emitInlineStatements(stmt.statements(), needResult)
|
return emitInlineStatements(stmt.statements(), needResult)
|
||||||
}
|
}
|
||||||
val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.slotPlan, emptyList()))
|
|
||||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
|
||||||
resetAddrCache()
|
|
||||||
val statements = stmt.statements()
|
val statements = stmt.statements()
|
||||||
var lastValue: CompiledValue? = null
|
var lastValue: CompiledValue? = null
|
||||||
for ((index, statement) in statements.withIndex()) {
|
for ((index, statement) in statements.withIndex()) {
|
||||||
@ -4680,19 +4880,13 @@ class BytecodeCompiler(
|
|||||||
lastValue = value
|
lastValue = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val result = if (needResult) {
|
return if (needResult) {
|
||||||
var value = lastValue ?: run {
|
lastValue ?: run {
|
||||||
val slot = allocSlot()
|
val slot = allocSlot()
|
||||||
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||||
builder.emit(Opcode.CONST_OBJ, voidId, slot)
|
builder.emit(Opcode.CONST_OBJ, voidId, slot)
|
||||||
CompiledValue(slot, SlotType.OBJ)
|
CompiledValue(slot, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
if (value.slot < scopeSlotCount) {
|
|
||||||
val captured = allocSlot()
|
|
||||||
emitMove(value, captured)
|
|
||||||
value = CompiledValue(captured, value.type)
|
|
||||||
}
|
|
||||||
value
|
|
||||||
} else {
|
} else {
|
||||||
lastValue ?: run {
|
lastValue ?: run {
|
||||||
val slot = allocSlot()
|
val slot = allocSlot()
|
||||||
@ -4701,9 +4895,6 @@ class BytecodeCompiler(
|
|||||||
CompiledValue(slot, SlotType.OBJ)
|
CompiledValue(slot, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.emit(Opcode.POP_SCOPE)
|
|
||||||
resetAddrCache()
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emitTry(stmt: net.sergeych.lyng.TryStatement, needResult: Boolean): CompiledValue? {
|
private fun emitTry(stmt: net.sergeych.lyng.TryStatement, needResult: Boolean): CompiledValue? {
|
||||||
@ -4801,15 +4992,8 @@ class BytecodeCompiler(
|
|||||||
): CompiledValue? {
|
): CompiledValue? {
|
||||||
val stmt = block as? BlockStatement
|
val stmt = block as? BlockStatement
|
||||||
if (stmt == null) {
|
if (stmt == null) {
|
||||||
val declId = builder.addConst(BytecodeConst.LocalDecl(catchVarName, false, Visibility.Public, false))
|
|
||||||
builder.emit(Opcode.DECL_LOCAL, declId, exceptionSlot)
|
|
||||||
return compileStatementValueOrFallback(block, needResult)
|
return compileStatementValueOrFallback(block, needResult)
|
||||||
}
|
}
|
||||||
val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.slotPlan, emptyList()))
|
|
||||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
|
||||||
resetAddrCache()
|
|
||||||
val declId = builder.addConst(BytecodeConst.LocalDecl(catchVarName, false, Visibility.Public, false))
|
|
||||||
builder.emit(Opcode.DECL_LOCAL, declId, exceptionSlot)
|
|
||||||
val catchSlotIndex = stmt.slotPlan[catchVarName]
|
val catchSlotIndex = stmt.slotPlan[catchVarName]
|
||||||
if (catchSlotIndex != null) {
|
if (catchSlotIndex != null) {
|
||||||
val key = ScopeSlotKey(stmt.scopeId, catchSlotIndex)
|
val key = ScopeSlotKey(stmt.scopeId, catchSlotIndex)
|
||||||
@ -4847,8 +5031,6 @@ class BytecodeCompiler(
|
|||||||
CompiledValue(slot, SlotType.OBJ)
|
CompiledValue(slot, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.emit(Opcode.POP_SCOPE)
|
|
||||||
resetAddrCache()
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4931,40 +5113,9 @@ class BytecodeCompiler(
|
|||||||
private fun emitVarDecl(stmt: VarDeclStatement): CompiledValue? {
|
private fun emitVarDecl(stmt: VarDeclStatement): CompiledValue? {
|
||||||
updateNameObjClass(stmt.name, stmt.initializer, stmt.initializerObjClass)
|
updateNameObjClass(stmt.name, stmt.initializer, stmt.initializerObjClass)
|
||||||
val scopeId = stmt.scopeId ?: 0
|
val scopeId = stmt.scopeId ?: 0
|
||||||
val isModuleSlot = isModuleSlot(scopeId, stmt.name)
|
|
||||||
val scopeSlot = stmt.slotIndex?.let { slotIndex ->
|
val scopeSlot = stmt.slotIndex?.let { slotIndex ->
|
||||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||||
scopeSlotMap[key]
|
scopeSlotMap[key]
|
||||||
} ?: run {
|
|
||||||
if (isModuleSlot) {
|
|
||||||
scopeSlotIndexByName[stmt.name]
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isModuleSlot && scopeSlot != null) {
|
|
||||||
val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run {
|
|
||||||
val unsetId = builder.addConst(BytecodeConst.ObjRef(ObjUnset))
|
|
||||||
builder.emit(Opcode.CONST_OBJ, unsetId, scopeSlot)
|
|
||||||
updateSlotType(scopeSlot, SlotType.OBJ)
|
|
||||||
CompiledValue(scopeSlot, SlotType.OBJ)
|
|
||||||
}
|
|
||||||
if (value.slot != scopeSlot) {
|
|
||||||
emitMove(value, scopeSlot)
|
|
||||||
}
|
|
||||||
updateSlotType(scopeSlot, value.type)
|
|
||||||
updateNameObjClassFromSlot(stmt.name, scopeSlot)
|
|
||||||
updateSlotObjClass(scopeSlot, stmt.initializer, stmt.initializerObjClass)
|
|
||||||
val declId = builder.addConst(
|
|
||||||
BytecodeConst.LocalDecl(
|
|
||||||
stmt.name,
|
|
||||||
stmt.isMutable,
|
|
||||||
stmt.visibility,
|
|
||||||
stmt.isTransient
|
|
||||||
)
|
|
||||||
)
|
|
||||||
builder.emit(Opcode.DECL_LOCAL, declId, scopeSlot)
|
|
||||||
return CompiledValue(scopeSlot, value.type)
|
|
||||||
}
|
}
|
||||||
val localSlot = if (allowLocalSlots && stmt.slotIndex != null) {
|
val localSlot = if (allowLocalSlots && stmt.slotIndex != null) {
|
||||||
val key = ScopeSlotKey(scopeId, stmt.slotIndex)
|
val key = ScopeSlotKey(scopeId, stmt.slotIndex)
|
||||||
@ -5182,7 +5333,6 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null
|
val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null
|
||||||
val loopSlotPlan = stmt.loopSlotPlan
|
val loopSlotPlan = stmt.loopSlotPlan
|
||||||
var useLoopScope = loopSlotPlan.isNotEmpty()
|
|
||||||
val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
|
val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
|
||||||
val loopKey = loopSlotIndex?.let { ScopeSlotKey(stmt.loopScopeId, it) }
|
val loopKey = loopSlotIndex?.let { ScopeSlotKey(stmt.loopScopeId, it) }
|
||||||
val loopLocalIndex = loopKey?.let { localSlotIndexByKey[it] } ?: localSlotIndexByName[stmt.loopVarName]
|
val loopLocalIndex = loopKey?.let { localSlotIndexByKey[it] } ?: localSlotIndexByName[stmt.loopVarName]
|
||||||
@ -5203,47 +5353,13 @@ class BytecodeCompiler(
|
|||||||
usedOverride = true
|
usedOverride = true
|
||||||
slot
|
slot
|
||||||
}
|
}
|
||||||
var emitDeclLocal = usedOverride
|
// Loop scopes are intentionally disabled; loop vars live in frame slots only.
|
||||||
if (useLoopScope) {
|
|
||||||
val loopVarOnly = loopSlotPlan.size == 1 && loopSlotPlan.containsKey(stmt.loopVarName)
|
|
||||||
val loopVarIsLocal = loopSlotId >= scopeSlotCount
|
|
||||||
if (loopVarOnly && loopVarIsLocal) {
|
|
||||||
useLoopScope = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (useLoopScope && allowLocalSlots) {
|
|
||||||
val needsScope = allowedScopeNames?.let { names ->
|
|
||||||
loopSlotPlan.keys.any { names.contains(it) }
|
|
||||||
} == true
|
|
||||||
if (!needsScope) {
|
|
||||||
useLoopScope = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emitDeclLocal = emitDeclLocal && useLoopScope
|
|
||||||
if (loopSlotId < scopeSlotCount) {
|
if (loopSlotId < scopeSlotCount) {
|
||||||
val localSlot = allocSlot()
|
val localSlot = allocSlot()
|
||||||
loopSlotOverrides[stmt.loopVarName] = localSlot
|
loopSlotOverrides[stmt.loopVarName] = localSlot
|
||||||
usedOverride = true
|
usedOverride = true
|
||||||
emitDeclLocal = false
|
|
||||||
loopSlotId = localSlot
|
loopSlotId = localSlot
|
||||||
}
|
}
|
||||||
val planId = if (useLoopScope) {
|
|
||||||
builder.addConst(BytecodeConst.SlotPlan(loopSlotPlan, emptyList()))
|
|
||||||
} else {
|
|
||||||
-1
|
|
||||||
}
|
|
||||||
val loopDeclId = if (emitDeclLocal) {
|
|
||||||
builder.addConst(
|
|
||||||
BytecodeConst.LocalDecl(
|
|
||||||
stmt.loopVarName,
|
|
||||||
true,
|
|
||||||
Visibility.Public,
|
|
||||||
isTransient = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
-1
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (range == null && rangeRef == null && typedRangeLocal == null) {
|
if (range == null && rangeRef == null && typedRangeLocal == null) {
|
||||||
@ -5284,10 +5400,6 @@ class BytecodeCompiler(
|
|||||||
val loopLabel = builder.label()
|
val loopLabel = builder.label()
|
||||||
val continueLabel = builder.label()
|
val continueLabel = builder.label()
|
||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
if (useLoopScope) {
|
|
||||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
|
||||||
resetAddrCache()
|
|
||||||
}
|
|
||||||
builder.mark(loopLabel)
|
builder.mark(loopLabel)
|
||||||
|
|
||||||
val hasNextSlot = allocSlot()
|
val hasNextSlot = allocSlot()
|
||||||
@ -5305,9 +5417,6 @@ class BytecodeCompiler(
|
|||||||
emitMove(CompiledValue(nextObj.slot, SlotType.OBJ), loopSlotId)
|
emitMove(CompiledValue(nextObj.slot, SlotType.OBJ), loopSlotId)
|
||||||
updateSlotType(loopSlotId, SlotType.OBJ)
|
updateSlotType(loopSlotId, SlotType.OBJ)
|
||||||
updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ)
|
updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ)
|
||||||
if (emitDeclLocal) {
|
|
||||||
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
|
|
||||||
}
|
|
||||||
|
|
||||||
loopStack.addLast(
|
loopStack.addLast(
|
||||||
LoopContext(
|
LoopContext(
|
||||||
@ -5349,10 +5458,6 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
builder.mark(afterElse)
|
builder.mark(afterElse)
|
||||||
}
|
}
|
||||||
if (useLoopScope) {
|
|
||||||
builder.emit(Opcode.POP_SCOPE)
|
|
||||||
resetAddrCache()
|
|
||||||
}
|
|
||||||
return resultSlot
|
return resultSlot
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5398,10 +5503,6 @@ class BytecodeCompiler(
|
|||||||
val continueLabel = builder.label()
|
val continueLabel = builder.label()
|
||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
val doneLabel = builder.label()
|
val doneLabel = builder.label()
|
||||||
if (useLoopScope) {
|
|
||||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
|
||||||
resetAddrCache()
|
|
||||||
}
|
|
||||||
builder.mark(loopLabel)
|
builder.mark(loopLabel)
|
||||||
val cmpSlot = allocSlot()
|
val cmpSlot = allocSlot()
|
||||||
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
|
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
|
||||||
@ -5412,9 +5513,6 @@ class BytecodeCompiler(
|
|||||||
emitMove(CompiledValue(iSlot, SlotType.INT), loopSlotId)
|
emitMove(CompiledValue(iSlot, SlotType.INT), loopSlotId)
|
||||||
updateSlotType(loopSlotId, SlotType.INT)
|
updateSlotType(loopSlotId, SlotType.INT)
|
||||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||||
if (emitDeclLocal) {
|
|
||||||
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
|
|
||||||
}
|
|
||||||
loopStack.addLast(
|
loopStack.addLast(
|
||||||
LoopContext(
|
LoopContext(
|
||||||
stmt.label,
|
stmt.label,
|
||||||
@ -5449,10 +5547,6 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
builder.mark(afterElse)
|
builder.mark(afterElse)
|
||||||
}
|
}
|
||||||
if (useLoopScope) {
|
|
||||||
builder.emit(Opcode.POP_SCOPE)
|
|
||||||
resetAddrCache()
|
|
||||||
}
|
|
||||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(doneLabel)))
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(doneLabel)))
|
||||||
builder.mark(badRangeLabel)
|
builder.mark(badRangeLabel)
|
||||||
val msgId = builder.addConst(BytecodeConst.StringVal("expected Int range"))
|
val msgId = builder.addConst(BytecodeConst.StringVal("expected Int range"))
|
||||||
@ -5475,10 +5569,6 @@ class BytecodeCompiler(
|
|||||||
val loopLabel = builder.label()
|
val loopLabel = builder.label()
|
||||||
val continueLabel = builder.label()
|
val continueLabel = builder.label()
|
||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
if (useLoopScope) {
|
|
||||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
|
||||||
resetAddrCache()
|
|
||||||
}
|
|
||||||
builder.mark(loopLabel)
|
builder.mark(loopLabel)
|
||||||
val cmpSlot = allocSlot()
|
val cmpSlot = allocSlot()
|
||||||
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
|
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
|
||||||
@ -5489,9 +5579,6 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
|
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
|
||||||
updateSlotType(loopSlotId, SlotType.INT)
|
updateSlotType(loopSlotId, SlotType.INT)
|
||||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||||
if (emitDeclLocal) {
|
|
||||||
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
|
|
||||||
}
|
|
||||||
loopStack.addLast(
|
loopStack.addLast(
|
||||||
LoopContext(
|
LoopContext(
|
||||||
stmt.label,
|
stmt.label,
|
||||||
@ -5526,10 +5613,6 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
builder.mark(afterElse)
|
builder.mark(afterElse)
|
||||||
}
|
}
|
||||||
if (useLoopScope) {
|
|
||||||
builder.emit(Opcode.POP_SCOPE)
|
|
||||||
resetAddrCache()
|
|
||||||
}
|
|
||||||
return resultSlot
|
return resultSlot
|
||||||
} finally {
|
} finally {
|
||||||
if (usedOverride) {
|
if (usedOverride) {
|
||||||
@ -5550,13 +5633,8 @@ class BytecodeCompiler(
|
|||||||
val loopLabel = builder.label()
|
val loopLabel = builder.label()
|
||||||
val continueLabel = builder.label()
|
val continueLabel = builder.label()
|
||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(continueLabel)))
|
||||||
builder.mark(loopLabel)
|
builder.mark(loopLabel)
|
||||||
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
|
|
||||||
if (condition.type != SlotType.BOOL) return null
|
|
||||||
builder.emit(
|
|
||||||
Opcode.JMP_IF_FALSE,
|
|
||||||
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(endLabel))
|
|
||||||
)
|
|
||||||
loopStack.addLast(
|
loopStack.addLast(
|
||||||
LoopContext(
|
LoopContext(
|
||||||
stmt.label,
|
stmt.label,
|
||||||
@ -5574,7 +5652,12 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
||||||
}
|
}
|
||||||
builder.mark(continueLabel)
|
builder.mark(continueLabel)
|
||||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
|
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
|
||||||
|
if (condition.type != SlotType.BOOL) return null
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel))
|
||||||
|
)
|
||||||
|
|
||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
if (stmt.elseStatement != null) {
|
if (stmt.elseStatement != null) {
|
||||||
@ -5605,18 +5688,8 @@ class BytecodeCompiler(
|
|||||||
val loopLabel = builder.label()
|
val loopLabel = builder.label()
|
||||||
val continueLabel = builder.label()
|
val continueLabel = builder.label()
|
||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
val useLoopScope = stmt.loopSlotPlan.isNotEmpty()
|
val breakLabel = endLabel
|
||||||
val breakLabel = if (useLoopScope) builder.label() else endLabel
|
|
||||||
val planId = if (useLoopScope) {
|
|
||||||
builder.addConst(BytecodeConst.SlotPlan(stmt.loopSlotPlan, emptyList()))
|
|
||||||
} else {
|
|
||||||
-1
|
|
||||||
}
|
|
||||||
builder.mark(loopLabel)
|
builder.mark(loopLabel)
|
||||||
if (useLoopScope) {
|
|
||||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
|
||||||
resetAddrCache()
|
|
||||||
}
|
|
||||||
loopStack.addLast(
|
loopStack.addLast(
|
||||||
LoopContext(
|
LoopContext(
|
||||||
stmt.label,
|
stmt.label,
|
||||||
@ -5627,12 +5700,7 @@ class BytecodeCompiler(
|
|||||||
hasIterator = false
|
hasIterator = false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val bodyTarget = if (stmt.body is BytecodeStatement) stmt.body.original else stmt.body
|
val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null
|
||||||
val bodyValue = if (useLoopScope && bodyTarget is BlockStatement) {
|
|
||||||
emitInlineBlock(bodyTarget, wantResult)
|
|
||||||
} else {
|
|
||||||
compileStatementValueOrFallback(stmt.body, wantResult)
|
|
||||||
} ?: return null
|
|
||||||
loopStack.removeLast()
|
loopStack.removeLast()
|
||||||
if (wantResult) {
|
if (wantResult) {
|
||||||
val bodyObj = ensureObjSlot(bodyValue)
|
val bodyObj = ensureObjSlot(bodyValue)
|
||||||
@ -5641,20 +5709,10 @@ class BytecodeCompiler(
|
|||||||
builder.mark(continueLabel)
|
builder.mark(continueLabel)
|
||||||
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
|
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
|
||||||
if (condition.type != SlotType.BOOL) return null
|
if (condition.type != SlotType.BOOL) return null
|
||||||
if (useLoopScope) {
|
|
||||||
builder.emit(Opcode.POP_SCOPE)
|
|
||||||
resetAddrCache()
|
|
||||||
}
|
|
||||||
builder.emit(
|
builder.emit(
|
||||||
Opcode.JMP_IF_TRUE,
|
Opcode.JMP_IF_TRUE,
|
||||||
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel))
|
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel))
|
||||||
)
|
)
|
||||||
if (useLoopScope) {
|
|
||||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
|
||||||
builder.mark(breakLabel)
|
|
||||||
builder.emit(Opcode.POP_SCOPE)
|
|
||||||
resetAddrCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
if (stmt.elseStatement != null) {
|
if (stmt.elseStatement != null) {
|
||||||
@ -6094,6 +6152,21 @@ class BytecodeCompiler(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun compileScopeSlotObj(ref: ObjRef): CompiledValue? {
|
||||||
|
val slot = when (ref) {
|
||||||
|
is LocalSlotRef -> resolveSlot(ref)
|
||||||
|
is LocalVarRef -> scopeSlotIndexByName[ref.name]
|
||||||
|
is FastLocalVarRef -> scopeSlotIndexByName[ref.name]
|
||||||
|
else -> null
|
||||||
|
} ?: return null
|
||||||
|
if (slot >= scopeSlotCount) return null
|
||||||
|
val addrSlot = ensureScopeAddr(slot)
|
||||||
|
val local = allocSlot()
|
||||||
|
emitLoadFromAddr(addrSlot, local, SlotType.OBJ)
|
||||||
|
updateSlotType(local, SlotType.OBJ)
|
||||||
|
return CompiledValue(local, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
|
||||||
private fun resolveReceiverClass(ref: ObjRef): ObjClass? {
|
private fun resolveReceiverClass(ref: ObjRef): ObjClass? {
|
||||||
return when (ref) {
|
return when (ref) {
|
||||||
is LocalSlotRef -> {
|
is LocalSlotRef -> {
|
||||||
@ -6189,6 +6262,14 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isThisReceiver(ref: ObjRef): Boolean {
|
||||||
|
return when (ref) {
|
||||||
|
is LocalSlotRef -> ref.name == "this"
|
||||||
|
is QualifiedThisRef -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun isAllowedObjectMember(memberName: String): Boolean {
|
private fun isAllowedObjectMember(memberName: String): Boolean {
|
||||||
return when (memberName) {
|
return when (memberName) {
|
||||||
"toString",
|
"toString",
|
||||||
@ -6484,6 +6565,8 @@ class BytecodeCompiler(
|
|||||||
val scopeId = refScopeId(ref)
|
val scopeId = refScopeId(ref)
|
||||||
if (!ref.isDelegated && isModuleSlot(scopeId, ref.name)) {
|
if (!ref.isDelegated && isModuleSlot(scopeId, ref.name)) {
|
||||||
val key = ScopeSlotKey(scopeId, refSlot(ref))
|
val key = ScopeSlotKey(scopeId, refSlot(ref))
|
||||||
|
val localIndex = localSlotIndexByKey[key]
|
||||||
|
if (localIndex != null) return scopeSlotCount + localIndex
|
||||||
scopeSlotMap[key]?.let { return it }
|
scopeSlotMap[key]?.let { return it }
|
||||||
scopeSlotIndexByName[ref.name]?.let { return it }
|
scopeSlotIndexByName[ref.name]?.let { return it }
|
||||||
}
|
}
|
||||||
@ -6495,29 +6578,16 @@ class BytecodeCompiler(
|
|||||||
return scopeSlotCount + localIndex
|
return scopeSlotCount + localIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val nameLocal = localSlotIndexByName[ref.name]
|
|
||||||
if (nameLocal != null && localSlotCaptures.getOrNull(nameLocal) == true) {
|
|
||||||
return scopeSlotCount + nameLocal
|
|
||||||
}
|
|
||||||
for (idx in localSlotNames.indices) {
|
|
||||||
if (localSlotNames[idx] != ref.name) continue
|
|
||||||
if (localSlotCaptures.getOrNull(idx) != true) continue
|
|
||||||
return scopeSlotCount + idx
|
|
||||||
}
|
|
||||||
return scopeSlotMap[scopeKey]
|
return scopeSlotMap[scopeKey]
|
||||||
}
|
}
|
||||||
if (ref.isDelegated) {
|
if (ref.isDelegated) {
|
||||||
val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
|
val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
|
||||||
val localIndex = localSlotIndexByKey[localKey]
|
val localIndex = localSlotIndexByKey[localKey]
|
||||||
if (localIndex != null) return scopeSlotCount + localIndex
|
if (localIndex != null) return scopeSlotCount + localIndex
|
||||||
val nameIndex = localSlotIndexByName[ref.name]
|
|
||||||
if (nameIndex != null) return scopeSlotCount + nameIndex
|
|
||||||
}
|
}
|
||||||
val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
|
val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
|
||||||
val localIndex = localSlotIndexByKey[localKey]
|
val localIndex = localSlotIndexByKey[localKey]
|
||||||
if (localIndex != null) return scopeSlotCount + localIndex
|
if (localIndex != null) return scopeSlotCount + localIndex
|
||||||
val nameIndex = localSlotIndexByName[ref.name]
|
|
||||||
if (nameIndex != null) return scopeSlotCount + nameIndex
|
|
||||||
val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
|
val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
|
||||||
return scopeSlotMap[scopeKey]
|
return scopeSlotMap[scopeKey]
|
||||||
}
|
}
|
||||||
@ -6705,6 +6775,7 @@ class BytecodeCompiler(
|
|||||||
is VarDeclStatement -> {
|
is VarDeclStatement -> {
|
||||||
val slotIndex = stmt.slotIndex
|
val slotIndex = stmt.slotIndex
|
||||||
val scopeId = stmt.scopeId ?: 0
|
val scopeId = stmt.scopeId ?: 0
|
||||||
|
val isModuleDecl = moduleScopeId != null && scopeId == moduleScopeId
|
||||||
val cls = stmt.initializerObjClass ?: objClassForInitializer(stmt.initializer)
|
val cls = stmt.initializerObjClass ?: objClassForInitializer(stmt.initializer)
|
||||||
if (cls != null) {
|
if (cls != null) {
|
||||||
nameObjClass[stmt.name] = cls
|
nameObjClass[stmt.name] = cls
|
||||||
@ -6712,8 +6783,7 @@ class BytecodeCompiler(
|
|||||||
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
|
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val isModuleSlot = isModuleSlot(scopeId, stmt.name)
|
if (allowLocalSlots && slotIndex != null && !isModuleDecl) {
|
||||||
if (allowLocalSlots && slotIndex != null && !isModuleSlot) {
|
|
||||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||||
declaredLocalKeys.add(key)
|
declaredLocalKeys.add(key)
|
||||||
if (!localSlotInfoMap.containsKey(key)) {
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
@ -6740,7 +6810,8 @@ class BytecodeCompiler(
|
|||||||
val scopeId = stmt.spec.scopeId ?: 0
|
val scopeId = stmt.spec.scopeId ?: 0
|
||||||
if (slotIndex != null) {
|
if (slotIndex != null) {
|
||||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||||
if (allowLocalSlots) {
|
val isModuleDecl = moduleScopeId != null && scopeId == moduleScopeId
|
||||||
|
if (allowLocalSlots && !isModuleDecl) {
|
||||||
if (!localSlotInfoMap.containsKey(key)) {
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false)
|
localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false)
|
||||||
}
|
}
|
||||||
@ -6757,7 +6828,8 @@ class BytecodeCompiler(
|
|||||||
is DelegatedVarDeclStatement -> {
|
is DelegatedVarDeclStatement -> {
|
||||||
val slotIndex = stmt.slotIndex
|
val slotIndex = stmt.slotIndex
|
||||||
val scopeId = stmt.scopeId ?: 0
|
val scopeId = stmt.scopeId ?: 0
|
||||||
if (allowLocalSlots && slotIndex != null) {
|
val isModuleDecl = moduleScopeId != null && scopeId == moduleScopeId
|
||||||
|
if (allowLocalSlots && slotIndex != null && !isModuleDecl) {
|
||||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||||
declaredLocalKeys.add(key)
|
declaredLocalKeys.add(key)
|
||||||
if (!localSlotInfoMap.containsKey(key)) {
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
@ -6895,12 +6967,11 @@ class BytecodeCompiler(
|
|||||||
private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
|
private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
|
||||||
val moduleId = moduleScopeId
|
val moduleId = moduleScopeId
|
||||||
if (moduleId == null) {
|
if (moduleId == null) {
|
||||||
if (allowedScopeNames == null || name == null) return false
|
val scopeNames = scopeSlotNameSet ?: allowedScopeNames
|
||||||
return allowedScopeNames.contains(name)
|
if (scopeNames == null || name == null) return false
|
||||||
|
return scopeNames.contains(name)
|
||||||
}
|
}
|
||||||
if (scopeId != moduleId) return false
|
return scopeId == moduleId
|
||||||
if (allowedScopeNames == null || name == null) return true
|
|
||||||
return allowedScopeNames.contains(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectLoopVarNames(stmt: Statement) {
|
private fun collectLoopVarNames(stmt: Statement) {
|
||||||
|
|||||||
@ -43,6 +43,7 @@ class BytecodeStatement private constructor(
|
|||||||
returnLabels: Set<String> = emptySet(),
|
returnLabels: Set<String> = emptySet(),
|
||||||
rangeLocalNames: Set<String> = emptySet(),
|
rangeLocalNames: Set<String> = emptySet(),
|
||||||
allowedScopeNames: Set<String>? = null,
|
allowedScopeNames: Set<String>? = null,
|
||||||
|
scopeSlotNameSet: Set<String>? = null,
|
||||||
moduleScopeId: Int? = null,
|
moduleScopeId: Int? = null,
|
||||||
forcedLocalSlots: Map<String, Int> = emptyMap(),
|
forcedLocalSlots: Map<String, Int> = emptyMap(),
|
||||||
forcedLocalScopeId: Int? = null,
|
forcedLocalScopeId: Int? = null,
|
||||||
@ -71,6 +72,7 @@ class BytecodeStatement private constructor(
|
|||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
rangeLocalNames = rangeLocalNames,
|
rangeLocalNames = rangeLocalNames,
|
||||||
allowedScopeNames = allowedScopeNames,
|
allowedScopeNames = allowedScopeNames,
|
||||||
|
scopeSlotNameSet = scopeSlotNameSet,
|
||||||
moduleScopeId = moduleScopeId,
|
moduleScopeId = moduleScopeId,
|
||||||
forcedLocalSlots = forcedLocalSlots,
|
forcedLocalSlots = forcedLocalSlots,
|
||||||
forcedLocalScopeId = forcedLocalScopeId,
|
forcedLocalScopeId = forcedLocalScopeId,
|
||||||
|
|||||||
@ -2219,8 +2219,30 @@ class CmdGetClassScope(
|
|||||||
val scope = frame.ensureScope()
|
val scope = frame.ensureScope()
|
||||||
val cls = frame.slotToObj(classSlot) as? ObjClass
|
val cls = frame.slotToObj(classSlot) as? ObjClass
|
||||||
?: scope.raiseSymbolNotFound(nameConst.value)
|
?: scope.raiseSymbolNotFound(nameConst.value)
|
||||||
val rec = cls.readField(scope, nameConst.value)
|
val name = nameConst.value
|
||||||
val value = scope.resolve(rec, nameConst.value)
|
var rec: ObjRecord? = null
|
||||||
|
var decl: ObjClass? = null
|
||||||
|
for (c in cls.mro) {
|
||||||
|
if (c.className == "Obj") break
|
||||||
|
val candidate = c.classScope?.objects?.get(name) ?: c.members[name]
|
||||||
|
if (candidate == null || candidate.isAbstract) continue
|
||||||
|
val declared = candidate.declaringClass ?: c
|
||||||
|
if (!canAccessMember(candidate.visibility, declared, scope.currentClassCtx, name)) {
|
||||||
|
scope.raiseError(
|
||||||
|
ObjIllegalAccessException(
|
||||||
|
scope,
|
||||||
|
"can't access field ${name}: not visible (declared in ${declared.className}, caller ${scope.currentClassCtx?.className ?: "?"})"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rec = candidate
|
||||||
|
decl = declared
|
||||||
|
break
|
||||||
|
}
|
||||||
|
val resolved = rec ?: scope.raiseSymbolNotFound(name)
|
||||||
|
val declClass = decl ?: cls
|
||||||
|
val resolvedRec = cls.resolveRecord(scope, resolved, name, declClass)
|
||||||
|
val value = resolvedRec.value
|
||||||
frame.storeObjResult(dst, value)
|
frame.storeObjResult(dst, value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -3187,7 +3209,11 @@ class CmdFrame(
|
|||||||
SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse
|
SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse
|
||||||
SlotType.OBJ.code -> {
|
SlotType.OBJ.code -> {
|
||||||
val obj = frame.getObj(local)
|
val obj = frame.getObj(local)
|
||||||
if (obj is FrameSlotRef) obj.read() else obj
|
when (obj) {
|
||||||
|
is FrameSlotRef -> obj.read()
|
||||||
|
is RecordSlotRef -> obj.read()
|
||||||
|
else -> obj
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else -> ObjVoid
|
else -> ObjVoid
|
||||||
}
|
}
|
||||||
@ -3347,6 +3373,7 @@ class CmdFrame(
|
|||||||
val record = target.getSlotRecord(index)
|
val record = target.getSlotRecord(index)
|
||||||
val direct = record.value
|
val direct = record.value
|
||||||
if (direct is FrameSlotRef) return direct.read()
|
if (direct is FrameSlotRef) return direct.read()
|
||||||
|
if (direct is RecordSlotRef) return direct.read()
|
||||||
val name = fn.scopeSlotNames[slot]
|
val name = fn.scopeSlotNames[slot]
|
||||||
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)
|
||||||
@ -3368,6 +3395,7 @@ class CmdFrame(
|
|||||||
val record = target.getSlotRecord(index)
|
val record = target.getSlotRecord(index)
|
||||||
val direct = record.value
|
val direct = record.value
|
||||||
if (direct is FrameSlotRef) return direct.read()
|
if (direct is FrameSlotRef) 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.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)) {
|
||||||
|
|||||||
@ -265,13 +265,35 @@ open class Obj {
|
|||||||
open val objClass: ObjClass get() = rootObjectType
|
open val objClass: ObjClass get() = rootObjectType
|
||||||
|
|
||||||
open suspend fun plus(scope: Scope, other: Obj): Obj {
|
open suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||||
return invokeInstanceMethod(scope, "plus", Arguments(other)) {
|
val otherValue = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
val self = when (this) {
|
||||||
|
is FrameSlotRef -> this.read()
|
||||||
|
is RecordSlotRef -> this.read()
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
if (self !== this) return self.plus(scope, otherValue)
|
||||||
|
return invokeInstanceMethod(scope, "plus", Arguments(otherValue)) {
|
||||||
scope.raiseNotImplemented("plus for ${objClass.className}")
|
scope.raiseNotImplemented("plus for ${objClass.className}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun minus(scope: Scope, other: Obj): Obj {
|
open suspend fun minus(scope: Scope, other: Obj): Obj {
|
||||||
return invokeInstanceMethod(scope, "minus", Arguments(other)) {
|
val otherValue = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
val self = when (this) {
|
||||||
|
is FrameSlotRef -> this.read()
|
||||||
|
is RecordSlotRef -> this.read()
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
if (self !== this) return self.minus(scope, otherValue)
|
||||||
|
return invokeInstanceMethod(scope, "minus", Arguments(otherValue)) {
|
||||||
scope.raiseNotImplemented("minus for ${objClass.className}")
|
scope.raiseNotImplemented("minus for ${objClass.className}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -283,19 +305,52 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun mul(scope: Scope, other: Obj): Obj {
|
open suspend fun mul(scope: Scope, other: Obj): Obj {
|
||||||
return invokeInstanceMethod(scope, "mul", Arguments(other)) {
|
val otherValue = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
val self = when (this) {
|
||||||
|
is FrameSlotRef -> this.read()
|
||||||
|
is RecordSlotRef -> this.read()
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
if (self !== this) return self.mul(scope, otherValue)
|
||||||
|
return invokeInstanceMethod(scope, "mul", Arguments(otherValue)) {
|
||||||
scope.raiseNotImplemented("mul for ${objClass.className}")
|
scope.raiseNotImplemented("mul for ${objClass.className}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun div(scope: Scope, other: Obj): Obj {
|
open suspend fun div(scope: Scope, other: Obj): Obj {
|
||||||
return invokeInstanceMethod(scope, "div", Arguments(other)) {
|
val otherValue = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
val self = when (this) {
|
||||||
|
is FrameSlotRef -> this.read()
|
||||||
|
is RecordSlotRef -> this.read()
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
if (self !== this) return self.div(scope, otherValue)
|
||||||
|
return invokeInstanceMethod(scope, "div", Arguments(otherValue)) {
|
||||||
scope.raiseNotImplemented("div for ${objClass.className}")
|
scope.raiseNotImplemented("div for ${objClass.className}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun mod(scope: Scope, other: Obj): Obj {
|
open suspend fun mod(scope: Scope, other: Obj): Obj {
|
||||||
return invokeInstanceMethod(scope, "mod", Arguments(other)) {
|
val otherValue = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
val self = when (this) {
|
||||||
|
is FrameSlotRef -> this.read()
|
||||||
|
is RecordSlotRef -> this.read()
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
if (self !== this) return self.mod(scope, otherValue)
|
||||||
|
return invokeInstanceMethod(scope, "mod", Arguments(otherValue)) {
|
||||||
scope.raiseNotImplemented("mod for ${objClass.className}")
|
scope.raiseNotImplemented("mod for ${objClass.className}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -755,6 +810,14 @@ open class Obj {
|
|||||||
thisObj.putAt(this, requiredArg<Obj>(0), newValue)
|
thisObj.putAt(this, requiredArg<Obj>(0), newValue)
|
||||||
newValue
|
newValue
|
||||||
}
|
}
|
||||||
|
addFnDoc(
|
||||||
|
name = "toJson",
|
||||||
|
doc = "Encodes this object to JSON.",
|
||||||
|
returns = type("lyng.String"),
|
||||||
|
moduleName = "lyng.stdlib"
|
||||||
|
) {
|
||||||
|
thisObj.toJson(this).toString().toObj()
|
||||||
|
}
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "toJsonString",
|
name = "toJsonString",
|
||||||
doc = "Encodes this object to a JSON string.",
|
doc = "Encodes this object to a JSON string.",
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import net.sergeych.lyng.Scope
|
|||||||
import net.sergeych.lyng.miniast.addConstDoc
|
import net.sergeych.lyng.miniast.addConstDoc
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
import net.sergeych.lyng.miniast.addFnDoc
|
||||||
import net.sergeych.lyng.miniast.type
|
import net.sergeych.lyng.miniast.type
|
||||||
import net.sergeych.lyng.statement
|
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
import net.sergeych.lynon.LynonType
|
import net.sergeych.lynon.LynonType
|
||||||
|
|||||||
@ -35,22 +35,7 @@ sealed interface ObjRef {
|
|||||||
* Default implementation calls [get] and returns its value. Nodes can override to avoid record traffic.
|
* Default implementation calls [get] and returns its value. Nodes can override to avoid record traffic.
|
||||||
*/
|
*/
|
||||||
suspend fun evalValue(scope: Scope): Obj {
|
suspend fun evalValue(scope: Scope): Obj {
|
||||||
val rec = get(scope)
|
scope.raiseIllegalState("interpreter execution is not supported; ObjRef evaluation requires bytecode")
|
||||||
if (rec.type == ObjRecord.Type.Delegated) {
|
|
||||||
val receiver = rec.receiver ?: scope.thisObj
|
|
||||||
// Use resolve to handle delegated property logic
|
|
||||||
return scope.resolve(rec, "unknown")
|
|
||||||
}
|
|
||||||
// Template record: must map to instance storage
|
|
||||||
if (rec.receiver != null && rec.declaringClass != null) {
|
|
||||||
return rec.receiver!!.resolveRecord(scope, rec, "unknown", rec.declaringClass).value
|
|
||||||
}
|
|
||||||
val value = rec.value
|
|
||||||
return when (value) {
|
|
||||||
is FrameSlotRef -> value.read()
|
|
||||||
is RecordSlotRef -> value.read()
|
|
||||||
else -> value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
throw ScriptError(pos, "can't assign value")
|
throw ScriptError(pos, "can't assign value")
|
||||||
|
|||||||
@ -19,17 +19,6 @@ package 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.ObjException
|
|
||||||
import net.sergeych.lyng.obj.ObjInt
|
|
||||||
import net.sergeych.lyng.obj.ObjIterable
|
|
||||||
import net.sergeych.lyng.obj.ObjNull
|
|
||||||
import net.sergeych.lyng.obj.ObjRange
|
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
|
||||||
import net.sergeych.lyng.obj.ObjString
|
|
||||||
import net.sergeych.lyng.obj.ObjVoid
|
|
||||||
import net.sergeych.lyng.obj.toBool
|
|
||||||
import net.sergeych.lyng.obj.toInt
|
|
||||||
import net.sergeych.lyng.obj.toLong
|
|
||||||
|
|
||||||
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
||||||
|
|
||||||
@ -105,117 +94,6 @@ class ForInStatement(
|
|||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
return interpreterDisabled(scope, "for-in statement")
|
return interpreterDisabled(scope, "for-in statement")
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loopIntRange(
|
|
||||||
forScope: Scope,
|
|
||||||
start: Long,
|
|
||||||
end: Long,
|
|
||||||
loopVar: ObjRecord,
|
|
||||||
loopSlotIndex: Int,
|
|
||||||
body: Statement,
|
|
||||||
elseStatement: Statement?,
|
|
||||||
label: String?,
|
|
||||||
catchBreak: Boolean,
|
|
||||||
): Obj {
|
|
||||||
var result: Obj = ObjVoid
|
|
||||||
val cacheLow = ObjInt.CACHE_LOW
|
|
||||||
val cacheHigh = ObjInt.CACHE_HIGH
|
|
||||||
val useCache = start >= cacheLow && end <= cacheHigh + 1
|
|
||||||
val cache = if (useCache) ObjInt.cacheArray() else null
|
|
||||||
val useSlot = loopSlotIndex >= 0
|
|
||||||
if (catchBreak) {
|
|
||||||
if (useCache && cache != null) {
|
|
||||||
var i = start
|
|
||||||
while (i < end) {
|
|
||||||
val v = cache[(i - cacheLow).toInt()]
|
|
||||||
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
|
||||||
try {
|
|
||||||
result = body.execute(forScope)
|
|
||||||
} catch (lbe: LoopBreakContinueException) {
|
|
||||||
if (lbe.label == label || lbe.label == null) {
|
|
||||||
if (lbe.doContinue) {
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return lbe.result
|
|
||||||
}
|
|
||||||
throw lbe
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (i in start..<end) {
|
|
||||||
val v = ObjInt.of(i)
|
|
||||||
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
|
||||||
try {
|
|
||||||
result = body.execute(forScope)
|
|
||||||
} catch (lbe: LoopBreakContinueException) {
|
|
||||||
if (lbe.label == label || lbe.label == null) {
|
|
||||||
if (lbe.doContinue) continue
|
|
||||||
return lbe.result
|
|
||||||
}
|
|
||||||
throw lbe
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (useCache && cache != null) {
|
|
||||||
var i = start
|
|
||||||
while (i < end) {
|
|
||||||
val v = cache[(i - cacheLow).toInt()]
|
|
||||||
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
|
||||||
result = body.execute(forScope)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (i in start..<end) {
|
|
||||||
val v = ObjInt.of(i)
|
|
||||||
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
|
||||||
result = body.execute(forScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return elseStatement?.execute(forScope) ?: result
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun loopIterable(
|
|
||||||
forScope: Scope,
|
|
||||||
sourceObj: Obj,
|
|
||||||
loopVar: ObjRecord,
|
|
||||||
body: Statement,
|
|
||||||
elseStatement: Statement?,
|
|
||||||
label: String?,
|
|
||||||
catchBreak: Boolean,
|
|
||||||
): Obj {
|
|
||||||
var result: Obj = ObjVoid
|
|
||||||
var breakCaught = false
|
|
||||||
sourceObj.enumerate(forScope) { item ->
|
|
||||||
loopVar.value = item
|
|
||||||
if (catchBreak) {
|
|
||||||
try {
|
|
||||||
result = body.execute(forScope)
|
|
||||||
true
|
|
||||||
} catch (lbe: LoopBreakContinueException) {
|
|
||||||
if (lbe.label == label || lbe.label == null) {
|
|
||||||
breakCaught = true
|
|
||||||
if (lbe.doContinue) true else {
|
|
||||||
result = lbe.result
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw lbe
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = body.execute(forScope)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!breakCaught && elseStatement != null) {
|
|
||||||
result = elseStatement.execute(forScope)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class WhileStatement(
|
class WhileStatement(
|
||||||
@ -299,18 +177,6 @@ fun Statement.require(cond: Boolean, message: () -> String) {
|
|||||||
if (!cond) raise(message())
|
if (!cond) raise(message())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Scope) -> Obj): Statement =
|
|
||||||
object : Statement(isStaticConst, isConst) {
|
|
||||||
override val pos: Pos = pos
|
|
||||||
override suspend fun execute(scope: Scope): Obj = interpreterDisabled(scope, "statement bridge")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend Scope.() -> Obj): Statement =
|
|
||||||
object : Statement(isStaticConst, isConst) {
|
|
||||||
override val pos: Pos = Pos.builtIn
|
|
||||||
override suspend fun execute(scope: Scope): Obj = interpreterDisabled(scope, "statement bridge")
|
|
||||||
}
|
|
||||||
|
|
||||||
object NopStatement: Statement(true, true, ObjType.Void) {
|
object NopStatement: Statement(true, true, ObjType.Void) {
|
||||||
override val pos: Pos = Pos.builtIn
|
override val pos: Pos = Pos.builtIn
|
||||||
|
|
||||||
|
|||||||
@ -397,4 +397,5 @@ class BytecodeRecentOpsTest {
|
|||||||
val result = script.execute(Script.defaultImportManager.newStdScope())
|
val result = script.execute(Script.defaultImportManager.newStdScope())
|
||||||
assertEquals(2, result.toInt())
|
assertEquals(2, result.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -656,6 +656,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun whileTest() = runTest {
|
fun whileTest() = runTest {
|
||||||
|
withTimeout(2.seconds) {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
5.0,
|
5.0,
|
||||||
eval(
|
eval(
|
||||||
@ -707,6 +708,7 @@ class ScriptTest {
|
|||||||
).toDouble()
|
).toDouble()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testAssignArgumentsNoEllipsis() = runTest {
|
fun testAssignArgumentsNoEllipsis() = runTest {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user