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