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
266#[cfg(feature = "bellard")]
267pub(crate) const TAG_STRING_ROPE: i64 = libquickjs_sys::JS_TAG_STRING_ROPE as i64;
268
269pub(crate) const TAG_MODULE: i64 = libquickjs_sys::JS_TAG_MODULE as i64;
270pub(crate) const TAG_FUNCTION_BYTECODE: i64 = libquickjs_sys::JS_TAG_FUNCTION_BYTECODE as i64;
271pub(crate) const TAG_OBJECT: i64 = libquickjs_sys::JS_TAG_OBJECT as i64;
272pub(crate) const TAG_INT: i64 = libquickjs_sys::JS_TAG_INT as i64;
273pub(crate) const TAG_BOOL: i64 = libquickjs_sys::JS_TAG_BOOL as i64;
274pub(crate) const TAG_NULL: i64 = libquickjs_sys::JS_TAG_NULL as i64;
275pub(crate) const TAG_UNDEFINED: i64 = libquickjs_sys::JS_TAG_UNDEFINED as i64;
276pub(crate) const TAG_EXCEPTION: i64 = libquickjs_sys::JS_TAG_EXCEPTION as i64;
277pub(crate) const TAG_FLOAT64: i64 = libquickjs_sys::JS_TAG_FLOAT64 as i64;
278
279impl QuickJsValueAdapter {
280 pub fn is_function(&self) -> bool {
281 self.is_object() && self.get_js_type() == JsValueType::Function
282 }
283 pub fn is_array(&self) -> bool {
284 self.is_object() && self.get_js_type() == JsValueType::Array
285 }
286 pub fn is_error(&self) -> bool {
287 self.is_object() && self.get_js_type() == JsValueType::Error
288 }
289 pub fn is_promise(&self) -> bool {
290 self.is_object() && self.get_js_type() == JsValueType::Promise
291 }
292
293 pub fn get_js_type(&self) -> JsValueType {
294 match self.get_tag() {
295 TAG_EXCEPTION => JsValueType::Error,
296 TAG_NULL => JsValueType::Null,
297 TAG_UNDEFINED => JsValueType::Undefined,
298 TAG_BOOL => JsValueType::Boolean,
299 TAG_INT => JsValueType::I32,
300 TAG_FLOAT64 => JsValueType::F64,
301 TAG_STRING => JsValueType::String,
302 #[cfg(feature = "bellard")]
303 TAG_STRING_ROPE => JsValueType::String,
304 TAG_OBJECT => {
305 if unsafe { functions::is_function(self.context, self) } {
308 JsValueType::Function
309 } else if unsafe { errors::is_error(self.context, self) } {
310 JsValueType::Error
311 } else if unsafe { arrays::is_array(self.context, self) } {
312 JsValueType::Array
313 } else if unsafe { promises::is_promise(self.context, self) } {
314 JsValueType::Promise
315 } else {
316 JsValueType::Object
317 }
318 }
319 TAG_BIG_INT | TAG_SHORT_BIG_INT => JsValueType::BigInt,
320 TAG_MODULE => todo!(),
321 _ => JsValueType::Undefined,
322 }
323 }
324
325 pub fn is_typed_array(&self) -> bool {
326 self.is_object() && unsafe { is_typed_array(self.context, self) }
327 }
328
329 pub fn is_proxy_instance(&self) -> bool {
330 self.is_object() && unsafe { is_proxy_instance(self.context, self) }
331 }
332
333 pub fn type_of(&self) -> &'static str {
334 match self.get_tag() {
335 TAG_BIG_INT => "bigint",
336 TAG_STRING => "string",
337 #[cfg(feature = "bellard")]
338 TAG_STRING_ROPE => "string",
339 TAG_MODULE => "module",
340 TAG_FUNCTION_BYTECODE => "function",
341 TAG_OBJECT => {
342 if self.get_js_type() == JsValueType::Function {
343 "function"
344 } else {
345 "object"
346 }
347 }
348 TAG_INT => "number",
349 TAG_BOOL => "boolean",
350 TAG_NULL => "object",
351 TAG_UNDEFINED => "undefined",
352 TAG_EXCEPTION => "object",
353 TAG_FLOAT64 => "number",
354 _ => "unknown",
355 }
356 }
357
358 pub fn to_bool(&self) -> bool {
359 if self.get_js_type() == JsValueType::Boolean {
360 primitives::to_bool(self).expect("could not convert bool to bool")
361 } else {
362 panic!("not a boolean");
363 }
364 }
365
366 pub fn to_i32(&self) -> i32 {
367 if self.get_js_type() == JsValueType::I32 {
368 primitives::to_i32(self).expect("could not convert to i32")
369 } else {
370 panic!("not an i32");
371 }
372 }
373
374 pub fn to_f64(&self) -> f64 {
375 if self.get_js_type() == JsValueType::F64 {
376 primitives::to_f64(self).expect("could not convert to f64")
377 } else {
378 panic!("not a f64");
379 }
380 }
381
382 pub fn to_string(&self) -> Result<String, JsError> {
383 match self.get_js_type() {
384 JsValueType::I32 => Ok(self.to_i32().to_string()),
385 JsValueType::F64 => Ok(self.to_f64().to_string()),
386 JsValueType::String => unsafe { primitives::to_string(self.context, self) },
387 JsValueType::Boolean => {
388 if self.to_bool() {
389 Ok("true".to_string())
390 } else {
391 Ok("false".to_string())
392 }
393 }
394 JsValueType::Error => {
395 let js_error = unsafe { errors::error_to_js_error(self.context, self) };
396 Ok(format!("{js_error}"))
397 }
398 _ => unsafe { functions::call_to_string(self.context, self) },
399 }
400 }
401
402 pub fn to_str(&self) -> Result<&str, JsError> {
403 if self.get_js_type() == JsValueType::String {
404 unsafe { primitives::to_str(self.context, self) }
405 } else {
406 Err(JsError::new_str("this value is not a string"))
407 }
408 }
409}
410
411#[cfg(test)]
412pub mod tests {
413 use crate::facades::tests::init_test_rt;
414 use crate::jsutils::{JsValueType, Script};
415
416 #[test]
417 fn test_to_str() {
418 let rt = init_test_rt();
419 rt.exe_rt_task_in_event_loop(|q_js_rt| {
420 let q_ctx = q_js_rt.get_main_realm();
421 let res = q_ctx.eval(Script::new("test_to_str.es", "('hello ' + 'world');"));
422
423 match res {
424 Ok(res) => {
425 log::info!("script ran ok: {:?}", res);
426 assert!(res.get_js_type() == JsValueType::String);
427 assert_eq!(res.to_str().expect("str conv failed"), "hello world");
428 }
429 Err(e) => {
430 log::error!("script failed: {}", e);
431 panic!("script failed");
432 }
433 }
434 });
435 }
436}