nativeMain.com.dokar.quickjs.bridge.defineObject.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.bridge
import com.dokar.quickjs.QuickJs
import com.dokar.quickjs.binding.JsProperty
import com.dokar.quickjs.binding.ObjectBinding
import com.dokar.quickjs.util.allocArrayOf
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.CValue
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.StableRef
import kotlinx.cinterop.asStableRef
import kotlinx.cinterop.cstr
import kotlinx.cinterop.get
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.readValue
import kotlinx.cinterop.staticCFunction
import kotlinx.cinterop.toCPointer
import kotlinx.cinterop.toLong
import platform.posix.int64_tVar
import quickjs.JSContext
import quickjs.JSValue
import quickjs.JS_DefinePropertyGetSet
import quickjs.JS_DefinePropertyValueStr
import quickjs.JS_FreeAtom
import quickjs.JS_FreeValue
import quickjs.JS_GetGlobalObject
import quickjs.JS_NewAtom
import quickjs.JS_NewCFunctionData
import quickjs.JS_NewInt64
import quickjs.JS_NewObject
import quickjs.JS_NewString
import quickjs.JS_PROP_CONFIGURABLE
import quickjs.JS_PROP_C_W_E
import quickjs.JS_PROP_ENUMERABLE
import quickjs.JS_PROP_WRITABLE
import quickjs.JS_Throw
import quickjs.JsException
import quickjs.JsUndefined
@OptIn(ExperimentalForeignApi::class)
internal fun CPointer.defineObject(
quickJsRef: StableRef,
parentHandle: Long?,
name: String,
binding: ObjectBinding
): Long {
val instance = JS_NewObject(this)
val handle = jsValueToObjectHandle(instance)
val properties = binding.properties
for (prop in properties) {
defineProperty(
quickJsRef = quickJsRef,
instance = instance,
handle = handle,
property = prop,
)
}
val functions = binding.functions
for (func in functions) {
defineFunction(
quickJsRef = quickJsRef,
parent = instance,
parentHandle = handle,
name = func.name,
isAsync = func.isAsync,
)
}
val parent = parentHandle?.let { objectHandleToStableRef(it) }?.get()
if (parent == null) {
val globalThis = JS_GetGlobalObject(this)
JS_DefinePropertyValueStr(this, globalThis, name, instance, JS_PROP_C_W_E)
JS_FreeValue(this, globalThis)
} else {
JS_DefinePropertyValueStr(this, parent, name, instance, JS_PROP_C_W_E)
}
return handle
}
@OptIn(ExperimentalForeignApi::class)
internal fun CPointer.defineProperty(
quickJsRef: StableRef,
instance: CValue,
handle: Long,
property: JsProperty,
) = memScoped {
val context = this@defineProperty
val quickJs = quickJsRef.get()
val qjsVoidPtr = quickJsRef.asCPointer()
val qjsPtrAddress = qjsVoidPtr.toLong()
val funcDataArray = arrayOf(
JS_NewString(context, property.name.cstr),
JS_NewInt64(context, qjsPtrAddress),
JS_NewInt64(context, handle),
)
// Free function data when closing
quickJs.addManagedJsValues(*funcDataArray)
val getter = JS_NewCFunctionData(
ctx = context,
func = staticCFunction(::invokeGetter),
length = 0,
magic = 0,
data_len = 3,
data = allocArrayOf(*funcDataArray),
)
val setter = if (property.writable) {
JS_NewCFunctionData(
ctx = context,
func = staticCFunction(::invokeSetter),
length = 0,
magic = 0,
data_len = 3,
data = allocArrayOf(*funcDataArray),
)
} else {
JsUndefined()
}
var flags = JS_PROP_WRITABLE.inv()
if (!property.configurable) {
flags = flags and JS_PROP_CONFIGURABLE.inv()
}
if (!property.writable) {
flags = flags and JS_PROP_WRITABLE.inv()
}
if (!property.enumerable) {
flags = flags and JS_PROP_ENUMERABLE.inv()
}
val prop = JS_NewAtom(context, property.name)
JS_DefinePropertyGetSet(
ctx = context,
this_obj = instance,
prop = prop,
getter = getter,
setter = setter,
flags = flags,
)
JS_FreeAtom(context, prop)
}
@OptIn(ExperimentalForeignApi::class)
@Suppress("unused_parameter")
private fun invokeGetter(
ctx: CPointer?,
thisVal: CValue,
argc: Int,
argv: CPointer?,
magic: Int,
funcData: CPointer?,
): CValue = memScoped {
ctx ?: return@memScoped JsException()
funcData ?: return@memScoped JsException()
val (propName, quickJs, objectHandle) = BindingFunctionData.fromJsValues(ctx, funcData)
try {
quickJs
.onCallBindingGetter(parentHandle = objectHandle, name = propName)
.toJsValue(context = ctx)
} catch (e: Throwable) {
JS_Throw(ctx, ktErrorToJsError(ctx, e))
JsException()
}
}
@OptIn(ExperimentalForeignApi::class)
@Suppress("unused_parameter")
private fun invokeSetter(
ctx: CPointer?,
thisVal: CValue,
argc: Int,
argv: CPointer?,
magic: Int,
funcData: CPointer?,
): CValue = memScoped {
ctx ?: return@memScoped JsException()
funcData ?: return@memScoped JsException()
if (argc <= 0) {
return@memScoped JsException()
}
val (propName, quickJs, objectHandle) = BindingFunctionData.fromJsValues(ctx, funcData)
val value = argv!![0].readValue().toKtValue(ctx)
try {
quickJs.onCallBindingSetter(
parentHandle = objectHandle,
name = propName,
value = value,
)
JsUndefined()
} catch (e: Throwable) {
JS_Throw(ctx, ktErrorToJsError(ctx, e))
JsException()
}
}
@OptIn(ExperimentalForeignApi::class)
internal fun objectHandleToStableRef(handle: Long): StableRef>? {
val voidPtr = handle.toCPointer() ?: return null
return voidPtr.asStableRef>()
}
@OptIn(ExperimentalForeignApi::class)
private fun jsValueToObjectHandle(value: CValue): Long {
val ref = StableRef.create(value)
return ref.asCPointer().toLong()
}