use crate::runtime::{QJSRuntimeInner, runtime_guard_from_ctx};
use crate::{QJSContext, qjs};
use rong_core::{
JSContextImpl, JSRawContext, JSTypeOf, JSValueImpl, RongJSError, impl_js_converter,
};
use std::ffi::CString;
use std::hash::Hash;
use std::rc::Rc;
use std::slice;
mod array;
mod array_buffer;
mod object;
mod proxy;
mod typed_array;
mod valuetype;
pub struct QJSValue {
value: qjs::JSValue,
ctx: *mut qjs::JSContext,
rt_guard: Option<Rc<QJSRuntimeInner>>,
value_type: QJSValueType,
}
unsafe fn dup_value(
ctx: *mut qjs::JSContext,
rt_guard: &Option<Rc<QJSRuntimeInner>>,
value: qjs::JSValue,
) -> qjs::JSValue {
if let Some(rt) = rt_guard {
unsafe { qjs::JS_DupValueRT(rt.rt, value) }
} else if !ctx.is_null() {
unsafe { qjs::JS_DupValue(ctx, value) }
} else {
value
}
}
unsafe fn free_value(
ctx: *mut qjs::JSContext,
rt_guard: &Option<Rc<QJSRuntimeInner>>,
value: qjs::JSValue,
) {
if let Some(rt) = rt_guard {
unsafe { qjs::JS_FreeValueRT(rt.rt, value) }
} else if !ctx.is_null() {
unsafe { qjs::JS_FreeValue(ctx, value) }
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub(crate) enum QJSValueType {
Error,
Exception,
Other,
}
impl PartialEq for QJSValue {
fn eq(&self, other: &Self) -> bool {
if self.ctx != other.ctx {
return false;
}
unsafe {
qjs::QJS_ValueIdentTag(self.value) == qjs::QJS_ValueIdentTag(other.value)
&& qjs::QJS_ValueIdentPayload(self.value) == qjs::QJS_ValueIdentPayload(other.value)
}
}
}
impl Hash for QJSValue {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
unsafe {
qjs::QJS_ValueIdentTag(self.value).hash(state);
qjs::QJS_ValueIdentPayload(self.value).hash(state);
}
self.ctx.hash(state);
}
}
impl Clone for QJSValue {
fn clone(&self) -> Self {
let value = unsafe { dup_value(self.ctx, &self.rt_guard, self.value) };
Self {
value,
ctx: self.ctx,
rt_guard: self.rt_guard.clone(),
value_type: self.value_type,
}
}
}
impl Drop for QJSValue {
fn drop(&mut self) {
unsafe {
free_value(self.ctx, &self.rt_guard, self.value);
}
}
}
impl JSRawContext for QJSValue {
type RawContext = *mut qjs::JSContext;
}
impl QJSValue {
fn _from_raw(ctx: *mut qjs::JSContext, value: qjs::JSValue) -> Self {
let rt_guard = runtime_guard_from_ctx(ctx);
let value = unsafe { dup_value(ctx, &rt_guard, value) };
Self {
value,
ctx,
rt_guard,
value_type: QJSValueType::Other,
}
}
pub(crate) fn new(ctx: *mut qjs::JSContext, value: qjs::JSValue) -> Self {
let rt_guard = runtime_guard_from_ctx(ctx);
Self {
value,
ctx,
rt_guard,
value_type: QJSValueType::Other,
}
}
pub(crate) fn protect(mut self) -> Self {
self.value = unsafe { dup_value(self.ctx, &self.rt_guard, self.value) };
self
}
pub(crate) fn with_exception(mut self) -> Self {
self.value_type = QJSValueType::Exception;
self
}
pub(crate) fn with_error(mut self) -> Self {
self.value_type = QJSValueType::Error;
self
}
pub(crate) fn _is_err(&self) -> bool {
self.value_type == QJSValueType::Error
}
pub(crate) fn _is_exception(&self) -> bool {
self.value_type == QJSValueType::Exception
}
}
impl JSValueImpl for QJSValue {
type RawValue = qjs::JSValue;
type Context = QJSContext;
fn from_borrowed_raw(
ctx: <Self::Context as JSContextImpl>::RawContext,
value: Self::RawValue,
) -> Self {
QJSValue::new(ctx, value).protect()
}
fn from_owned_raw(
ctx: <Self::Context as JSContextImpl>::RawContext,
value: Self::RawValue,
) -> Self {
QJSValue::new(ctx, value)
}
fn into_raw_value(self) -> Self::RawValue {
let value = self.value;
std::mem::forget(self); value
}
fn as_raw_value(&self) -> &Self::RawValue {
&self.value
}
fn as_raw_context(&self) -> &<Self::Context as JSContextImpl>::RawContext {
&self.ctx
}
fn create_null(ctx: &Self::Context) -> Self {
let ctx = ctx.to_raw();
let raw = unsafe { qjs::QJS_NewNull(ctx) };
Self::from_owned_raw(ctx, raw)
}
fn create_undefined(ctx: &Self::Context) -> Self {
let ctx = ctx.to_raw();
let raw = unsafe { qjs::QJS_NewUndefined(ctx) };
Self::from_owned_raw(ctx, raw)
}
fn from_json_str(ctx: &Self::Context, str: &str) -> Self {
let c_str = std::ffi::CString::new(str).unwrap();
let raw =
unsafe { qjs::JS_ParseJSON(ctx.to_raw(), c_str.as_ptr(), str.len(), c"JSON".as_ptr()) };
ctx.to_owned_value(raw)
}
fn create_symbol(ctx: &Self::Context, description: &str) -> Self {
let description = CString::new(description).unwrap();
let raw = unsafe { qjs::JS_NewSymbol(ctx.to_raw(), description.as_ptr(), false) };
ctx.to_owned_value(raw)
}
fn create_date(ctx: &Self::Context, epoch_ms: f64) -> Self {
let raw = unsafe { qjs::JS_NewDate(ctx.to_raw(), epoch_ms) };
ctx.to_owned_value(raw)
}
}
impl QJSValue {
#[allow(dead_code)]
pub fn get_ref_count(value: qjs::JSValue) -> i32 {
unsafe { qjs::QJS_GetRefCount(value) }
}
}
impl_js_converter!(
QJSValue,
bool,
|ctx, value| qjs::QJS_NewBool(ctx, value),
|ctx, value, result: *mut bool| {
if qjs::QJS_IsBool(ctx, value) {
let status = qjs::JS_ToBool(ctx, value);
*result = status != 0;
0
} else {
-1
}
}
);
impl_js_converter!(
QJSValue,
i32,
|ctx, value| { qjs::QJS_NewInt32(ctx, value) },
|ctx, value, result| {
if !qjs::QJS_IsNumber(ctx, value) {
return -1;
}
qjs::JS_ToInt32(ctx, result, value)
}
);
impl_js_converter!(
QJSValue,
u32,
|ctx, value| { qjs::QJS_NewUint32(ctx, value) },
|ctx, value, result| {
if !qjs::QJS_IsNumber(ctx, value) {
return -1;
}
qjs::QJS_ToUint32(ctx, result, value)
}
);
impl_js_converter!(
QJSValue,
i64,
|ctx, value| {
const JS_MAX_SAFE_INTEGER: i64 = (1i64 << 53) - 1;
const JS_MIN_SAFE_INTEGER: i64 = -JS_MAX_SAFE_INTEGER;
if (JS_MIN_SAFE_INTEGER..=JS_MAX_SAFE_INTEGER).contains(&value) {
qjs::QJS_NewFloat64(ctx, value as f64)
} else {
qjs::JS_NewBigInt64(ctx, value)
}
},
|ctx, value, result: &mut i64| {
if qjs::QJS_IsBigInt(ctx, value) {
qjs::JS_ToBigInt64(ctx, result, value)
} else if qjs::QJS_IsNumber(ctx, value) {
let mut temp: f64 = 0.0;
let ret = qjs::JS_ToFloat64(ctx, &mut temp, value);
*result = temp as i64;
ret
} else {
-1
}
}
);
impl_js_converter!(
QJSValue,
u64,
|ctx, value| {
const JS_MAX_SAFE_INTEGER: u64 = (1u64 << 53) - 1;
if value <= JS_MAX_SAFE_INTEGER {
qjs::QJS_NewFloat64(ctx, value as f64)
} else {
qjs::JS_NewBigUint64(ctx, value)
}
},
|ctx, value, result| {
if qjs::QJS_IsBigInt(ctx, value) {
qjs::JS_ToBigUint64(ctx, result, value)
} else if qjs::QJS_IsNumber(ctx, value) {
let mut temp: f64 = 0.0;
let ret = qjs::JS_ToFloat64(ctx, &mut temp, value);
if temp >= 0.0 {
*result = temp as u64;
ret
} else {
-1 }
} else {
-1
}
}
);
impl_js_converter!(
QJSValue,
f64,
|ctx, value| qjs::QJS_NewFloat64(ctx, value),
|ctx, value, result| {
if !qjs::QJS_IsNumber(ctx, value) {
return -1;
}
qjs::JS_ToFloat64(ctx, result, value)
}
);
impl_js_converter!(
QJSValue,
&str,
String,
|ctx, value: &str| {
let len = value.len();
qjs::JS_NewStringLen(ctx, value.as_ptr() as _, len as _)
},
|ctx, value, result: *mut String| {
let mut len: usize = 0;
let ptr = qjs::JS_ToCStringLen2(ctx, &mut len as _, value, false);
if ptr.is_null() {
return -1;
}
let slice = slice::from_raw_parts(ptr as *const u8, len);
*result = String::from_utf8_lossy(slice).into_owned();
qjs::JS_FreeCString(ctx, ptr);
0
}
);