Remove interpreter fallbacks and enforce bytecode execution

This commit is contained in:
Sergey Chernov 2026-02-14 00:41:33 +03:00
parent 26564438e2
commit e73fe0d3c5
16 changed files with 700 additions and 701 deletions

View File

@ -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)
}

View File

@ -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

View File

@ -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,15 +184,20 @@ 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
seedingSlotPlan = true
try {
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)
scopeSeedNames.add(name)
val instance = record.value as? ObjInstance
if (instance != null && nameObjClass[name] == null) {
nameObjClass[name] = instance.objClass
@ -204,6 +216,7 @@ class Compiler(
isMutable = false,
isDelegated = false
)
scopeSeedNames.add(getterName)
}
val prop = record.value as? ObjProperty
if (prop?.setter != null) {
@ -215,6 +228,7 @@ class Compiler(
isMutable = false,
isDelegated = false
)
scopeSeedNames.add(setterName)
}
}
}
@ -227,6 +241,7 @@ class Compiler(
isMutable = false,
isDelegated = false
)
scopeSeedNames.add(callableName)
}
}
}
@ -237,10 +252,14 @@ class Compiler(
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
}
} finally {
seedingSlotPlan = false
}
}
private fun seedSlotPlanFromSeedScope(scope: Scope) {
@ -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 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()
}

View File

@ -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

View File

@ -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
val rec = get(first) ?: raiseSymbolNotFound(first)
resolve(rec, first)
}
if (slot == null) raiseSymbolNotFound(first)
LocalSlotRef(first, slot, 0, isMutable = false, isDelegated = false, Pos.builtIn, strict = true)
}
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 {

View File

@ -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>()

View File

@ -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)
}
}

View File

@ -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,9 +2648,45 @@ 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 fieldId = receiverClass.instanceFieldIdMap()[target.name]
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name]
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 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 (!target.isOptional) {
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, newValue.slot)
@ -2661,6 +2737,7 @@ class BytecodeCompiler(
}
}
}
}
is IndexRef -> {
val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null
if (!target.optionalRef) {
@ -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()) {
@ -4680,19 +4880,13 @@ class BytecodeCompiler(
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) {

View File

@ -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,

View File

@ -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)) {

View File

@ -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.",

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -397,4 +397,5 @@ class BytecodeRecentOpsTest {
val result = script.execute(Script.defaultImportManager.newStdScope())
assertEquals(2, result.toInt())
}
}

View File

@ -656,6 +656,7 @@ class ScriptTest {
@Test
fun whileTest() = runTest {
withTimeout(2.seconds) {
assertEquals(
5.0,
eval(
@ -707,6 +708,7 @@ class ScriptTest {
).toDouble()
)
}
}
@Test
fun testAssignArgumentsNoEllipsis() = runTest {