Update compile-time resolution and tests

This commit is contained in:
sergeych committed 2026-02-03 02:07:29 +03:00
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
)
}
}
}
}