All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nativeMain.com.dokar.quickjs.bridge.defineObject.kt Maven / Gradle / Ivy

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()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy