commonMain.com.dokar.quickjs.binding.DslObjectBinding.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of quickjs-kt Show documentation
Show all versions of quickjs-kt Show documentation
A QuickJS binding for idiomatic Kotlin, with Async/DSL/ES Modules support.
The newest version!
package com.dokar.quickjs.binding
import com.dokar.quickjs.QuickJs
import com.dokar.quickjs.converter.TypeConverters
import com.dokar.quickjs.converter.canConvertReturnInternally
import com.dokar.quickjs.converter.typeOfInstance
import com.dokar.quickjs.qjsError
import kotlin.reflect.typeOf
internal class DslObjectBinding(
private val scope: ObjectBindingScopeImpl,
private val quickJs: QuickJs,
) : ObjectBinding {
private val propertiesDef = scope.properties.associateBy { it.name }
private val functionsDef = scope.functions.associateBy { it.name }
override val properties: List = scope.properties.toJsProperties()
override val functions: List = scope.functions.toJsFunctions()
override fun getter(name: String): Any? {
val prop = propertiesDef[name]
?: qjsError("Property '$name' not found on object '${scope.name}'")
val propGetter = prop.getter ?: qjsError("The getter of property '$name' is null")
return propGetter()
}
override fun setter(name: String, value: Any?) {
val prop = propertiesDef[name]
?: qjsError("Property '$name' not found on object '${scope.name}'")
val propSetter = prop.setter ?: qjsError("The setter of property '$name' is null")
propSetter(value)
}
override fun invoke(name: String, args: Array): Any? {
val func = functionsDef[name]
?: qjsError("Function '$name' not found on object '${scope.name}'")
val result = when (val call = func.call) {
is AsyncFunctionBinding<*> -> quickJs.invokeAsyncFunction(args) { call.invoke(it) }
is FunctionBinding<*> -> call.invoke(args)
is ObjectBinding -> qjsError("Object cannot be invoked!")
}
if (canConvertReturnInternally(result)) {
return result
}
// Convert result to JsObject
val typeConverters = quickJs.typeConverters
return typeConverters.convert(
source = result,
sourceType = typeOfInstance(typeConverters, result),
targetType = typeOf(),
)
}
}
private fun List>.toJsProperties(): List = map {
JsProperty(
name = it.name,
configurable = it.configurable,
writable = it.writable ?: (it.setter != null),
enumerable = it.enumerable,
)
}
private fun List.toJsFunctions(): List = map {
JsFunction(
name = it.name,
isAsync = it.call is AsyncFunctionBinding<*>,
)
}
@PublishedApi
internal class ObjectBindingScopeImpl(
val typeConverters: TypeConverters,
val name: String,
) : ObjectBindingScope {
val properties = mutableListOf>()
val functions = mutableListOf()
val subScopes = mutableListOf()
override fun define(name: String, block: ObjectBindingScope.() -> Unit) {
subScopes.add(
ObjectBindingScopeImpl(
typeConverters = typeConverters,
name = name,
).also(block)
)
}
override fun property(name: String, block: PropertyScope.() -> Unit) {
val prop = DslProperty(name = name).also(block)
if (prop.getter == null) {
qjsError("property($name) requires a getter {}.")
}
properties.add(prop)
}
override fun function(name: String, block: FunctionBinding) {
functions.add(DslFunction(name = name, call = block))
}
override fun asyncFunction(name: String, block: AsyncFunctionBinding) {
functions.add(DslFunction(name = name, call = block))
}
}
internal class DslProperty(
val name: String,
override var configurable: Boolean = true,
override var writable: Boolean? = null,
override var enumerable: Boolean = true,
) : PropertyScope {
var getter: (() -> Any?)? = null
var setter: ((Any?) -> Unit)? = null
override fun getter(block: () -> T) {
this.getter = { block() as Any }
}
@Suppress("unchecked_cast")
override fun setter(block: (value: T) -> Unit) {
this.setter = { block(it as T) }
}
}
internal class DslFunction(
val name: String,
val call: Binding,
)