1use libquickjs_sys as q;
2
3use crate::{ExecutionError, JsValue, ValueError};
4
5use super::make_cstring;
6use crate::bindings::ContextWrapper;
7
8#[repr(i32)]
9#[derive(PartialEq, Eq, Clone, Copy, Debug)]
10pub enum JsTag {
11 Int = q::JS_TAG_INT,
15 Bool = q::JS_TAG_BOOL,
16 Null = q::JS_TAG_NULL,
17 Module = q::JS_TAG_MODULE,
18 Object = q::JS_TAG_OBJECT,
19 String = q::JS_TAG_STRING,
20 Symbol = q::JS_TAG_SYMBOL,
21 #[cfg(feature = "bigint")]
22 BigInt = q::JS_TAG_BIG_INT,
23 Float64 = q::JS_TAG_FLOAT64,
24 Exception = q::JS_TAG_EXCEPTION,
26 Undefined = q::JS_TAG_UNDEFINED,
27 CatchOffset = q::JS_TAG_CATCH_OFFSET,
29 Uninitialized = q::JS_TAG_UNINITIALIZED,
30 FunctionBytecode = q::JS_TAG_FUNCTION_BYTECODE,
31}
32
33impl JsTag {
34 #[inline]
35 pub(super) fn from_c(value: &q::JSValue) -> JsTag {
36 let inner = unsafe { q::JS_VALUE_GET_TAG(*value) };
37 match inner {
38 q::JS_TAG_INT => JsTag::Int,
39 q::JS_TAG_BOOL => JsTag::Bool,
40 q::JS_TAG_NULL => JsTag::Null,
41 q::JS_TAG_MODULE => JsTag::Module,
42 q::JS_TAG_OBJECT => JsTag::Object,
43 q::JS_TAG_STRING => JsTag::String,
44 q::JS_TAG_SYMBOL => JsTag::Symbol,
45 q::JS_TAG_FLOAT64 => JsTag::Float64,
46 q::JS_TAG_EXCEPTION => JsTag::Exception,
48 q::JS_TAG_UNDEFINED => JsTag::Undefined,
49 q::JS_TAG_CATCH_OFFSET => JsTag::CatchOffset,
51 q::JS_TAG_UNINITIALIZED => JsTag::Uninitialized,
52 q::JS_TAG_FUNCTION_BYTECODE => JsTag::FunctionBytecode,
53 #[cfg(feature = "bigint")]
54 q::JS_TAG_BIG_INT => JsTag::BigInt,
55 _other => {
56 unreachable!()
57 }
58 }
59 }
60
61 pub(super) fn to_c(self) -> i32 {
62 match self {
65 JsTag::Int => q::JS_TAG_INT,
66 JsTag::Bool => q::JS_TAG_BOOL,
67 JsTag::Null => q::JS_TAG_NULL,
68 JsTag::Module => q::JS_TAG_MODULE,
69 JsTag::Object => q::JS_TAG_OBJECT,
70 JsTag::String => q::JS_TAG_STRING,
71 JsTag::Symbol => q::JS_TAG_SYMBOL,
72 JsTag::Float64 => q::JS_TAG_FLOAT64,
73 JsTag::Exception => q::JS_TAG_EXCEPTION,
75 JsTag::Undefined => q::JS_TAG_UNDEFINED,
76 JsTag::CatchOffset => q::JS_TAG_CATCH_OFFSET,
78 JsTag::Uninitialized => q::JS_TAG_UNINITIALIZED,
79 JsTag::FunctionBytecode => q::JS_TAG_FUNCTION_BYTECODE,
80 #[cfg(feature = "bigint")]
81 JsTag::BigInt => q::JS_TAG_FUNCTION_BYTECODE,
82 }
83 }
84
85 #[inline]
87 pub fn is_undefined(&self) -> bool {
88 matches!(self, Self::Undefined)
89 }
90
91 #[inline]
93 pub fn is_object(&self) -> bool {
94 matches!(self, Self::Object)
95 }
96
97 #[inline]
99 pub fn is_exception(&self) -> bool {
100 matches!(self, Self::Exception)
101 }
102
103 #[inline]
105 pub fn is_int(&self) -> bool {
106 matches!(self, Self::Int)
107 }
108
109 #[inline]
111 pub fn is_bool(&self) -> bool {
112 matches!(self, Self::Bool)
113 }
114
115 #[inline]
117 pub fn is_null(&self) -> bool {
118 matches!(self, Self::Null)
119 }
120
121 #[inline]
123 pub fn is_module(&self) -> bool {
124 matches!(self, Self::Module)
125 }
126
127 #[inline]
129 pub fn is_string(&self) -> bool {
130 matches!(self, Self::String)
131 }
132
133 #[inline]
135 pub fn is_symbol(&self) -> bool {
136 matches!(self, Self::Symbol)
137 }
138
139 #[cfg(feature = "bigint")]
141 #[inline]
142 pub fn is_big_int(&self) -> bool {
143 matches!(self, Self::BigInt)
144 }
145
146 #[inline]
148 pub fn is_float64(&self) -> bool {
149 matches!(self, Self::Float64)
150 }
151
152 }
164
165pub struct OwnedJsAtom<'a> {
166 context: &'a ContextWrapper,
167 value: q::JSAtom,
168}
169
170impl<'a> OwnedJsAtom<'a> {
171 #[inline]
172 pub fn new(context: &'a ContextWrapper, value: q::JSAtom) -> Self {
173 Self { context, value }
174 }
175}
176
177impl<'a> Drop for OwnedJsAtom<'a> {
178 fn drop(&mut self) {
179 unsafe {
180 q::JS_FreeAtom(self.context.context, self.value);
181 }
182 }
183}
184
185impl<'a> Clone for OwnedJsAtom<'a> {
186 fn clone(&self) -> Self {
187 unsafe { q::JS_DupAtom(self.context.context, self.value) };
188 Self {
189 context: self.context,
190 value: self.value,
191 }
192 }
193}
194
195pub struct OwnedJsValue<'a> {
206 context: &'a ContextWrapper,
207 pub(crate) value: q::JSValue,
209}
210
211impl<'a> OwnedJsValue<'a> {
212 #[inline]
213 pub(crate) fn context(&self) -> &ContextWrapper {
214 self.context
215 }
216
217 #[inline]
218 pub(crate) fn new(context: &'a ContextWrapper, value: q::JSValue) -> Self {
219 Self { context, value }
220 }
221
222 #[inline]
223 pub(crate) fn tag(&self) -> JsTag {
224 JsTag::from_c(&self.value)
225 }
226
227 pub(super) unsafe fn as_inner(&self) -> &q::JSValue {
231 &self.value
232 }
233
234 pub(super) unsafe fn extract(self) -> q::JSValue {
238 let v = self.value;
239 std::mem::forget(self);
240 v
241 }
242
243 #[inline]
245 pub fn is_null(&self) -> bool {
246 self.tag().is_null()
247 }
248
249 #[inline]
251 pub fn is_undefined(&self) -> bool {
252 self.tag() == JsTag::Undefined
253 }
254
255 #[inline]
257 pub fn is_bool(&self) -> bool {
258 self.tag() == JsTag::Bool
259 }
260
261 #[inline]
263 pub fn is_exception(&self) -> bool {
264 self.tag() == JsTag::Exception
265 }
266
267 #[inline]
269 pub fn is_object(&self) -> bool {
270 self.tag() == JsTag::Object
271 }
272
273 #[inline]
275 pub fn is_array(&self) -> bool {
276 unsafe { q::JS_IsArray(self.context.context, self.value) == 1 }
277 }
278
279 #[inline]
281 pub fn is_function(&self) -> bool {
282 unsafe { q::JS_IsFunction(self.context.context, self.value) == 1 }
283 }
284
285 #[inline]
287 pub fn is_module(&self) -> bool {
288 self.tag().is_module()
289 }
290
291 #[inline]
293 pub fn is_string(&self) -> bool {
294 self.tag() == JsTag::String
295 }
296
297 #[inline]
299 pub fn is_compiled_function(&self) -> bool {
300 self.tag() == JsTag::FunctionBytecode
301 }
302
303 pub fn to_value(&self) -> Result<JsValue, ValueError> {
305 self.context.to_value(&self.value)
306 }
307
308 pub(crate) fn to_bool(&self) -> Result<bool, ValueError> {
309 match self.to_value()? {
310 JsValue::Bool(b) => Ok(b),
311 _ => Err(ValueError::UnexpectedType),
312 }
313 }
314
315 pub(crate) fn try_into_object(self) -> Result<OwnedJsObject<'a>, ValueError> {
316 OwnedJsObject::try_from_value(self)
317 }
318
319 pub(crate) fn try_into_function(self) -> Result<JsFunction<'a>, ValueError> {
320 JsFunction::try_from_value(self)
321 }
322
323 pub(crate) fn try_into_compiled_function(self) -> Result<JsCompiledFunction<'a>, ValueError> {
324 JsCompiledFunction::try_from_value(self)
325 }
326
327 pub(crate) fn try_into_module(self) -> Result<JsModule<'a>, ValueError> {
328 JsModule::try_from_value(self)
329 }
330
331 pub(crate) fn js_to_string(&self) -> Result<String, ExecutionError> {
333 let value = if self.is_string() {
334 self.to_value()?
335 } else {
336 let raw = unsafe { q::JS_ToString(self.context.context, self.value) };
337 let value = OwnedJsValue::new(self.context, raw);
338
339 if !value.is_string() {
340 return Err(ExecutionError::Exception(
341 "Could not convert value to string".into(),
342 ));
343 }
344 value.to_value()?
345 };
346
347 Ok(value.as_str().unwrap().to_string())
348 }
349
350 #[cfg(test)]
351 pub(crate) fn get_ref_count(&self) -> i32 {
352 if self.value.tag < 0 {
353 let ptr = unsafe { self.value.u.ptr as *mut q::JSRefCountHeader };
356 let pref: &mut q::JSRefCountHeader = &mut unsafe { *ptr };
357 pref.ref_count
358 } else {
359 -1
360 }
361 }
362}
363
364impl<'a> Drop for OwnedJsValue<'a> {
365 fn drop(&mut self) {
366 unsafe {
367 q::JS_FreeValue(self.context.context, self.value);
368 }
369 }
370}
371
372impl<'a> Clone for OwnedJsValue<'a> {
373 fn clone(&self) -> Self {
374 unsafe { q::JS_DupValue(self.context.context, self.value) };
375 Self {
376 context: self.context,
377 value: self.value,
378 }
379 }
380}
381
382impl<'a> std::fmt::Debug for OwnedJsValue<'a> {
383 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
384 write!(f, "{:?}(_)", self.tag())
385 }
386}
387
388pub struct OwnedJsArray<'a> {
389 value: OwnedJsValue<'a>,
390}
391
392impl<'a> OwnedJsArray<'a> {
393 pub fn new(value: OwnedJsValue<'a>) -> Option<Self> {
394 if value.is_array() {
395 Some(Self { value })
396 } else {
397 None
398 }
399 }
400}
401
402#[derive(Clone, Debug)]
405pub struct OwnedJsObject<'a> {
406 value: OwnedJsValue<'a>,
407}
408
409impl<'a> OwnedJsObject<'a> {
410 pub fn try_from_value(value: OwnedJsValue<'a>) -> Result<Self, ValueError> {
411 if !value.is_object() {
412 Err(ValueError::Internal("Expected an object".into()))
413 } else {
414 Ok(Self { value })
415 }
416 }
417
418 pub fn into_value(self) -> OwnedJsValue<'a> {
419 self.value
420 }
421
422 pub fn property(&self, name: &str) -> Result<Option<OwnedJsValue<'a>>, ExecutionError> {
423 let cname = make_cstring(name)?;
425 let value = {
426 let raw = unsafe {
427 q::JS_GetPropertyStr(self.value.context.context, self.value.value, cname.as_ptr())
428 };
429 OwnedJsValue::new(self.value.context, raw)
430 };
431 let tag = value.tag();
432
433 if tag.is_exception() {
434 Err(ExecutionError::Internal(format!(
435 "Exception while getting property '{}'",
436 name
437 )))
438 } else if tag.is_undefined() {
439 Ok(None)
440 } else {
441 Ok(Some(value))
442 }
443 }
444
445 pub fn property_require(&self, name: &str) -> Result<OwnedJsValue<'a>, ExecutionError> {
446 self.property(name)?
447 .ok_or_else(|| ExecutionError::Internal(format!("Property '{}' not found", name)))
448 }
449
450 pub fn is_promise(&self) -> Result<bool, ExecutionError> {
453 if let Some(p) = self.property("then")? {
454 if p.is_function() {
455 return Ok(true);
456 }
457 }
458 if let Some(p) = self.property("catch")? {
459 if p.is_function() {
460 return Ok(true);
461 }
462 }
463 Ok(false)
464 }
465
466 pub fn set_property(&self, name: &str, value: OwnedJsValue<'a>) -> Result<(), ExecutionError> {
467 let cname = make_cstring(name)?;
468 unsafe {
469 let ret = q::JS_SetPropertyStr(
475 self.value.context.context,
476 self.value.value,
477 cname.as_ptr(),
478 value.value,
479 );
480
481 if ret < 0 {
482 Err(ExecutionError::Exception("Could not set property".into()))
483 } else {
484 std::mem::forget(value);
486 Ok(())
487 }
488 }
489 }
490}
491
492#[derive(Clone, Debug)]
495pub struct JsFunction<'a> {
496 value: OwnedJsValue<'a>,
497}
498
499impl<'a> JsFunction<'a> {
500 pub fn try_from_value(value: OwnedJsValue<'a>) -> Result<Self, ValueError> {
501 if !value.is_function() {
502 Err(ValueError::Internal(format!(
503 "Expected a function, got {:?}",
504 value.tag()
505 )))
506 } else {
507 Ok(Self { value })
508 }
509 }
510
511 pub fn into_value(self) -> OwnedJsValue<'a> {
512 self.value
513 }
514
515 pub fn call(&self, args: Vec<OwnedJsValue<'a>>) -> Result<OwnedJsValue<'a>, ExecutionError> {
516 let mut qargs = args.iter().map(|arg| arg.value).collect::<Vec<_>>();
517
518 let qres_raw = unsafe {
519 q::JS_Call(
520 self.value.context.context,
521 self.value.value,
522 q::JSValue {
523 u: q::JSValueUnion { int32: 0 },
524 tag: JsTag::Null as i64,
525 },
526 qargs.len() as i32,
527 qargs.as_mut_ptr(),
528 )
529 };
530 Ok(OwnedJsValue::new(self.value.context, qres_raw))
531 }
532}
533
534#[derive(Clone, Debug)]
536pub struct JsCompiledFunction<'a> {
537 value: OwnedJsValue<'a>,
538}
539
540impl<'a> JsCompiledFunction<'a> {
541 pub(crate) fn try_from_value(value: OwnedJsValue<'a>) -> Result<Self, ValueError> {
542 if !value.is_compiled_function() {
543 Err(ValueError::Internal(format!(
544 "Expected a compiled function, got {:?}",
545 value.tag()
546 )))
547 } else {
548 Ok(Self { value })
549 }
550 }
551
552 pub(crate) fn as_value(&self) -> &OwnedJsValue<'_> {
553 &self.value
554 }
555
556 pub(crate) fn into_value(self) -> OwnedJsValue<'a> {
557 self.value
558 }
559
560 pub fn eval(&'a self) -> Result<OwnedJsValue<'a>, ExecutionError> {
563 super::compile::run_compiled_function(self)
564 }
565
566 pub fn to_bytecode(&self) -> Result<Vec<u8>, ExecutionError> {
571 Ok(super::compile::to_bytecode(self.value.context, self))
572 }
573}
574
575pub struct JsModule<'a> {
577 value: OwnedJsValue<'a>,
578}
579
580impl<'a> JsModule<'a> {
581 pub fn try_from_value(value: OwnedJsValue<'a>) -> Result<Self, ValueError> {
582 if !value.is_module() {
583 Err(ValueError::Internal(format!(
584 "Expected a compiled function, got {:?}",
585 value.tag()
586 )))
587 } else {
588 Ok(Self { value })
589 }
590 }
591
592 pub fn into_value(self) -> OwnedJsValue<'a> {
593 self.value
594 }
595}
596
597pub enum JsCompiledValue<'a> {
600 Function(JsCompiledFunction<'a>),
601 Module(JsModule<'a>),
602}