1use crate::jsutils::{JsError, JsValueType};
4use crate::quickjs_utils::typedarrays::is_typed_array;
5use crate::quickjs_utils::{arrays, errors, functions, primitives, promises};
6use crate::reflection::is_proxy_instance;
7use libquickjs_sys as q;
8use std::hash::{Hash, Hasher};
9use std::ptr::null_mut;
10
11#[allow(clippy::upper_case_acronyms)]
12pub struct QuickJsValueAdapter {
13 pub(crate) context: *mut q::JSContext,
14 value: q::JSValue,
15 ref_ct_decr_on_drop: bool,
16 label: String,
17}
18
19impl Hash for QuickJsValueAdapter {
20 fn hash<H: Hasher>(&self, state: &mut H) {
21 let self_u = self.value.u;
22 if self.is_i32() || self.is_bool() {
23 #[cfg(feature = "bellard")]
24 unsafe {
25 self_u.uint64.hash(state)
26 };
27 #[cfg(feature = "quickjs-ng")]
28 unsafe {
29 self_u.int32.hash(state)
30 };
31 } else if self.is_f64() {
32 unsafe { (self_u.float64 as i32).hash(state) };
33 } else {
34 unsafe { self_u.ptr.hash(state) };
35 }
36 }
37}
38
39impl PartialEq for QuickJsValueAdapter {
40 fn eq(&self, other: &Self) -> bool {
41 if self.get_tag() != other.get_tag() {
42 false
43 } else {
44 let self_u = self.value.u;
45 let other_u = other.value.u;
46 unsafe {
47 #[cfg(feature = "bellard")]
48 return self_u.uint64 == other_u.uint64
49 && self_u.float64 == other_u.float64
50 && self_u.ptr == other_u.ptr;
51 #[cfg(feature = "quickjs-ng")]
52 return self_u.int32 == other_u.int32
53 && self_u.float64 == other_u.float64
54 && self_u.ptr == other_u.ptr;
55 }
56 }
57 }
58}
59
60impl Eq for QuickJsValueAdapter {}
61
62impl QuickJsValueAdapter {
63 #[allow(dead_code)]
64 pub(crate) fn label(&mut self, label: &str) {
65 self.label = label.to_string()
66 }
67}
68
69impl Clone for QuickJsValueAdapter {
70 fn clone(&self) -> Self {
71 Self::new(
72 self.context,
73 self.value,
74 true,
75 true,
76 format!("clone of {}", self.label).as_str(),
77 )
78 }
79}
80
81impl Drop for QuickJsValueAdapter {
82 fn drop(&mut self) {
83 if self.value.tag < 0 {
92 if self.ref_ct_decr_on_drop {
96 #[cfg(feature = "bellard")]
97 if self.get_ref_count() <= 0 {
98 log::error!(
99 "dropping ref while refcount already 0, which is bad mmkay.. {}",
100 self.label
101 );
102 panic!(
103 "dropping ref while refcount already 0, which is bad mmkay.. {}",
104 self.label
105 );
106 }
107 self.decrement_ref_count();
108 }
109 }
110 }
112}
113
114impl std::fmt::Debug for QuickJsValueAdapter {
115 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
116 match self.value.tag {
117 TAG_EXCEPTION => write!(f, "Exception(?)"),
118 TAG_NULL => write!(f, "NULL"),
119 TAG_UNDEFINED => write!(f, "UNDEFINED"),
120 TAG_BOOL => write!(f, "Bool(?)",),
121 TAG_INT => write!(f, "Int(?)"),
122 TAG_FLOAT64 => write!(f, "Float(?)"),
123 TAG_STRING => write!(f, "String(?)"),
124 #[cfg(feature = "bellard")]
125 TAG_STRING_ROPE => write!(f, "String(?)"),
126 TAG_OBJECT => write!(f, "Object(?)"),
127 TAG_MODULE => write!(f, "Module(?)"),
128 TAG_BIG_INT => write!(f, "BigInt(?)"),
129 _ => write!(f, "Unknown Tag(?)"),
130 }
131 }
132}
133
134impl QuickJsValueAdapter {
135 pub(crate) fn increment_ref_count(&self) {
136 if self.get_tag() < 0 {
137 unsafe {
138 #[allow(clippy::let_unit_value)]
139 let _ = libquickjs_sys::JS_DupValue(self.context, *self.borrow_value());
140 }
141 }
142 }
143
144 pub(crate) fn decrement_ref_count(&self) {
145 if self.get_tag() < 0 {
146 unsafe { libquickjs_sys::JS_FreeValue(self.context, *self.borrow_value()) }
147 }
148 }
149
150 pub fn get_tag(&self) -> i64 {
151 self.value.tag
152 }
153
154 pub fn new_no_context(value: q::JSValue, label: &str) -> Self {
155 Self {
156 context: null_mut(),
157 value,
158 ref_ct_decr_on_drop: false,
159 label: label.to_string(),
160 }
161 }
162
163 pub fn new(
164 context: *mut q::JSContext,
165 value: q::JSValue,
166 ref_ct_incr: bool,
167 ref_ct_decr_on_drop: bool,
168 label: &str,
169 ) -> Self {
170 debug_assert!(!label.is_empty());
171
172 let s = Self {
173 context,
174 value,
175 ref_ct_decr_on_drop,
176 label: label.to_string(),
177 };
178 if ref_ct_incr {
179 s.increment_ref_count();
180 }
181 s
182 }
183
184 #[cfg(feature = "bellard")]
185 pub fn get_ref_count(&self) -> i32 {
186 if self.get_tag() < 0 {
187 unsafe { q::JS_ValueGetRefCount(*self.borrow_value()) }
188 } else {
189 -1
190 }
191 }
192
193 pub fn clone_value_incr_rc(&self) -> q::JSValue {
195 self.increment_ref_count();
196 self.value
197 }
198
199 pub fn borrow_value(&self) -> &q::JSValue {
200 &self.value
201 }
202
203 pub fn borrow_value_mut(&mut self) -> &mut q::JSValue {
204 &mut self.value
205 }
206
207 pub fn is_null_or_undefined(&self) -> bool {
208 self.is_null() || self.is_undefined()
209 }
210
211 pub fn is_undefined(&self) -> bool {
213 unsafe { q::JS_IsUndefined(self.value) }
214 }
215
216 pub fn is_null(&self) -> bool {
218 unsafe { q::JS_IsNull(self.value) }
219 }
220
221 pub fn is_bool(&self) -> bool {
223 unsafe { q::JS_IsBool(self.value) }
224 }
225
226 pub fn is_i32(&self) -> bool {
228 self.borrow_value().tag == TAG_INT
231 }
232
233 pub fn is_module(&self) -> bool {
235 self.borrow_value().tag == TAG_MODULE
236 }
237
238 pub fn is_compiled_function(&self) -> bool {
240 self.borrow_value().tag == TAG_FUNCTION_BYTECODE
241 }
242
243 pub fn is_f64(&self) -> bool {
245 self.borrow_value().tag == TAG_FLOAT64
246 }
247
248 pub fn is_big_int(&self) -> bool {
249 self.borrow_value().tag == TAG_BIG_INT || self.borrow_value().tag == TAG_SHORT_BIG_INT
251 }
252
253 pub fn is_exception(&self) -> bool {
255 unsafe { q::JS_IsException(self.value) }
256 }
257
258 pub fn is_object(&self) -> bool {
260 unsafe { q::JS_IsObject(self.value) }
261 }
262
263 pub fn is_string(&self) -> bool {
265 unsafe { q::JS_IsString(self.value) }
266 }
267}
268
269pub(crate) const TAG_BIG_INT: i64 = libquickjs_sys::JS_TAG_BIG_INT as i64;
270pub(crate) const TAG_SHORT_BIG_INT: i64 = libquickjs_sys::JS_TAG_SHORT_BIG_INT as i64;
271
272pub(crate) const TAG_STRING: i64 = libquickjs_sys::JS_TAG_STRING as i64;
273
274pub(crate) const TAG_STRING_ROPE: i64 = libquickjs_sys::JS_TAG_STRING_ROPE as i64;
275
276pub(crate) const TAG_MODULE: i64 = libquickjs_sys::JS_TAG_MODULE as i64;
277pub(crate) const TAG_FUNCTION_BYTECODE: i64 = libquickjs_sys::JS_TAG_FUNCTION_BYTECODE as i64;
278pub(crate) const TAG_OBJECT: i64 = libquickjs_sys::JS_TAG_OBJECT as i64;
279pub(crate) const TAG_INT: i64 = libquickjs_sys::JS_TAG_INT as i64;
280pub(crate) const TAG_BOOL: i64 = libquickjs_sys::JS_TAG_BOOL as i64;
281pub(crate) const TAG_NULL: i64 = libquickjs_sys::JS_TAG_NULL as i64;
282pub(crate) const TAG_UNDEFINED: i64 = libquickjs_sys::JS_TAG_UNDEFINED as i64;
283pub(crate) const TAG_EXCEPTION: i64 = libquickjs_sys::JS_TAG_EXCEPTION as i64;
284pub(crate) const TAG_FLOAT64: i64 = libquickjs_sys::JS_TAG_FLOAT64 as i64;
285
286impl QuickJsValueAdapter {
287 pub fn is_function(&self) -> bool {
288 self.is_object() && self.get_js_type() == JsValueType::Function
289 }
290 pub fn is_array(&self) -> bool {
291 self.is_object() && self.get_js_type() == JsValueType::Array
292 }
293 pub fn is_error(&self) -> bool {
294 self.is_object() && self.get_js_type() == JsValueType::Error
295 }
296 pub fn is_promise(&self) -> bool {
297 self.is_object() && self.get_js_type() == JsValueType::Promise
298 }
299
300 pub fn get_js_type(&self) -> JsValueType {
301 match self.get_tag() {
302 TAG_EXCEPTION => JsValueType::Error,
303 TAG_NULL => JsValueType::Null,
304 TAG_UNDEFINED => JsValueType::Undefined,
305 TAG_BOOL => JsValueType::Boolean,
306 TAG_INT => JsValueType::I32,
307 TAG_FLOAT64 => JsValueType::F64,
308 TAG_STRING => JsValueType::String,
309 TAG_STRING_ROPE => JsValueType::String,
310 TAG_OBJECT => {
311 if unsafe { functions::is_function(self.context, self) } {
314 JsValueType::Function
315 } else if unsafe { errors::is_error(self.context, self) } {
316 JsValueType::Error
317 } else if unsafe { arrays::is_array(self.context, self) } {
318 JsValueType::Array
319 } else if unsafe { promises::is_promise(self.context, self) } {
320 JsValueType::Promise
321 } else {
322 JsValueType::Object
323 }
324 }
325 TAG_BIG_INT | TAG_SHORT_BIG_INT => JsValueType::BigInt,
326 TAG_MODULE => todo!(),
327 _ => JsValueType::Undefined,
328 }
329 }
330
331 pub fn is_typed_array(&self) -> bool {
332 self.is_object() && unsafe { is_typed_array(self.context, self) }
333 }
334
335 pub fn is_proxy_instance(&self) -> bool {
336 self.is_object() && unsafe { is_proxy_instance(self.context, self) }
337 }
338
339 pub fn type_of(&self) -> &'static str {
340 match self.get_tag() {
341 TAG_BIG_INT => "bigint",
342 TAG_STRING => "string",
343 TAG_STRING_ROPE => "string",
344 TAG_MODULE => "module",
345 TAG_FUNCTION_BYTECODE => "function",
346 TAG_OBJECT => {
347 if self.get_js_type() == JsValueType::Function {
348 "function"
349 } else {
350 "object"
351 }
352 }
353 TAG_INT => "number",
354 TAG_BOOL => "boolean",
355 TAG_NULL => "object",
356 TAG_UNDEFINED => "undefined",
357 TAG_EXCEPTION => "object",
358 TAG_FLOAT64 => "number",
359 _ => "unknown",
360 }
361 }
362
363 pub fn to_bool(&self) -> bool {
364 if self.get_js_type() == JsValueType::Boolean {
365 primitives::to_bool(self).expect("could not convert bool to bool")
366 } else {
367 panic!("not a boolean");
368 }
369 }
370
371 pub fn to_i32(&self) -> i32 {
372 if self.get_js_type() == JsValueType::I32 {
373 primitives::to_i32(self).expect("could not convert to i32")
374 } else {
375 panic!("not an i32");
376 }
377 }
378
379 pub fn to_f64(&self) -> f64 {
380 if self.get_js_type() == JsValueType::F64 {
381 primitives::to_f64(self).expect("could not convert to f64")
382 } else {
383 panic!("not a f64");
384 }
385 }
386
387 pub fn to_string(&self) -> Result<String, JsError> {
388 match self.get_js_type() {
389 JsValueType::I32 => Ok(self.to_i32().to_string()),
390 JsValueType::F64 => Ok(self.to_f64().to_string()),
391 JsValueType::String => unsafe { primitives::to_string(self.context, self) },
392 JsValueType::Boolean => {
393 if self.to_bool() {
394 Ok("true".to_string())
395 } else {
396 Ok("false".to_string())
397 }
398 }
399 JsValueType::Error => {
400 let js_error = unsafe { errors::error_to_js_error(self.context, self) };
401 Ok(format!("{js_error}"))
402 }
403 _ => unsafe { functions::call_to_string(self.context, self) },
404 }
405 }
406}
407
408#[cfg(test)]
409pub mod tests {
410 use crate::facades::tests::init_test_rt;
411 use crate::jsutils::{JsValueType, Script};
412
413 #[test]
414 fn test_to_str() {
415 let rt = init_test_rt();
416 rt.exe_rt_task_in_event_loop(|q_js_rt| {
417 let q_ctx = q_js_rt.get_main_realm();
418 let res = q_ctx.eval(Script::new("test_to_str.es", "('hello ' + 'world');"));
419
420 match res {
421 Ok(res) => {
422 log::info!("script ran ok: {:?}", res);
423 assert!(res.get_js_type() == JsValueType::String);
424 assert_eq!(res.to_string().expect("str conv failed"), "hello world");
425 }
426 Err(e) => {
427 log::error!("script failed: {}", e);
428 panic!("script failed");
429 }
430 }
431 });
432 }
433}