Add Kotlin bridge reflection handles and tests
This commit is contained in:
parent
cd145f6a96
commit
9cf87d1075
@ -73,6 +73,9 @@ internal class ScopeBridge(internal val scope: Scope) : ScopeFacade {
|
|||||||
override fun trace(text: String) = scope.trace(text)
|
override fun trace(text: String) = scope.trace(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Public factory for bridge facades. */
|
||||||
|
fun Scope.asFacade(): ScopeFacade = ScopeBridge(this)
|
||||||
|
|
||||||
inline fun <reified T : Obj> ScopeFacade.requiredArg(index: Int): T {
|
inline fun <reified T : Obj> ScopeFacade.requiredArg(index: Int): T {
|
||||||
if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}")
|
if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}")
|
||||||
return (args.list[index].byValueCopy() as? T)
|
return (args.list[index].byValueCopy() as? T)
|
||||||
|
|||||||
@ -0,0 +1,533 @@
|
|||||||
|
/*
|
||||||
|
* Kotlin bridge reflection facade: handle-based access for fast get/set/call.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng.bridge
|
||||||
|
|
||||||
|
import net.sergeych.lyng.Arguments
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.ScopeFacade
|
||||||
|
import net.sergeych.lyng.canAccessMember
|
||||||
|
import net.sergeych.lyng.extensionCallableName
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ObjIllegalAccessException
|
||||||
|
import net.sergeych.lyng.obj.ObjInstance
|
||||||
|
import net.sergeych.lyng.obj.ObjProperty
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
import net.sergeych.lyng.obj.ObjString
|
||||||
|
import net.sergeych.lyng.obj.ObjUnset
|
||||||
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
import net.sergeych.lyng.requireScope
|
||||||
|
import net.sergeych.lyng.ModuleScope
|
||||||
|
|
||||||
|
/** Where to resolve names from. */
|
||||||
|
enum class LookupTarget {
|
||||||
|
CurrentFrame,
|
||||||
|
ParentChain,
|
||||||
|
ModuleFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Explicit receiver view (like this@Base). */
|
||||||
|
data class ReceiverView(
|
||||||
|
val type: ObjClass? = null,
|
||||||
|
val typeName: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Lookup rules for bridge resolution. */
|
||||||
|
data class LookupSpec(
|
||||||
|
val targets: Set<LookupTarget> = setOf(LookupTarget.CurrentFrame, LookupTarget.ModuleFrame),
|
||||||
|
val receiverView: ReceiverView? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Base handle type. */
|
||||||
|
sealed interface BridgeHandle {
|
||||||
|
val name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read-only value handle. */
|
||||||
|
interface ValHandle : BridgeHandle {
|
||||||
|
suspend fun get(scope: ScopeFacade): Obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read/write value handle. */
|
||||||
|
interface VarHandle : ValHandle {
|
||||||
|
suspend fun set(scope: ScopeFacade, value: Obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Callable handle (function/closure/method). */
|
||||||
|
interface CallableHandle : BridgeHandle {
|
||||||
|
suspend fun call(scope: ScopeFacade, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Member handle resolved against an instance or receiver view. */
|
||||||
|
interface MemberHandle : BridgeHandle {
|
||||||
|
val declaringClass: ObjClass?
|
||||||
|
val receiverView: ReceiverView?
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Member field/property. */
|
||||||
|
interface MemberValHandle : MemberHandle, ValHandle
|
||||||
|
|
||||||
|
/** Member var/property with write access. */
|
||||||
|
interface MemberVarHandle : MemberHandle, VarHandle
|
||||||
|
|
||||||
|
/** Member callable (method or extension). */
|
||||||
|
interface MemberCallableHandle : MemberHandle, CallableHandle
|
||||||
|
|
||||||
|
/** Direct record handle (debug/inspection). */
|
||||||
|
interface RecordHandle : BridgeHandle {
|
||||||
|
fun record(): ObjRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Bridge resolver API (entry point for Kotlin bindings). */
|
||||||
|
interface BridgeResolver {
|
||||||
|
val pos: Pos
|
||||||
|
|
||||||
|
fun selfAs(type: ObjClass): BridgeResolver
|
||||||
|
fun selfAs(typeName: String): BridgeResolver
|
||||||
|
|
||||||
|
fun resolveVal(name: String, lookup: LookupSpec = LookupSpec()): ValHandle
|
||||||
|
fun resolveVar(name: String, lookup: LookupSpec = LookupSpec()): VarHandle
|
||||||
|
fun resolveCallable(name: String, lookup: LookupSpec = LookupSpec()): CallableHandle
|
||||||
|
|
||||||
|
fun resolveMemberVal(
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): MemberValHandle
|
||||||
|
|
||||||
|
fun resolveMemberVar(
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): MemberVarHandle
|
||||||
|
|
||||||
|
fun resolveMemberCallable(
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): MemberCallableHandle
|
||||||
|
|
||||||
|
/** Extension function treated as a member for reflection. */
|
||||||
|
fun resolveExtensionCallable(
|
||||||
|
receiverClass: ObjClass,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): MemberCallableHandle
|
||||||
|
|
||||||
|
/** Debug: resolve locals by name (optional, for tooling). */
|
||||||
|
fun resolveLocalVal(name: String): ValHandle
|
||||||
|
fun resolveLocalVar(name: String): VarHandle
|
||||||
|
|
||||||
|
/** Debug: access raw record handles if needed. */
|
||||||
|
fun resolveRecord(name: String, lookup: LookupSpec = LookupSpec()): RecordHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience: call by name with implicit caching in resolver implementation. */
|
||||||
|
interface BridgeCallByName {
|
||||||
|
suspend fun callByName(
|
||||||
|
scope: ScopeFacade,
|
||||||
|
name: String,
|
||||||
|
args: Arguments = Arguments.EMPTY,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): Obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Optional typed wrappers (sugar). */
|
||||||
|
interface TypedHandle<T : Obj> : ValHandle {
|
||||||
|
suspend fun getTyped(scope: ScopeFacade): T
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Factory for bridge resolver. */
|
||||||
|
fun ScopeFacade.resolver(): BridgeResolver = BridgeResolverImpl(this)
|
||||||
|
|
||||||
|
private class BridgeResolverImpl(
|
||||||
|
private val facade: ScopeFacade,
|
||||||
|
private val receiverView: ReceiverView? = null
|
||||||
|
) : BridgeResolver, BridgeCallByName {
|
||||||
|
private val cachedCallables: MutableMap<String, CallableHandle> = LinkedHashMap()
|
||||||
|
|
||||||
|
override val pos: Pos
|
||||||
|
get() = facade.pos
|
||||||
|
|
||||||
|
override fun selfAs(type: ObjClass): BridgeResolver = BridgeResolverImpl(facade, ReceiverView(type = type))
|
||||||
|
|
||||||
|
override fun selfAs(typeName: String): BridgeResolver = BridgeResolverImpl(facade, ReceiverView(typeName = typeName))
|
||||||
|
|
||||||
|
override fun resolveVal(name: String, lookup: LookupSpec): ValHandle =
|
||||||
|
LocalValHandle(this, name, lookup)
|
||||||
|
|
||||||
|
override fun resolveVar(name: String, lookup: LookupSpec): VarHandle =
|
||||||
|
LocalVarHandle(this, name, lookup)
|
||||||
|
|
||||||
|
override fun resolveCallable(name: String, lookup: LookupSpec): CallableHandle =
|
||||||
|
LocalCallableHandle(this, name, lookup)
|
||||||
|
|
||||||
|
override fun resolveMemberVal(receiver: Obj, name: String, lookup: LookupSpec): MemberValHandle =
|
||||||
|
MemberValHandleImpl(this, receiver, name, lookup.receiverView ?: receiverView)
|
||||||
|
|
||||||
|
override fun resolveMemberVar(receiver: Obj, name: String, lookup: LookupSpec): MemberVarHandle =
|
||||||
|
MemberVarHandleImpl(this, receiver, name, lookup.receiverView ?: receiverView)
|
||||||
|
|
||||||
|
override fun resolveMemberCallable(receiver: Obj, name: String, lookup: LookupSpec): MemberCallableHandle =
|
||||||
|
MemberCallableHandleImpl(this, receiver, name, lookup.receiverView ?: receiverView)
|
||||||
|
|
||||||
|
override fun resolveExtensionCallable(receiverClass: ObjClass, name: String, lookup: LookupSpec): MemberCallableHandle =
|
||||||
|
ExtensionCallableHandleImpl(this, receiverClass, name, lookup)
|
||||||
|
|
||||||
|
override fun resolveLocalVal(name: String): ValHandle =
|
||||||
|
LocalValHandle(this, name, LookupSpec(targets = setOf(LookupTarget.CurrentFrame)))
|
||||||
|
|
||||||
|
override fun resolveLocalVar(name: String): VarHandle =
|
||||||
|
LocalVarHandle(this, name, LookupSpec(targets = setOf(LookupTarget.CurrentFrame)))
|
||||||
|
|
||||||
|
override fun resolveRecord(name: String, lookup: LookupSpec): RecordHandle =
|
||||||
|
RecordHandleImpl(this, name, lookup)
|
||||||
|
|
||||||
|
override suspend fun callByName(scope: ScopeFacade, name: String, args: Arguments, lookup: LookupSpec): Obj {
|
||||||
|
val handle = cachedCallables.getOrPut(name) { resolveCallable(name, lookup) }
|
||||||
|
return handle.call(scope, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun facade(): ScopeFacade = facade
|
||||||
|
|
||||||
|
fun resolveLocalRecord(scope: Scope, name: String, lookup: LookupSpec): ObjRecord {
|
||||||
|
val caller = scope.currentClassCtx
|
||||||
|
if (LookupTarget.CurrentFrame in lookup.targets) {
|
||||||
|
scope.tryGetLocalRecord(scope, name, caller)?.let { return it }
|
||||||
|
}
|
||||||
|
if (LookupTarget.ParentChain in lookup.targets) {
|
||||||
|
scope.chainLookupIgnoreClosure(name, followClosure = false, caller = caller)?.let { return it }
|
||||||
|
}
|
||||||
|
if (LookupTarget.ModuleFrame in lookup.targets) {
|
||||||
|
findModuleScope(scope)?.let { module ->
|
||||||
|
module.tryGetLocalRecord(module, name, caller)?.let { return it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
facade.raiseSymbolNotFound(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveReceiver(scope: Scope, receiver: Obj, view: ReceiverView?): Obj {
|
||||||
|
if (view == null) return receiver
|
||||||
|
if (receiver !== scope.thisObj) return receiver
|
||||||
|
val target = when {
|
||||||
|
view.type != null -> scope.thisVariants.firstOrNull { it.isInstanceOf(view.type) }
|
||||||
|
view.typeName != null -> scope.thisVariants.firstOrNull { it.isInstanceOf(view.typeName) }
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
return target ?: facade.raiseSymbolNotFound(view.typeName ?: view.type?.className ?: "<receiver>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveMemberRecord(scope: Scope, receiver: Obj, name: String): MemberResolution {
|
||||||
|
if (receiver is ObjClass) {
|
||||||
|
val rec = receiver.classScope?.objects?.get(name) ?: receiver.members[name]
|
||||||
|
?: facade.raiseSymbolNotFound("member $name not found on ${receiver.className}")
|
||||||
|
val decl = rec.declaringClass ?: receiver
|
||||||
|
if (!canAccessMember(rec.visibility, decl, scope.currentClassCtx, name)) {
|
||||||
|
facade.raiseError(
|
||||||
|
ObjIllegalAccessException(
|
||||||
|
scope,
|
||||||
|
"can't access ${name}: not visible (declared in ${decl.className}, caller ${scope.currentClassCtx?.className ?: "?"})"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return MemberResolution(rec, decl, receiver, rec.fieldId, rec.methodId)
|
||||||
|
}
|
||||||
|
val cls = receiver.objClass
|
||||||
|
val resolved = cls.resolveInstanceMember(name)
|
||||||
|
?: facade.raiseSymbolNotFound("member $name not found on ${cls.className}")
|
||||||
|
val decl = resolved.declaringClass
|
||||||
|
if (!canAccessMember(resolved.record.visibility, decl, scope.currentClassCtx, name)) {
|
||||||
|
facade.raiseError(
|
||||||
|
ObjIllegalAccessException(
|
||||||
|
scope,
|
||||||
|
"can't access ${name}: not visible (declared in ${decl.className}, caller ${scope.currentClassCtx?.className ?: "?"})"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val fieldId = if (resolved.record.type == ObjRecord.Type.Field ||
|
||||||
|
resolved.record.type == ObjRecord.Type.ConstructorField
|
||||||
|
) {
|
||||||
|
resolved.record.fieldId ?: cls.instanceFieldIdMap()[name]
|
||||||
|
} else null
|
||||||
|
val methodId = if (resolved.record.type == ObjRecord.Type.Fun ||
|
||||||
|
resolved.record.type == ObjRecord.Type.Property ||
|
||||||
|
resolved.record.type == ObjRecord.Type.Delegated
|
||||||
|
) {
|
||||||
|
resolved.record.methodId ?: cls.instanceMethodIdMap(includeAbstract = true)[name]
|
||||||
|
} else null
|
||||||
|
return MemberResolution(resolved.record, decl, receiver.objClass, fieldId, methodId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findModuleScope(scope: Scope): ModuleScope? {
|
||||||
|
var s: Scope? = scope
|
||||||
|
var hops = 0
|
||||||
|
while (s != null && hops++ < 1024) {
|
||||||
|
if (s is ModuleScope) return s
|
||||||
|
s = s.parent
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class LocalResolution(
|
||||||
|
val record: ObjRecord,
|
||||||
|
val frameId: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class MemberResolution(
|
||||||
|
val record: ObjRecord,
|
||||||
|
val declaringClass: ObjClass,
|
||||||
|
val receiverClass: ObjClass,
|
||||||
|
val fieldId: Int?,
|
||||||
|
val methodId: Int?
|
||||||
|
)
|
||||||
|
|
||||||
|
private abstract class LocalHandleBase(
|
||||||
|
protected val resolver: BridgeResolverImpl,
|
||||||
|
override val name: String,
|
||||||
|
private val lookup: LookupSpec
|
||||||
|
) : BridgeHandle {
|
||||||
|
private var cached: LocalResolution? = null
|
||||||
|
|
||||||
|
protected fun resolve(scope: Scope): ObjRecord {
|
||||||
|
val cachedLocal = cached
|
||||||
|
if (cachedLocal != null && cachedLocal.frameId == scope.frameId) {
|
||||||
|
return cachedLocal.record
|
||||||
|
}
|
||||||
|
val rec = resolver.resolveLocalRecord(scope, name, lookup)
|
||||||
|
cached = LocalResolution(rec, scope.frameId)
|
||||||
|
return rec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LocalValHandle(
|
||||||
|
resolver: BridgeResolverImpl,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec
|
||||||
|
) : LocalHandleBase(resolver, name, lookup), ValHandle {
|
||||||
|
override suspend fun get(scope: ScopeFacade): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val rec = resolve(real)
|
||||||
|
return real.resolve(rec, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LocalVarHandle(
|
||||||
|
resolver: BridgeResolverImpl,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec
|
||||||
|
) : LocalHandleBase(resolver, name, lookup), VarHandle {
|
||||||
|
override suspend fun get(scope: ScopeFacade): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val rec = resolve(real)
|
||||||
|
return real.resolve(rec, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun set(scope: ScopeFacade, value: Obj) {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val rec = resolve(real)
|
||||||
|
real.assign(rec, name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LocalCallableHandle(
|
||||||
|
resolver: BridgeResolverImpl,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec
|
||||||
|
) : LocalHandleBase(resolver, name, lookup), CallableHandle {
|
||||||
|
override suspend fun call(scope: ScopeFacade, args: Arguments, newThisObj: Obj?): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val rec = resolve(real)
|
||||||
|
val callee = rec.value
|
||||||
|
return scope.call(callee, args, newThisObj ?: rec.receiver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class MemberHandleBase(
|
||||||
|
protected val resolver: BridgeResolverImpl,
|
||||||
|
receiver: Obj,
|
||||||
|
override val name: String,
|
||||||
|
override val receiverView: ReceiverView?
|
||||||
|
) : MemberHandle {
|
||||||
|
private val baseReceiver: Obj = receiver
|
||||||
|
private var cachedResolution: MemberResolution? = null
|
||||||
|
private var cachedDeclaringClass: ObjClass? = null
|
||||||
|
|
||||||
|
protected fun resolve(scope: Scope): Pair<Obj, MemberResolution> {
|
||||||
|
val resolvedReceiver = resolver.resolveReceiver(scope, baseReceiver, receiverView)
|
||||||
|
val cached = cachedResolution
|
||||||
|
if (cached != null && resolvedReceiver.objClass === cached.receiverClass) {
|
||||||
|
cachedDeclaringClass = cached.declaringClass
|
||||||
|
return Pair(resolvedReceiver, cached)
|
||||||
|
}
|
||||||
|
val res = resolver.resolveMemberRecord(scope, resolvedReceiver, name)
|
||||||
|
cachedResolution = res
|
||||||
|
cachedDeclaringClass = res.declaringClass
|
||||||
|
return Pair(resolvedReceiver, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun declaringClass(): ObjClass? = cachedDeclaringClass
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemberValHandleImpl(
|
||||||
|
resolver: BridgeResolverImpl,
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
receiverView: ReceiverView?
|
||||||
|
) : MemberHandleBase(resolver, receiver, name, receiverView), MemberValHandle {
|
||||||
|
override val declaringClass: ObjClass?
|
||||||
|
get() = declaringClass()
|
||||||
|
|
||||||
|
override suspend fun get(scope: ScopeFacade): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val (receiver, res) = resolve(real)
|
||||||
|
val rec = resolveMemberRecordFast(receiver, res)
|
||||||
|
return receiver.resolveRecord(real, rec, name, res.declaringClass).value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemberVarHandleImpl(
|
||||||
|
resolver: BridgeResolverImpl,
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
receiverView: ReceiverView?
|
||||||
|
) : MemberHandleBase(resolver, receiver, name, receiverView), MemberVarHandle {
|
||||||
|
override val declaringClass: ObjClass?
|
||||||
|
get() = declaringClass()
|
||||||
|
|
||||||
|
override suspend fun get(scope: ScopeFacade): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val (receiver, res) = resolve(real)
|
||||||
|
val rec = resolveMemberRecordFast(receiver, res)
|
||||||
|
return receiver.resolveRecord(real, rec, name, res.declaringClass).value
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun set(scope: ScopeFacade, value: Obj) {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val (receiver, res) = resolve(real)
|
||||||
|
val rec = resolveMemberRecordFast(receiver, res)
|
||||||
|
assignMemberRecord(real, receiver, res.declaringClass, rec, name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemberCallableHandleImpl(
|
||||||
|
resolver: BridgeResolverImpl,
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
receiverView: ReceiverView?
|
||||||
|
) : MemberHandleBase(resolver, receiver, name, receiverView), MemberCallableHandle {
|
||||||
|
override val declaringClass: ObjClass?
|
||||||
|
get() = declaringClass()
|
||||||
|
|
||||||
|
override suspend fun call(scope: ScopeFacade, args: Arguments, newThisObj: Obj?): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val (receiver, res) = resolve(real)
|
||||||
|
val rec = resolveMemberRecordFast(receiver, res)
|
||||||
|
if (rec.type != ObjRecord.Type.Fun) {
|
||||||
|
scope.raiseError("member $name is not callable")
|
||||||
|
}
|
||||||
|
return rec.value.invoke(real, receiver, args, res.declaringClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ExtensionCallableHandleImpl(
|
||||||
|
private val resolver: BridgeResolverImpl,
|
||||||
|
private val receiverClass: ObjClass,
|
||||||
|
override val name: String,
|
||||||
|
private val lookup: LookupSpec
|
||||||
|
) : MemberCallableHandle {
|
||||||
|
override val receiverView: ReceiverView?
|
||||||
|
get() = null
|
||||||
|
override val declaringClass: ObjClass?
|
||||||
|
get() = receiverClass
|
||||||
|
|
||||||
|
override suspend fun call(scope: ScopeFacade, args: Arguments, newThisObj: Obj?): Obj {
|
||||||
|
val real = scope.requireScope()
|
||||||
|
val wrapperName = extensionCallableName(receiverClass.className, name)
|
||||||
|
val rec = resolver.resolveLocalRecord(real, wrapperName, lookup)
|
||||||
|
val receiver = newThisObj ?: real.thisObj
|
||||||
|
val callArgs = Arguments(listOf(receiver) + args.list)
|
||||||
|
return scope.call(rec.value, callArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RecordHandleImpl(
|
||||||
|
private val resolver: BridgeResolverImpl,
|
||||||
|
override val name: String,
|
||||||
|
private val lookup: LookupSpec
|
||||||
|
) : RecordHandle {
|
||||||
|
override fun record(): ObjRecord {
|
||||||
|
val scope = resolver.facade().requireScope()
|
||||||
|
return resolver.resolveLocalRecord(scope, name, lookup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TypedHandleImpl<T : Obj>(
|
||||||
|
private val inner: ValHandle,
|
||||||
|
private val clazzName: String
|
||||||
|
) : TypedHandle<T> {
|
||||||
|
override val name: String
|
||||||
|
get() = inner.name
|
||||||
|
|
||||||
|
override suspend fun get(scope: ScopeFacade): Obj = inner.get(scope)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override suspend fun getTyped(scope: ScopeFacade): T {
|
||||||
|
val value = inner.get(scope)
|
||||||
|
return (value as? T)
|
||||||
|
?: scope.raiseClassCastError("Expected $clazzName, got ${value.objClass.className}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveMemberRecordFast(receiver: Obj, res: MemberResolution): ObjRecord {
|
||||||
|
val inst = receiver as? ObjInstance
|
||||||
|
if (inst != null) {
|
||||||
|
res.fieldId?.let { inst.fieldRecordForId(it)?.let { return it } }
|
||||||
|
res.methodId?.let { inst.methodRecordForId(it)?.let { return it } }
|
||||||
|
}
|
||||||
|
return res.record
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun assignMemberRecord(
|
||||||
|
scope: Scope,
|
||||||
|
receiver: Obj,
|
||||||
|
declaringClass: ObjClass,
|
||||||
|
rec: ObjRecord,
|
||||||
|
name: String,
|
||||||
|
value: Obj
|
||||||
|
) {
|
||||||
|
val caller = scope.currentClassCtx
|
||||||
|
if (!canAccessMember(rec.effectiveWriteVisibility, declaringClass, caller, name)) {
|
||||||
|
scope.raiseError(
|
||||||
|
ObjIllegalAccessException(
|
||||||
|
scope,
|
||||||
|
"can't assign ${name}: not visible (declared in ${declaringClass.className}, caller ${caller?.className ?: "?"})"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
when {
|
||||||
|
rec.type == ObjRecord.Type.Delegated -> {
|
||||||
|
val del = rec.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
||||||
|
val th = if (receiver === ObjVoid) net.sergeych.lyng.obj.ObjNull else receiver
|
||||||
|
del.invokeInstanceMethod(scope, "setValue", Arguments(th, ObjString(name), value))
|
||||||
|
}
|
||||||
|
rec.value is ObjProperty || rec.type == ObjRecord.Type.Property -> {
|
||||||
|
val prop = rec.value as? ObjProperty
|
||||||
|
?: scope.raiseError("Expected ObjProperty for property member $name")
|
||||||
|
prop.callSetter(scope, receiver, value, declaringClass)
|
||||||
|
}
|
||||||
|
rec.isMutable -> {
|
||||||
|
val slotRef = rec.value
|
||||||
|
if (slotRef is net.sergeych.lyng.FrameSlotRef) {
|
||||||
|
if (!rec.isMutable && slotRef.read() !== ObjUnset) scope.raiseError("can't reassign val $name")
|
||||||
|
slotRef.write(value)
|
||||||
|
} else {
|
||||||
|
rec.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> scope.raiseError("can't assign to read-only field: $name")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,286 @@
|
|||||||
|
/*
|
||||||
|
* Kotlin bridge bindings for Lyng classes (Lyng-first workflow).
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng.bridge
|
||||||
|
|
||||||
|
import net.sergeych.lyng.Arguments
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
|
import net.sergeych.lyng.ScopeFacade
|
||||||
|
import net.sergeych.lyng.Script
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ObjExternCallable
|
||||||
|
import net.sergeych.lyng.obj.ObjInstance
|
||||||
|
import net.sergeych.lyng.obj.ObjProperty
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
|
import net.sergeych.lyng.ModuleScope
|
||||||
|
import net.sergeych.lyng.ScriptError
|
||||||
|
import net.sergeych.lyng.requiredArg
|
||||||
|
import net.sergeych.lyng.InstanceFieldInitStatement
|
||||||
|
import net.sergeych.lyng.Statement
|
||||||
|
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||||
|
|
||||||
|
interface BridgeInstanceContext {
|
||||||
|
val instance: Obj
|
||||||
|
var data: Any?
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClassBridgeBinder {
|
||||||
|
var classData: Any?
|
||||||
|
fun init(block: suspend BridgeInstanceContext.(ScopeFacade) -> Unit)
|
||||||
|
fun initWithInstance(block: suspend (ScopeFacade, Obj) -> Unit)
|
||||||
|
fun addFun(name: String, impl: suspend (ScopeFacade, Obj, Arguments) -> Obj)
|
||||||
|
fun addVal(name: String, impl: suspend (ScopeFacade, Obj) -> Obj)
|
||||||
|
fun addVar(
|
||||||
|
name: String,
|
||||||
|
get: suspend (ScopeFacade, Obj) -> Obj,
|
||||||
|
set: suspend (ScopeFacade, Obj, Obj) -> Unit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object LyngClassBridge {
|
||||||
|
suspend fun bind(
|
||||||
|
className: String,
|
||||||
|
module: String? = null,
|
||||||
|
importManager: ImportManager = Script.defaultImportManager,
|
||||||
|
block: ClassBridgeBinder.() -> Unit
|
||||||
|
): ObjClass {
|
||||||
|
val cls = resolveClass(className, module, null, importManager)
|
||||||
|
return bind(cls, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun bind(
|
||||||
|
moduleScope: ModuleScope,
|
||||||
|
className: String,
|
||||||
|
block: ClassBridgeBinder.() -> Unit
|
||||||
|
): ObjClass {
|
||||||
|
val cls = resolveClass(className, null, moduleScope, Script.defaultImportManager)
|
||||||
|
return bind(cls, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(clazz: ObjClass, block: ClassBridgeBinder.() -> Unit): ObjClass {
|
||||||
|
val binder = ClassBridgeBinderImpl(clazz)
|
||||||
|
binder.block()
|
||||||
|
binder.commit()
|
||||||
|
return clazz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ObjInstance.data: Any?
|
||||||
|
get() = kotlinInstanceData
|
||||||
|
set(value) { kotlinInstanceData = value }
|
||||||
|
|
||||||
|
var ObjClass.classData: Any?
|
||||||
|
get() = kotlinClassData
|
||||||
|
set(value) { kotlinClassData = value }
|
||||||
|
|
||||||
|
private enum class MemberKind { Instance, Static }
|
||||||
|
|
||||||
|
private data class MemberTarget(
|
||||||
|
val name: String,
|
||||||
|
val record: ObjRecord,
|
||||||
|
val kind: MemberKind,
|
||||||
|
val mirrorClassScope: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
private class BridgeInstanceContextImpl(
|
||||||
|
override val instance: Obj
|
||||||
|
) : BridgeInstanceContext {
|
||||||
|
private fun instanceObj(): ObjInstance =
|
||||||
|
instance as? ObjInstance ?: error("Bridge instance is not an ObjInstance")
|
||||||
|
|
||||||
|
override var data: Any?
|
||||||
|
get() = instanceObj().kotlinInstanceData
|
||||||
|
set(value) { instanceObj().kotlinInstanceData = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClassBridgeBinderImpl(
|
||||||
|
private val cls: ObjClass
|
||||||
|
) : ClassBridgeBinder {
|
||||||
|
private val initHooks = mutableListOf<suspend (ScopeFacade, ObjInstance) -> Unit>()
|
||||||
|
private var checkedTemplate = false
|
||||||
|
|
||||||
|
override var classData: Any?
|
||||||
|
get() = cls.kotlinClassData
|
||||||
|
set(value) { cls.kotlinClassData = value }
|
||||||
|
|
||||||
|
override fun init(block: suspend BridgeInstanceContext.(ScopeFacade) -> Unit) {
|
||||||
|
initHooks.add { scope, inst ->
|
||||||
|
val ctx = BridgeInstanceContextImpl(inst)
|
||||||
|
ctx.block(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initWithInstance(block: suspend (ScopeFacade, Obj) -> Unit) {
|
||||||
|
initHooks.add { scope, inst ->
|
||||||
|
block(scope, inst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addFun(name: String, impl: suspend (ScopeFacade, Obj, Arguments) -> Obj) {
|
||||||
|
ensureTemplateNotBuilt()
|
||||||
|
val target = findMember(name)
|
||||||
|
val callable = ObjExternCallable.fromBridge {
|
||||||
|
impl(this, thisObj, args)
|
||||||
|
}
|
||||||
|
val methodId = cls.ensureMethodIdForBridge(name, target.record)
|
||||||
|
val newRecord = target.record.copy(
|
||||||
|
value = callable,
|
||||||
|
type = ObjRecord.Type.Fun,
|
||||||
|
methodId = methodId
|
||||||
|
)
|
||||||
|
replaceMember(target, newRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addVal(name: String, impl: suspend (ScopeFacade, Obj) -> Obj) {
|
||||||
|
ensureTemplateNotBuilt()
|
||||||
|
val target = findMember(name)
|
||||||
|
if (target.record.isMutable) {
|
||||||
|
throw ScriptError(Pos.builtIn, "extern val $name is mutable in class ${cls.className}")
|
||||||
|
}
|
||||||
|
val getter = ObjExternCallable.fromBridge {
|
||||||
|
impl(this, thisObj)
|
||||||
|
}
|
||||||
|
val prop = ObjProperty(name, getter, null)
|
||||||
|
val isFieldLike = target.record.type == ObjRecord.Type.Field ||
|
||||||
|
target.record.type == ObjRecord.Type.ConstructorField
|
||||||
|
val newRecord = if (isFieldLike) {
|
||||||
|
removeFieldInitializersFor(name)
|
||||||
|
target.record.copy(
|
||||||
|
value = prop,
|
||||||
|
type = target.record.type,
|
||||||
|
fieldId = target.record.fieldId,
|
||||||
|
methodId = target.record.methodId
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val methodId = cls.ensureMethodIdForBridge(name, target.record)
|
||||||
|
target.record.copy(
|
||||||
|
value = prop,
|
||||||
|
type = ObjRecord.Type.Property,
|
||||||
|
methodId = methodId,
|
||||||
|
fieldId = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
replaceMember(target, newRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addVar(
|
||||||
|
name: String,
|
||||||
|
get: suspend (ScopeFacade, Obj) -> Obj,
|
||||||
|
set: suspend (ScopeFacade, Obj, Obj) -> Unit
|
||||||
|
) {
|
||||||
|
ensureTemplateNotBuilt()
|
||||||
|
val target = findMember(name)
|
||||||
|
if (!target.record.isMutable) {
|
||||||
|
throw ScriptError(Pos.builtIn, "extern var $name is readonly in class ${cls.className}")
|
||||||
|
}
|
||||||
|
val getter = ObjExternCallable.fromBridge {
|
||||||
|
get(this, thisObj)
|
||||||
|
}
|
||||||
|
val setter = ObjExternCallable.fromBridge {
|
||||||
|
val value = requiredArg<Obj>(0)
|
||||||
|
set(this, thisObj, value)
|
||||||
|
ObjVoid
|
||||||
|
}
|
||||||
|
val prop = ObjProperty(name, getter, setter)
|
||||||
|
val isFieldLike = target.record.type == ObjRecord.Type.Field ||
|
||||||
|
target.record.type == ObjRecord.Type.ConstructorField
|
||||||
|
val newRecord = if (isFieldLike) {
|
||||||
|
removeFieldInitializersFor(name)
|
||||||
|
target.record.copy(
|
||||||
|
value = prop,
|
||||||
|
type = target.record.type,
|
||||||
|
fieldId = target.record.fieldId,
|
||||||
|
methodId = target.record.methodId
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val methodId = cls.ensureMethodIdForBridge(name, target.record)
|
||||||
|
target.record.copy(
|
||||||
|
value = prop,
|
||||||
|
type = ObjRecord.Type.Property,
|
||||||
|
methodId = methodId,
|
||||||
|
fieldId = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
replaceMember(target, newRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun commit() {
|
||||||
|
if (initHooks.isNotEmpty()) {
|
||||||
|
val target = cls.bridgeInitHooks ?: mutableListOf<suspend (ScopeFacade, ObjInstance) -> Unit>().also {
|
||||||
|
cls.bridgeInitHooks = it
|
||||||
|
}
|
||||||
|
target.addAll(initHooks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureTemplateNotBuilt() {
|
||||||
|
if (!checkedTemplate) {
|
||||||
|
if (cls.instanceTemplateBuilt) {
|
||||||
|
throw ScriptError(
|
||||||
|
Pos.builtIn,
|
||||||
|
"bridge binding for ${cls.className} must happen before first instance is created"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
checkedTemplate = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceMember(target: MemberTarget, newRecord: ObjRecord) {
|
||||||
|
when (target.kind) {
|
||||||
|
MemberKind.Instance -> {
|
||||||
|
cls.replaceMemberForBridge(target.name, newRecord)
|
||||||
|
if (target.mirrorClassScope && cls.classScope?.objects?.containsKey(target.name) == true) {
|
||||||
|
cls.replaceClassScopeMemberForBridge(target.name, newRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MemberKind.Static -> cls.replaceClassScopeMemberForBridge(target.name, newRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findMember(name: String): MemberTarget {
|
||||||
|
val inst = cls.members[name]
|
||||||
|
val stat = cls.classScope?.objects?.get(name)
|
||||||
|
if (inst != null) {
|
||||||
|
return MemberTarget(name, inst, MemberKind.Instance, mirrorClassScope = stat != null)
|
||||||
|
}
|
||||||
|
if (stat != null) return MemberTarget(name, stat, MemberKind.Static)
|
||||||
|
throw ScriptError(Pos.builtIn, "extern member $name not found in class ${cls.className}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeFieldInitializersFor(name: String) {
|
||||||
|
if (cls.instanceInitializers.isEmpty()) return
|
||||||
|
val storageName = cls.mangledName(name)
|
||||||
|
cls.instanceInitializers.removeAll { init ->
|
||||||
|
val stmt = init as? Statement ?: return@removeAll false
|
||||||
|
val original = (stmt as? BytecodeStatement)?.original ?: stmt
|
||||||
|
original is InstanceFieldInitStatement && original.storageName == storageName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun resolveClass(
|
||||||
|
className: String,
|
||||||
|
module: String?,
|
||||||
|
moduleScope: ModuleScope?,
|
||||||
|
importManager: ImportManager
|
||||||
|
): ObjClass {
|
||||||
|
val scope = moduleScope ?: run {
|
||||||
|
if (module == null) {
|
||||||
|
throw ScriptError(Pos.builtIn, "module is required to resolve $className")
|
||||||
|
}
|
||||||
|
importManager.createModuleScope(Pos.builtIn, module)
|
||||||
|
}
|
||||||
|
val rec = scope.get(className)
|
||||||
|
val direct = rec?.value as? ObjClass
|
||||||
|
if (direct != null) return direct
|
||||||
|
if (className.contains('.')) {
|
||||||
|
val resolved = scope.resolveQualifiedIdentifier(className)
|
||||||
|
val cls = resolved as? ObjClass
|
||||||
|
if (cls != null) return cls
|
||||||
|
}
|
||||||
|
throw ScriptError(Pos.builtIn, "class $className not found in module ${scope.packageName}")
|
||||||
|
}
|
||||||
@ -289,6 +289,14 @@ open class ObjClass(
|
|||||||
private var methodSlotMap: Map<String, MethodSlot> = emptyMap()
|
private var methodSlotMap: Map<String, MethodSlot> = emptyMap()
|
||||||
private var methodSlotCount: Int = 0
|
private var methodSlotCount: Int = 0
|
||||||
|
|
||||||
|
/** Kotlin bridge class-level storage (no name lookup). */
|
||||||
|
internal var kotlinClassData: Any? = null
|
||||||
|
|
||||||
|
/** Kotlin bridge instance init hooks. */
|
||||||
|
internal var bridgeInitHooks: MutableList<suspend (net.sergeych.lyng.ScopeFacade, ObjInstance) -> Unit>? = null
|
||||||
|
|
||||||
|
internal var instanceTemplateBuilt: Boolean = false
|
||||||
|
|
||||||
private fun ensureFieldSlots(): Map<String, FieldSlot> {
|
private fun ensureFieldSlots(): Map<String, FieldSlot> {
|
||||||
if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap
|
if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap
|
||||||
val res = mutableMapOf<String, FieldSlot>()
|
val res = mutableMapOf<String, FieldSlot>()
|
||||||
@ -480,12 +488,20 @@ open class ObjClass(
|
|||||||
val existingId = rec.methodId
|
val existingId = rec.methodId
|
||||||
if (existingId != null) {
|
if (existingId != null) {
|
||||||
methodIdMap[name] = existingId
|
methodIdMap[name] = existingId
|
||||||
|
if (existingId >= nextMethodId) {
|
||||||
|
nextMethodId = existingId + 1
|
||||||
|
}
|
||||||
return existingId
|
return existingId
|
||||||
}
|
}
|
||||||
val id = methodIdMap.getOrPut(name) { nextMethodId++ }
|
val id = methodIdMap.getOrPut(name) { nextMethodId++ }
|
||||||
|
if (id >= nextMethodId) {
|
||||||
|
nextMethodId = id + 1
|
||||||
|
}
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun ensureMethodIdForBridge(name: String, rec: ObjRecord): Int = assignMethodId(name, rec)
|
||||||
|
|
||||||
private fun ensureMethodIdSeeded() {
|
private fun ensureMethodIdSeeded() {
|
||||||
if (methodIdSeeded) return
|
if (methodIdSeeded) return
|
||||||
var maxId = -1
|
var maxId = -1
|
||||||
@ -515,6 +531,17 @@ open class ObjClass(
|
|||||||
methodIdSeeded = true
|
methodIdSeeded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun replaceMemberForBridge(name: String, newRecord: ObjRecord) {
|
||||||
|
members[name] = newRecord
|
||||||
|
layoutVersion += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun replaceClassScopeMemberForBridge(name: String, newRecord: ObjRecord) {
|
||||||
|
initClassScope()
|
||||||
|
classScope!!.objects[name] = newRecord
|
||||||
|
layoutVersion += 1
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String = className
|
override fun toString(): String = className
|
||||||
|
|
||||||
override suspend fun compareTo(scope: Scope, other: Obj): Int = if (other === this) 0 else -1
|
override suspend fun compareTo(scope: Scope, other: Obj): Int = if (other === this) 0 else -1
|
||||||
@ -552,6 +579,7 @@ open class ObjClass(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
instanceTemplateBuilt = true
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -634,6 +662,14 @@ open class ObjClass(
|
|||||||
args: Arguments?,
|
args: Arguments?,
|
||||||
runConstructors: Boolean
|
runConstructors: Boolean
|
||||||
) {
|
) {
|
||||||
|
bridgeInitHooks?.let { hooks ->
|
||||||
|
if (hooks.isNotEmpty()) {
|
||||||
|
val facade = instance.instanceScope.asFacade()
|
||||||
|
for (hook in hooks) {
|
||||||
|
hook(facade, instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
val visited = hashSetOf<ObjClass>()
|
val visited = hashSetOf<ObjClass>()
|
||||||
initClassInternal(instance, visited, this, args, isRoot = true, runConstructors = runConstructors)
|
initClassInternal(instance, visited, this, args, isRoot = true, runConstructors = runConstructors)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
internal lateinit var instanceScope: Scope
|
internal lateinit var instanceScope: Scope
|
||||||
internal var fieldSlots: Array<ObjRecord?> = emptyArray()
|
internal var fieldSlots: Array<ObjRecord?> = emptyArray()
|
||||||
internal var methodSlots: Array<ObjRecord?> = emptyArray()
|
internal var methodSlots: Array<ObjRecord?> = emptyArray()
|
||||||
|
internal var kotlinInstanceData: Any? = null
|
||||||
|
|
||||||
internal fun initFieldSlots(size: Int) {
|
internal fun initFieldSlots(size: Int) {
|
||||||
fieldSlots = arrayOfNulls(size)
|
fieldSlots = arrayOfNulls(size)
|
||||||
|
|||||||
148
lynglib/src/commonTest/kotlin/BridgeBindingTest.kt
Normal file
148
lynglib/src/commonTest/kotlin/BridgeBindingTest.kt
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.bridge.LyngClassBridge
|
||||||
|
import net.sergeych.lyng.bridge.data
|
||||||
|
import net.sergeych.lyng.obj.ObjInt
|
||||||
|
import net.sergeych.lyng.obj.ObjString
|
||||||
|
|
||||||
|
class BridgeBindingTest {
|
||||||
|
private data class CounterState(var count: Long)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExternClassBinding() = runTest {
|
||||||
|
val im = Script.defaultImportManager.copy()
|
||||||
|
im.addPackage("bridge.mod") { scope ->
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
class Foo {
|
||||||
|
extern fun add(a: Int, b: Int): Int
|
||||||
|
extern val status: String
|
||||||
|
extern var count: Int
|
||||||
|
private extern fun secret(): Int
|
||||||
|
static extern fun ping(): Int
|
||||||
|
fun callAdd() = add(2, 3)
|
||||||
|
fun callSecret() = secret()
|
||||||
|
fun bump() { count = count + 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Bar {
|
||||||
|
extern var count: Int
|
||||||
|
fun inc() { count = count + 1 }
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LyngClassBridge.bind(className = "Foo", module = "bridge.mod", importManager = im) {
|
||||||
|
classData = "OK"
|
||||||
|
init { _ ->
|
||||||
|
data = CounterState(0)
|
||||||
|
}
|
||||||
|
addFun("add") { _, _, args ->
|
||||||
|
val a = (args.list[0] as ObjInt).value
|
||||||
|
val b = (args.list[1] as ObjInt).value
|
||||||
|
ObjInt.of(a + b)
|
||||||
|
}
|
||||||
|
addVal("status") { _, _ -> ObjString(classData as String) }
|
||||||
|
addVar(
|
||||||
|
"count",
|
||||||
|
get = { _, instance ->
|
||||||
|
val st = (instance as net.sergeych.lyng.obj.ObjInstance).data as CounterState
|
||||||
|
ObjInt.of(st.count)
|
||||||
|
},
|
||||||
|
set = { _, instance, value ->
|
||||||
|
val st = (instance as net.sergeych.lyng.obj.ObjInstance).data as CounterState
|
||||||
|
st.count = (value as ObjInt).value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
addFun("secret") { _, _, _ -> ObjInt.of(42) }
|
||||||
|
addFun("ping") { _, _, _ -> ObjInt.of(7) }
|
||||||
|
}
|
||||||
|
|
||||||
|
LyngClassBridge.bind(className = "Bar", module = "bridge.mod", importManager = im) {
|
||||||
|
initWithInstance { _, instance ->
|
||||||
|
(instance as net.sergeych.lyng.obj.ObjInstance).data = CounterState(10)
|
||||||
|
}
|
||||||
|
addVar(
|
||||||
|
"count",
|
||||||
|
get = { _, instance ->
|
||||||
|
val st = (instance as net.sergeych.lyng.obj.ObjInstance).data as CounterState
|
||||||
|
ObjInt.of(st.count)
|
||||||
|
},
|
||||||
|
set = { _, instance, value ->
|
||||||
|
val st = (instance as net.sergeych.lyng.obj.ObjInstance).data as CounterState
|
||||||
|
st.count = (value as ObjInt).value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val scope = im.newStdScope()
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
import bridge.mod
|
||||||
|
val f = Foo()
|
||||||
|
assertEquals(5, f.callAdd())
|
||||||
|
assertEquals("OK", f.status)
|
||||||
|
assertEquals(0, f.count)
|
||||||
|
f.bump()
|
||||||
|
assertEquals(1, f.count)
|
||||||
|
assertEquals(42, f.callSecret())
|
||||||
|
assertEquals(7, Foo.ping())
|
||||||
|
|
||||||
|
val b = Bar()
|
||||||
|
assertEquals(10, b.count)
|
||||||
|
b.inc()
|
||||||
|
assertEquals(11, b.count)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
val privateCallFails = try {
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
import bridge.mod
|
||||||
|
Foo().secret()
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
false
|
||||||
|
} catch (_: ScriptError) {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
assertTrue(privateCallFails)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBindAfterInstanceFails() = runTest {
|
||||||
|
val im = Script.defaultImportManager.copy()
|
||||||
|
im.addPackage("bridge.late") { scope ->
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
class Late {
|
||||||
|
extern val status: String
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val scope = im.newStdScope()
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
import bridge.late
|
||||||
|
val l = Late()
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
val bindFailed = try {
|
||||||
|
LyngClassBridge.bind(className = "Late", module = "bridge.late", importManager = im) {
|
||||||
|
addVal("status") { _, _ -> ObjString("late") }
|
||||||
|
}
|
||||||
|
false
|
||||||
|
} catch (_: ScriptError) {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
assertTrue(bindFailed)
|
||||||
|
}
|
||||||
|
}
|
||||||
54
lynglib/src/commonTest/kotlin/BridgeResolverTest.kt
Normal file
54
lynglib/src/commonTest/kotlin/BridgeResolverTest.kt
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.bridge.BridgeCallByName
|
||||||
|
import net.sergeych.lyng.bridge.resolver
|
||||||
|
import net.sergeych.lyng.obj.ObjInstance
|
||||||
|
import net.sergeych.lyng.obj.ObjInt
|
||||||
|
|
||||||
|
class BridgeResolverTest {
|
||||||
|
@Test
|
||||||
|
fun testLocalAndMemberHandles() = runTest {
|
||||||
|
val im = Script.defaultImportManager.copy()
|
||||||
|
val scope = im.newStdScope()
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
var x = 1
|
||||||
|
fun add(a: Int, b: Int) = a + b
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
var count: Int = 0
|
||||||
|
fun bump() { count = count + 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
val f = Foo()
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
val facade = scope.asFacade()
|
||||||
|
val resolver = facade.resolver()
|
||||||
|
|
||||||
|
val xHandle = resolver.resolveVar("x")
|
||||||
|
assertEquals(1, (xHandle.get(facade) as ObjInt).value)
|
||||||
|
xHandle.set(facade, ObjInt.of(5))
|
||||||
|
assertEquals(5, (xHandle.get(facade) as ObjInt).value)
|
||||||
|
|
||||||
|
val addHandle = resolver.resolveCallable("add")
|
||||||
|
val addResult = addHandle.call(facade, Arguments(ObjInt.of(2), ObjInt.of(3)))
|
||||||
|
assertEquals(5, (addResult as ObjInt).value)
|
||||||
|
|
||||||
|
val fRec = scope["f"] ?: error("f not found")
|
||||||
|
val fObj = scope.resolve(fRec, "f") as ObjInstance
|
||||||
|
val countHandle = resolver.resolveMemberVar(fObj, "count")
|
||||||
|
assertEquals(0, (countHandle.get(facade) as ObjInt).value)
|
||||||
|
val bumpHandle = resolver.resolveMemberCallable(fObj, "bump")
|
||||||
|
bumpHandle.call(facade)
|
||||||
|
assertEquals(1, (countHandle.get(facade) as ObjInt).value)
|
||||||
|
|
||||||
|
val callByName = resolver as BridgeCallByName
|
||||||
|
val addByName = callByName.callByName(facade, "add", Arguments(ObjInt.of(4), ObjInt.of(6)))
|
||||||
|
assertEquals(10, (addByName as ObjInt).value)
|
||||||
|
}
|
||||||
|
}
|
||||||
164
notes/bridge_facade_draft.kt
Normal file
164
notes/bridge_facade_draft.kt
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* Draft: Kotlin bridge facade + handle API (declarations only)
|
||||||
|
* For discussion; not wired into runtime yet.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng.bridge
|
||||||
|
|
||||||
|
import net.sergeych.lyng.Arguments
|
||||||
|
import net.sergeych.lyng.ObjClass
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
|
import net.sergeych.lyng.ScopeFacade
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
|
||||||
|
/** Where to resolve names from. */
|
||||||
|
enum class LookupTarget {
|
||||||
|
CurrentFrame,
|
||||||
|
ParentChain,
|
||||||
|
ModuleFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Explicit receiver view (like this@Base). */
|
||||||
|
data class ReceiverView(
|
||||||
|
val type: ObjClass? = null,
|
||||||
|
val typeName: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Lookup rules for bridge resolution. */
|
||||||
|
data class LookupSpec(
|
||||||
|
val targets: Set<LookupTarget> = setOf(LookupTarget.CurrentFrame, LookupTarget.ModuleFrame),
|
||||||
|
val receiverView: ReceiverView? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Base handle type. */
|
||||||
|
sealed interface BridgeHandle {
|
||||||
|
val name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read-only value handle. */
|
||||||
|
interface ValHandle : BridgeHandle {
|
||||||
|
suspend fun get(scope: ScopeFacade): Obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read/write value handle. */
|
||||||
|
interface VarHandle : ValHandle {
|
||||||
|
suspend fun set(scope: ScopeFacade, value: Obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Callable handle (function/closure/method). */
|
||||||
|
interface CallableHandle : BridgeHandle {
|
||||||
|
suspend fun call(scope: ScopeFacade, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Member handle resolved against an instance or receiver view. */
|
||||||
|
interface MemberHandle : BridgeHandle {
|
||||||
|
val declaringClass: ObjClass?
|
||||||
|
val receiverView: ReceiverView?
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Member field/property. */
|
||||||
|
interface MemberValHandle : MemberHandle, ValHandle
|
||||||
|
|
||||||
|
/** Member var/property with write access. */
|
||||||
|
interface MemberVarHandle : MemberHandle, VarHandle
|
||||||
|
|
||||||
|
/** Member callable (method or extension). */
|
||||||
|
interface MemberCallableHandle : MemberHandle, CallableHandle
|
||||||
|
|
||||||
|
/** Direct record handle (debug/inspection). */
|
||||||
|
interface RecordHandle : BridgeHandle {
|
||||||
|
fun record(): ObjRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Bridge resolver API (entry point for Kotlin bindings). */
|
||||||
|
interface BridgeResolver {
|
||||||
|
val pos: Pos
|
||||||
|
|
||||||
|
fun selfAs(type: ObjClass): BridgeResolver
|
||||||
|
fun selfAs(typeName: String): BridgeResolver
|
||||||
|
|
||||||
|
fun resolveVal(name: String, lookup: LookupSpec = LookupSpec()): ValHandle
|
||||||
|
fun resolveVar(name: String, lookup: LookupSpec = LookupSpec()): VarHandle
|
||||||
|
fun resolveCallable(name: String, lookup: LookupSpec = LookupSpec()): CallableHandle
|
||||||
|
|
||||||
|
fun resolveMemberVal(
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): MemberValHandle
|
||||||
|
|
||||||
|
fun resolveMemberVar(
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): MemberVarHandle
|
||||||
|
|
||||||
|
fun resolveMemberCallable(
|
||||||
|
receiver: Obj,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): MemberCallableHandle
|
||||||
|
|
||||||
|
/** Extension function treated as a member for reflection. */
|
||||||
|
fun resolveExtensionCallable(
|
||||||
|
receiverClass: ObjClass,
|
||||||
|
name: String,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): MemberCallableHandle
|
||||||
|
|
||||||
|
/** Debug: resolve locals by name (optional, for tooling). */
|
||||||
|
fun resolveLocalVal(name: String): ValHandle
|
||||||
|
fun resolveLocalVar(name: String): VarHandle
|
||||||
|
|
||||||
|
/** Debug: access raw record handles if needed. */
|
||||||
|
fun resolveRecord(name: String, lookup: LookupSpec = LookupSpec()): RecordHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience: call by name with implicit caching in resolver implementation. */
|
||||||
|
interface BridgeCallByName {
|
||||||
|
suspend fun callByName(
|
||||||
|
scope: ScopeFacade,
|
||||||
|
name: String,
|
||||||
|
args: Arguments = Arguments.EMPTY,
|
||||||
|
lookup: LookupSpec = LookupSpec()
|
||||||
|
): Obj
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Optional typed wrappers (sugar). */
|
||||||
|
interface TypedHandle<T : Obj> : ValHandle {
|
||||||
|
suspend fun getTyped(scope: ScopeFacade): T
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Debug view of frame stack for tooling. */
|
||||||
|
interface FrameStackView {
|
||||||
|
fun frames(): List<FrameView>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FrameView {
|
||||||
|
val pos: Pos
|
||||||
|
val thisObj: Obj
|
||||||
|
fun locals(): Map<String, ObjRecord>
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Binder API for Kotlin -> Lyng extern wiring. */
|
||||||
|
interface ClassBridgeBinder {
|
||||||
|
var classData: Any?
|
||||||
|
|
||||||
|
fun init(block: suspend BridgeInstanceContext.(ScopeFacade) -> Unit)
|
||||||
|
fun initWithInstance(block: suspend (ScopeFacade, Obj) -> Unit)
|
||||||
|
|
||||||
|
fun addFun(name: String, impl: suspend (ScopeFacade, Obj, Arguments) -> Obj)
|
||||||
|
fun addVal(name: String, impl: suspend (ScopeFacade, Obj) -> Obj)
|
||||||
|
fun addVar(
|
||||||
|
name: String,
|
||||||
|
get: suspend (ScopeFacade, Obj) -> Obj,
|
||||||
|
set: suspend (ScopeFacade, Obj, Obj) -> Unit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Instance receiver context for init/impl blocks. */
|
||||||
|
interface BridgeInstanceContext {
|
||||||
|
val instance: Obj
|
||||||
|
var data: Any?
|
||||||
|
}
|
||||||
72
notes/kotlin_bridge_binding.md
Normal file
72
notes/kotlin_bridge_binding.md
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# Kotlin Bridge: Lyng-First Class Binding
|
||||||
|
|
||||||
|
This note describes the Lyng-first workflow where a class is declared in Lyng and Kotlin provides extern implementations.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
- Lyng code declares a class and marks members as `extern`.
|
||||||
|
- Kotlin binds implementations with `LyngClassBridge.bind(...)`.
|
||||||
|
- Binding must happen **before the first instance is created**.
|
||||||
|
- `bind(className, module, importManager)` requires `module` to resolve class names; use
|
||||||
|
`bind(moduleScope, className, ...)` or `bind(ObjClass, ...)` if you already have a scope/class.
|
||||||
|
- Kotlin can store two opaque payloads:
|
||||||
|
- `instance.data` (per instance)
|
||||||
|
- `classData` (per class)
|
||||||
|
|
||||||
|
## Lyng: declare extern members
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class Foo {
|
||||||
|
extern fun add(a: Int, b: Int): Int
|
||||||
|
extern val status: String
|
||||||
|
extern var count: Int
|
||||||
|
private extern fun secret(): Int
|
||||||
|
static extern fun ping(): Int
|
||||||
|
|
||||||
|
fun callAdd() = add(2, 3)
|
||||||
|
fun callSecret() = secret()
|
||||||
|
fun bump() { count = count + 1 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kotlin: bind extern implementations
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
LyngClassBridge.bind(className = "Foo", module = "bridge.mod", importManager = im) {
|
||||||
|
classData = "OK"
|
||||||
|
|
||||||
|
init { _ ->
|
||||||
|
data = CounterState(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
addFun("add") { _, _, args ->
|
||||||
|
val a = (args.list[0] as ObjInt).value
|
||||||
|
val b = (args.list[1] as ObjInt).value
|
||||||
|
ObjInt.of(a + b)
|
||||||
|
}
|
||||||
|
|
||||||
|
addVal("status") { _, _ -> ObjString(classData as String) }
|
||||||
|
|
||||||
|
addVar(
|
||||||
|
"count",
|
||||||
|
get = { _, instance ->
|
||||||
|
val st = (instance as ObjInstance).data as CounterState
|
||||||
|
ObjInt.of(st.count)
|
||||||
|
},
|
||||||
|
set = { _, instance, value ->
|
||||||
|
val st = (instance as ObjInstance).data as CounterState
|
||||||
|
st.count = (value as ObjInt).value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
addFun("secret") { _, _, _ -> ObjInt.of(42) }
|
||||||
|
addFun("ping") { _, _, _ -> ObjInt.of(7) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Visibility is respected by usual Lyng access rules; private extern members can be used only within class code.
|
||||||
|
- Use `init { scope -> ... }` for receiver-style init, or `initWithInstance { scope, instance -> ... }` for explicit instance access.
|
||||||
|
- `classData` and `instance.data` are Kotlin-only payloads and do not appear in Lyng reflection.
|
||||||
|
- Binding after the first instance of a class is created throws a `ScriptError`.
|
||||||
42
notes/kotlin_bridge_reflection.md
Normal file
42
notes/kotlin_bridge_reflection.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Kotlin Bridge Reflection Facade (Handles)
|
||||||
|
|
||||||
|
This note documents the Kotlin-side reflection facade used by extern bindings to access Lyng values efficiently.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
- `ScopeFacade.resolver()` returns a `BridgeResolver` bound to the current call scope.
|
||||||
|
- Resolution returns stable handles (val/var/callable/member) that avoid repeated name lookup.
|
||||||
|
- Handles are safe across calls; they re-resolve automatically when the frame changes.
|
||||||
|
- Call-by-name is available via `BridgeCallByName` for quick cached calls.
|
||||||
|
|
||||||
|
## Example: locals + functions
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val facade = scope.asFacade()
|
||||||
|
val resolver = facade.resolver()
|
||||||
|
|
||||||
|
val x = resolver.resolveVar("x")
|
||||||
|
val add = resolver.resolveCallable("add")
|
||||||
|
|
||||||
|
val a = x.get(facade) as ObjInt
|
||||||
|
x.set(facade, ObjInt.of(a.value + 1))
|
||||||
|
|
||||||
|
val res = add.call(facade, Arguments(ObjInt.of(2), ObjInt.of(3))) as ObjInt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: member handles
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val f = scope["f"]!!.value as ObjInstance
|
||||||
|
val count = resolver.resolveMemberVar(f, "count")
|
||||||
|
val bump = resolver.resolveMemberCallable(f, "bump")
|
||||||
|
|
||||||
|
count.set(facade, ObjInt.of(10))
|
||||||
|
bump.call(facade)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Visibility rules are enforced exactly as in Lyng.
|
||||||
|
- `resolveExtensionCallable` treats extensions as member callables using wrapper names.
|
||||||
|
- `selfAs(type)` creates a receiver view (this@Base) when resolving members on the current `this`.
|
||||||
Loading…
x
Reference in New Issue
Block a user