Update compile-time resolution and tests
This commit is contained in:
1 parent
8f60a84e3b
commit
523b9d338b
46 files changed
+3771
-967
No files matched your search
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
/**
|
||||
* Compile-time call metadata for known functions. Used to select lambda receiver semantics.
|
||||
*/
|
||||
data class CallSignature(
|
||||
val tailBlockReceiverType: String? = null
|
||||
)
|
||||
@@ -26,12 +26,25 @@ import net.sergeych.lyng.obj.ObjRecord
|
||||
* Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols
|
||||
* from [closureScope] with proper precedence
|
||||
*/
|
||||
class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
||||
class ClosureScope(
|
||||
val callScope: Scope,
|
||||
val closureScope: Scope,
|
||||
private val preferredThisType: String? = null
|
||||
) :
|
||||
// Important: use closureScope.thisObj so unqualified members (e.g., fields) resolve to the instance
|
||||
// we captured, not to the caller's `this` (e.g., FlowBuilder).
|
||||
Scope(callScope, callScope.args, thisObj = closureScope.thisObj) {
|
||||
|
||||
init {
|
||||
val desired = preferredThisType?.let { typeName ->
|
||||
callScope.thisVariants.firstOrNull { it.objClass.className == typeName }
|
||||
}
|
||||
val primaryThis = closureScope.thisObj
|
||||
val merged = ArrayList<Obj>(callScope.thisVariants.size + closureScope.thisVariants.size + 1)
|
||||
desired?.let { merged.add(it) }
|
||||
merged.addAll(callScope.thisVariants)
|
||||
merged.addAll(closureScope.thisVariants)
|
||||
setThisVariants(primaryThis, merged)
|
||||
// Preserve the lexical class context of the closure by default. This ensures that lambdas
|
||||
// created inside a class method keep access to that class's private/protected members even
|
||||
// when executed from within another object's method (e.g., Mutex.withLock), which may set
|
||||
@@ -72,14 +85,14 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
||||
}
|
||||
|
||||
class ApplyScope(val callScope: Scope, val applied: Scope) :
|
||||
Scope(callScope.parent?.parent ?: callScope.parent ?: callScope, thisObj = applied.thisObj) {
|
||||
Scope(callScope, thisObj = applied.thisObj) {
|
||||
|
||||
override fun get(name: String): ObjRecord? {
|
||||
return applied.get(name) ?: super.get(name)
|
||||
}
|
||||
|
||||
override fun applyClosure(closure: Scope): Scope {
|
||||
return this
|
||||
override fun applyClosure(closure: Scope, preferredThisType: String?): Scope {
|
||||
return ClosureScope(this, closure, preferredThisType)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,10 +19,19 @@ package net.sergeych.lyng
|
||||
|
||||
sealed class CodeContext {
|
||||
class Module(@Suppress("unused") val packageName: String?): CodeContext()
|
||||
class Function(val name: String, val implicitThisMembers: Boolean = false): CodeContext()
|
||||
class Function(
|
||||
val name: String,
|
||||
val implicitThisMembers: Boolean = false,
|
||||
val implicitThisTypeName: String? = null
|
||||
): CodeContext()
|
||||
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
|
||||
val pendingInitializations = mutableMapOf<String, Pos>()
|
||||
val declaredMembers = mutableSetOf<String>()
|
||||
val memberOverrides = mutableMapOf<String, Boolean>()
|
||||
val memberFieldIds = mutableMapOf<String, Int>()
|
||||
val memberMethodIds = mutableMapOf<String, Int>()
|
||||
var nextFieldId: Int = 0
|
||||
var nextMethodId: Int = 0
|
||||
var slotPlanId: Int? = null
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
internal fun extensionCallableName(typeName: String, memberName: String): String {
|
||||
return "__ext__${sanitizeExtensionTypeName(typeName)}__${memberName}"
|
||||
}
|
||||
|
||||
internal fun extensionPropertyGetterName(typeName: String, memberName: String): String {
|
||||
return "__ext_get__${sanitizeExtensionTypeName(typeName)}__${memberName}"
|
||||
}
|
||||
|
||||
internal fun extensionPropertySetterName(typeName: String, memberName: String): String {
|
||||
return "__ext_set__${sanitizeExtensionTypeName(typeName)}__${memberName}"
|
||||
}
|
||||
|
||||
private fun sanitizeExtensionTypeName(typeName: String): String {
|
||||
return typeName.replace('.', '_')
|
||||
}
|
||||
@@ -18,6 +18,8 @@ package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.ObjExtensionPropertyGetterCallable
|
||||
import net.sergeych.lyng.obj.ObjExtensionPropertySetterCallable
|
||||
import net.sergeych.lyng.obj.ObjProperty
|
||||
import net.sergeych.lyng.obj.ObjRecord
|
||||
|
||||
@@ -45,6 +47,14 @@ class ExtensionPropertyDeclStatement(
|
||||
type = ObjRecord.Type.Property
|
||||
)
|
||||
)
|
||||
val getterName = extensionPropertyGetterName(extTypeName, property.name)
|
||||
val getterWrapper = ObjExtensionPropertyGetterCallable(property.name, property)
|
||||
context.addItem(getterName, false, getterWrapper, visibility, recordType = ObjRecord.Type.Fun)
|
||||
if (property.setter != null) {
|
||||
val setterName = extensionPropertySetterName(extTypeName, property.name)
|
||||
val setterWrapper = ObjExtensionPropertySetterCallable(property.name, property)
|
||||
context.addItem(setterName, false, setterWrapper, visibility, recordType = ObjRecord.Type.Fun)
|
||||
}
|
||||
return property
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjVoid
|
||||
|
||||
class InlineBlockStatement(
|
||||
private val statements: List<Statement>,
|
||||
private val startPos: Pos,
|
||||
) : Statement() {
|
||||
override val pos: Pos = startPos
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
var last: Obj = ObjVoid
|
||||
for (stmt in statements) {
|
||||
last = stmt.execute(scope)
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
fun statements(): List<Statement> = statements
|
||||
}
|
||||
@@ -52,6 +52,8 @@ open class Scope(
|
||||
var currentClassCtx: net.sergeych.lyng.obj.ObjClass? = parent?.currentClassCtx
|
||||
// Unique id per scope frame for PICs; regenerated on each borrow from the pool.
|
||||
var frameId: Long = nextFrameId()
|
||||
@PublishedApi
|
||||
internal val thisVariants: MutableList<Obj> = mutableListOf()
|
||||
|
||||
// Fast-path storage for local variables/arguments accessed by slot index.
|
||||
// Enabled by default for child scopes; module/class scopes can ignore it.
|
||||
@@ -66,6 +68,21 @@ open class Scope(
|
||||
|
||||
internal val extensions: MutableMap<ObjClass, MutableMap<String, ObjRecord>> = mutableMapOf()
|
||||
|
||||
init {
|
||||
setThisVariants(thisObj, parent?.thisVariants ?: emptyList())
|
||||
}
|
||||
|
||||
internal fun setThisVariants(primary: Obj, extras: List<Obj>) {
|
||||
thisObj = primary
|
||||
thisVariants.clear()
|
||||
thisVariants.add(primary)
|
||||
for (obj in extras) {
|
||||
if (obj !== primary && !thisVariants.contains(obj)) {
|
||||
thisVariants.add(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addExtension(cls: ObjClass, name: String, record: ObjRecord) {
|
||||
extensions.getOrPut(cls) { mutableMapOf() }[name] = record
|
||||
}
|
||||
@@ -340,11 +357,8 @@ open class Scope(
|
||||
}
|
||||
|
||||
inline fun <reified T : Obj> thisAs(): T {
|
||||
var s: Scope? = this
|
||||
while (s != null) {
|
||||
val t = s.thisObj
|
||||
if (t is T) return t
|
||||
s = s.parent
|
||||
for (obj in thisVariants) {
|
||||
if (obj is T) return obj
|
||||
}
|
||||
raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
||||
}
|
||||
@@ -406,6 +420,8 @@ open class Scope(
|
||||
get() = slots.size
|
||||
|
||||
fun getSlotIndexOf(name: String): Int? = nameToSlot[name]
|
||||
|
||||
internal fun slotNameToIndexSnapshot(): Map<String, Int> = nameToSlot.toMap()
|
||||
fun allocateSlotFor(name: String, record: ObjRecord): Int {
|
||||
val idx = slots.size
|
||||
slots.add(record)
|
||||
@@ -490,6 +506,7 @@ open class Scope(
|
||||
this.parent = null
|
||||
this.skipScopeCreation = false
|
||||
this.currentClassCtx = null
|
||||
thisVariants.clear()
|
||||
objects.clear()
|
||||
slots.clear()
|
||||
nameToSlot.clear()
|
||||
@@ -520,7 +537,7 @@ open class Scope(
|
||||
this.parent = parent
|
||||
this.args = args
|
||||
this.pos = pos
|
||||
this.thisObj = thisObj
|
||||
setThisVariants(thisObj, parent?.thisVariants ?: emptyList())
|
||||
// Pre-size local slots for upcoming parameter assignment where possible
|
||||
reserveLocalCapacity(args.list.size + 4)
|
||||
}
|
||||
@@ -609,7 +626,10 @@ open class Scope(
|
||||
isAbstract: Boolean = false,
|
||||
isClosed: Boolean = false,
|
||||
isOverride: Boolean = false,
|
||||
isTransient: Boolean = false
|
||||
isTransient: Boolean = false,
|
||||
callSignature: CallSignature? = null,
|
||||
fieldId: Int? = null,
|
||||
methodId: Int? = null
|
||||
): ObjRecord {
|
||||
val rec = ObjRecord(
|
||||
value, isMutable, visibility, writeVisibility,
|
||||
@@ -618,15 +638,19 @@ open class Scope(
|
||||
isAbstract = isAbstract,
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient
|
||||
isTransient = isTransient,
|
||||
callSignature = callSignature,
|
||||
memberName = name,
|
||||
fieldId = fieldId,
|
||||
methodId = methodId
|
||||
)
|
||||
objects[name] = rec
|
||||
bumpClassLayoutIfNeeded(name, value, recordType)
|
||||
if (recordType == ObjRecord.Type.Field || recordType == ObjRecord.Type.ConstructorField) {
|
||||
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
|
||||
if (inst != null) {
|
||||
val slot = inst.objClass.fieldSlotForKey(name)
|
||||
if (slot != null) inst.setFieldSlotRecord(slot.slot, rec)
|
||||
val slotId = rec.fieldId ?: inst.objClass.fieldSlotForKey(name)?.slot
|
||||
if (slotId != null) inst.setFieldSlotRecord(slotId, rec)
|
||||
}
|
||||
}
|
||||
if (value is Statement ||
|
||||
@@ -635,8 +659,8 @@ open class Scope(
|
||||
recordType == ObjRecord.Type.Property) {
|
||||
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
|
||||
if (inst != null) {
|
||||
val slot = inst.objClass.methodSlotForKey(name)
|
||||
if (slot != null) inst.setMethodSlotRecord(slot.slot, rec)
|
||||
val slotId = rec.methodId ?: inst.objClass.methodSlotForKey(name)?.slot
|
||||
if (slotId != null) inst.setMethodSlotRecord(slotId, rec)
|
||||
}
|
||||
}
|
||||
// Index this binding within the current frame to help resolve locals across suspension
|
||||
@@ -697,7 +721,7 @@ open class Scope(
|
||||
return CmdDisassembler.disassemble(bytecode)
|
||||
}
|
||||
|
||||
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) {
|
||||
fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend Scope.() -> Obj) {
|
||||
val newFn = object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
@@ -708,7 +732,9 @@ open class Scope(
|
||||
addItem(
|
||||
name,
|
||||
false,
|
||||
newFn
|
||||
newFn,
|
||||
recordType = ObjRecord.Type.Fun,
|
||||
callSignature = callSignature
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -778,7 +804,8 @@ open class Scope(
|
||||
println("--------------------")
|
||||
}
|
||||
|
||||
open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure)
|
||||
open fun applyClosure(closure: Scope, preferredThisType: String? = null): Scope =
|
||||
ClosureScope(this, closure, preferredThisType)
|
||||
|
||||
/**
|
||||
* Resolve and evaluate a qualified identifier exactly as compiled code would.
|
||||
|
||||
@@ -428,8 +428,11 @@ class Script(
|
||||
addConst("CompletableDeferred", ObjCompletableDeferred.type)
|
||||
addConst("Mutex", ObjMutex.type)
|
||||
addConst("Flow", ObjFlow.type)
|
||||
addConst("FlowBuilder", ObjFlowBuilder.type)
|
||||
|
||||
addConst("Regex", ObjRegex.type)
|
||||
addConst("RegexMatch", ObjRegexMatch.type)
|
||||
addConst("MapEntry", ObjMapEntry.type)
|
||||
|
||||
addFn("launch") {
|
||||
val callable = requireOnlyArg<Statement>()
|
||||
@@ -443,7 +446,7 @@ class Script(
|
||||
ObjVoid
|
||||
}
|
||||
|
||||
addFn("flow") {
|
||||
addFn("flow", callSignature = CallSignature(tailBlockReceiverType = "FlowBuilder")) {
|
||||
// important is: current context contains closure often used in call;
|
||||
// we'll need it for the producer
|
||||
ObjFlow(requireOnlyArg<Statement>(), this)
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.ObjNull
|
||||
import net.sergeych.lyng.obj.ObjRecord
|
||||
import net.sergeych.lyng.obj.ObjUnset
|
||||
|
||||
class VarDeclStatement(
|
||||
val name: String,
|
||||
@@ -29,11 +31,12 @@ class VarDeclStatement(
|
||||
val slotIndex: Int?,
|
||||
val scopeId: Int?,
|
||||
private val startPos: Pos,
|
||||
val initializerObjClass: ObjClass? = null,
|
||||
) : Statement() {
|
||||
override val pos: Pos = startPos
|
||||
|
||||
override suspend fun execute(context: Scope): Obj {
|
||||
val initValue = initializer?.execute(context)?.byValueCopy() ?: ObjNull
|
||||
val initValue = initializer?.execute(context)?.byValueCopy() ?: ObjUnset
|
||||
context.addItem(
|
||||
name,
|
||||
isMutable,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -49,6 +49,7 @@ class BytecodeStatement private constructor(
|
||||
allowLocalSlots: Boolean,
|
||||
returnLabels: Set<String> = emptySet(),
|
||||
rangeLocalNames: Set<String> = emptySet(),
|
||||
allowedScopeNames: Set<String>? = null,
|
||||
): Statement {
|
||||
if (statement is BytecodeStatement) return statement
|
||||
val hasUnsupported = containsUnsupportedStatement(statement)
|
||||
@@ -63,7 +64,8 @@ class BytecodeStatement private constructor(
|
||||
val compiler = BytecodeCompiler(
|
||||
allowLocalSlots = safeLocals,
|
||||
returnLabels = returnLabels,
|
||||
rangeLocalNames = rangeLocalNames
|
||||
rangeLocalNames = rangeLocalNames,
|
||||
allowedScopeNames = allowedScopeNames
|
||||
)
|
||||
val compiled = compiler.compileStatement(nameHint, statement)
|
||||
val fn = compiled ?: throw BytecodeFallbackException(
|
||||
@@ -76,7 +78,14 @@ class BytecodeStatement private constructor(
|
||||
private fun containsUnsupportedStatement(stmt: Statement): Boolean {
|
||||
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
||||
return when (target) {
|
||||
is net.sergeych.lyng.ExpressionStatement -> false
|
||||
is net.sergeych.lyng.ExpressionStatement -> {
|
||||
val ref = target.ref
|
||||
if (ref is net.sergeych.lyng.obj.StatementRef) {
|
||||
containsUnsupportedStatement(ref.statement)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
is net.sergeych.lyng.IfStatement -> {
|
||||
containsUnsupportedStatement(target.condition) ||
|
||||
containsUnsupportedStatement(target.ifBody) ||
|
||||
@@ -100,6 +109,8 @@ class BytecodeStatement private constructor(
|
||||
}
|
||||
is net.sergeych.lyng.BlockStatement ->
|
||||
target.statements().any { containsUnsupportedStatement(it) }
|
||||
is net.sergeych.lyng.InlineBlockStatement ->
|
||||
target.statements().any { containsUnsupportedStatement(it) }
|
||||
is net.sergeych.lyng.VarDeclStatement ->
|
||||
target.initializer?.let { containsUnsupportedStatement(it) } ?: false
|
||||
is net.sergeych.lyng.DelegatedVarDeclStatement ->
|
||||
@@ -117,7 +128,7 @@ class BytecodeStatement private constructor(
|
||||
is net.sergeych.lyng.ClassDeclStatement -> false
|
||||
is net.sergeych.lyng.FunctionDeclStatement -> false
|
||||
is net.sergeych.lyng.EnumDeclStatement -> false
|
||||
is net.sergeych.lyng.TryStatement -> false
|
||||
is net.sergeych.lyng.TryStatement -> true
|
||||
is net.sergeych.lyng.WhenStatement -> {
|
||||
containsUnsupportedStatement(target.value) ||
|
||||
target.cases.any { case ->
|
||||
@@ -151,7 +162,8 @@ class BytecodeStatement private constructor(
|
||||
stmt.isTransient,
|
||||
stmt.slotIndex,
|
||||
stmt.scopeId,
|
||||
stmt.pos
|
||||
stmt.pos,
|
||||
stmt.initializerObjClass
|
||||
)
|
||||
}
|
||||
is net.sergeych.lyng.DestructuringVarDeclStatement -> {
|
||||
|
||||
@@ -137,7 +137,7 @@ class CmdBuilder {
|
||||
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||
Opcode.CONST_NULL ->
|
||||
listOf(OperandKind.SLOT)
|
||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL, Opcode.MAKE_VALUE_FN ->
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||
listOf(OperandKind.CONST)
|
||||
@@ -161,14 +161,18 @@ class CmdBuilder {
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.ASSIGN_OP_OBJ ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST)
|
||||
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->
|
||||
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.LOAD_THIS ->
|
||||
listOf(OperandKind.SLOT)
|
||||
Opcode.LOAD_THIS_VARIANT ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.JMP ->
|
||||
listOf(OperandKind.IP)
|
||||
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
|
||||
listOf(OperandKind.SLOT, OperandKind.IP)
|
||||
Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK ->
|
||||
Opcode.CALL_DIRECT ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.CALL_MEMBER_SLOT ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.CALL_SLOT ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.CALL_VIRTUAL ->
|
||||
@@ -177,8 +181,6 @@ class CmdBuilder {
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.SET_FIELD ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.GET_NAME ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.GET_INDEX ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.SET_INDEX ->
|
||||
@@ -187,12 +189,10 @@ class CmdBuilder {
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.LIST_LITERAL ->
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.GET_THIS_MEMBER ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.SET_THIS_MEMBER ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.GET_MEMBER_SLOT ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.SET_MEMBER_SLOT ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.ITER_PUSH ->
|
||||
listOf(OperandKind.SLOT)
|
||||
Opcode.ITER_POP, Opcode.ITER_CANCEL ->
|
||||
@@ -229,9 +229,12 @@ class CmdBuilder {
|
||||
Opcode.CONST_REAL -> CmdConstReal(operands[0], operands[1])
|
||||
Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1])
|
||||
Opcode.CONST_NULL -> CmdConstNull(operands[0])
|
||||
Opcode.MAKE_VALUE_FN -> CmdMakeValueFn(operands[0], operands[1])
|
||||
Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1])
|
||||
Opcode.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
|
||||
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3])
|
||||
Opcode.LOAD_THIS -> CmdLoadThis(operands[0])
|
||||
Opcode.LOAD_THIS_VARIANT -> CmdLoadThisVariant(operands[0], operands[1])
|
||||
Opcode.MAKE_RANGE -> CmdMakeRange(operands[0], operands[1], operands[2], operands[3])
|
||||
Opcode.CHECK_IS -> CmdCheckIs(operands[0], operands[1], operands[2])
|
||||
Opcode.ASSERT_IS -> CmdAssertIs(operands[0], operands[1])
|
||||
@@ -378,21 +381,16 @@ class CmdBuilder {
|
||||
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
|
||||
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
|
||||
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
|
||||
Opcode.CALL_MEMBER_SLOT -> CmdCallMemberSlot(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||
Opcode.CALL_VIRTUAL -> CmdCallVirtual(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||
Opcode.CALL_FALLBACK -> CmdCallFallback(operands[0], operands[1], operands[2], operands[3])
|
||||
Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3])
|
||||
Opcode.GET_FIELD -> CmdGetField(operands[0], operands[1], operands[2])
|
||||
Opcode.SET_FIELD -> CmdSetField(operands[0], operands[1], operands[2])
|
||||
Opcode.GET_NAME -> CmdGetName(operands[0], operands[1])
|
||||
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
|
||||
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
|
||||
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
|
||||
Opcode.GET_THIS_MEMBER -> CmdGetThisMember(operands[0], operands[1])
|
||||
Opcode.SET_THIS_MEMBER -> CmdSetThisMember(operands[0], operands[1])
|
||||
Opcode.EVAL_FALLBACK -> CmdEvalFallback(operands[0], operands[1])
|
||||
Opcode.EVAL_REF -> CmdEvalRef(operands[0], operands[1])
|
||||
Opcode.EVAL_STMT -> CmdEvalStmt(operands[0], operands[1])
|
||||
Opcode.EVAL_VALUE_FN -> CmdEvalValueFn(operands[0], operands[1])
|
||||
Opcode.GET_MEMBER_SLOT -> CmdGetMemberSlot(operands[0], operands[1], operands[2], operands[3])
|
||||
Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3])
|
||||
Opcode.ITER_PUSH -> CmdIterPush(operands[0])
|
||||
Opcode.ITER_POP -> CmdIterPop()
|
||||
Opcode.ITER_CANCEL -> CmdIterCancel()
|
||||
|
||||
@@ -66,7 +66,10 @@ object CmdDisassembler {
|
||||
is CmdConstIntLocal -> Opcode.CONST_INT to intArrayOf(cmd.constId, cmd.dst + fn.scopeSlotCount)
|
||||
is CmdConstReal -> Opcode.CONST_REAL to intArrayOf(cmd.constId, cmd.dst)
|
||||
is CmdConstBool -> Opcode.CONST_BOOL to intArrayOf(cmd.constId, cmd.dst)
|
||||
is CmdLoadThis -> Opcode.LOAD_THIS to intArrayOf(cmd.dst)
|
||||
is CmdLoadThisVariant -> Opcode.LOAD_THIS_VARIANT to intArrayOf(cmd.typeId, cmd.dst)
|
||||
is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst)
|
||||
is CmdMakeValueFn -> Opcode.MAKE_VALUE_FN to intArrayOf(cmd.id, cmd.dst)
|
||||
is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst)
|
||||
is CmdObjToBool -> Opcode.OBJ_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
|
||||
is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
||||
@@ -178,23 +181,19 @@ object CmdDisassembler {
|
||||
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
|
||||
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
||||
is CmdCallVirtual -> Opcode.CALL_VIRTUAL to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
|
||||
is CmdCallFallback -> Opcode.CALL_FALLBACK to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
||||
is CmdCallMemberSlot -> Opcode.CALL_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
|
||||
is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
|
||||
is CmdGetField -> Opcode.GET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.dst)
|
||||
is CmdSetField -> Opcode.SET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.valueSlot)
|
||||
is CmdGetName -> Opcode.GET_NAME to intArrayOf(cmd.nameId, cmd.dst)
|
||||
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
|
||||
is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
|
||||
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
|
||||
is CmdGetThisMember -> Opcode.GET_THIS_MEMBER to intArrayOf(cmd.nameId, cmd.dst)
|
||||
is CmdSetThisMember -> Opcode.SET_THIS_MEMBER to intArrayOf(cmd.nameId, cmd.valueSlot)
|
||||
is CmdEvalFallback -> Opcode.EVAL_FALLBACK to intArrayOf(cmd.id, cmd.dst)
|
||||
is CmdEvalRef -> Opcode.EVAL_REF to intArrayOf(cmd.id, cmd.dst)
|
||||
is CmdEvalStmt -> Opcode.EVAL_STMT to intArrayOf(cmd.id, cmd.dst)
|
||||
is CmdEvalValueFn -> Opcode.EVAL_VALUE_FN to intArrayOf(cmd.id, cmd.dst)
|
||||
is CmdGetMemberSlot -> Opcode.GET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.dst)
|
||||
is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot)
|
||||
is CmdIterPush -> Opcode.ITER_PUSH to intArrayOf(cmd.iterSlot)
|
||||
is CmdIterPop -> Opcode.ITER_POP to intArrayOf()
|
||||
is CmdIterCancel -> Opcode.ITER_CANCEL to intArrayOf()
|
||||
else -> error("Unsupported cmd in disassembler: ${cmd::class.simpleName}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +230,7 @@ object CmdDisassembler {
|
||||
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||
Opcode.CONST_NULL ->
|
||||
listOf(OperandKind.SLOT)
|
||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL, Opcode.MAKE_VALUE_FN ->
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||
listOf(OperandKind.CONST)
|
||||
@@ -255,36 +254,36 @@ object CmdDisassembler {
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.ASSIGN_OP_OBJ ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST)
|
||||
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.ITER_PUSH ->
|
||||
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.ITER_PUSH, Opcode.LOAD_THIS ->
|
||||
listOf(OperandKind.SLOT)
|
||||
Opcode.LOAD_THIS_VARIANT ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.JMP ->
|
||||
listOf(OperandKind.IP)
|
||||
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
|
||||
listOf(OperandKind.SLOT, OperandKind.IP)
|
||||
Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK ->
|
||||
Opcode.CALL_DIRECT ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.CALL_SLOT ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.CALL_VIRTUAL ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.CALL_MEMBER_SLOT ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.GET_FIELD ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.SET_FIELD ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.GET_NAME ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.GET_INDEX ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.SET_INDEX ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.LIST_LITERAL ->
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.GET_THIS_MEMBER ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.SET_THIS_MEMBER ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.GET_MEMBER_SLOT ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.SET_MEMBER_SLOT ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,6 +152,28 @@ class CmdConstBool(internal val constId: Int, internal val dst: Int) : Cmd() {
|
||||
}
|
||||
}
|
||||
|
||||
class CmdLoadThis(internal val dst: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
frame.setObj(dst, frame.scope.thisObj)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdLoadThisVariant(
|
||||
internal val typeId: Int,
|
||||
internal val dst: Int,
|
||||
) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val typeConst = frame.fn.constants.getOrNull(typeId) as? BytecodeConst.StringVal
|
||||
?: error("LOAD_THIS_VARIANT expects StringVal at $typeId")
|
||||
val typeName = typeConst.value
|
||||
val receiver = frame.scope.thisVariants.firstOrNull { it.isInstanceOf(typeName) }
|
||||
?: frame.scope.raiseClassCastError("Cannot cast ${frame.scope.thisObj.objClass.className} to $typeName")
|
||||
frame.setObj(dst, receiver)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdMakeRange(
|
||||
internal val startSlot: Int,
|
||||
internal val endSlot: Int,
|
||||
@@ -1146,20 +1168,7 @@ class CmdCallVirtual(
|
||||
internal val dst: Int,
|
||||
) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||
frame.syncFrameToScope()
|
||||
}
|
||||
val receiver = frame.slotToObj(recvSlot)
|
||||
val nameConst = frame.fn.constants.getOrNull(methodId) as? BytecodeConst.StringVal
|
||||
?: error("CALL_VIRTUAL expects StringVal at $methodId")
|
||||
val args = frame.buildArguments(argBase, argCount)
|
||||
val site = frame.methodCallSites.getOrPut(frame.ip - 1) { MethodCallSite(nameConst.value) }
|
||||
val result = site.invoke(frame.scope, receiver, args)
|
||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||
frame.syncScopeToFrame()
|
||||
}
|
||||
frame.storeObjResult(dst, result)
|
||||
return
|
||||
frame.scope.raiseError("CALL_VIRTUAL is not allowed: compile-time member resolution is required")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1313,38 +1322,87 @@ class CmdListLiteral(
|
||||
}
|
||||
}
|
||||
|
||||
class CmdGetThisMember(
|
||||
internal val nameId: Int,
|
||||
class CmdGetMemberSlot(
|
||||
internal val recvSlot: Int,
|
||||
internal val fieldId: Int,
|
||||
internal val methodId: Int,
|
||||
internal val dst: Int,
|
||||
) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val receiver = frame.slotToObj(recvSlot)
|
||||
val inst = receiver as? ObjInstance
|
||||
val fieldRec = if (fieldId >= 0) {
|
||||
inst?.fieldRecordForId(fieldId) ?: receiver.objClass.fieldRecordForId(fieldId)
|
||||
} else null
|
||||
val rec = fieldRec ?: run {
|
||||
if (methodId >= 0) {
|
||||
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
|
||||
} else null
|
||||
} ?: frame.scope.raiseSymbolNotFound("member")
|
||||
val name = rec.memberName ?: "<member>"
|
||||
val resolved = receiver.resolveRecord(frame.scope, rec, name, rec.declaringClass)
|
||||
frame.storeObjResult(dst, resolved.value)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdSetMemberSlot(
|
||||
internal val recvSlot: Int,
|
||||
internal val fieldId: Int,
|
||||
internal val methodId: Int,
|
||||
internal val valueSlot: Int,
|
||||
) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val receiver = frame.slotToObj(recvSlot)
|
||||
val inst = receiver as? ObjInstance
|
||||
val fieldRec = if (fieldId >= 0) {
|
||||
inst?.fieldRecordForId(fieldId) ?: receiver.objClass.fieldRecordForId(fieldId)
|
||||
} else null
|
||||
val rec = fieldRec ?: run {
|
||||
if (methodId >= 0) {
|
||||
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
|
||||
} else null
|
||||
} ?: frame.scope.raiseSymbolNotFound("member")
|
||||
val name = rec.memberName ?: "<member>"
|
||||
frame.scope.assign(rec, name, frame.slotToObj(valueSlot))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdCallMemberSlot(
|
||||
internal val recvSlot: Int,
|
||||
internal val methodId: Int,
|
||||
internal val argBase: Int,
|
||||
internal val argCount: Int,
|
||||
internal val dst: Int,
|
||||
) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||
frame.syncFrameToScope()
|
||||
}
|
||||
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal
|
||||
?: error("GET_THIS_MEMBER expects StringVal at $nameId")
|
||||
val ref = net.sergeych.lyng.obj.ImplicitThisMemberRef(nameConst.value, frame.scope.pos)
|
||||
val result = ref.evalValue(frame.scope)
|
||||
frame.storeObjResult(dst, result)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdSetThisMember(
|
||||
internal val nameId: Int,
|
||||
internal val valueSlot: Int,
|
||||
) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||
frame.syncFrameToScope()
|
||||
val receiver = frame.slotToObj(recvSlot)
|
||||
val inst = receiver as? ObjInstance
|
||||
val rec = inst?.methodRecordForId(methodId)
|
||||
?: receiver.objClass.methodRecordForId(methodId)
|
||||
?: frame.scope.raiseError("member id $methodId not found on ${receiver.objClass.className}")
|
||||
val callArgs = frame.buildArguments(argBase, argCount)
|
||||
val name = rec.memberName ?: "<member>"
|
||||
val decl = rec.declaringClass ?: receiver.objClass
|
||||
val result = when (rec.type) {
|
||||
ObjRecord.Type.Property -> {
|
||||
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(frame.scope, receiver, decl)
|
||||
else frame.scope.raiseError("property $name cannot be called with arguments")
|
||||
}
|
||||
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> {
|
||||
val callScope = inst?.instanceScope ?: frame.scope
|
||||
rec.value.invoke(callScope, receiver, callArgs, decl)
|
||||
}
|
||||
else -> frame.scope.raiseError("member $name is not callable")
|
||||
}
|
||||
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal
|
||||
?: error("SET_THIS_MEMBER expects StringVal at $nameId")
|
||||
val ref = net.sergeych.lyng.obj.ImplicitThisMemberRef(nameConst.value, frame.scope.pos)
|
||||
ref.setAt(frame.scope.pos, frame.scope, frame.slotToObj(valueSlot))
|
||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||
frame.syncScopeToFrame()
|
||||
}
|
||||
frame.storeObjResult(dst, result)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1449,13 +1507,13 @@ class CmdEvalStmt(internal val id: Int, internal val dst: Int) : Cmd() {
|
||||
}
|
||||
}
|
||||
|
||||
class CmdEvalValueFn(internal val id: Int, internal val dst: Int) : Cmd() {
|
||||
class CmdMakeValueFn(internal val id: Int, internal val dst: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||
frame.syncFrameToScope()
|
||||
}
|
||||
val valueFn = frame.fn.constants.getOrNull(id) as? BytecodeConst.ValueFn
|
||||
?: error("EVAL_VALUE_FN expects ValueFn at $id")
|
||||
?: error("MAKE_VALUE_FN expects ValueFn at $id")
|
||||
val result = valueFn.fn(frame.scope).value
|
||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||
frame.syncScopeToFrame()
|
||||
|
||||
@@ -30,6 +30,9 @@ enum class Opcode(val code: Int) {
|
||||
BOX_OBJ(0x0A),
|
||||
RANGE_INT_BOUNDS(0x0B),
|
||||
MAKE_RANGE(0x0C),
|
||||
LOAD_THIS(0x0D),
|
||||
MAKE_VALUE_FN(0x0E),
|
||||
LOAD_THIS_VARIANT(0x0F),
|
||||
|
||||
INT_TO_REAL(0x10),
|
||||
REAL_TO_INT(0x11),
|
||||
@@ -124,19 +127,17 @@ enum class Opcode(val code: Int) {
|
||||
|
||||
CALL_DIRECT(0x90),
|
||||
CALL_VIRTUAL(0x91),
|
||||
CALL_FALLBACK(0x92),
|
||||
CALL_MEMBER_SLOT(0x92),
|
||||
CALL_SLOT(0x93),
|
||||
|
||||
GET_FIELD(0xA0),
|
||||
SET_FIELD(0xA1),
|
||||
GET_INDEX(0xA2),
|
||||
SET_INDEX(0xA3),
|
||||
GET_NAME(0xA4),
|
||||
LIST_LITERAL(0xA5),
|
||||
GET_THIS_MEMBER(0xA6),
|
||||
SET_THIS_MEMBER(0xA7),
|
||||
GET_MEMBER_SLOT(0xA8),
|
||||
SET_MEMBER_SLOT(0xA9),
|
||||
|
||||
EVAL_FALLBACK(0xB0),
|
||||
RESOLVE_SCOPE_SLOT(0xB1),
|
||||
LOAD_OBJ_ADDR(0xB2),
|
||||
STORE_OBJ_ADDR(0xB3),
|
||||
@@ -147,9 +148,6 @@ enum class Opcode(val code: Int) {
|
||||
LOAD_BOOL_ADDR(0xB8),
|
||||
STORE_BOOL_ADDR(0xB9),
|
||||
THROW(0xBB),
|
||||
EVAL_REF(0xBC),
|
||||
EVAL_STMT(0xBD),
|
||||
EVAL_VALUE_FN(0xBE),
|
||||
ITER_PUSH(0xBF),
|
||||
ITER_POP(0xC0),
|
||||
ITER_CANCEL(0xC1),
|
||||
|
||||
@@ -39,10 +39,11 @@ inline fun <reified T : Obj> Scope.addFnDoc(
|
||||
returns: TypeDoc? = null,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
callSignature: net.sergeych.lyng.CallSignature? = null,
|
||||
crossinline fn: suspend Scope.() -> T
|
||||
) {
|
||||
// Register runtime function(s)
|
||||
addFn(*names) { fn() }
|
||||
addFn(*names, callSignature = callSignature) { fn() }
|
||||
// Determine module
|
||||
val mod = moduleName ?: findModuleNameOrUnknown()
|
||||
// Register docs once per name
|
||||
|
||||
@@ -124,17 +124,7 @@ open class Obj {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Extensions in scope
|
||||
val extension = scope.findExtension(objClass, name)
|
||||
if (extension != null) {
|
||||
if (extension.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (extension.value as ObjProperty).callGetter(scope, this, extension.declaringClass)
|
||||
} else if (extension.type != ObjRecord.Type.Delegated) {
|
||||
return extension.value.invoke(scope, this, args)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Root object fallback
|
||||
// 2. Root object fallback
|
||||
for (cls in objClass.mro) {
|
||||
if (cls.className == "Obj") {
|
||||
cls.members[name]?.let { rec ->
|
||||
@@ -181,7 +171,7 @@ open class Obj {
|
||||
|
||||
open suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||
if (other === this) return true
|
||||
val m = objClass.getInstanceMemberOrNull("equals") ?: scope.findExtension(objClass, "equals")
|
||||
val m = objClass.getInstanceMemberOrNull("equals")
|
||||
if (m != null) {
|
||||
return invokeInstanceMethod(scope, "equals", Arguments(other)).toBool()
|
||||
}
|
||||
@@ -375,7 +365,7 @@ open class Obj {
|
||||
* to generate it as 'this = this + other', reassigning its variable
|
||||
*/
|
||||
open suspend fun plusAssign(scope: Scope, other: Obj): Obj? {
|
||||
val m = objClass.getInstanceMemberOrNull("plusAssign") ?: scope.findExtension(objClass, "plusAssign")
|
||||
val m = objClass.getInstanceMemberOrNull("plusAssign")
|
||||
return if (m != null) {
|
||||
invokeInstanceMethod(scope, "plusAssign", Arguments(other))
|
||||
} else null
|
||||
@@ -385,28 +375,28 @@ open class Obj {
|
||||
* `-=` operations, see [plusAssign]
|
||||
*/
|
||||
open suspend fun minusAssign(scope: Scope, other: Obj): Obj? {
|
||||
val m = objClass.getInstanceMemberOrNull("minusAssign") ?: scope.findExtension(objClass, "minusAssign")
|
||||
val m = objClass.getInstanceMemberOrNull("minusAssign")
|
||||
return if (m != null) {
|
||||
invokeInstanceMethod(scope, "minusAssign", Arguments(other))
|
||||
} else null
|
||||
}
|
||||
|
||||
open suspend fun mulAssign(scope: Scope, other: Obj): Obj? {
|
||||
val m = objClass.getInstanceMemberOrNull("mulAssign") ?: scope.findExtension(objClass, "mulAssign")
|
||||
val m = objClass.getInstanceMemberOrNull("mulAssign")
|
||||
return if (m != null) {
|
||||
invokeInstanceMethod(scope, "mulAssign", Arguments(other))
|
||||
} else null
|
||||
}
|
||||
|
||||
open suspend fun divAssign(scope: Scope, other: Obj): Obj? {
|
||||
val m = objClass.getInstanceMemberOrNull("divAssign") ?: scope.findExtension(objClass, "divAssign")
|
||||
val m = objClass.getInstanceMemberOrNull("divAssign")
|
||||
return if (m != null) {
|
||||
invokeInstanceMethod(scope, "divAssign", Arguments(other))
|
||||
} else null
|
||||
}
|
||||
|
||||
open suspend fun modAssign(scope: Scope, other: Obj): Obj? {
|
||||
val m = objClass.getInstanceMemberOrNull("modAssign") ?: scope.findExtension(objClass, "modAssign")
|
||||
val m = objClass.getInstanceMemberOrNull("modAssign")
|
||||
return if (m != null) {
|
||||
invokeInstanceMethod(scope, "modAssign", Arguments(other))
|
||||
} else null
|
||||
@@ -467,16 +457,7 @@ open class Obj {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Extensions
|
||||
val extension = scope.findExtension(objClass, name)
|
||||
if (extension != null) {
|
||||
val resolved = resolveRecord(scope, extension, name, extension.declaringClass)
|
||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, extension.declaringClass))
|
||||
return resolved
|
||||
}
|
||||
|
||||
// 3. Root fallback
|
||||
// 2. Root fallback
|
||||
for (cls in objClass.mro) {
|
||||
if (cls.className == "Obj") {
|
||||
cls.members[name]?.let { rec ->
|
||||
@@ -558,11 +539,7 @@ open class Obj {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. Extensions
|
||||
if (field == null) {
|
||||
field = scope.findExtension(objClass, name)
|
||||
}
|
||||
// 3. Root fallback
|
||||
// 2. Root fallback
|
||||
if (field == null) {
|
||||
for (cls in objClass.mro) {
|
||||
if (cls.className == "Obj") {
|
||||
|
||||
@@ -275,6 +275,11 @@ open class ObjClass(
|
||||
internal data class FieldSlot(val slot: Int, val record: ObjRecord)
|
||||
internal data class ResolvedMember(val record: ObjRecord, val declaringClass: ObjClass)
|
||||
internal data class MethodSlot(val slot: Int, val record: ObjRecord)
|
||||
private var nextFieldId: Int = 0
|
||||
private var nextMethodId: Int = 0
|
||||
private val fieldIdMap: MutableMap<String, Int> = mutableMapOf()
|
||||
private val methodIdMap: MutableMap<String, Int> = mutableMapOf()
|
||||
private var methodIdSeeded: Boolean = false
|
||||
private var fieldSlotLayoutVersion: Int = -1
|
||||
private var fieldSlotMap: Map<String, FieldSlot> = emptyMap()
|
||||
private var fieldSlotCount: Int = 0
|
||||
@@ -287,19 +292,29 @@ open class ObjClass(
|
||||
private fun ensureFieldSlots(): Map<String, FieldSlot> {
|
||||
if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap
|
||||
val res = mutableMapOf<String, FieldSlot>()
|
||||
var idx = 0
|
||||
var maxId = -1
|
||||
for (cls in mro) {
|
||||
for ((name, rec) in cls.members) {
|
||||
if (rec.isAbstract) continue
|
||||
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
|
||||
val key = cls.mangledName(name)
|
||||
if (res.containsKey(key)) continue
|
||||
res[key] = FieldSlot(idx, rec)
|
||||
idx += 1
|
||||
val fieldId = rec.fieldId ?: cls.assignFieldId(name, rec)
|
||||
res[key] = FieldSlot(fieldId, rec)
|
||||
if (fieldId > maxId) maxId = fieldId
|
||||
}
|
||||
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||
if (rec.isAbstract) return@forEach
|
||||
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) return@forEach
|
||||
val key = cls.mangledName(name)
|
||||
if (res.containsKey(key)) return@forEach
|
||||
val fieldId = rec.fieldId ?: cls.assignFieldId(name, rec)
|
||||
res[key] = FieldSlot(fieldId, rec)
|
||||
if (fieldId > maxId) maxId = fieldId
|
||||
}
|
||||
}
|
||||
fieldSlotMap = res
|
||||
fieldSlotCount = idx
|
||||
fieldSlotCount = maxId + 1
|
||||
fieldSlotLayoutVersion = layoutVersion
|
||||
return fieldSlotMap
|
||||
}
|
||||
@@ -308,7 +323,6 @@ open class ObjClass(
|
||||
if (instanceMemberLayoutVersion == layoutVersion) return instanceMemberCache
|
||||
val res = mutableMapOf<String, ResolvedMember>()
|
||||
for (cls in mro) {
|
||||
if (cls.className == "Obj") break
|
||||
for ((name, rec) in cls.members) {
|
||||
if (rec.isAbstract) continue
|
||||
if (res.containsKey(name)) continue
|
||||
@@ -330,7 +344,7 @@ open class ObjClass(
|
||||
private fun ensureMethodSlots(): Map<String, MethodSlot> {
|
||||
if (methodSlotLayoutVersion == layoutVersion) return methodSlotMap
|
||||
val res = mutableMapOf<String, MethodSlot>()
|
||||
var idx = 0
|
||||
var maxId = -1
|
||||
for (cls in mro) {
|
||||
if (cls.className == "Obj") break
|
||||
for ((name, rec) in cls.members) {
|
||||
@@ -343,8 +357,9 @@ open class ObjClass(
|
||||
}
|
||||
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
||||
if (res.containsKey(key)) continue
|
||||
res[key] = MethodSlot(idx, rec)
|
||||
idx += 1
|
||||
val methodId = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||
res[key] = MethodSlot(methodId, rec)
|
||||
if (methodId > maxId) maxId = methodId
|
||||
}
|
||||
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||
if (rec.isAbstract) return@forEach
|
||||
@@ -353,12 +368,13 @@ open class ObjClass(
|
||||
rec.type != ObjRecord.Type.Property) return@forEach
|
||||
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
||||
if (res.containsKey(key)) return@forEach
|
||||
res[key] = MethodSlot(idx, rec)
|
||||
idx += 1
|
||||
val methodId = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||
res[key] = MethodSlot(methodId, rec)
|
||||
if (methodId > maxId) maxId = methodId
|
||||
}
|
||||
}
|
||||
methodSlotMap = res
|
||||
methodSlotCount = idx
|
||||
methodSlotCount = maxId + 1
|
||||
methodSlotLayoutVersion = layoutVersion
|
||||
return methodSlotMap
|
||||
}
|
||||
@@ -374,6 +390,10 @@ open class ObjClass(
|
||||
}
|
||||
|
||||
internal fun fieldSlotMap(): Map<String, FieldSlot> = ensureFieldSlots()
|
||||
internal fun fieldRecordForId(fieldId: Int): ObjRecord? {
|
||||
ensureFieldSlots()
|
||||
return fieldSlotMap.values.firstOrNull { it.slot == fieldId }?.record
|
||||
}
|
||||
internal fun resolveInstanceMember(name: String): ResolvedMember? = ensureInstanceMemberCache()[name]
|
||||
internal fun methodSlotCount(): Int {
|
||||
ensureMethodSlots()
|
||||
@@ -384,6 +404,117 @@ open class ObjClass(
|
||||
return methodSlotMap[key]
|
||||
}
|
||||
internal fun methodSlotMap(): Map<String, MethodSlot> = ensureMethodSlots()
|
||||
internal fun methodRecordForId(methodId: Int): ObjRecord? {
|
||||
ensureMethodSlots()
|
||||
methodSlotMap.values.firstOrNull { it.slot == methodId }?.record?.let { return it }
|
||||
// Fallback to scanning the MRO in case a parent method id was added after slot cache creation.
|
||||
for (cls in mro) {
|
||||
for ((_, rec) in cls.members) {
|
||||
if (rec.methodId == methodId) return rec
|
||||
}
|
||||
cls.classScope?.objects?.forEach { (_, rec) ->
|
||||
if (rec.methodId == methodId) return rec
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun instanceFieldIdMap(): Map<String, Int> {
|
||||
val result = mutableMapOf<String, Int>()
|
||||
for (cls in mro) {
|
||||
if (cls.className == "Obj") break
|
||||
for ((name, rec) in cls.members) {
|
||||
if (rec.isAbstract) continue
|
||||
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
|
||||
if (rec.visibility == Visibility.Private) continue
|
||||
val id = rec.fieldId ?: cls.assignFieldId(name, rec)
|
||||
result.putIfAbsent(name, id)
|
||||
}
|
||||
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||
if (rec.isAbstract) return@forEach
|
||||
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) return@forEach
|
||||
if (rec.visibility == Visibility.Private) return@forEach
|
||||
val id = rec.fieldId ?: cls.assignFieldId(name, rec)
|
||||
result.putIfAbsent(name, id)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
internal fun instanceMethodIdMap(includeAbstract: Boolean = false): Map<String, Int> {
|
||||
val result = mutableMapOf<String, Int>()
|
||||
for (cls in mro) {
|
||||
for ((name, rec) in cls.members) {
|
||||
if (!includeAbstract && rec.isAbstract) continue
|
||||
if (rec.visibility == Visibility.Private) continue
|
||||
if (rec.type != ObjRecord.Type.Fun &&
|
||||
rec.type != ObjRecord.Type.Property &&
|
||||
rec.type != ObjRecord.Type.Delegated) continue
|
||||
val id = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||
result.putIfAbsent(name, id)
|
||||
}
|
||||
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||
if (!includeAbstract && rec.isAbstract) return@forEach
|
||||
if (rec.visibility == Visibility.Private) return@forEach
|
||||
if (rec.type != ObjRecord.Type.Fun &&
|
||||
rec.type != ObjRecord.Type.Property &&
|
||||
rec.type != ObjRecord.Type.Delegated) return@forEach
|
||||
val id = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||
result.putIfAbsent(name, id)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun assignFieldId(name: String, rec: ObjRecord): Int {
|
||||
val existingId = rec.fieldId
|
||||
if (existingId != null) {
|
||||
fieldIdMap[name] = existingId
|
||||
return existingId
|
||||
}
|
||||
val id = fieldIdMap.getOrPut(name) { nextFieldId++ }
|
||||
return id
|
||||
}
|
||||
|
||||
private fun assignMethodId(name: String, rec: ObjRecord): Int {
|
||||
ensureMethodIdSeeded()
|
||||
val existingId = rec.methodId
|
||||
if (existingId != null) {
|
||||
methodIdMap[name] = existingId
|
||||
return existingId
|
||||
}
|
||||
val id = methodIdMap.getOrPut(name) { nextMethodId++ }
|
||||
return id
|
||||
}
|
||||
|
||||
private fun ensureMethodIdSeeded() {
|
||||
if (methodIdSeeded) return
|
||||
var maxId = -1
|
||||
for (cls in mroParents) {
|
||||
for ((name, rec) in cls.members) {
|
||||
if (rec.type != ObjRecord.Type.Fun &&
|
||||
rec.type != ObjRecord.Type.Property &&
|
||||
rec.type != ObjRecord.Type.Delegated
|
||||
) continue
|
||||
val id = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||
methodIdMap.putIfAbsent(name, id)
|
||||
if (id > maxId) maxId = id
|
||||
}
|
||||
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||
if (rec.type != ObjRecord.Type.Fun &&
|
||||
rec.type != ObjRecord.Type.Property &&
|
||||
rec.type != ObjRecord.Type.Delegated
|
||||
) return@forEach
|
||||
val id = rec.methodId ?: cls.assignMethodId(name, rec)
|
||||
methodIdMap.putIfAbsent(name, id)
|
||||
if (id > maxId) maxId = id
|
||||
}
|
||||
}
|
||||
if (nextMethodId <= maxId) {
|
||||
nextMethodId = maxId + 1
|
||||
}
|
||||
methodIdSeeded = true
|
||||
}
|
||||
|
||||
override fun toString(): String = className
|
||||
|
||||
@@ -618,12 +749,15 @@ open class ObjClass(
|
||||
isOverride: Boolean = false,
|
||||
isTransient: Boolean = false,
|
||||
type: ObjRecord.Type = ObjRecord.Type.Field,
|
||||
fieldId: Int? = null,
|
||||
methodId: Int? = null,
|
||||
): ObjRecord {
|
||||
// Validation of override rules: only for non-system declarations
|
||||
var existing: ObjRecord? = null
|
||||
var actualOverride = false
|
||||
if (pos != Pos.builtIn) {
|
||||
// Only consider TRUE instance members from ancestors for overrides
|
||||
val existing = getInstanceMemberOrNull(name, includeAbstract = true, includeStatic = false)
|
||||
var actualOverride = false
|
||||
existing = getInstanceMemberOrNull(name, includeAbstract = true, includeStatic = false)
|
||||
if (existing != null && existing.declaringClass != this) {
|
||||
// If the existing member is private in the ancestor, it's not visible for overriding.
|
||||
// It should be treated as a new member in this class.
|
||||
@@ -654,6 +788,56 @@ open class ObjClass(
|
||||
throw ScriptError(pos, "$name is already defined in $objClass")
|
||||
|
||||
// Install/override in this class
|
||||
val effectiveFieldId = if (type == ObjRecord.Type.Field || type == ObjRecord.Type.ConstructorField) {
|
||||
fieldId ?: fieldIdMap[name]?.let { it } ?: run {
|
||||
fieldIdMap[name] = nextFieldId
|
||||
nextFieldId++
|
||||
fieldIdMap[name]!!
|
||||
}
|
||||
} else {
|
||||
fieldId
|
||||
}
|
||||
val inheritedCandidate = run {
|
||||
var found: ObjRecord? = null
|
||||
for (cls in mro) {
|
||||
if (cls === this) continue
|
||||
if (cls.className == "Obj") break
|
||||
cls.members[name]?.let {
|
||||
found = it
|
||||
return@run found
|
||||
}
|
||||
}
|
||||
found
|
||||
}
|
||||
if (type == ObjRecord.Type.Fun ||
|
||||
type == ObjRecord.Type.Property ||
|
||||
type == ObjRecord.Type.Delegated
|
||||
) {
|
||||
ensureMethodIdSeeded()
|
||||
}
|
||||
val effectiveMethodId = if (type == ObjRecord.Type.Fun ||
|
||||
type == ObjRecord.Type.Property ||
|
||||
type == ObjRecord.Type.Delegated
|
||||
) {
|
||||
val inherited = if (actualOverride) {
|
||||
existing?.methodId
|
||||
} else {
|
||||
val candidate = inheritedCandidate
|
||||
if (candidate != null &&
|
||||
candidate.declaringClass != this &&
|
||||
(candidate.visibility.isPublic || canAccessMember(candidate.visibility, candidate.declaringClass, this, name))
|
||||
) {
|
||||
candidate.methodId
|
||||
} else null
|
||||
}
|
||||
methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run {
|
||||
methodIdMap[name] = nextMethodId
|
||||
nextMethodId++
|
||||
methodIdMap[name]!!
|
||||
}
|
||||
} else {
|
||||
methodId
|
||||
}
|
||||
val rec = ObjRecord(
|
||||
initialValue, isMutable, visibility, writeVisibility,
|
||||
declaringClass = declaringClass,
|
||||
@@ -661,7 +845,10 @@ open class ObjClass(
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient,
|
||||
type = type
|
||||
type = type,
|
||||
memberName = name,
|
||||
fieldId = effectiveFieldId,
|
||||
methodId = effectiveMethodId
|
||||
)
|
||||
members[name] = rec
|
||||
// Structural change: bump layout version for PIC invalidation
|
||||
@@ -682,13 +869,52 @@ open class ObjClass(
|
||||
writeVisibility: Visibility? = null,
|
||||
pos: Pos = Pos.builtIn,
|
||||
isTransient: Boolean = false,
|
||||
type: ObjRecord.Type = ObjRecord.Type.Field
|
||||
type: ObjRecord.Type = ObjRecord.Type.Field,
|
||||
fieldId: Int? = null,
|
||||
methodId: Int? = null
|
||||
): ObjRecord {
|
||||
initClassScope()
|
||||
val existing = classScope!!.objects[name]
|
||||
if (existing != null)
|
||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||
val rec = classScope!!.addItem(name, isMutable, initialValue, visibility, writeVisibility, recordType = type, isTransient = isTransient)
|
||||
val effectiveFieldId = if (type == ObjRecord.Type.Field || type == ObjRecord.Type.ConstructorField) {
|
||||
fieldId ?: fieldIdMap[name]?.let { it } ?: run {
|
||||
fieldIdMap[name] = nextFieldId
|
||||
nextFieldId++
|
||||
fieldIdMap[name]!!
|
||||
}
|
||||
} else {
|
||||
fieldId
|
||||
}
|
||||
if (type == ObjRecord.Type.Fun ||
|
||||
type == ObjRecord.Type.Property ||
|
||||
type == ObjRecord.Type.Delegated
|
||||
) {
|
||||
ensureMethodIdSeeded()
|
||||
}
|
||||
val effectiveMethodId = if (type == ObjRecord.Type.Fun ||
|
||||
type == ObjRecord.Type.Property ||
|
||||
type == ObjRecord.Type.Delegated
|
||||
) {
|
||||
methodId ?: methodIdMap[name]?.let { it } ?: run {
|
||||
methodIdMap[name] = nextMethodId
|
||||
nextMethodId++
|
||||
methodIdMap[name]!!
|
||||
}
|
||||
} else {
|
||||
methodId
|
||||
}
|
||||
val rec = classScope!!.addItem(
|
||||
name,
|
||||
isMutable,
|
||||
initialValue,
|
||||
visibility,
|
||||
writeVisibility,
|
||||
recordType = type,
|
||||
isTransient = isTransient,
|
||||
fieldId = effectiveFieldId,
|
||||
methodId = effectiveMethodId
|
||||
)
|
||||
// Structural change: bump layout version for PIC invalidation
|
||||
layoutVersion += 1
|
||||
return rec
|
||||
@@ -704,13 +930,15 @@ open class ObjClass(
|
||||
isClosed: Boolean = false,
|
||||
isOverride: Boolean = false,
|
||||
pos: Pos = Pos.builtIn,
|
||||
methodId: Int? = null,
|
||||
code: (suspend Scope.() -> Obj)? = null
|
||||
) {
|
||||
val stmt = code?.let { statement { it() } } ?: ObjNull
|
||||
createField(
|
||||
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
|
||||
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||
type = ObjRecord.Type.Fun
|
||||
type = ObjRecord.Type.Fun,
|
||||
methodId = methodId
|
||||
)
|
||||
}
|
||||
|
||||
@@ -727,7 +955,8 @@ open class ObjClass(
|
||||
isClosed: Boolean = false,
|
||||
isOverride: Boolean = false,
|
||||
pos: Pos = Pos.builtIn,
|
||||
prop: ObjProperty? = null
|
||||
prop: ObjProperty? = null,
|
||||
methodId: Int? = null
|
||||
) {
|
||||
val g = getter?.let { statement { it() } }
|
||||
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } }
|
||||
@@ -735,7 +964,8 @@ open class ObjClass(
|
||||
createField(
|
||||
name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
|
||||
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||
type = ObjRecord.Type.Property
|
||||
type = ObjRecord.Type.Property,
|
||||
methodId = methodId
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.Scope
|
||||
|
||||
class ObjExtensionMethodCallable(
|
||||
private val name: String,
|
||||
private val target: Obj,
|
||||
private val declaringClass: ObjClass? = null
|
||||
) : Obj() {
|
||||
override suspend fun callOn(scope: Scope): Obj {
|
||||
val args = scope.args
|
||||
if (args.isEmpty()) scope.raiseError("extension call $name requires receiver")
|
||||
val receiver = args.first()
|
||||
val rest = if (args.size <= 1) {
|
||||
Arguments.EMPTY
|
||||
} else {
|
||||
Arguments(args.list.subList(1, args.size), args.tailBlockMode, args.named)
|
||||
}
|
||||
return target.invoke(scope, receiver, rest, declaringClass)
|
||||
}
|
||||
}
|
||||
|
||||
class ObjExtensionPropertyGetterCallable(
|
||||
private val name: String,
|
||||
private val property: ObjProperty,
|
||||
private val declaringClass: ObjClass? = null
|
||||
) : Obj() {
|
||||
override suspend fun callOn(scope: Scope): Obj {
|
||||
val args = scope.args
|
||||
if (args.isEmpty()) scope.raiseError("extension property $name requires receiver")
|
||||
val receiver = args.first()
|
||||
if (args.size > 1) scope.raiseError("extension property $name getter takes no arguments")
|
||||
return property.callGetter(scope, receiver, declaringClass)
|
||||
}
|
||||
}
|
||||
|
||||
class ObjExtensionPropertySetterCallable(
|
||||
private val name: String,
|
||||
private val property: ObjProperty,
|
||||
private val declaringClass: ObjClass? = null
|
||||
) : Obj() {
|
||||
override suspend fun callOn(scope: Scope): Obj {
|
||||
val args = scope.args
|
||||
if (args.size < 2) scope.raiseError("extension property $name setter requires value")
|
||||
val receiver = args[0]
|
||||
val value = args[1]
|
||||
property.callSetter(scope, receiver, value, declaringClass)
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
@@ -81,8 +81,8 @@ private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChann
|
||||
} catch (x: ScriptFlowIsNoMoreCollected) {
|
||||
// premature flow closing, OK
|
||||
} catch (x: Exception) {
|
||||
// Suppress stack traces in background producer to avoid noisy stderr during tests.
|
||||
// If needed, consider routing to a logger in the future.
|
||||
channel.close(x)
|
||||
return@globalLaunch
|
||||
}
|
||||
channel.close()
|
||||
}
|
||||
@@ -107,9 +107,7 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() {
|
||||
) {
|
||||
val objFlow = thisAs<ObjFlow>()
|
||||
ObjFlowIterator(statement {
|
||||
objFlow.producer.execute(
|
||||
ClosureScope(this, objFlow.scope)
|
||||
)
|
||||
objFlow.producer.execute(this)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -137,6 +135,7 @@ class ObjFlowIterator(val producer: Statement) : Obj() {
|
||||
// cold start:
|
||||
if (channel == null) channel = createLyngFlowInput(scope, producer)
|
||||
if (nextItem == null) nextItem = channel!!.receiveCatching()
|
||||
nextItem?.exceptionOrNull()?.let { throw it }
|
||||
return ObjBool(nextItem!!.isSuccess)
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
return if (idx >= 0 && idx < methodSlots.size) methodSlots[idx] else null
|
||||
}
|
||||
|
||||
internal fun fieldRecordForId(fieldId: Int): ObjRecord? {
|
||||
return if (fieldId >= 0 && fieldId < fieldSlots.size) fieldSlots[fieldId] else null
|
||||
}
|
||||
|
||||
internal fun methodRecordForId(methodId: Int): ObjRecord? {
|
||||
return if (methodId >= 0 && methodId < methodSlots.size) methodSlots[methodId] else null
|
||||
}
|
||||
|
||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||
val caller = scope.currentClassCtx
|
||||
|
||||
|
||||
@@ -29,6 +29,12 @@ import net.sergeych.lyng.miniast.type
|
||||
*/
|
||||
val ObjIterable by lazy {
|
||||
ObjClass("Iterable").apply {
|
||||
addFn(
|
||||
name = "iterator",
|
||||
isAbstract = true,
|
||||
isClosed = false,
|
||||
code = null
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
name = "toList",
|
||||
@@ -241,4 +247,4 @@ val ObjIterable by lazy {
|
||||
list
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,10 @@ data class ObjRecord(
|
||||
var delegate: Obj? = null,
|
||||
/** The receiver object to resolve this member against (for instance fields/methods). */
|
||||
var receiver: Obj? = null,
|
||||
val callSignature: net.sergeych.lyng.CallSignature? = null,
|
||||
val memberName: String? = null,
|
||||
val fieldId: Int? = null,
|
||||
val methodId: Int? = null,
|
||||
) {
|
||||
val effectiveWriteVisibility: Visibility get() = writeVisibility ?: visibility
|
||||
enum class Type(val comparable: Boolean = false,val serializable: Boolean = false) {
|
||||
@@ -59,4 +63,4 @@ data class ObjRecord(
|
||||
@Suppress("unused")
|
||||
fun qualifiedName(name: String): String =
|
||||
"${importedFrom?.packageName ?: "anonymous"}.$name"
|
||||
}
|
||||
}
|
||||
@@ -417,6 +417,11 @@ class CastRef(
|
||||
private val isNullable: Boolean,
|
||||
private val atPos: Pos,
|
||||
) : ObjRef {
|
||||
internal fun castValueRef(): ObjRef = valueRef
|
||||
internal fun castTypeRef(): ObjRef = typeRef
|
||||
internal fun castIsNullable(): Boolean = isNullable
|
||||
internal fun castPos(): Pos = atPos
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val v0 = valueRef.evalValue(scope)
|
||||
val t = typeRef.evalValue(scope)
|
||||
@@ -501,107 +506,64 @@ private suspend fun resolveQualifiedThisInstance(scope: Scope, typeName: String)
|
||||
class QualifiedThisFieldSlotRef(
|
||||
private val typeName: String,
|
||||
val name: String,
|
||||
private val fieldId: Int?,
|
||||
private val methodId: Int?,
|
||||
private val isOptional: Boolean
|
||||
) : ObjRef {
|
||||
internal fun fieldId(): Int? = fieldId
|
||||
internal fun methodId(): Int? = methodId
|
||||
internal fun receiverTypeName(): String = typeName
|
||||
internal fun optional(): Boolean = isOptional
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
|
||||
val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance
|
||||
?: scope.raiseClassCastError("No instance of type $typeName found in scope")
|
||||
if (isOptional && inst == ObjNull) return ObjNull.asMutable
|
||||
|
||||
if (startClass !== inst.objClass) {
|
||||
return ObjQualifiedView(inst, startClass).readField(scope, name)
|
||||
}
|
||||
|
||||
val caller = scope.currentClassCtx
|
||||
if (caller != null) {
|
||||
val mangled = caller.mangledName(name)
|
||||
inst.fieldRecordForKey(mangled)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private) {
|
||||
return inst.resolveRecord(scope, rec, name, caller)
|
||||
}
|
||||
}
|
||||
inst.methodRecordForKey(mangled)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private) {
|
||||
return inst.resolveRecord(scope, rec, name, caller)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val key = inst.objClass.publicMemberResolution[name] ?: name
|
||||
inst.fieldRecordForKey(key)?.let { rec ->
|
||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
|
||||
fieldId?.let { id ->
|
||||
val rec = inst.fieldRecordForId(id)
|
||||
if (rec != null && (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
|
||||
return rec
|
||||
}
|
||||
inst.methodRecordForKey(key)?.let { rec ->
|
||||
if (!rec.isAbstract) {
|
||||
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass
|
||||
return inst.resolveRecord(scope, rec, name, decl)
|
||||
}
|
||||
}
|
||||
|
||||
return inst.readField(scope, name)
|
||||
methodId?.let { id ->
|
||||
val rec = inst.methodRecordForId(id)
|
||||
if (rec != null && !rec.isAbstract) {
|
||||
val decl = rec.declaringClass ?: inst.objClass
|
||||
return inst.resolveRecord(scope, rec, rec.memberName ?: name, decl)
|
||||
}
|
||||
}
|
||||
scope.raiseSymbolNotFound(name)
|
||||
}
|
||||
|
||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
|
||||
val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance
|
||||
?: scope.raiseClassCastError("No instance of type $typeName found in scope")
|
||||
if (isOptional && inst == ObjNull) return
|
||||
|
||||
if (startClass !== inst.objClass) {
|
||||
ObjQualifiedView(inst, startClass).writeField(scope, name, newValue)
|
||||
return
|
||||
}
|
||||
|
||||
val caller = scope.currentClassCtx
|
||||
if (caller != null) {
|
||||
val mangled = caller.mangledName(name)
|
||||
inst.fieldRecordForKey(mangled)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private) {
|
||||
writeDirectOrFallback(scope, inst, rec, name, newValue, caller)
|
||||
return
|
||||
}
|
||||
}
|
||||
inst.methodRecordForKey(mangled)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private &&
|
||||
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
|
||||
inst.writeField(scope, name, newValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val key = inst.objClass.publicMemberResolution[name] ?: name
|
||||
inst.fieldRecordForKey(key)?.let { rec ->
|
||||
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name)
|
||||
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
|
||||
writeDirectOrFallback(scope, inst, rec, name, newValue, decl)
|
||||
fieldId?.let { id ->
|
||||
val rec = inst.fieldRecordForId(id)
|
||||
if (rec != null) {
|
||||
assignToRecord(scope, rec, newValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
inst.methodRecordForKey(key)?.let { rec ->
|
||||
if (rec.effectiveWriteVisibility == Visibility.Public &&
|
||||
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
|
||||
inst.writeField(scope, name, newValue)
|
||||
methodId?.let { id ->
|
||||
val rec = inst.methodRecordForId(id)
|
||||
if (rec != null) {
|
||||
scope.assign(rec, rec.memberName ?: name, newValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
inst.writeField(scope, name, newValue)
|
||||
scope.raiseSymbolNotFound(name)
|
||||
}
|
||||
|
||||
private suspend fun writeDirectOrFallback(
|
||||
scope: Scope,
|
||||
inst: ObjInstance,
|
||||
rec: ObjRecord,
|
||||
name: String,
|
||||
newValue: Obj,
|
||||
decl: ObjClass?
|
||||
) {
|
||||
private suspend fun assignToRecord(scope: Scope, rec: ObjRecord, newValue: Obj) {
|
||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
|
||||
if (!rec.isMutable && rec.value !== ObjUnset) {
|
||||
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||
ObjIllegalAssignmentException(scope, "can't reassign val ${rec.memberName ?: name}").raise()
|
||||
}
|
||||
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
|
||||
} else {
|
||||
inst.writeField(scope, name, newValue)
|
||||
scope.assign(rec, rec.memberName ?: name, newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -613,51 +575,36 @@ class QualifiedThisFieldSlotRef(
|
||||
class QualifiedThisMethodSlotCallRef(
|
||||
private val typeName: String,
|
||||
private val name: String,
|
||||
private val methodId: Int?,
|
||||
private val args: List<ParsedArgument>,
|
||||
private val tailBlock: Boolean,
|
||||
private val isOptional: Boolean
|
||||
) : ObjRef {
|
||||
internal fun receiverTypeName(): String = typeName
|
||||
internal fun methodName(): String = name
|
||||
internal fun methodId(): Int? = methodId
|
||||
internal fun arguments(): List<ParsedArgument> = args
|
||||
internal fun hasTailBlock(): Boolean = tailBlock
|
||||
internal fun optionalInvoke(): Boolean = isOptional
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
|
||||
|
||||
override suspend fun evalValue(scope: Scope): Obj {
|
||||
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
|
||||
val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance
|
||||
?: scope.raiseClassCastError("No instance of type $typeName found in scope")
|
||||
if (isOptional && inst == ObjNull) return ObjNull
|
||||
val callArgs = args.toArguments(scope, tailBlock)
|
||||
|
||||
if (startClass !== inst.objClass) {
|
||||
return ObjQualifiedView(inst, startClass).invokeInstanceMethod(scope, name, callArgs, null)
|
||||
}
|
||||
|
||||
val caller = scope.currentClassCtx
|
||||
if (caller != null) {
|
||||
val mangled = caller.mangledName(name)
|
||||
inst.methodRecordForKey(mangled)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, caller)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(inst.instanceScope, inst, callArgs, caller)
|
||||
}
|
||||
}
|
||||
val id = methodId ?: scope.raiseSymbolNotFound(name)
|
||||
val rec = inst.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name)
|
||||
val decl = rec.declaringClass ?: inst.objClass
|
||||
return when (rec.type) {
|
||||
ObjRecord.Type.Property -> {
|
||||
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, inst, decl)
|
||||
else scope.raiseError("property $name cannot be called with arguments")
|
||||
}
|
||||
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
|
||||
else -> scope.raiseError("member $name is not callable")
|
||||
}
|
||||
|
||||
val key = inst.objClass.publicMemberResolution[name] ?: name
|
||||
inst.methodRecordForKey(key)?.let { rec ->
|
||||
if (!rec.isAbstract) {
|
||||
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass
|
||||
val effectiveCaller = caller ?: if (scope.thisObj === inst) inst.objClass else null
|
||||
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, decl)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inst.invokeInstanceMethod(scope, name, callArgs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,6 +710,9 @@ class ElvisRef(internal val left: ObjRef, internal val right: ObjRef) : ObjRef {
|
||||
|
||||
/** Logical OR with short-circuit: a || b */
|
||||
class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||
internal fun left(): ObjRef = left
|
||||
internal fun right(): ObjRef = right
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
return evalValue(scope).asReadonly
|
||||
}
|
||||
@@ -782,6 +732,9 @@ class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef
|
||||
|
||||
/** Logical AND with short-circuit: a && b */
|
||||
class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||
internal fun left(): ObjRef = left
|
||||
internal fun right(): ObjRef = right
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
return evalValue(scope).asReadonly
|
||||
}
|
||||
@@ -1229,103 +1182,59 @@ class FieldRef(
|
||||
*/
|
||||
class ThisFieldSlotRef(
|
||||
val name: String,
|
||||
private val fieldId: Int?,
|
||||
private val methodId: Int?,
|
||||
private val isOptional: Boolean
|
||||
) : ObjRef {
|
||||
internal fun fieldId(): Int? = fieldId
|
||||
internal fun methodId(): Int? = methodId
|
||||
internal fun optional(): Boolean = isOptional
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val th = scope.thisObj
|
||||
if (th == ObjNull && isOptional) return ObjNull.asMutable
|
||||
if (th !is ObjInstance) return th.readField(scope, name)
|
||||
|
||||
val caller = scope.currentClassCtx
|
||||
if (caller != null) {
|
||||
val mangled = caller.mangledName(name)
|
||||
th.fieldRecordForKey(mangled)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private) {
|
||||
return th.resolveRecord(scope, rec, name, caller)
|
||||
}
|
||||
}
|
||||
th.methodRecordForKey(mangled)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private) {
|
||||
return th.resolveRecord(scope, rec, name, caller)
|
||||
}
|
||||
}
|
||||
val inst = th as? ObjInstance ?: scope.raiseClassCastError("member access on non-instance")
|
||||
val field = fieldId?.let { inst.fieldRecordForId(it) }
|
||||
if (field != null && (field.type == ObjRecord.Type.Field || field.type == ObjRecord.Type.ConstructorField) && !field.isAbstract) {
|
||||
return field
|
||||
}
|
||||
|
||||
val key = th.objClass.publicMemberResolution[name] ?: name
|
||||
th.fieldRecordForKey(key)?.let { rec ->
|
||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
|
||||
return rec
|
||||
val method = methodId?.let { inst.methodRecordForId(it) }
|
||||
if (method != null && !method.isAbstract) {
|
||||
val decl = method.declaringClass ?: inst.objClass
|
||||
return inst.resolveRecord(scope, method, method.memberName ?: name, decl)
|
||||
}
|
||||
th.methodRecordForKey(key)?.let { rec ->
|
||||
if (!rec.isAbstract) {
|
||||
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass
|
||||
return th.resolveRecord(scope, rec, name, decl)
|
||||
}
|
||||
}
|
||||
|
||||
return th.readField(scope, name)
|
||||
scope.raiseSymbolNotFound(name)
|
||||
}
|
||||
|
||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
val th = scope.thisObj
|
||||
if (th == ObjNull && isOptional) return
|
||||
if (th !is ObjInstance) {
|
||||
th.writeField(scope, name, newValue)
|
||||
val inst = th as? ObjInstance ?: scope.raiseClassCastError("member access on non-instance")
|
||||
val field = fieldId?.let { inst.fieldRecordForId(it) }
|
||||
if (field != null) {
|
||||
assignToRecord(scope, field, newValue)
|
||||
return
|
||||
}
|
||||
|
||||
val caller = scope.currentClassCtx
|
||||
if (caller != null) {
|
||||
val mangled = caller.mangledName(name)
|
||||
th.fieldRecordForKey(mangled)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private) {
|
||||
writeDirectOrFallback(scope, th, rec, name, newValue, caller)
|
||||
return
|
||||
}
|
||||
}
|
||||
th.methodRecordForKey(mangled)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private &&
|
||||
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
|
||||
th.writeField(scope, name, newValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
val method = methodId?.let { inst.methodRecordForId(it) }
|
||||
if (method != null) {
|
||||
scope.assign(method, method.memberName ?: name, newValue)
|
||||
return
|
||||
}
|
||||
|
||||
val key = th.objClass.publicMemberResolution[name] ?: name
|
||||
th.fieldRecordForKey(key)?.let { rec ->
|
||||
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name)
|
||||
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
|
||||
writeDirectOrFallback(scope, th, rec, name, newValue, decl)
|
||||
return
|
||||
}
|
||||
}
|
||||
th.methodRecordForKey(key)?.let { rec ->
|
||||
if (rec.effectiveWriteVisibility == Visibility.Public &&
|
||||
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
|
||||
th.writeField(scope, name, newValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
th.writeField(scope, name, newValue)
|
||||
scope.raiseSymbolNotFound(name)
|
||||
}
|
||||
|
||||
private suspend fun writeDirectOrFallback(
|
||||
private suspend fun assignToRecord(
|
||||
scope: Scope,
|
||||
inst: ObjInstance,
|
||||
rec: ObjRecord,
|
||||
name: String,
|
||||
newValue: Obj,
|
||||
decl: ObjClass?
|
||||
newValue: Obj
|
||||
) {
|
||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
|
||||
if (!rec.isMutable && rec.value !== ObjUnset) {
|
||||
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||
ObjIllegalAssignmentException(scope, "can't reassign val ${rec.memberName ?: name}").raise()
|
||||
}
|
||||
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
|
||||
} else {
|
||||
inst.writeField(scope, name, newValue)
|
||||
scope.assign(rec, rec.memberName ?: name, newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1863,11 +1772,13 @@ class MethodCallRef(
|
||||
*/
|
||||
class ThisMethodSlotCallRef(
|
||||
private val name: String,
|
||||
private val methodId: Int?,
|
||||
private val args: List<ParsedArgument>,
|
||||
private val tailBlock: Boolean,
|
||||
private val isOptional: Boolean
|
||||
) : ObjRef {
|
||||
internal fun methodName(): String = name
|
||||
internal fun methodId(): Int? = methodId
|
||||
internal fun arguments(): List<ParsedArgument> = args
|
||||
internal fun hasTailBlock(): Boolean = tailBlock
|
||||
internal fun optionalInvoke(): Boolean = isOptional
|
||||
@@ -1879,37 +1790,17 @@ class ThisMethodSlotCallRef(
|
||||
if (base == ObjNull && isOptional) return ObjNull
|
||||
val callArgs = args.toArguments(scope, tailBlock)
|
||||
if (base !is ObjInstance) return base.invokeInstanceMethod(scope, name, callArgs)
|
||||
|
||||
val caller = scope.currentClassCtx
|
||||
if (caller != null) {
|
||||
val mangled = caller.mangledName(name)
|
||||
base.methodRecordForKey(mangled)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, caller)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(base.instanceScope, base, callArgs, caller)
|
||||
}
|
||||
}
|
||||
val id = methodId ?: scope.raiseSymbolNotFound(name)
|
||||
val rec = base.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name)
|
||||
val decl = rec.declaringClass ?: base.objClass
|
||||
return when (rec.type) {
|
||||
ObjRecord.Type.Property -> {
|
||||
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, base, decl)
|
||||
else scope.raiseError("property $name cannot be called with arguments")
|
||||
}
|
||||
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(base.instanceScope, base, callArgs, decl)
|
||||
else -> scope.raiseError("member $name is not callable")
|
||||
}
|
||||
|
||||
val key = base.objClass.publicMemberResolution[name] ?: name
|
||||
base.methodRecordForKey(key)?.let { rec ->
|
||||
if (!rec.isAbstract) {
|
||||
val decl = rec.declaringClass ?: base.objClass.findDeclaringClassOf(name) ?: base.objClass
|
||||
val effectiveCaller = caller ?: if (scope.thisObj === base) base.objClass else null
|
||||
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, decl)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(base.instanceScope, base, callArgs, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.invokeInstanceMethod(scope, name, callArgs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2258,23 +2149,12 @@ class FastLocalVarRef(
|
||||
*/
|
||||
class ImplicitThisMemberRef(
|
||||
val name: String,
|
||||
val atPos: Pos
|
||||
val atPos: Pos,
|
||||
internal val fieldId: Int?,
|
||||
internal val methodId: Int?,
|
||||
private val preferredThisTypeName: String? = null
|
||||
) : ObjRef {
|
||||
private fun resolveInstanceFieldRecord(th: ObjInstance, caller: ObjClass?): ObjRecord? {
|
||||
if (caller == null) return null
|
||||
for (cls in th.objClass.mro) {
|
||||
if (cls.className == "Obj") break
|
||||
val rec = cls.members[name] ?: continue
|
||||
if (rec.isAbstract) continue
|
||||
val decl = rec.declaringClass ?: cls
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name)) continue
|
||||
val key = decl.mangledName(name)
|
||||
th.fieldRecordForKey(key)?.let { return it }
|
||||
th.instanceScope.objects[key]?.let { return it }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun preferredThisTypeName(): String? = preferredThisTypeName
|
||||
override fun forEachVariable(block: (String) -> Unit) {
|
||||
block(name)
|
||||
}
|
||||
@@ -2285,46 +2165,18 @@ class ImplicitThisMemberRef(
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
scope.pos = atPos
|
||||
val caller = scope.currentClassCtx
|
||||
val th = scope.thisObj
|
||||
|
||||
if (th is ObjClass) {
|
||||
return th.readField(scope, name)
|
||||
val th = preferredThisTypeName?.let { typeName ->
|
||||
scope.thisVariants.firstOrNull { it.objClass.className == typeName }
|
||||
} ?: scope.thisObj
|
||||
val inst = th as? ObjInstance
|
||||
val field = fieldId?.let { inst?.fieldRecordForId(it) ?: th.objClass.fieldRecordForId(it) }
|
||||
if (field != null && (field.type == ObjRecord.Type.Field || field.type == ObjRecord.Type.ConstructorField) && !field.isAbstract) {
|
||||
return field
|
||||
}
|
||||
if (th != null && th !is ObjInstance) {
|
||||
return th.readField(scope, name)
|
||||
}
|
||||
|
||||
// member slots on this instance
|
||||
if (th is ObjInstance) {
|
||||
// private member access for current class context
|
||||
caller?.let { c ->
|
||||
val mangled = c.mangledName(name)
|
||||
th.fieldRecordForKey(mangled)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private) {
|
||||
return th.resolveRecord(scope, rec, name, c)
|
||||
}
|
||||
}
|
||||
th.methodRecordForKey(mangled)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private) {
|
||||
return th.resolveRecord(scope, rec, name, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolveInstanceFieldRecord(th, caller)?.let { return it }
|
||||
|
||||
val key = th.objClass.publicMemberResolution[name] ?: name
|
||||
th.fieldRecordForKey(key)?.let { rec ->
|
||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
|
||||
return rec
|
||||
}
|
||||
th.methodRecordForKey(key)?.let { rec ->
|
||||
if (!rec.isAbstract) {
|
||||
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass
|
||||
return th.resolveRecord(scope, rec, name, decl)
|
||||
}
|
||||
}
|
||||
val method = methodId?.let { inst?.methodRecordForId(it) ?: th.objClass.methodRecordForId(it) }
|
||||
if (method != null && !method.isAbstract) {
|
||||
val decl = method.declaringClass ?: th.objClass
|
||||
return th.resolveRecord(scope, method, method.memberName ?: name, decl)
|
||||
}
|
||||
|
||||
scope.raiseSymbolNotFound(name)
|
||||
@@ -2337,49 +2189,21 @@ class ImplicitThisMemberRef(
|
||||
|
||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
scope.pos = atPos
|
||||
val caller = scope.currentClassCtx
|
||||
val th = scope.thisObj
|
||||
|
||||
if (th is ObjClass) {
|
||||
th.writeField(scope, name, newValue)
|
||||
val th = preferredThisTypeName?.let { typeName ->
|
||||
scope.thisVariants.firstOrNull { it.objClass.className == typeName }
|
||||
} ?: scope.thisObj
|
||||
val inst = th as? ObjInstance
|
||||
val field = fieldId?.let { inst?.fieldRecordForId(it) ?: th.objClass.fieldRecordForId(it) }
|
||||
if (field != null) {
|
||||
scope.assign(field, field.memberName ?: name, newValue)
|
||||
return
|
||||
}
|
||||
if (th != null && th !is ObjInstance) {
|
||||
th.writeField(scope, name, newValue)
|
||||
val method = methodId?.let { inst?.methodRecordForId(it) ?: th.objClass.methodRecordForId(it) }
|
||||
if (method != null) {
|
||||
scope.assign(method, method.memberName ?: name, newValue)
|
||||
return
|
||||
}
|
||||
|
||||
// member slots on this instance
|
||||
if (th is ObjInstance) {
|
||||
val key = th.objClass.publicMemberResolution[name] ?: name
|
||||
th.fieldRecordForKey(key)?.let { rec ->
|
||||
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name)
|
||||
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
|
||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
|
||||
if (!rec.isMutable && rec.value !== ObjUnset) {
|
||||
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||
}
|
||||
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
|
||||
} else {
|
||||
th.writeField(scope, name, newValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
th.methodRecordForKey(key)?.let { rec ->
|
||||
if (rec.effectiveWriteVisibility == Visibility.Public &&
|
||||
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
|
||||
th.writeField(scope, name, newValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resolveInstanceFieldRecord(th, caller)?.let { rec ->
|
||||
scope.assign(rec, name, newValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
scope.raiseSymbolNotFound(name)
|
||||
}
|
||||
}
|
||||
@@ -2390,16 +2214,19 @@ class ImplicitThisMemberRef(
|
||||
*/
|
||||
class ImplicitThisMethodCallRef(
|
||||
private val name: String,
|
||||
private val methodId: Int?,
|
||||
private val args: List<ParsedArgument>,
|
||||
private val tailBlock: Boolean,
|
||||
private val isOptional: Boolean,
|
||||
private val atPos: Pos
|
||||
private val atPos: Pos,
|
||||
private val preferredThisTypeName: String? = null
|
||||
) : ObjRef {
|
||||
private val memberRef = ImplicitThisMemberRef(name, atPos)
|
||||
internal fun methodName(): String = name
|
||||
internal fun arguments(): List<ParsedArgument> = args
|
||||
internal fun hasTailBlock(): Boolean = tailBlock
|
||||
internal fun optionalInvoke(): Boolean = isOptional
|
||||
internal fun preferredThisTypeName(): String? = preferredThisTypeName
|
||||
internal fun slotId(): Int? = methodId
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
|
||||
|
||||
@@ -2419,7 +2246,25 @@ class ImplicitThisMethodCallRef(
|
||||
callee.callOn(scope.createChildScope(scope.pos, callArgs))
|
||||
}
|
||||
}
|
||||
return scope.thisObj.invokeInstanceMethod(scope, name, callArgs)
|
||||
val receiver = preferredThisTypeName?.let { typeName ->
|
||||
scope.thisVariants.firstOrNull { it.objClass.className == typeName }
|
||||
} ?: scope.thisObj
|
||||
if (receiver == ObjNull && isOptional) return ObjNull
|
||||
val inst = receiver as? ObjInstance ?: return receiver.invokeInstanceMethod(scope, name, callArgs)
|
||||
if (methodId == null) {
|
||||
return inst.invokeInstanceMethod(scope, name, callArgs)
|
||||
}
|
||||
val id = methodId
|
||||
val rec = inst.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name)
|
||||
val decl = rec.declaringClass ?: inst.objClass
|
||||
return when (rec.type) {
|
||||
ObjRecord.Type.Property -> {
|
||||
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, inst, decl)
|
||||
else scope.raiseError("property $name cannot be called with arguments")
|
||||
}
|
||||
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
|
||||
else -> scope.raiseError("member $name is not callable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2598,6 +2443,8 @@ sealed class MapLiteralEntry {
|
||||
}
|
||||
|
||||
class MapLiteralRef(private val entries: List<MapLiteralEntry>) : ObjRef {
|
||||
internal fun entries(): List<MapLiteralEntry> = entries
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
return evalValue(scope).asReadonly
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.Pos
|
||||
import net.sergeych.lyng.RegexCache
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.miniast.*
|
||||
|
||||
class ObjRegex(val regex: Regex) : Obj() {
|
||||
@@ -72,6 +74,19 @@ class ObjRegex(val regex: Regex) : Obj() {
|
||||
val s = requireOnlyArg<ObjString>().value
|
||||
ObjList(thisAs<ObjRegex>().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList())
|
||||
}
|
||||
createField(
|
||||
name = "operatorMatch",
|
||||
initialValue = object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
val other = scope.args.firstAndOnly(pos)
|
||||
val targetScope = scope.parent ?: scope
|
||||
return (scope.thisObj as ObjRegex).operatorMatch(targetScope, other)
|
||||
}
|
||||
},
|
||||
type = ObjRecord.Type.Fun
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,4 +159,4 @@ class ObjRegexMatch(val match: MatchResult) : Obj() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,10 @@ import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.Pos
|
||||
import net.sergeych.lyng.RegexCache
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
@@ -338,6 +340,36 @@ data class ObjString(val value: String) : Obj() {
|
||||
}
|
||||
)
|
||||
}
|
||||
createField(
|
||||
name = "re",
|
||||
initialValue = ObjProperty(
|
||||
name = "re",
|
||||
getter = object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
val pattern = (scope.thisObj as ObjString).value
|
||||
val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(pattern) else pattern.toRegex()
|
||||
return ObjRegex(re)
|
||||
}
|
||||
},
|
||||
setter = null
|
||||
),
|
||||
type = ObjRecord.Type.Property
|
||||
)
|
||||
createField(
|
||||
name = "operatorMatch",
|
||||
initialValue = object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
val other = scope.args.firstAndOnly(pos)
|
||||
val targetScope = scope.parent ?: scope
|
||||
return (scope.thisObj as ObjString).operatorMatch(targetScope, other)
|
||||
}
|
||||
},
|
||||
type = ObjRecord.Type.Fun
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user