Add support for closed classes and enhancements to the Kotlin reflection bridge
This commit is contained in:
parent
7f2f99524f
commit
c5b8871c3a
@ -182,6 +182,77 @@ instance.value = 42
|
||||
println(instance.value) // -> 42
|
||||
```
|
||||
|
||||
### 6.5) Preferred: bind Kotlin implementations to declared Lyng classes
|
||||
|
||||
For extensions and libraries, the **preferred** workflow is Lyng‑first: declare the class and its members in Lyng, then bind the Kotlin implementations using the bridge.
|
||||
|
||||
This keeps Lyng semantics (visibility, overrides, type checks) in Lyng, while Kotlin supplies the behavior.
|
||||
|
||||
```lyng
|
||||
// Lyng side (in a module)
|
||||
class Counter {
|
||||
extern var value: Int
|
||||
extern fun inc(by: Int): Int
|
||||
}
|
||||
```
|
||||
|
||||
Note: members must be marked `extern` so the compiler emits the ABI slots that Kotlin bindings attach to. This applies to functions and properties bound via `addFun` / `addVal` / `addVar`.
|
||||
|
||||
```kotlin
|
||||
// Kotlin side (binding)
|
||||
val moduleScope = Script.newScope() // or an existing module scope
|
||||
moduleScope.eval("class Counter { extern var value: Int; extern fun inc(by: Int): Int }")
|
||||
|
||||
moduleScope.bind("Counter") {
|
||||
addVar(
|
||||
name = "value",
|
||||
get = { _, self -> self.readField(this, "value").value },
|
||||
set = { _, self, v -> self.writeField(this, "value", v) }
|
||||
)
|
||||
addFun("inc") { _, self, args ->
|
||||
val by = args.requiredArg<ObjInt>(0).value
|
||||
val current = self.readField(this, "value").value as ObjInt
|
||||
val next = ObjInt(current.value + by)
|
||||
self.writeField(this, "value", next)
|
||||
next
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Binding must happen **before** the first instance is created.
|
||||
- Use [LyngClassBridge] to bind by name/module, or by an already resolved `ObjClass`.
|
||||
- Use `ObjInstance.data` / `ObjClass.classData` to attach Kotlin‑side state when needed.
|
||||
|
||||
### 6.6) Preferred: Kotlin reflection bridge for call‑by‑name
|
||||
|
||||
For Kotlin code that needs dynamic access to Lyng variables, functions, or members, use the bridge resolver.
|
||||
It provides explicit, cached handles and predictable lookup rules.
|
||||
|
||||
```kotlin
|
||||
val scope = Script.newScope()
|
||||
scope.eval("""
|
||||
val x = 40
|
||||
fun add(a, b) = a + b
|
||||
class Box { var value = 1 }
|
||||
""")
|
||||
|
||||
val resolver = scope.resolver()
|
||||
|
||||
// Read a top‑level value
|
||||
val x = resolver.resolveVal("x").get(scope)
|
||||
|
||||
// Call a function by name (cached inside the resolver)
|
||||
val sum = (resolver as BridgeCallByName).callByName(scope, "add", Arguments(ObjInt(1), ObjInt(2)))
|
||||
|
||||
// Member access
|
||||
val box = scope.eval("Box()")
|
||||
val valueHandle = resolver.resolveMemberVar(box, "value")
|
||||
valueHandle.set(scope, ObjInt(10))
|
||||
val value = valueHandle.get(scope)
|
||||
```
|
||||
|
||||
### 7) Read variable values back in Kotlin
|
||||
|
||||
The simplest approach: evaluate an expression that yields the value and convert it.
|
||||
|
||||
@ -243,3 +243,11 @@ You can enable it in **Settings | Lyng Formatter | Enable Lyng autocompletion**.
|
||||
|
||||
### Kotlin API: Exception Handling
|
||||
The `Obj.getLyngExceptionMessageWithStackTrace()` extension method has been added to simplify retrieving detailed error information from Lyng exception objects in Kotlin. Additionally, `getLyngExceptionMessage()` and `raiseAsExecutionError()` now accept an optional `Scope`, making it easier to use them when a scope is not immediately available.
|
||||
|
||||
### Kotlin API: Bridge Reflection and Class Binding (Preferred Extensions)
|
||||
Lyng now provides a public Kotlin reflection bridge and a Lyng‑first class binding workflow. This is the **preferred** way to write Kotlin extensions and library integrations:
|
||||
|
||||
- **Bridge resolver**: explicit handles for values, vars, and callables with predictable lookup rules.
|
||||
- **Class bridge binding**: declare classes/members in Lyng (marked `extern`) and bind the implementations in Kotlin before the first instance is created.
|
||||
|
||||
See **Embedding Lyng** for full samples and usage details.
|
||||
|
||||
@ -31,6 +31,7 @@ data class ClassDeclSpec(
|
||||
val startPos: Pos,
|
||||
val isExtern: Boolean,
|
||||
val isAbstract: Boolean,
|
||||
val isClosed: Boolean,
|
||||
val isObject: Boolean,
|
||||
val isAnonymous: Boolean,
|
||||
val baseSpecs: List<ClassDeclBaseSpec>,
|
||||
@ -118,6 +119,7 @@ internal suspend fun executeClassDecl(
|
||||
|
||||
val newClass = ObjInstanceClass(spec.className, *parentClasses.toTypedArray()).also {
|
||||
it.isAbstract = spec.isAbstract
|
||||
it.isClosed = spec.isClosed
|
||||
it.instanceConstructor = constructorCode
|
||||
it.constructorMeta = spec.constructorArgs
|
||||
for (i in parentClasses.indices) {
|
||||
|
||||
@ -5331,8 +5331,8 @@ class Compiler(
|
||||
|
||||
val isMember = (codeContexts.lastOrNull() is CodeContext.ClassBody)
|
||||
|
||||
if (!isMember && isClosed)
|
||||
throw ScriptError(currentToken.pos, "modifier closed is only allowed for class members")
|
||||
if (!isMember && isClosed && currentToken.value != "class")
|
||||
throw ScriptError(currentToken.pos, "modifier closed at top level is only allowed for classes")
|
||||
|
||||
if (!isMember && isOverride && currentToken.value != "fun" && currentToken.value != "fn")
|
||||
throw ScriptError(currentToken.pos, "modifier override outside class is only allowed for extension functions")
|
||||
@ -5345,12 +5345,12 @@ class Compiler(
|
||||
"var" -> parseVarDeclaration(true, visibility, isAbstract, isClosed, isOverride, isStatic, isExtern)
|
||||
"fun", "fn" -> parseFunctionDeclaration(visibility, isAbstract, isClosed, isOverride, isExtern, isStatic)
|
||||
"class" -> {
|
||||
if (isStatic || isClosed || isOverride)
|
||||
if (isStatic || isOverride)
|
||||
throw ScriptError(
|
||||
currentToken.pos,
|
||||
"unsupported modifiers for class: ${modifiers.joinToString(" ")}"
|
||||
)
|
||||
parseClassDeclaration(isAbstract, isExtern)
|
||||
parseClassDeclaration(isAbstract, isExtern, isClosed)
|
||||
}
|
||||
|
||||
"object" -> {
|
||||
@ -5478,8 +5478,11 @@ class Compiler(
|
||||
"type" -> {
|
||||
pendingDeclStart = id.pos
|
||||
pendingDeclDoc = consumePendingDoc()
|
||||
if (!looksLikeTypeAliasDeclaration()) return null
|
||||
parseTypeAliasDeclaration()
|
||||
if (looksLikeTypeAliasDeclaration()) {
|
||||
parseTypeAliasDeclaration()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
"try" -> parseTryStatement()
|
||||
@ -6116,6 +6119,7 @@ class Compiler(
|
||||
startPos = startPos,
|
||||
isExtern = false,
|
||||
isAbstract = false,
|
||||
isClosed = false,
|
||||
isObject = true,
|
||||
isAnonymous = nameToken == null,
|
||||
baseSpecs = baseSpecs.map { ClassDeclBaseSpec(it.name, it.args) },
|
||||
@ -6127,7 +6131,7 @@ class Compiler(
|
||||
return ClassDeclStatement(spec)
|
||||
}
|
||||
|
||||
private suspend fun parseClassDeclaration(isAbstract: Boolean = false, isExtern: Boolean = false): Statement {
|
||||
private suspend fun parseClassDeclaration(isAbstract: Boolean = false, isExtern: Boolean = false, isClosed: Boolean = false): Statement {
|
||||
val nameToken = cc.requireToken(Token.Type.ID)
|
||||
val startPos = pendingDeclStart ?: nameToken.pos
|
||||
val doc = pendingDeclDoc ?: consumePendingDoc()
|
||||
@ -6474,6 +6478,7 @@ class Compiler(
|
||||
startPos = startPos,
|
||||
isExtern = isExtern,
|
||||
isAbstract = isAbstract,
|
||||
isClosed = isClosed,
|
||||
isObject = false,
|
||||
isAnonymous = false,
|
||||
baseSpecs = baseSpecs.map { ClassDeclBaseSpec(it.name, it.args) },
|
||||
|
||||
@ -1,15 +1,27 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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.*
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.ObjIllegalAccessException
|
||||
@ -20,54 +32,85 @@ 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. */
|
||||
/**
|
||||
* Where a bridge resolver should search for names.
|
||||
*
|
||||
* Used by [LookupSpec] to control reflection scope for Kotlin-side tooling and bindings.
|
||||
*/
|
||||
enum class LookupTarget {
|
||||
/** Resolve from the current frame only (locals/params declared in the active scope). */
|
||||
CurrentFrame,
|
||||
/** Resolve by walking the raw parent chain of frames (locals only, no member fallback). */
|
||||
ParentChain,
|
||||
/** Resolve against the module frame (top-level declarations in the module). */
|
||||
ModuleFrame
|
||||
}
|
||||
|
||||
/** Explicit receiver view (like this@Base). */
|
||||
/**
|
||||
* Explicit receiver view, similar to `this@Base` in Lyng.
|
||||
*
|
||||
* When provided, the resolver will treat `this` as the requested type
|
||||
* for member resolution and visibility checks.
|
||||
*/
|
||||
data class ReceiverView(
|
||||
val type: ObjClass? = null,
|
||||
val typeName: String? = null
|
||||
)
|
||||
|
||||
/** Lookup rules for bridge resolution. */
|
||||
/**
|
||||
* Lookup rules for bridge resolution.
|
||||
*
|
||||
* @property targets where to resolve names from
|
||||
* @property receiverView optional explicit receiver for member lookup (like `this@Base`)
|
||||
*/
|
||||
data class LookupSpec(
|
||||
val targets: Set<LookupTarget> = setOf(LookupTarget.CurrentFrame, LookupTarget.ModuleFrame),
|
||||
val receiverView: ReceiverView? = null
|
||||
)
|
||||
|
||||
/** Base handle type. */
|
||||
/**
|
||||
* Base handle type returned by the Kotlin reflection bridge.
|
||||
*
|
||||
* Handles are inexpensive to keep and cache; they resolve lazily and
|
||||
* may internally cache slots/records once a frame is known.
|
||||
*/
|
||||
sealed interface BridgeHandle {
|
||||
/** Name of the underlying symbol (as written in Lyng). */
|
||||
val name: String
|
||||
}
|
||||
|
||||
/** Read-only value handle. */
|
||||
/** Read-only value handle resolved in a [ScopeFacade]. */
|
||||
interface ValHandle : BridgeHandle {
|
||||
/** Read the current value. */
|
||||
suspend fun get(scope: ScopeFacade): Obj
|
||||
}
|
||||
|
||||
/** Read/write value handle. */
|
||||
/** Read/write value handle resolved in a [ScopeFacade]. */
|
||||
interface VarHandle : ValHandle {
|
||||
/** Assign a new value. */
|
||||
suspend fun set(scope: ScopeFacade, value: Obj)
|
||||
}
|
||||
|
||||
/** Callable handle (function/closure/method). */
|
||||
interface CallableHandle : BridgeHandle {
|
||||
/**
|
||||
* Call the target with optional [args].
|
||||
*
|
||||
* @param newThisObj overrides receiver for member calls (defaults to current `this`/record receiver).
|
||||
*/
|
||||
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 {
|
||||
/** Declaring class resolved for the last call/get/set (if known). */
|
||||
val declaringClass: ObjClass?
|
||||
/** Explicit receiver view used for resolution (if any). */
|
||||
val receiverView: ReceiverView?
|
||||
}
|
||||
|
||||
/** Member field/property. */
|
||||
/** Member field/property (read-only). */
|
||||
interface MemberValHandle : MemberHandle, ValHandle
|
||||
|
||||
/** Member var/property with write access. */
|
||||
@ -76,41 +119,64 @@ interface MemberVarHandle : MemberHandle, VarHandle
|
||||
/** Member callable (method or extension). */
|
||||
interface MemberCallableHandle : MemberHandle, CallableHandle
|
||||
|
||||
/** Direct record handle (debug/inspection). */
|
||||
/**
|
||||
* Direct record handle (debug/inspection).
|
||||
*
|
||||
* Exposes raw [ObjRecord] access and should be used only in tooling.
|
||||
*/
|
||||
interface RecordHandle : BridgeHandle {
|
||||
/** Resolve and return the raw [ObjRecord]. */
|
||||
fun record(): ObjRecord
|
||||
}
|
||||
|
||||
/** Bridge resolver API (entry point for Kotlin bindings). */
|
||||
/**
|
||||
* Bridge resolver API (entry point for Kotlin reflection and bindings).
|
||||
*
|
||||
* Obtain via [ScopeFacade.resolver] and reuse for multiple lookups.
|
||||
* Resolver methods return handles that can be cached and reused across calls.
|
||||
*/
|
||||
interface BridgeResolver {
|
||||
/** Source position used for error reporting. */
|
||||
val pos: Pos
|
||||
|
||||
/** Treat `this` as [type] for member lookup (like `this@Type`). */
|
||||
fun selfAs(type: ObjClass): BridgeResolver
|
||||
/** Treat `this` as [typeName] for member lookup (like `this@Type`). */
|
||||
fun selfAs(typeName: String): BridgeResolver
|
||||
|
||||
/** Resolve a read-only value by name using [lookup]. */
|
||||
fun resolveVal(name: String, lookup: LookupSpec = LookupSpec()): ValHandle
|
||||
/** Resolve a mutable value by name using [lookup]. */
|
||||
fun resolveVar(name: String, lookup: LookupSpec = LookupSpec()): VarHandle
|
||||
/** Resolve a callable by name using [lookup]. */
|
||||
fun resolveCallable(name: String, lookup: LookupSpec = LookupSpec()): CallableHandle
|
||||
|
||||
/** Resolve a member value on [receiver]. */
|
||||
fun resolveMemberVal(
|
||||
receiver: Obj,
|
||||
name: String,
|
||||
lookup: LookupSpec = LookupSpec()
|
||||
): MemberValHandle
|
||||
|
||||
/** Resolve a mutable member on [receiver]. */
|
||||
fun resolveMemberVar(
|
||||
receiver: Obj,
|
||||
name: String,
|
||||
lookup: LookupSpec = LookupSpec()
|
||||
): MemberVarHandle
|
||||
|
||||
/** Resolve a member callable on [receiver]. */
|
||||
fun resolveMemberCallable(
|
||||
receiver: Obj,
|
||||
name: String,
|
||||
lookup: LookupSpec = LookupSpec()
|
||||
): MemberCallableHandle
|
||||
|
||||
/** Extension function treated as a member for reflection. */
|
||||
/**
|
||||
* Resolve an extension function treated as a member for reflection.
|
||||
*
|
||||
* This uses the extension wrapper name (same rules as Lyng compiler).
|
||||
*/
|
||||
fun resolveExtensionCallable(
|
||||
receiverClass: ObjClass,
|
||||
name: String,
|
||||
@ -119,14 +185,20 @@ interface BridgeResolver {
|
||||
|
||||
/** Debug: resolve locals by name (optional, for tooling). */
|
||||
fun resolveLocalVal(name: String): ValHandle
|
||||
/** Debug: resolve mutable locals by name (optional, for tooling). */
|
||||
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. */
|
||||
/**
|
||||
* Convenience: call by name with implicit caching in resolver implementation.
|
||||
*
|
||||
* Implemented by the default resolver; useful for lightweight call-by-name flows.
|
||||
*/
|
||||
interface BridgeCallByName {
|
||||
/** Resolve and call [name] with [args] using [lookup]. */
|
||||
suspend fun callByName(
|
||||
scope: ScopeFacade,
|
||||
name: String,
|
||||
@ -135,12 +207,21 @@ interface BridgeCallByName {
|
||||
): Obj
|
||||
}
|
||||
|
||||
/** Optional typed wrappers (sugar). */
|
||||
/**
|
||||
* Optional typed wrapper (sugar) around [ValHandle].
|
||||
*
|
||||
* Performs a runtime cast to [T] and raises a class cast error on mismatch.
|
||||
*/
|
||||
interface TypedHandle<T : Obj> : ValHandle {
|
||||
/** Read value and cast it to [T]. */
|
||||
suspend fun getTyped(scope: ScopeFacade): T
|
||||
}
|
||||
|
||||
/** Factory for bridge resolver. */
|
||||
/**
|
||||
* Factory for bridge resolver.
|
||||
*
|
||||
* Prefer this over ad-hoc lookups when writing Kotlin extensions or tooling.
|
||||
*/
|
||||
fun ScopeFacade.resolver(): BridgeResolver = BridgeResolverImpl(this)
|
||||
|
||||
private class BridgeResolverImpl(
|
||||
|
||||
@ -1,13 +1,28 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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.*
|
||||
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.ObjExternCallable
|
||||
@ -16,24 +31,41 @@ 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
|
||||
|
||||
/**
|
||||
* Per-instance bridge context passed to init hooks.
|
||||
*
|
||||
* Exposes the underlying [instance] and a mutable [data] slot for Kotlin-side state.
|
||||
*/
|
||||
interface BridgeInstanceContext {
|
||||
/** The Lyng instance being initialized. */
|
||||
val instance: Obj
|
||||
/** Arbitrary Kotlin-side data attached to the instance. */
|
||||
var data: Any?
|
||||
}
|
||||
|
||||
/**
|
||||
* Binder DSL for attaching Kotlin implementations to a declared Lyng class.
|
||||
*
|
||||
* Use [LyngClassBridge.bind] to obtain a binder and register implementations.
|
||||
* Bindings must happen before the first instance of the class is created.
|
||||
*
|
||||
* Important: members you bind here must be declared as `extern` in Lyng so the
|
||||
* compiler emits the ABI slots that Kotlin bindings attach to.
|
||||
*/
|
||||
interface ClassBridgeBinder {
|
||||
/** Arbitrary Kotlin-side data attached to the class. */
|
||||
var classData: Any?
|
||||
/** Register an initialization hook that runs for each instance. */
|
||||
fun init(block: suspend BridgeInstanceContext.(ScopeFacade) -> Unit)
|
||||
/** Register an initialization hook with direct access to the instance. */
|
||||
fun initWithInstance(block: suspend (ScopeFacade, Obj) -> Unit)
|
||||
/** Bind a Lyng function/member to a Kotlin implementation (requires `extern` in Lyng). */
|
||||
fun addFun(name: String, impl: suspend (ScopeFacade, Obj, Arguments) -> Obj)
|
||||
/** Bind a read-only member (val/property getter) declared as `extern`. */
|
||||
fun addVal(name: String, impl: suspend (ScopeFacade, Obj) -> Obj)
|
||||
/** Bind a mutable member (var/property getter/setter) declared as `extern`. */
|
||||
fun addVar(
|
||||
name: String,
|
||||
get: suspend (ScopeFacade, Obj) -> Obj,
|
||||
@ -41,7 +73,20 @@ interface ClassBridgeBinder {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for Kotlin bindings to declared Lyng classes.
|
||||
*
|
||||
* The workflow is Lyng-first: declare the class and its members in Lyng,
|
||||
* then bind the implementations from Kotlin. Bound members must be marked
|
||||
* `extern` so the compiler emits the ABI slots for Kotlin to attach to.
|
||||
*/
|
||||
object LyngClassBridge {
|
||||
/**
|
||||
* Resolve a Lyng class by [className] and bind Kotlin implementations.
|
||||
*
|
||||
* @param module module name used for resolution (required when [module] scope is not provided)
|
||||
* @param importManager import manager used to resolve the module
|
||||
*/
|
||||
suspend fun bind(
|
||||
className: String,
|
||||
module: String? = null,
|
||||
@ -52,6 +97,9 @@ object LyngClassBridge {
|
||||
return bind(cls, block)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a Lyng class within an existing [moduleScope] and bind Kotlin implementations.
|
||||
*/
|
||||
suspend fun bind(
|
||||
moduleScope: ModuleScope,
|
||||
className: String,
|
||||
@ -61,6 +109,11 @@ object LyngClassBridge {
|
||||
return bind(cls, block)
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind Kotlin implementations to an already resolved [clazz].
|
||||
*
|
||||
* This must run before the first instance is created.
|
||||
*/
|
||||
fun bind(clazz: ObjClass, block: ClassBridgeBinder.() -> Unit): ObjClass {
|
||||
val binder = ClassBridgeBinderImpl(clazz)
|
||||
binder.block()
|
||||
@ -69,10 +122,22 @@ object LyngClassBridge {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sugar for [LyngClassBridge.bind] on a module scope.
|
||||
*
|
||||
* Bound members must be declared as `extern` in Lyng.
|
||||
*/
|
||||
suspend fun ModuleScope.bind(
|
||||
className: String,
|
||||
block: ClassBridgeBinder.() -> Unit
|
||||
): ObjClass = LyngClassBridge.bind(this, className, block)
|
||||
|
||||
/** Kotlin-side data slot attached to a Lyng instance. */
|
||||
var ObjInstance.data: Any?
|
||||
get() = kotlinInstanceData
|
||||
set(value) { kotlinInstanceData = value }
|
||||
|
||||
/** Kotlin-side data slot attached to a Lyng class. */
|
||||
var ObjClass.classData: Any?
|
||||
get() = kotlinClassData
|
||||
set(value) { kotlinClassData = value }
|
||||
|
||||
59
lynglib/src/commonTest/kotlin/ClosedClassTest.kt
Normal file
59
lynglib/src/commonTest/kotlin/ClosedClassTest.kt
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.Script
|
||||
import net.sergeych.lyng.ScriptError
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class ClosedClassTest {
|
||||
@Test
|
||||
fun testClosedClass() = runTest {
|
||||
val scope = Script.newScope()
|
||||
scope.eval("""
|
||||
closed class MyClosedClass {
|
||||
fun foo() = 42
|
||||
}
|
||||
""".trimIndent())
|
||||
|
||||
assertFailsWith<ScriptError> {
|
||||
scope.eval("""
|
||||
class SubClass : MyClosedClass()
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStdlibClosedClasses() = runTest {
|
||||
val scope = Script.newScope()
|
||||
|
||||
assertFailsWith<ScriptError> {
|
||||
scope.eval("class MyInt : Int()")
|
||||
}
|
||||
assertFailsWith<ScriptError> {
|
||||
scope.eval("class MyReal : Real()")
|
||||
}
|
||||
assertFailsWith<ScriptError> {
|
||||
scope.eval("class MyString : String()")
|
||||
}
|
||||
assertFailsWith<ScriptError> {
|
||||
scope.eval("class MyBool : Bool()")
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user