Add Kotlin bridge reflection handles and tests
This commit is contained in:
1 parent
cd145f6a96
commit
9cf87d1075
10 files changed
+1339
No files matched your search
@@ -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?
|
||||
}
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
Reference in new issue
Block a user