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 net.sergeych.lyng.Compiler
|
||||
import net.sergeych.lyng.LyngVersion
|
||||
import net.sergeych.lyng.Pos
|
||||
import net.sergeych.lyng.Script
|
||||
import net.sergeych.lyng.ScriptError
|
||||
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 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 execute: String? by option(
|
||||
"-x", "--execute", help = """
|
||||
@ -170,7 +168,7 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
||||
|
||||
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:
|
||||
https://gitea.sergeych.net/SergeychWorks/lyng
|
||||
@ -201,15 +199,12 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
||||
launcher {
|
||||
// there is no script name, it is a first argument instead:
|
||||
processErrors {
|
||||
val reporter = bytecodeFallbackReporter(bytecodeFallbacks)
|
||||
val script = Compiler.compileWithResolution(
|
||||
Source("<eval>", execute!!),
|
||||
baseScope.currentImportProvider,
|
||||
seedScope = baseScope,
|
||||
bytecodeFallbackReporter = reporter
|
||||
seedScope = baseScope
|
||||
)
|
||||
script.execute(baseScope)
|
||||
flushBytecodeFallbacks(reporter)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -220,7 +215,7 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
||||
echoFormattedHelp()
|
||||
} else {
|
||||
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 {
|
||||
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 ->
|
||||
fileSource.buffer().use { bs ->
|
||||
bs.readUtf8()
|
||||
@ -248,15 +243,12 @@ suspend fun executeFile(fileName: String, reportBytecodeFallbacks: Boolean = fal
|
||||
}
|
||||
processErrors {
|
||||
val scope = baseScopeDefer.await()
|
||||
val reporter = bytecodeFallbackReporter(reportBytecodeFallbacks)
|
||||
val script = Compiler.compileWithResolution(
|
||||
Source(fileName, text),
|
||||
scope.currentImportProvider,
|
||||
seedScope = scope,
|
||||
bytecodeFallbackReporter = reporter
|
||||
seedScope = scope
|
||||
)
|
||||
script.execute(scope)
|
||||
flushBytecodeFallbacks(reporter)
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,22 +260,3 @@ suspend fun processErrors(block: suspend () -> Unit) {
|
||||
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.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import net.sergeych.lyng.statement
|
||||
import net.sergeych.lyngio.process.*
|
||||
import net.sergeych.lyngio.process.security.ProcessAccessDeniedException
|
||||
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 {
|
||||
val producer = statement {
|
||||
val producer = ObjNativeCallable {
|
||||
val builder = (this as? net.sergeych.lyng.BytecodeClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
|
||||
?: this.thisObj as? ObjFlowBuilder
|
||||
|
||||
|
||||
@ -112,6 +112,9 @@ class Compiler(
|
||||
if (!added) {
|
||||
currentShadowedLocalNames?.add(name)
|
||||
}
|
||||
if (added) {
|
||||
scopeSeedNames.remove(name)
|
||||
}
|
||||
if (added && localDeclCountStack.isNotEmpty()) {
|
||||
localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1
|
||||
}
|
||||
@ -136,6 +139,9 @@ class Compiler(
|
||||
if (plan.slots.containsKey(name)) return
|
||||
plan.slots[name] = SlotEntry(plan.nextIndex, isMutable, isDelegated)
|
||||
plan.nextIndex += 1
|
||||
if (!seedingSlotPlan && plan == moduleSlotPlan()) {
|
||||
moduleDeclaredNames.add(name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun declareSlotNameAt(
|
||||
@ -155,6 +161,7 @@ class Compiler(
|
||||
private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull()
|
||||
private val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, 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 nameTypeDecl: MutableMap<String, TypeDecl> = mutableMapOf()
|
||||
private data class TypeAliasDecl(
|
||||
@ -177,69 +184,81 @@ class Compiler(
|
||||
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
||||
private val objectDeclNames: 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) {
|
||||
val plan = moduleSlotPlan() ?: return
|
||||
var current: Scope? = scope
|
||||
while (current != null) {
|
||||
for ((name, record) in current.objects) {
|
||||
if (!record.visibility.isPublic) continue
|
||||
if (plan.slots.containsKey(name)) continue
|
||||
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
||||
val instance = record.value as? ObjInstance
|
||||
if (instance != null && nameObjClass[name] == null) {
|
||||
nameObjClass[name] = instance.objClass
|
||||
}
|
||||
}
|
||||
for ((cls, map) in current.extensions) {
|
||||
for ((name, record) in map) {
|
||||
seedingSlotPlan = true
|
||||
try {
|
||||
var current: Scope? = scope
|
||||
while (current != null) {
|
||||
for ((name, record) in current.objects) {
|
||||
if (!record.visibility.isPublic) continue
|
||||
when (record.type) {
|
||||
ObjRecord.Type.Property -> {
|
||||
val getterName = extensionPropertyGetterName(cls.className, name)
|
||||
if (!plan.slots.containsKey(getterName)) {
|
||||
declareSlotNameIn(
|
||||
plan,
|
||||
getterName,
|
||||
isMutable = false,
|
||||
isDelegated = false
|
||||
)
|
||||
}
|
||||
val prop = record.value as? ObjProperty
|
||||
if (prop?.setter != null) {
|
||||
val setterName = extensionPropertySetterName(cls.className, name)
|
||||
if (!plan.slots.containsKey(setterName)) {
|
||||
if (plan.slots.containsKey(name)) continue
|
||||
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
||||
scopeSeedNames.add(name)
|
||||
val instance = record.value as? ObjInstance
|
||||
if (instance != null && nameObjClass[name] == null) {
|
||||
nameObjClass[name] = instance.objClass
|
||||
}
|
||||
}
|
||||
for ((cls, map) in current.extensions) {
|
||||
for ((name, record) in map) {
|
||||
if (!record.visibility.isPublic) continue
|
||||
when (record.type) {
|
||||
ObjRecord.Type.Property -> {
|
||||
val getterName = extensionPropertyGetterName(cls.className, name)
|
||||
if (!plan.slots.containsKey(getterName)) {
|
||||
declareSlotNameIn(
|
||||
plan,
|
||||
setterName,
|
||||
getterName,
|
||||
isMutable = false,
|
||||
isDelegated = false
|
||||
)
|
||||
scopeSeedNames.add(getterName)
|
||||
}
|
||||
val prop = record.value as? ObjProperty
|
||||
if (prop?.setter != null) {
|
||||
val setterName = extensionPropertySetterName(cls.className, name)
|
||||
if (!plan.slots.containsKey(setterName)) {
|
||||
declareSlotNameIn(
|
||||
plan,
|
||||
setterName,
|
||||
isMutable = false,
|
||||
isDelegated = false
|
||||
)
|
||||
scopeSeedNames.add(setterName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val callableName = extensionCallableName(cls.className, name)
|
||||
if (!plan.slots.containsKey(callableName)) {
|
||||
declareSlotNameIn(
|
||||
plan,
|
||||
callableName,
|
||||
isMutable = false,
|
||||
isDelegated = false
|
||||
)
|
||||
else -> {
|
||||
val callableName = extensionCallableName(cls.className, name)
|
||||
if (!plan.slots.containsKey(callableName)) {
|
||||
declareSlotNameIn(
|
||||
plan,
|
||||
callableName,
|
||||
isMutable = false,
|
||||
isDelegated = false
|
||||
)
|
||||
scopeSeedNames.add(callableName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for ((name, slotIndex) in current.slotNameToIndexSnapshot()) {
|
||||
val record = current.getSlotRecord(slotIndex)
|
||||
if (!record.visibility.isPublic) continue
|
||||
if (plan.slots.containsKey(name)) continue
|
||||
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
||||
scopeSeedNames.add(name)
|
||||
}
|
||||
if (!includeParents) return
|
||||
current = current.parent
|
||||
}
|
||||
for ((name, slotIndex) in current.slotNameToIndexSnapshot()) {
|
||||
val record = current.getSlotRecord(slotIndex)
|
||||
if (!record.visibility.isPublic) continue
|
||||
if (plan.slots.containsKey(name)) continue
|
||||
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
||||
}
|
||||
if (!includeParents) return
|
||||
current = current.parent
|
||||
} finally {
|
||||
seedingSlotPlan = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,6 +273,7 @@ class Compiler(
|
||||
record.isMutable,
|
||||
record.type == ObjRecord.Type.Delegated
|
||||
)
|
||||
scopeSeedNames.add(name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,6 +342,7 @@ class Compiler(
|
||||
val nameToken = nextNonWs()
|
||||
if (nameToken.type == Token.Type.ID) {
|
||||
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
|
||||
scopeSeedNames.add(nameToken.value)
|
||||
}
|
||||
}
|
||||
"enum" -> {
|
||||
@ -329,6 +350,7 @@ class Compiler(
|
||||
val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next
|
||||
if (nameToken.type == Token.Type.ID) {
|
||||
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
|
||||
scopeSeedNames.add(nameToken.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1557,6 +1579,7 @@ class Compiler(
|
||||
"<script>",
|
||||
allowLocalSlots = true,
|
||||
allowedScopeNames = modulePlan.keys,
|
||||
scopeSlotNameSet = scopeSeedNames,
|
||||
moduleScopeId = moduleSlotPlan()?.id,
|
||||
slotTypeByScopeId = slotTypeByScopeId,
|
||||
knownNameObjClass = knownClassMapForBytecode(),
|
||||
@ -1765,8 +1788,11 @@ class Compiler(
|
||||
} ?: -1
|
||||
val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId }
|
||||
if (functionIndex >= 0 && scopeIndex >= functionIndex) return null
|
||||
val moduleId = moduleSlotPlan()?.id
|
||||
if (moduleId != null && slotLoc.scopeId == moduleId) return null
|
||||
if (scopeSeedNames.contains(name)) return null
|
||||
val modulePlan = moduleSlotPlan()
|
||||
if (modulePlan != null && slotLoc.scopeId == modulePlan.id) {
|
||||
return null
|
||||
}
|
||||
recordCaptureSlot(name, slotLoc)
|
||||
val plan = capturePlanStack.lastOrNull() ?: return null
|
||||
val entry = plan.slotPlan.slots[name] ?: return null
|
||||
@ -1846,6 +1872,7 @@ class Compiler(
|
||||
returnLabels = returnLabels,
|
||||
rangeLocalNames = currentRangeParamNames,
|
||||
allowedScopeNames = allowedScopeNames,
|
||||
scopeSlotNameSet = scopeSeedNames,
|
||||
moduleScopeId = moduleSlotPlan()?.id,
|
||||
slotTypeByScopeId = slotTypeByScopeId,
|
||||
knownNameObjClass = knownClassMapForBytecode(),
|
||||
@ -1870,6 +1897,7 @@ class Compiler(
|
||||
returnLabels = returnLabels,
|
||||
rangeLocalNames = currentRangeParamNames,
|
||||
allowedScopeNames = allowedScopeNames,
|
||||
scopeSlotNameSet = scopeSeedNames,
|
||||
moduleScopeId = moduleSlotPlan()?.id,
|
||||
slotTypeByScopeId = slotTypeByScopeId,
|
||||
knownNameObjClass = knownClassMapForBytecode(),
|
||||
@ -1913,6 +1941,7 @@ class Compiler(
|
||||
returnLabels = returnLabels,
|
||||
rangeLocalNames = currentRangeParamNames,
|
||||
allowedScopeNames = allowedScopeNames,
|
||||
scopeSlotNameSet = scopeSeedNames,
|
||||
moduleScopeId = moduleSlotPlan()?.id,
|
||||
forcedLocalSlots = forcedLocalSlots,
|
||||
forcedLocalScopeId = forcedLocalScopeId,
|
||||
@ -2390,8 +2419,14 @@ class Compiler(
|
||||
val args = parsed.first
|
||||
val tailBlock = parsed.second
|
||||
isCall = true
|
||||
val field = FieldRef(left, next.value, isOptional)
|
||||
operand = CallRef(field, args, tailBlock, isOptional)
|
||||
val receiverClass = resolveReceiverClassForMember(left)
|
||||
val nestedClass = receiverClass?.let { resolveClassByName("${it.className}.${next.value}") }
|
||||
if (nestedClass != null) {
|
||||
val field = FieldRef(left, next.value, isOptional)
|
||||
operand = CallRef(field, args, tailBlock, isOptional)
|
||||
} else {
|
||||
operand = MethodCallRef(left, next.value, args, tailBlock, isOptional)
|
||||
}
|
||||
} else {
|
||||
// instance method call
|
||||
val receiverType = if (next.value == "apply" || next.value == "run") {
|
||||
@ -2967,17 +3002,11 @@ class Compiler(
|
||||
lambdaReturnTypeByRef[ref] = returnClass
|
||||
}
|
||||
if (captureSlots.isNotEmpty()) {
|
||||
val moduleScopeId = moduleSlotPlan()?.id
|
||||
val captureEntries = captureSlots.map { capture ->
|
||||
val owner = capturePlan.captureOwners[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(
|
||||
ownerKind = kind,
|
||||
ownerKind = net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.LOCAL,
|
||||
ownerScopeId = owner.scopeId,
|
||||
ownerSlotId = owner.slot,
|
||||
ownerName = capture.name,
|
||||
@ -5725,6 +5754,9 @@ class Compiler(
|
||||
val outerClassName = currentEnclosingClassName()
|
||||
val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
|
||||
resolutionSink?.declareSymbol(declaredName, SymbolKind.ENUM, isMutable = false, pos = nameToken.pos)
|
||||
if (codeContexts.lastOrNull() is CodeContext.Module) {
|
||||
scopeSeedNames.add(declaredName)
|
||||
}
|
||||
if (outerClassName != null) {
|
||||
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
||||
outerCtx?.classScopeMembers?.add(declaredName)
|
||||
@ -6024,6 +6056,9 @@ class Compiler(
|
||||
val outerClassName = currentEnclosingClassName()
|
||||
val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
|
||||
resolutionSink?.declareSymbol(declaredName, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
|
||||
if (codeContexts.lastOrNull() is CodeContext.Module) {
|
||||
scopeSeedNames.add(declaredName)
|
||||
}
|
||||
if (outerClassName != null) {
|
||||
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
||||
outerCtx?.classScopeMembers?.add(declaredName)
|
||||
@ -7909,29 +7944,7 @@ class Compiler(
|
||||
pendingDeclDoc = null
|
||||
}
|
||||
|
||||
if (declaringClassNameCaptured == null &&
|
||||
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
|
||||
)
|
||||
}
|
||||
fun buildVarDecl(initialExpression: Statement?): VarDeclStatement {
|
||||
val slotPlan = slotPlanStack.lastOrNull()
|
||||
val slotIndex = slotPlan?.slots?.get(name)?.index
|
||||
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 (declaringClassNameCaptured != null) {
|
||||
val directRef = unwrapDirectRef(initialExpression)
|
||||
@ -8288,56 +8325,7 @@ class Compiler(
|
||||
)
|
||||
}
|
||||
|
||||
return object : Statement() {
|
||||
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
|
||||
}
|
||||
}
|
||||
return buildVarDecl(initialExpression)
|
||||
} finally {
|
||||
if (implicitTypeParams.isNotEmpty()) pendingTypeParamStack.removeLast()
|
||||
}
|
||||
|
||||
@ -174,54 +174,49 @@ internal suspend fun executeFunctionDecl(
|
||||
spec.extTypeName?.let { typeName ->
|
||||
val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found")
|
||||
if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance")
|
||||
val callable = net.sergeych.lyng.obj.ObjNativeCallable {
|
||||
val result = (thisObj as? ObjInstance)?.let { i ->
|
||||
val execScope = applyClosureForBytecode(i.instanceScope).also {
|
||||
it.args = args
|
||||
}
|
||||
compiledFnBody.execute(execScope)
|
||||
} ?: compiledFnBody.execute(thisObj.autoInstanceScope(this))
|
||||
result
|
||||
}
|
||||
scope.addExtension(type, spec.name, ObjRecord(callable, isMutable = false, visibility = spec.visibility, declaringClass = null))
|
||||
scope.addExtension(
|
||||
type,
|
||||
spec.name,
|
||||
ObjRecord(
|
||||
compiledFnBody,
|
||||
isMutable = false,
|
||||
visibility = spec.visibility,
|
||||
declaringClass = null,
|
||||
type = ObjRecord.Type.Fun
|
||||
)
|
||||
)
|
||||
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)
|
||||
} ?: run {
|
||||
val th = scope.thisObj
|
||||
if (!spec.isStatic && th is ObjClass) {
|
||||
val cls: ObjClass = th
|
||||
cls.addFn(
|
||||
cls.createField(
|
||||
spec.name,
|
||||
compiledFnBody,
|
||||
isMutable = true,
|
||||
visibility = spec.visibility,
|
||||
pos = spec.startPos,
|
||||
declaringClass = cls,
|
||||
isAbstract = spec.isAbstract,
|
||||
isClosed = spec.isClosed,
|
||||
isOverride = spec.isOverride,
|
||||
pos = spec.startPos,
|
||||
type = ObjRecord.Type.Fun,
|
||||
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
|
||||
scope.addItem(spec.name, false, memberValue, spec.visibility, callSignature = spec.externCallSignature)
|
||||
compiledFnBody
|
||||
} 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
|
||||
|
||||
@ -78,8 +78,17 @@ open class Scope(
|
||||
thisObj = primary
|
||||
val extrasSnapshot = when {
|
||||
extras.isEmpty() -> emptyList()
|
||||
extras is MutableList<*> -> synchronized(extras) { extras.toList() }
|
||||
else -> extras.toList()
|
||||
else -> {
|
||||
try {
|
||||
if (extras is MutableList<*>) {
|
||||
synchronized(extras) { extras.toList() }
|
||||
} else {
|
||||
extras.toList()
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized(thisVariants) {
|
||||
thisVariants.clear()
|
||||
@ -818,34 +827,27 @@ open class Scope(
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve and evaluate a qualified identifier exactly as compiled code would.
|
||||
* For input like `A.B.C`, it builds the same ObjRef chain the compiler emits:
|
||||
* `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.
|
||||
* Resolve and evaluate a qualified identifier (e.g. `A.B.C`) using the same name/field
|
||||
* resolution rules as compiled code, without invoking the compiler or ObjRef evaluation.
|
||||
*/
|
||||
suspend fun resolveQualifiedIdentifier(qualifiedName: String): Obj {
|
||||
val trimmed = qualifiedName.trim()
|
||||
if (trimmed.isEmpty()) raiseSymbolNotFound("empty identifier")
|
||||
val parts = trimmed.split('.')
|
||||
val first = parts[0]
|
||||
val ref: ObjRef = if (first == "this") {
|
||||
ConstRef(thisObj.asReadonly)
|
||||
val base: Obj = if (first == "this") {
|
||||
thisObj
|
||||
} else {
|
||||
var s: Scope? = this
|
||||
var slot: Int? = null
|
||||
var guard = 0
|
||||
while (s != null && guard++ < 1024 && slot == null) {
|
||||
slot = s.getSlotIndexOf(first)
|
||||
s = s.parent
|
||||
}
|
||||
if (slot == null) raiseSymbolNotFound(first)
|
||||
LocalSlotRef(first, slot, 0, isMutable = false, isDelegated = false, Pos.builtIn, strict = true)
|
||||
val rec = get(first) ?: raiseSymbolNotFound(first)
|
||||
resolve(rec, first)
|
||||
}
|
||||
var ref0: ObjRef = ref
|
||||
var current = base
|
||||
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 {
|
||||
|
||||
@ -85,7 +85,9 @@ class Script(
|
||||
seedModuleSlots(moduleTarget)
|
||||
}
|
||||
moduleBytecode?.let { fn ->
|
||||
return CmdVm().execute(fn, scope, scope.args)
|
||||
return CmdVm().execute(fn, scope, scope.args) { frame, _ ->
|
||||
seedModuleLocals(frame, moduleTarget)
|
||||
}
|
||||
}
|
||||
if (statements.isNotEmpty()) {
|
||||
scope.raiseIllegalState("interpreter execution is not supported; missing module bytecode")
|
||||
@ -98,6 +100,31 @@ class Script(
|
||||
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) {
|
||||
val provider = scope.currentImportProvider
|
||||
val importedModules = LinkedHashSet<ModuleScope>()
|
||||
|
||||
@ -19,6 +19,10 @@ package net.sergeych.lyng
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -27,7 +31,7 @@ class WhenEqualsCondition(
|
||||
override val pos: Pos,
|
||||
) : WhenCondition(expr, pos) {
|
||||
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,
|
||||
) : WhenCondition(expr, pos) {
|
||||
override suspend fun matches(scope: Scope, value: Obj): Boolean {
|
||||
val result = expr.execute(scope).contains(scope, value)
|
||||
return if (negated) !result else result
|
||||
return interpreterDisabled(scope)
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,12 +51,7 @@ class WhenIsCondition(
|
||||
override val pos: Pos,
|
||||
) : WhenCondition(expr, pos) {
|
||||
override suspend fun matches(scope: Scope, value: Obj): Boolean {
|
||||
val typeExpr = expr.execute(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
|
||||
return interpreterDisabled(scope)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ class BytecodeCompiler(
|
||||
private val returnLabels: Set<String> = emptySet(),
|
||||
private val rangeLocalNames: Set<String> = emptySet(),
|
||||
private val allowedScopeNames: Set<String>? = null,
|
||||
private val scopeSlotNameSet: Set<String>? = null,
|
||||
private val moduleScopeId: Int? = null,
|
||||
private val forcedLocalSlots: Map<String, Int> = emptyMap(),
|
||||
private val forcedLocalScopeId: Int? = null,
|
||||
@ -498,13 +499,6 @@ class BytecodeCompiler(
|
||||
return CompiledValue(local, SlotType.OBJ)
|
||||
}
|
||||
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
|
||||
var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
|
||||
if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) {
|
||||
@ -518,6 +512,13 @@ class BytecodeCompiler(
|
||||
updateSlotType(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)
|
||||
}
|
||||
is LocalVarRef -> {
|
||||
@ -529,12 +530,6 @@ class BytecodeCompiler(
|
||||
return CompiledValue(slot, resolved)
|
||||
}
|
||||
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 ->
|
||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||
return CompiledValue(slot, resolved)
|
||||
@ -1292,6 +1287,23 @@ class BytecodeCompiler(
|
||||
val rightRef = binaryRight(ref)
|
||||
var a = compileRefWithFallback(leftRef, 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(
|
||||
BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT,
|
||||
BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR
|
||||
@ -1917,34 +1929,15 @@ class BytecodeCompiler(
|
||||
builder.emit(Opcode.THROW, posId, msgSlot)
|
||||
return value
|
||||
}
|
||||
val resolvedSlot = resolveSlot(localTarget)
|
||||
val slot = resolveSlot(localTarget)
|
||||
?: resolveAssignableSlotByName(localTarget.name)?.first
|
||||
?: 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) {
|
||||
val addrSlot = ensureScopeAddr(slot)
|
||||
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) {
|
||||
val addrSlot = ensureScopeAddr(slot)
|
||||
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 {
|
||||
when (value.type) {
|
||||
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
|
||||
@ -1980,19 +1973,9 @@ class BytecodeCompiler(
|
||||
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
|
||||
val addrSlot = ensureScopeAddr(slot)
|
||||
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) {
|
||||
val addrSlot = ensureScopeAddr(slot)
|
||||
emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ)
|
||||
localSlotIndexByName[nameTarget]?.let { mirror ->
|
||||
val mirrorSlot = scopeSlotCount + mirror
|
||||
emitMove(value, mirrorSlot)
|
||||
updateSlotType(mirrorSlot, value.type)
|
||||
}
|
||||
} else {
|
||||
when (value.type) {
|
||||
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
|
||||
@ -2337,8 +2320,65 @@ class BytecodeCompiler(
|
||||
updateSlotType(result, SlotType.OBJ)
|
||||
return CompiledValue(result, SlotType.OBJ)
|
||||
}
|
||||
val fieldId = receiverClass.instanceFieldIdMap()[fieldTarget.name]
|
||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[fieldTarget.name]
|
||||
if (receiverClass is ObjInstanceClass && !isThisReceiver(fieldTarget.target)) {
|
||||
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)) {
|
||||
val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
|
||||
if (!fieldTarget.isOptional) {
|
||||
@ -2608,12 +2648,29 @@ class BytecodeCompiler(
|
||||
builder.mark(endLabel)
|
||||
updateSlotType(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 {
|
||||
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 = receiverClass.instanceFieldIdMap()[target.name]
|
||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name]
|
||||
if (fieldId != null || methodId != null) {
|
||||
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_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, newValue.slot)
|
||||
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
|
||||
} else {
|
||||
val recvNull = allocSlot()
|
||||
builder.emit(Opcode.CONST_NULL, recvNull)
|
||||
@ -2624,39 +2681,59 @@ class BytecodeCompiler(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
|
||||
)
|
||||
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, newValue.slot)
|
||||
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
|
||||
builder.mark(skipLabel)
|
||||
}
|
||||
} else {
|
||||
val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
|
||||
?: throw BytecodeCompileException(
|
||||
"Unknown member ${target.name} on ${receiverClass.className}",
|
||||
Pos.builtIn
|
||||
)
|
||||
val callee = ensureObjSlot(extSlot)
|
||||
val receiverObj = ensureObjSlot(receiver)
|
||||
val valueObj = ensureObjSlot(newValue)
|
||||
val argSlots = intArrayOf(allocSlot(), allocSlot())
|
||||
builder.emit(Opcode.MOVE_OBJ, receiverObj.slot, argSlots[0])
|
||||
builder.emit(Opcode.MOVE_OBJ, valueObj.slot, argSlots[1])
|
||||
updateSlotType(argSlots[0], SlotType.OBJ)
|
||||
updateSlotType(argSlots[1], SlotType.OBJ)
|
||||
val callArgs = CallArgs(base = argSlots[0], count = argSlots.size, planId = null)
|
||||
val encodedCount = encodeCallArgCount(callArgs) ?: return null
|
||||
if (!target.isOptional) {
|
||||
builder.emit(Opcode.CALL_SLOT, callee.slot, callArgs.base, encodedCount, resultSlot)
|
||||
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 (!target.isOptional) {
|
||||
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, 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_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, newValue.slot)
|
||||
builder.mark(skipLabel)
|
||||
}
|
||||
} 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.CALL_SLOT, callee.slot, callArgs.base, encodedCount, resultSlot)
|
||||
builder.mark(skipLabel)
|
||||
val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
|
||||
?: throw BytecodeCompileException(
|
||||
"Unknown member ${target.name} on ${receiverClass.className}",
|
||||
Pos.builtIn
|
||||
)
|
||||
val callee = ensureObjSlot(extSlot)
|
||||
val receiverObj = ensureObjSlot(receiver)
|
||||
val valueObj = ensureObjSlot(newValue)
|
||||
val argSlots = intArrayOf(allocSlot(), allocSlot())
|
||||
builder.emit(Opcode.MOVE_OBJ, receiverObj.slot, argSlots[0])
|
||||
builder.emit(Opcode.MOVE_OBJ, valueObj.slot, argSlots[1])
|
||||
updateSlotType(argSlots[0], SlotType.OBJ)
|
||||
updateSlotType(argSlots[1], SlotType.OBJ)
|
||||
val callArgs = CallArgs(base = argSlots[0], count = argSlots.size, planId = null)
|
||||
val encodedCount = encodeCallArgCount(callArgs) ?: return null
|
||||
if (!target.isOptional) {
|
||||
builder.emit(Opcode.CALL_SLOT, callee.slot, callArgs.base, encodedCount, resultSlot)
|
||||
} 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.CALL_SLOT, callee.slot, callArgs.base, encodedCount, resultSlot)
|
||||
builder.mark(skipLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2718,8 +2795,61 @@ class BytecodeCompiler(
|
||||
updateSlotType(dst, SlotType.OBJ)
|
||||
return CompiledValue(dst, SlotType.OBJ)
|
||||
}
|
||||
val fieldId = receiverClass.instanceFieldIdMap()[ref.name]
|
||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
||||
if (receiverClass is ObjInstanceClass && !isThisReceiver(ref.target)) {
|
||||
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 encodedMethodId = encodeMemberId(receiverClass, methodId)
|
||||
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
|
||||
@ -3688,6 +3818,44 @@ class BytecodeCompiler(
|
||||
|
||||
private fun compileCall(ref: CallRef): CompiledValue? {
|
||||
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 isExternCall = localTarget != null && externCallableNames.contains(localTarget.name)
|
||||
if (localTarget != null) {
|
||||
@ -3791,7 +3959,7 @@ class BytecodeCompiler(
|
||||
val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type
|
||||
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
|
||||
val dst = allocSlot()
|
||||
if (receiverClass == ObjDynamic.type) {
|
||||
fun emitDynamicCall(): CompiledValue? {
|
||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
|
||||
@ -3818,8 +3986,25 @@ class BytecodeCompiler(
|
||||
builder.mark(endLabel)
|
||||
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]
|
||||
if (methodId != null) {
|
||||
if (methodId != null && resolvedMember == null) {
|
||||
return emitDynamicCall()
|
||||
}
|
||||
if (methodId != null && resolvedMember?.declaringClass?.className != "Obj") {
|
||||
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId
|
||||
if (!ref.isOptional) {
|
||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||
@ -3848,7 +4033,7 @@ class BytecodeCompiler(
|
||||
builder.mark(endLabel)
|
||||
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) {
|
||||
val encodedFieldId = encodeMemberId(receiverClass, fieldId) ?: fieldId
|
||||
val calleeSlot = allocSlot()
|
||||
@ -4045,7 +4230,12 @@ class BytecodeCompiler(
|
||||
private fun resolveExtensionCallableSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
|
||||
for (cls in receiverClass.mro) {
|
||||
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 }
|
||||
}
|
||||
return null
|
||||
@ -4054,7 +4244,12 @@ class BytecodeCompiler(
|
||||
private fun resolveExtensionGetterSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
|
||||
for (cls in receiverClass.mro) {
|
||||
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 }
|
||||
}
|
||||
return null
|
||||
@ -4063,7 +4258,12 @@ class BytecodeCompiler(
|
||||
private fun resolveExtensionSetterSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
|
||||
for (cls in receiverClass.mro) {
|
||||
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 }
|
||||
}
|
||||
return null
|
||||
@ -4339,9 +4539,12 @@ class BytecodeCompiler(
|
||||
|
||||
private fun emitDeclClass(stmt: net.sergeych.lyng.ClassDeclStatement): CompiledValue {
|
||||
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)
|
||||
updateSlotType(dst, SlotType.OBJ)
|
||||
stmt.spec.declaredName?.let { updateNameObjClassFromSlot(it, dst) }
|
||||
return CompiledValue(dst, SlotType.OBJ)
|
||||
}
|
||||
|
||||
@ -4658,9 +4861,6 @@ class BytecodeCompiler(
|
||||
if (shouldInlineBlock(stmt)) {
|
||||
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()
|
||||
var lastValue: CompiledValue? = null
|
||||
for ((index, statement) in statements.withIndex()) {
|
||||
@ -4675,24 +4875,18 @@ class BytecodeCompiler(
|
||||
"Bytecode compile error: failed to compile block statement ($name)",
|
||||
statement.pos
|
||||
)
|
||||
}
|
||||
}
|
||||
if (wantResult) {
|
||||
lastValue = value
|
||||
}
|
||||
}
|
||||
val result = if (needResult) {
|
||||
var value = lastValue ?: run {
|
||||
return if (needResult) {
|
||||
lastValue ?: run {
|
||||
val slot = allocSlot()
|
||||
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||
builder.emit(Opcode.CONST_OBJ, voidId, slot)
|
||||
CompiledValue(slot, SlotType.OBJ)
|
||||
}
|
||||
if (value.slot < scopeSlotCount) {
|
||||
val captured = allocSlot()
|
||||
emitMove(value, captured)
|
||||
value = CompiledValue(captured, value.type)
|
||||
}
|
||||
value
|
||||
} else {
|
||||
lastValue ?: run {
|
||||
val slot = allocSlot()
|
||||
@ -4701,9 +4895,6 @@ class BytecodeCompiler(
|
||||
CompiledValue(slot, SlotType.OBJ)
|
||||
}
|
||||
}
|
||||
builder.emit(Opcode.POP_SCOPE)
|
||||
resetAddrCache()
|
||||
return result
|
||||
}
|
||||
|
||||
private fun emitTry(stmt: net.sergeych.lyng.TryStatement, needResult: Boolean): CompiledValue? {
|
||||
@ -4801,15 +4992,8 @@ class BytecodeCompiler(
|
||||
): CompiledValue? {
|
||||
val stmt = block as? BlockStatement
|
||||
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)
|
||||
}
|
||||
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]
|
||||
if (catchSlotIndex != null) {
|
||||
val key = ScopeSlotKey(stmt.scopeId, catchSlotIndex)
|
||||
@ -4847,8 +5031,6 @@ class BytecodeCompiler(
|
||||
CompiledValue(slot, SlotType.OBJ)
|
||||
}
|
||||
}
|
||||
builder.emit(Opcode.POP_SCOPE)
|
||||
resetAddrCache()
|
||||
return result
|
||||
}
|
||||
|
||||
@ -4931,40 +5113,9 @@ class BytecodeCompiler(
|
||||
private fun emitVarDecl(stmt: VarDeclStatement): CompiledValue? {
|
||||
updateNameObjClass(stmt.name, stmt.initializer, stmt.initializerObjClass)
|
||||
val scopeId = stmt.scopeId ?: 0
|
||||
val isModuleSlot = isModuleSlot(scopeId, stmt.name)
|
||||
val scopeSlot = stmt.slotIndex?.let { slotIndex ->
|
||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||
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 key = ScopeSlotKey(scopeId, stmt.slotIndex)
|
||||
@ -5182,7 +5333,6 @@ class BytecodeCompiler(
|
||||
}
|
||||
val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null
|
||||
val loopSlotPlan = stmt.loopSlotPlan
|
||||
var useLoopScope = loopSlotPlan.isNotEmpty()
|
||||
val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
|
||||
val loopKey = loopSlotIndex?.let { ScopeSlotKey(stmt.loopScopeId, it) }
|
||||
val loopLocalIndex = loopKey?.let { localSlotIndexByKey[it] } ?: localSlotIndexByName[stmt.loopVarName]
|
||||
@ -5203,47 +5353,13 @@ class BytecodeCompiler(
|
||||
usedOverride = true
|
||||
slot
|
||||
}
|
||||
var emitDeclLocal = usedOverride
|
||||
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
|
||||
// Loop scopes are intentionally disabled; loop vars live in frame slots only.
|
||||
if (loopSlotId < scopeSlotCount) {
|
||||
val localSlot = allocSlot()
|
||||
loopSlotOverrides[stmt.loopVarName] = localSlot
|
||||
usedOverride = true
|
||||
emitDeclLocal = false
|
||||
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 {
|
||||
if (range == null && rangeRef == null && typedRangeLocal == null) {
|
||||
@ -5284,10 +5400,6 @@ class BytecodeCompiler(
|
||||
val loopLabel = builder.label()
|
||||
val continueLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
if (useLoopScope) {
|
||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
||||
resetAddrCache()
|
||||
}
|
||||
builder.mark(loopLabel)
|
||||
|
||||
val hasNextSlot = allocSlot()
|
||||
@ -5305,9 +5417,6 @@ class BytecodeCompiler(
|
||||
emitMove(CompiledValue(nextObj.slot, SlotType.OBJ), loopSlotId)
|
||||
updateSlotType(loopSlotId, SlotType.OBJ)
|
||||
updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ)
|
||||
if (emitDeclLocal) {
|
||||
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
|
||||
}
|
||||
|
||||
loopStack.addLast(
|
||||
LoopContext(
|
||||
@ -5349,10 +5458,6 @@ class BytecodeCompiler(
|
||||
}
|
||||
builder.mark(afterElse)
|
||||
}
|
||||
if (useLoopScope) {
|
||||
builder.emit(Opcode.POP_SCOPE)
|
||||
resetAddrCache()
|
||||
}
|
||||
return resultSlot
|
||||
}
|
||||
|
||||
@ -5398,10 +5503,6 @@ class BytecodeCompiler(
|
||||
val continueLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
val doneLabel = builder.label()
|
||||
if (useLoopScope) {
|
||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
||||
resetAddrCache()
|
||||
}
|
||||
builder.mark(loopLabel)
|
||||
val cmpSlot = allocSlot()
|
||||
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
|
||||
@ -5412,9 +5513,6 @@ class BytecodeCompiler(
|
||||
emitMove(CompiledValue(iSlot, SlotType.INT), loopSlotId)
|
||||
updateSlotType(loopSlotId, SlotType.INT)
|
||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||
if (emitDeclLocal) {
|
||||
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
|
||||
}
|
||||
loopStack.addLast(
|
||||
LoopContext(
|
||||
stmt.label,
|
||||
@ -5449,10 +5547,6 @@ class BytecodeCompiler(
|
||||
}
|
||||
builder.mark(afterElse)
|
||||
}
|
||||
if (useLoopScope) {
|
||||
builder.emit(Opcode.POP_SCOPE)
|
||||
resetAddrCache()
|
||||
}
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(doneLabel)))
|
||||
builder.mark(badRangeLabel)
|
||||
val msgId = builder.addConst(BytecodeConst.StringVal("expected Int range"))
|
||||
@ -5475,10 +5569,6 @@ class BytecodeCompiler(
|
||||
val loopLabel = builder.label()
|
||||
val continueLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
if (useLoopScope) {
|
||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
||||
resetAddrCache()
|
||||
}
|
||||
builder.mark(loopLabel)
|
||||
val cmpSlot = allocSlot()
|
||||
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
|
||||
@ -5489,9 +5579,6 @@ class BytecodeCompiler(
|
||||
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
|
||||
updateSlotType(loopSlotId, SlotType.INT)
|
||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||
if (emitDeclLocal) {
|
||||
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
|
||||
}
|
||||
loopStack.addLast(
|
||||
LoopContext(
|
||||
stmt.label,
|
||||
@ -5526,10 +5613,6 @@ class BytecodeCompiler(
|
||||
}
|
||||
builder.mark(afterElse)
|
||||
}
|
||||
if (useLoopScope) {
|
||||
builder.emit(Opcode.POP_SCOPE)
|
||||
resetAddrCache()
|
||||
}
|
||||
return resultSlot
|
||||
} finally {
|
||||
if (usedOverride) {
|
||||
@ -5550,13 +5633,8 @@ class BytecodeCompiler(
|
||||
val loopLabel = builder.label()
|
||||
val continueLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(continueLabel)))
|
||||
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(
|
||||
LoopContext(
|
||||
stmt.label,
|
||||
@ -5574,7 +5652,12 @@ class BytecodeCompiler(
|
||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
||||
}
|
||||
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)
|
||||
if (stmt.elseStatement != null) {
|
||||
@ -5605,18 +5688,8 @@ class BytecodeCompiler(
|
||||
val loopLabel = builder.label()
|
||||
val continueLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
val useLoopScope = stmt.loopSlotPlan.isNotEmpty()
|
||||
val breakLabel = if (useLoopScope) builder.label() else endLabel
|
||||
val planId = if (useLoopScope) {
|
||||
builder.addConst(BytecodeConst.SlotPlan(stmt.loopSlotPlan, emptyList()))
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
val breakLabel = endLabel
|
||||
builder.mark(loopLabel)
|
||||
if (useLoopScope) {
|
||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
||||
resetAddrCache()
|
||||
}
|
||||
loopStack.addLast(
|
||||
LoopContext(
|
||||
stmt.label,
|
||||
@ -5627,12 +5700,7 @@ class BytecodeCompiler(
|
||||
hasIterator = false
|
||||
)
|
||||
)
|
||||
val bodyTarget = if (stmt.body is BytecodeStatement) stmt.body.original else stmt.body
|
||||
val bodyValue = if (useLoopScope && bodyTarget is BlockStatement) {
|
||||
emitInlineBlock(bodyTarget, wantResult)
|
||||
} else {
|
||||
compileStatementValueOrFallback(stmt.body, wantResult)
|
||||
} ?: return null
|
||||
val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null
|
||||
loopStack.removeLast()
|
||||
if (wantResult) {
|
||||
val bodyObj = ensureObjSlot(bodyValue)
|
||||
@ -5641,20 +5709,10 @@ class BytecodeCompiler(
|
||||
builder.mark(continueLabel)
|
||||
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
|
||||
if (condition.type != SlotType.BOOL) return null
|
||||
if (useLoopScope) {
|
||||
builder.emit(Opcode.POP_SCOPE)
|
||||
resetAddrCache()
|
||||
}
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
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)
|
||||
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? {
|
||||
return when (ref) {
|
||||
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 {
|
||||
return when (memberName) {
|
||||
"toString",
|
||||
@ -6484,6 +6565,8 @@ class BytecodeCompiler(
|
||||
val scopeId = refScopeId(ref)
|
||||
if (!ref.isDelegated && isModuleSlot(scopeId, ref.name)) {
|
||||
val key = ScopeSlotKey(scopeId, refSlot(ref))
|
||||
val localIndex = localSlotIndexByKey[key]
|
||||
if (localIndex != null) return scopeSlotCount + localIndex
|
||||
scopeSlotMap[key]?.let { return it }
|
||||
scopeSlotIndexByName[ref.name]?.let { return it }
|
||||
}
|
||||
@ -6495,29 +6578,16 @@ class BytecodeCompiler(
|
||||
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]
|
||||
}
|
||||
if (ref.isDelegated) {
|
||||
val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
|
||||
val localIndex = localSlotIndexByKey[localKey]
|
||||
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 localIndex = localSlotIndexByKey[localKey]
|
||||
if (localIndex != null) return scopeSlotCount + localIndex
|
||||
val nameIndex = localSlotIndexByName[ref.name]
|
||||
if (nameIndex != null) return scopeSlotCount + nameIndex
|
||||
val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
|
||||
return scopeSlotMap[scopeKey]
|
||||
}
|
||||
@ -6705,6 +6775,7 @@ class BytecodeCompiler(
|
||||
is VarDeclStatement -> {
|
||||
val slotIndex = stmt.slotIndex
|
||||
val scopeId = stmt.scopeId ?: 0
|
||||
val isModuleDecl = moduleScopeId != null && scopeId == moduleScopeId
|
||||
val cls = stmt.initializerObjClass ?: objClassForInitializer(stmt.initializer)
|
||||
if (cls != null) {
|
||||
nameObjClass[stmt.name] = cls
|
||||
@ -6712,8 +6783,7 @@ class BytecodeCompiler(
|
||||
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
|
||||
}
|
||||
}
|
||||
val isModuleSlot = isModuleSlot(scopeId, stmt.name)
|
||||
if (allowLocalSlots && slotIndex != null && !isModuleSlot) {
|
||||
if (allowLocalSlots && slotIndex != null && !isModuleDecl) {
|
||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||
declaredLocalKeys.add(key)
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
@ -6740,7 +6810,8 @@ class BytecodeCompiler(
|
||||
val scopeId = stmt.spec.scopeId ?: 0
|
||||
if (slotIndex != null) {
|
||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||
if (allowLocalSlots) {
|
||||
val isModuleDecl = moduleScopeId != null && scopeId == moduleScopeId
|
||||
if (allowLocalSlots && !isModuleDecl) {
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false)
|
||||
}
|
||||
@ -6757,7 +6828,8 @@ class BytecodeCompiler(
|
||||
is DelegatedVarDeclStatement -> {
|
||||
val slotIndex = stmt.slotIndex
|
||||
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)
|
||||
declaredLocalKeys.add(key)
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
@ -6895,12 +6967,11 @@ class BytecodeCompiler(
|
||||
private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
|
||||
val moduleId = moduleScopeId
|
||||
if (moduleId == null) {
|
||||
if (allowedScopeNames == null || name == null) return false
|
||||
return allowedScopeNames.contains(name)
|
||||
val scopeNames = scopeSlotNameSet ?: allowedScopeNames
|
||||
if (scopeNames == null || name == null) return false
|
||||
return scopeNames.contains(name)
|
||||
}
|
||||
if (scopeId != moduleId) return false
|
||||
if (allowedScopeNames == null || name == null) return true
|
||||
return allowedScopeNames.contains(name)
|
||||
return scopeId == moduleId
|
||||
}
|
||||
|
||||
private fun collectLoopVarNames(stmt: Statement) {
|
||||
|
||||
@ -43,6 +43,7 @@ class BytecodeStatement private constructor(
|
||||
returnLabels: Set<String> = emptySet(),
|
||||
rangeLocalNames: Set<String> = emptySet(),
|
||||
allowedScopeNames: Set<String>? = null,
|
||||
scopeSlotNameSet: Set<String>? = null,
|
||||
moduleScopeId: Int? = null,
|
||||
forcedLocalSlots: Map<String, Int> = emptyMap(),
|
||||
forcedLocalScopeId: Int? = null,
|
||||
@ -71,6 +72,7 @@ class BytecodeStatement private constructor(
|
||||
returnLabels = returnLabels,
|
||||
rangeLocalNames = rangeLocalNames,
|
||||
allowedScopeNames = allowedScopeNames,
|
||||
scopeSlotNameSet = scopeSlotNameSet,
|
||||
moduleScopeId = moduleScopeId,
|
||||
forcedLocalSlots = forcedLocalSlots,
|
||||
forcedLocalScopeId = forcedLocalScopeId,
|
||||
|
||||
@ -2219,8 +2219,30 @@ class CmdGetClassScope(
|
||||
val scope = frame.ensureScope()
|
||||
val cls = frame.slotToObj(classSlot) as? ObjClass
|
||||
?: scope.raiseSymbolNotFound(nameConst.value)
|
||||
val rec = cls.readField(scope, nameConst.value)
|
||||
val value = scope.resolve(rec, nameConst.value)
|
||||
val name = 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)
|
||||
return
|
||||
}
|
||||
@ -3187,7 +3209,11 @@ class CmdFrame(
|
||||
SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse
|
||||
SlotType.OBJ.code -> {
|
||||
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
|
||||
}
|
||||
@ -3347,6 +3373,7 @@ class CmdFrame(
|
||||
val record = target.getSlotRecord(index)
|
||||
val direct = record.value
|
||||
if (direct is FrameSlotRef) return direct.read()
|
||||
if (direct is RecordSlotRef) return direct.read()
|
||||
val name = fn.scopeSlotNames[slot]
|
||||
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
|
||||
return target.resolve(record, name)
|
||||
@ -3368,6 +3395,7 @@ class CmdFrame(
|
||||
val record = target.getSlotRecord(index)
|
||||
val direct = record.value
|
||||
if (direct is FrameSlotRef) return direct.read()
|
||||
if (direct is RecordSlotRef) return direct.read()
|
||||
val slotId = addrScopeSlots[addrSlot]
|
||||
val name = fn.scopeSlotNames.getOrNull(slotId)
|
||||
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 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}")
|
||||
}
|
||||
}
|
||||
|
||||
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}")
|
||||
}
|
||||
}
|
||||
@ -283,19 +305,52 @@ open class 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}")
|
||||
}
|
||||
}
|
||||
|
||||
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}")
|
||||
}
|
||||
}
|
||||
|
||||
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}")
|
||||
}
|
||||
}
|
||||
@ -755,6 +810,14 @@ open class Obj {
|
||||
thisObj.putAt(this, requiredArg<Obj>(0), newValue)
|
||||
newValue
|
||||
}
|
||||
addFnDoc(
|
||||
name = "toJson",
|
||||
doc = "Encodes this object to JSON.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisObj.toJson(this).toString().toObj()
|
||||
}
|
||||
addFnDoc(
|
||||
name = "toJsonString",
|
||||
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.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lyng.statement
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
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.
|
||||
*/
|
||||
suspend fun evalValue(scope: Scope): Obj {
|
||||
val rec = get(scope)
|
||||
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
|
||||
}
|
||||
scope.raiseIllegalState("interpreter execution is not supported; ObjRef evaluation requires bytecode")
|
||||
}
|
||||
suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
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.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)
|
||||
|
||||
@ -105,117 +94,6 @@ class ForInStatement(
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
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(
|
||||
@ -299,18 +177,6 @@ fun Statement.require(cond: Boolean, message: () -> String) {
|
||||
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) {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
|
||||
@ -397,4 +397,5 @@ class BytecodeRecentOpsTest {
|
||||
val result = script.execute(Script.defaultImportManager.newStdScope())
|
||||
assertEquals(2, result.toInt())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -656,56 +656,58 @@ class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun whileTest() = runTest {
|
||||
assertEquals(
|
||||
5.0,
|
||||
eval(
|
||||
"""
|
||||
var acc = 0
|
||||
while( acc < 5 ) acc = acc + 0.5
|
||||
acc
|
||||
"""
|
||||
).toDouble()
|
||||
)
|
||||
assertEquals(
|
||||
5.0,
|
||||
eval(
|
||||
"""
|
||||
var acc = 0
|
||||
// return from while
|
||||
while( acc < 5 ) {
|
||||
acc = acc + 0.5
|
||||
withTimeout(2.seconds) {
|
||||
assertEquals(
|
||||
5.0,
|
||||
eval(
|
||||
"""
|
||||
var acc = 0
|
||||
while( acc < 5 ) acc = acc + 0.5
|
||||
acc
|
||||
}
|
||||
"""
|
||||
).toDouble()
|
||||
)
|
||||
assertEquals(
|
||||
3.0,
|
||||
eval(
|
||||
"""
|
||||
var acc = 0
|
||||
while( acc < 5 ) {
|
||||
acc = acc + 0.5
|
||||
if( acc >= 3 ) break
|
||||
}
|
||||
"""
|
||||
).toDouble()
|
||||
)
|
||||
assertEquals(
|
||||
5.0,
|
||||
eval(
|
||||
"""
|
||||
var acc = 0
|
||||
// return from while
|
||||
while( acc < 5 ) {
|
||||
acc = acc + 0.5
|
||||
acc
|
||||
}
|
||||
"""
|
||||
).toDouble()
|
||||
)
|
||||
assertEquals(
|
||||
3.0,
|
||||
eval(
|
||||
"""
|
||||
var acc = 0
|
||||
while( acc < 5 ) {
|
||||
acc = acc + 0.5
|
||||
if( acc >= 3 ) break
|
||||
}
|
||||
|
||||
acc
|
||||
acc
|
||||
|
||||
"""
|
||||
).toDouble()
|
||||
)
|
||||
assertEquals(
|
||||
17.0,
|
||||
eval(
|
||||
"""
|
||||
var acc = 0
|
||||
while( acc < 5 ) {
|
||||
acc = acc + 0.5
|
||||
if( acc >= 3 ) break 17
|
||||
}
|
||||
"""
|
||||
).toDouble()
|
||||
)
|
||||
"""
|
||||
).toDouble()
|
||||
)
|
||||
assertEquals(
|
||||
17.0,
|
||||
eval(
|
||||
"""
|
||||
var acc = 0
|
||||
while( acc < 5 ) {
|
||||
acc = acc + 0.5
|
||||
if( acc >= 3 ) break 17
|
||||
}
|
||||
"""
|
||||
).toDouble()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user