1use alloc::boxed::Box;
7use indexmap::IndexMap;
8use alloc::format;
9use alloc::rc::Rc;
10use alloc::string::String;
11use alloc::vec::Vec;
12use core::cell::RefCell;
13
14use super::ast::Param;
15use super::bigint::BigInt;
16use super::interp::{Interp, Scope};
17
18#[derive(Default)]
26pub struct FnvHasher(u64);
27impl core::hash::Hasher for FnvHasher {
28 fn finish(&self) -> u64 {
29 self.0
30 }
31 fn write(&mut self, bytes: &[u8]) {
32 const FNV_OFFSET: u64 = 0xcbf29ce484222325;
33 const FNV_PRIME: u64 = 0x100000001b3;
34 let mut hash = if self.0 == 0 { FNV_OFFSET } else { self.0 };
35 for &b in bytes {
36 hash ^= b as u64;
37 hash = hash.wrapping_mul(FNV_PRIME);
38 }
39 self.0 = hash;
40 }
41}
42pub type PropMap<V> = IndexMap<String, V, core::hash::BuildHasherDefault<FnvHasher>>;
44
45pub struct LazyPropMap<V>(Option<Box<PropMap<V>>>);
52
53impl<V> Default for LazyPropMap<V> {
54 fn default() -> Self {
55 LazyPropMap(None)
56 }
57}
58
59impl<V: Clone> Clone for LazyPropMap<V> {
60 fn clone(&self) -> Self {
61 LazyPropMap(self.0.clone())
62 }
63}
64
65impl<V> LazyPropMap<V> {
66 fn map_mut(&mut self) -> &mut PropMap<V> {
67 self.0.get_or_insert_with(|| Box::new(PropMap::default()))
68 }
69 pub fn get(&self, k: &str) -> Option<&V> {
70 self.0.as_ref()?.get(k)
71 }
72 pub fn get_mut(&mut self, k: &str) -> Option<&mut V> {
73 self.0.as_mut()?.get_mut(k)
74 }
75 pub fn contains_key(&self, k: &str) -> bool {
76 self.0.as_ref().is_some_and(|m| m.contains_key(k))
77 }
78 pub fn insert(&mut self, k: String, v: V) -> Option<V> {
79 self.map_mut().insert(k, v)
80 }
81 pub fn entry(&mut self, k: String) -> indexmap::map::Entry<'_, String, V> {
82 self.map_mut().entry(k)
83 }
84 pub fn remove(&mut self, k: &str) -> Option<V> {
86 self.0.as_mut()?.swap_remove(k)
87 }
88 pub fn shift_remove(&mut self, k: &str) -> Option<V> {
89 self.0.as_mut()?.shift_remove(k)
90 }
91 pub fn clear(&mut self) {
92 self.0 = None;
93 }
94 pub fn len(&self) -> usize {
95 self.0.as_ref().map_or(0, |m| m.len())
96 }
97 pub fn is_empty(&self) -> bool {
98 self.len() == 0
99 }
100 pub fn keys(&self) -> impl Iterator<Item = &String> {
101 self.0.iter().flat_map(|m| m.keys())
102 }
103 pub fn iter(&self) -> impl Iterator<Item = (&String, &V)> {
104 self.0.iter().flat_map(|m| m.iter())
105 }
106}
107
108impl<V> IntoIterator for LazyPropMap<V> {
109 type Item = (String, V);
110 type IntoIter = core::iter::Flatten<core::option::IntoIter<PropMap<V>>>;
111 fn into_iter(self) -> Self::IntoIter {
112 self.0.map(|b| *b).into_iter().flatten()
113 }
114}
115
116pub struct LazyPropIter<'a, V>(Option<indexmap::map::Iter<'a, String, V>>);
118
119impl<'a, V> Iterator for LazyPropIter<'a, V> {
120 type Item = (&'a String, &'a V);
121 fn next(&mut self) -> Option<Self::Item> {
122 self.0.as_mut()?.next()
123 }
124}
125
126impl<'a, V> IntoIterator for &'a LazyPropMap<V> {
127 type Item = (&'a String, &'a V);
128 type IntoIter = LazyPropIter<'a, V>;
129 fn into_iter(self) -> Self::IntoIter {
130 LazyPropIter(self.0.as_ref().map(|m| m.iter()))
131 }
132}
133
134impl Drop for Obj {
135 fn drop(&mut self) {
136 #[cfg(target_os = "none")]
137 super::cycle_gc::untrack_obj(self.gc_slot);
138 }
139}
140
141pub const NO_GC_SLOT: u32 = u32::MAX;
143
144fn gc_track(rc: ObjRef) -> ObjRef {
146 #[cfg(target_os = "none")]
147 {
148 super::cycle_gc::track_obj(rc)
149 }
150 #[cfg(not(target_os = "none"))]
151 {
152 rc
153 }
154}
155
156pub type ObjRef = Rc<RefCell<Obj>>;
158
159pub type NativeFn = fn(&mut Interp, this: Value, args: &[Value]) -> Result<Value, Value>;
161
162#[derive(Clone)]
164pub enum Value {
165 Undefined,
166 Null,
167 Bool(bool),
168 Number(f64),
169 Str(Rc<String>),
170 BigInt(Rc<BigInt>),
172 Object(ObjRef),
173}
174
175#[derive(Clone)]
177pub struct FunctionData {
178 pub name: String,
179 pub params: Rc<Vec<Param>>,
180 pub body: Rc<super::ast::FnBody>,
181 pub closure: Rc<RefCell<Scope>>,
183 pub is_arrow: bool,
185 pub is_async: bool,
187 pub is_generator: bool,
189 pub bound_this: Option<Box<Value>>,
191}
192
193#[derive(Clone)]
196pub struct ByteStore {
197 pub buf: Rc<RefCell<Vec<u8>>>,
198 pub off: usize,
199 pub len: usize,
200 pub kind: u8,
202}
203
204impl ByteStore {
205 pub fn new(bytes: Vec<u8>, kind: u8) -> Self {
206 let len = bytes.len();
207 ByteStore { buf: Rc::new(RefCell::new(bytes)), off: 0, len, kind }
208 }
209 pub fn view(&self, start: usize, len: usize, kind: u8) -> Self {
211 let start = start.min(self.len);
212 let len = len.min(self.len - start);
213 ByteStore { buf: self.buf.clone(), off: self.off + start, len, kind }
214 }
215 pub fn get(&self, i: usize) -> Option<f64> {
216 if i >= self.len {
217 return None;
218 }
219 let b = *self.buf.borrow().get(self.off + i)?;
220 Some(if self.kind == 1 { b as i8 as f64 } else { b as f64 })
221 }
222 pub fn set(&self, i: usize, v: f64) {
224 if i >= self.len {
225 return;
226 }
227 let byte = Self::coerce(v, self.kind);
228 if let Some(slot) = self.buf.borrow_mut().get_mut(self.off + i) {
229 *slot = byte;
230 }
231 }
232 pub fn coerce(v: f64, kind: u8) -> u8 {
233 if kind == 2 {
234 if v.is_nan() {
235 return 0;
236 }
237 let c = v.clamp(0.0, 255.0);
239 let f = libm::floor(c);
240 let r = if c - f > 0.5 || (c - f == 0.5 && (f as u32) % 2 == 1) { f + 1.0 } else { f };
241 return r as u8;
242 }
243 if !v.is_finite() {
244 return 0;
245 }
246 (libm::trunc(v) as i64) as u8
247 }
248 pub fn to_vec(&self) -> Vec<u8> {
249 let b = self.buf.borrow();
250 b.get(self.off..self.off + self.len).map(|s| s.to_vec()).unwrap_or_default()
251 }
252 pub fn values(&self) -> Vec<Value> {
253 (0..self.len).map(|i| Value::Number(self.get(i).unwrap_or(0.0))).collect()
254 }
255}
256
257#[derive(Clone)]
258pub enum ObjKind {
259 Plain,
260 Array(Vec<Value>),
261 Function(FunctionData),
262 Native {
263 name: String,
264 func: NativeFn,
265 },
266 Bytes(ByteStore),
272 DomElement(usize),
274 Host(String),
276 MapObj(Vec<(Value, Value)>),
278 SetObj(Vec<Value>),
280 PromiseObj(Rc<RefCell<PromiseState>>),
282 Resolver {
284 state: Rc<RefCell<PromiseState>>,
285 reject: bool,
286 },
287 Generator(Rc<RefCell<GenState>>),
289 RegExpObj(Rc<RefCell<super::regex::RegExpData>>),
291 Proxy {
293 target: ObjRef,
294 handler: ObjRef,
295 },
296 Bound {
299 target: Value,
300 bound_this: Value,
301 bound_args: Vec<Value>,
302 },
303 DateObj(f64),
305}
306
307#[derive(Clone)]
309pub enum GenCompletion {
310 Normal(Value),
311 Throw(Value),
312 Return(Value),
313}
314
315pub struct GenState {
324 pub func: FunctionData,
326 pub args: Vec<Value>,
328 pub this: Value,
330 pub sent: Vec<GenCompletion>,
332 pub started: bool,
334 pub done: bool,
336 pub returned: Value,
338}
339
340#[derive(Debug, Clone, Copy, PartialEq)]
341pub struct PropertyAttributes {
342 pub writable: bool,
343 pub configurable: bool,
344 pub enumerable: bool,
345}
346
347impl Default for PropertyAttributes {
348 fn default() -> Self {
349 PropertyAttributes {
350 writable: true,
351 configurable: true,
352 enumerable: true,
353 }
354 }
355}
356
357#[derive(Clone, Copy, PartialEq)]
359pub enum PromiseStatus {
360 Pending,
361 Fulfilled,
362 Rejected,
363}
364
365pub struct Reaction {
367 pub on_fulfilled: Option<Value>,
368 pub on_rejected: Option<Value>,
369 pub result: Rc<RefCell<PromiseState>>,
371}
372
373pub struct PromiseState {
374 pub status: PromiseStatus,
375 pub value: Value,
377 pub reactions: Vec<Reaction>,
379}
380
381impl PromiseState {
382 pub fn pending() -> Self {
383 PromiseState {
384 status: PromiseStatus::Pending,
385 value: Value::Undefined,
386 reactions: Vec::new(),
387 }
388 }
389}
390
391pub struct Job {
393 pub handler: Option<Value>,
395 pub arg: Value,
396 pub is_fulfill: bool,
397 pub result: Rc<RefCell<PromiseState>>,
398}
399
400pub struct Accessor {
402 pub get: Option<Value>,
403 pub set: Option<Value>,
404}
405
406pub struct Obj {
408 pub holes: Option<alloc::boxed::Box<Vec<bool>>>,
414 pub gc_slot: u32,
416 pub props: PropMap<Value>,
417 pub accessors: LazyPropMap<Accessor>,
419 pub attrs: LazyPropMap<PropertyAttributes>,
420 pub proto: Option<ObjRef>,
421 pub kind: ObjKind,
422 pub frozen: bool,
425 pub sealed: bool,
428 pub non_extensible: bool,
431 pub null_proto: bool,
439}
440
441impl Obj {
442 pub fn plain() -> ObjRef {
443 gc_track(Rc::new(RefCell::new(Obj {
444 props: PropMap::default(),
445 accessors: LazyPropMap::default(),
446 attrs: LazyPropMap::default(),
447 proto: None,
448 frozen: false,
449 sealed: false,
450 non_extensible: false,
451 null_proto: false,
452 holes: None,
453 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
454 kind: ObjKind::Plain,
455 })))
456 }
457 pub fn bytes(store: ByteStore) -> ObjRef {
458 gc_track(Rc::new(RefCell::new(Obj {
459 props: PropMap::default(),
460 accessors: LazyPropMap::default(),
461 attrs: LazyPropMap::default(),
462 proto: None,
463 frozen: false,
464 sealed: false,
465 non_extensible: false,
466 null_proto: false,
467 holes: None,
468 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
469 kind: ObjKind::Bytes(store),
470 })))
471 }
472 pub fn is_hole(&self, i: usize) -> bool {
474 match (&self.holes, &self.kind) {
475 (Some(h), ObjKind::Array(items)) => {
476 h.get(i).copied().unwrap_or(false) && matches!(items.get(i), Some(Value::Undefined))
477 }
478 _ => false,
479 }
480 }
481 pub fn hole_mask(&self) -> Vec<bool> {
483 match (&self.holes, &self.kind) {
484 (Some(_), ObjKind::Array(items)) => (0..items.len()).map(|i| self.is_hole(i)).collect(),
485 _ => Vec::new(),
486 }
487 }
488 pub fn array(items: Vec<Value>) -> ObjRef {
489 gc_track(Rc::new(RefCell::new(Obj {
490 props: PropMap::default(),
491 accessors: LazyPropMap::default(),
492 attrs: LazyPropMap::default(),
493 proto: None,
494 frozen: false,
495 sealed: false,
496 non_extensible: false,
497 null_proto: false,
498 holes: None,
499 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
500 kind: ObjKind::Array(items),
501 })))
502 }
503 pub fn function(data: FunctionData) -> ObjRef {
504 gc_track(Rc::new(RefCell::new(Obj {
505 props: PropMap::default(),
506 accessors: LazyPropMap::default(),
507 attrs: LazyPropMap::default(),
508 proto: None,
509 frozen: false,
510 sealed: false,
511 non_extensible: false,
512 null_proto: false,
513 holes: None,
514 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
515 kind: ObjKind::Function(data),
516 })))
517 }
518 pub fn native(name: &str, func: NativeFn) -> ObjRef {
519 gc_track(Rc::new(RefCell::new(Obj {
520 props: PropMap::default(),
521 accessors: LazyPropMap::default(),
522 attrs: LazyPropMap::default(),
523 proto: None,
524 frozen: false,
525 sealed: false,
526 non_extensible: false,
527 null_proto: false,
528 holes: None,
529 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
530 kind: ObjKind::Native {
531 name: String::from(name),
532 func,
533 },
534 })))
535 }
536 pub fn host(tag: &str) -> ObjRef {
537 gc_track(Rc::new(RefCell::new(Obj {
538 props: PropMap::default(),
539 accessors: LazyPropMap::default(),
540 attrs: LazyPropMap::default(),
541 proto: None,
542 frozen: false,
543 sealed: false,
544 non_extensible: false,
545 null_proto: false,
546 holes: None,
547 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
548 kind: ObjKind::Host(String::from(tag)),
549 })))
550 }
551 pub fn dom(node_idx: usize) -> ObjRef {
552 gc_track(Rc::new(RefCell::new(Obj {
553 props: PropMap::default(),
554 accessors: LazyPropMap::default(),
555 attrs: LazyPropMap::default(),
556 proto: None,
557 frozen: false,
558 sealed: false,
559 non_extensible: false,
560 null_proto: false,
561 holes: None,
562 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
563 kind: ObjKind::DomElement(node_idx),
564 })))
565 }
566 pub fn map_obj(entries: Vec<(Value, Value)>) -> ObjRef {
567 gc_track(Rc::new(RefCell::new(Obj {
568 props: PropMap::default(),
569 accessors: LazyPropMap::default(),
570 attrs: LazyPropMap::default(),
571 proto: None,
572 frozen: false,
573 sealed: false,
574 non_extensible: false,
575 null_proto: false,
576 holes: None,
577 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
578 kind: ObjKind::MapObj(entries),
579 })))
580 }
581 pub fn set_obj(items: Vec<Value>) -> ObjRef {
582 gc_track(Rc::new(RefCell::new(Obj {
583 props: PropMap::default(),
584 accessors: LazyPropMap::default(),
585 attrs: LazyPropMap::default(),
586 proto: None,
587 frozen: false,
588 sealed: false,
589 non_extensible: false,
590 null_proto: false,
591 holes: None,
592 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
593 kind: ObjKind::SetObj(items),
594 })))
595 }
596 pub fn promise(state: Rc<RefCell<PromiseState>>) -> ObjRef {
597 gc_track(Rc::new(RefCell::new(Obj {
598 props: PropMap::default(),
599 accessors: LazyPropMap::default(),
600 attrs: LazyPropMap::default(),
601 proto: None,
602 frozen: false,
603 sealed: false,
604 non_extensible: false,
605 null_proto: false,
606 holes: None,
607 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
608 kind: ObjKind::PromiseObj(state),
609 })))
610 }
611 pub fn resolver(state: Rc<RefCell<PromiseState>>, reject: bool) -> ObjRef {
612 gc_track(Rc::new(RefCell::new(Obj {
613 props: PropMap::default(),
614 accessors: LazyPropMap::default(),
615 attrs: LazyPropMap::default(),
616 proto: None,
617 frozen: false,
618 sealed: false,
619 non_extensible: false,
620 null_proto: false,
621 holes: None,
622 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
623 kind: ObjKind::Resolver { state, reject },
624 })))
625 }
626 pub fn generator(state: GenState) -> ObjRef {
627 gc_track(Rc::new(RefCell::new(Obj {
628 props: PropMap::default(),
629 accessors: LazyPropMap::default(),
630 attrs: LazyPropMap::default(),
631 proto: None,
632 frozen: false,
633 sealed: false,
634 non_extensible: false,
635 null_proto: false,
636 holes: None,
637 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
638 kind: ObjKind::Generator(Rc::new(RefCell::new(state))),
639 })))
640 }
641 pub fn regexp(data: super::regex::RegExpData) -> ObjRef {
642 gc_track(Rc::new(RefCell::new(Obj {
643 props: PropMap::default(),
644 accessors: LazyPropMap::default(),
645 attrs: LazyPropMap::default(),
646 proto: None,
647 frozen: false,
648 sealed: false,
649 non_extensible: false,
650 null_proto: false,
651 holes: None,
652 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
653 kind: ObjKind::RegExpObj(Rc::new(RefCell::new(data))),
654 })))
655 }
656 pub fn proxy(target: ObjRef, handler: ObjRef) -> ObjRef {
657 gc_track(Rc::new(RefCell::new(Obj {
658 props: PropMap::default(),
659 accessors: LazyPropMap::default(),
660 attrs: LazyPropMap::default(),
661 proto: None,
662 frozen: false,
663 sealed: false,
664 non_extensible: false,
665 null_proto: false,
666 holes: None,
667 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
668 kind: ObjKind::Proxy { target, handler },
669 })))
670 }
671 pub fn date_obj(epoch_ms: f64) -> ObjRef {
672 gc_track(Rc::new(RefCell::new(Obj {
673 props: PropMap::default(),
674 accessors: LazyPropMap::default(),
675 attrs: LazyPropMap::default(),
676 proto: None,
677 frozen: false,
678 sealed: false,
679 non_extensible: false,
680 null_proto: false,
681 holes: None,
682 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
683 kind: ObjKind::DateObj(epoch_ms),
684 })))
685 }
686 pub fn bound(target: Value, bound_this: Value, bound_args: Vec<Value>) -> ObjRef {
687 gc_track(Rc::new(RefCell::new(Obj {
688 props: PropMap::default(),
689 accessors: LazyPropMap::default(),
690 attrs: LazyPropMap::default(),
691 proto: None,
692 frozen: false,
693 sealed: false,
694 non_extensible: false,
695 null_proto: false,
696 holes: None,
697 gc_slot: crate::os_lib::js::value::NO_GC_SLOT,
698 kind: ObjKind::Bound {
699 target,
700 bound_this,
701 bound_args,
702 },
703 })))
704 }
705 pub fn clear_references(&mut self) {
706 self.props.clear();
707 self.accessors.clear();
708 self.proto = None;
709 match &mut self.kind {
710 ObjKind::Array(items) => items.clear(),
711 ObjKind::Function(f) => {
712 f.bound_this = None;
713 f.closure = Scope::new_root();
714 }
715 ObjKind::MapObj(entries) => entries.clear(),
716 ObjKind::SetObj(items) => items.clear(),
717 ObjKind::PromiseObj(p) => {
718 if let Ok(mut p) = p.try_borrow_mut() {
719 p.reactions.clear();
720 p.value = Value::Undefined;
721 }
722 }
723 ObjKind::Resolver { state, .. } => {
724 if let Ok(mut s) = state.try_borrow_mut() {
725 s.reactions.clear();
726 s.value = Value::Undefined;
727 }
728 }
729 ObjKind::Bound { target, bound_this, bound_args } => {
730 *target = Value::Undefined;
731 *bound_this = Value::Undefined;
732 bound_args.clear();
733 }
734 _ => {}
735 }
736 }
737
738 pub fn is_callable(&self) -> bool {
739 match &self.kind {
740 ObjKind::Function(_) | ObjKind::Native { .. } | ObjKind::Resolver { .. } => true,
741 ObjKind::Bound { .. } => true,
742 ObjKind::Proxy { target, .. } => target.borrow().is_callable(),
744 _ => false,
745 }
746 }
747}
748
749impl Value {
750 pub fn str(s: impl Into<String>) -> Value {
751 Value::Str(Rc::new(s.into()))
752 }
753 pub fn bigint(b: BigInt) -> Value {
754 Value::BigInt(Rc::new(b))
755 }
756 pub fn object(o: ObjRef) -> Value {
757 Value::Object(o)
758 }
759
760 pub fn truthy(&self) -> bool {
762 match self {
763 Value::Undefined | Value::Null => false,
764 Value::Bool(b) => *b,
765 Value::Number(n) => *n != 0.0 && !n.is_nan(),
766 Value::Str(s) => !s.is_empty(),
767 Value::BigInt(b) => !b.is_zero(),
768 Value::Object(_) => true,
769 }
770 }
771
772 pub fn to_number(&self) -> f64 {
774 match self {
775 Value::Undefined => f64::NAN,
776 Value::Null => 0.0,
777 Value::Bool(b) => {
778 if *b {
779 1.0
780 } else {
781 0.0
782 }
783 }
784 Value::Number(n) => *n,
785 Value::Str(s) => {
786 let t = s.trim();
787 if t.is_empty() {
788 0.0
789 } else if let Some(rest) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
790 u64::from_str_radix(rest, 16).map(|n| n as f64).unwrap_or(f64::NAN)
793 } else if let Some(rest) = t.strip_prefix("0o").or_else(|| t.strip_prefix("0O")) {
794 u64::from_str_radix(rest, 8).map(|n| n as f64).unwrap_or(f64::NAN)
795 } else if let Some(rest) = t.strip_prefix("0b").or_else(|| t.strip_prefix("0B")) {
796 u64::from_str_radix(rest, 2).map(|n| n as f64).unwrap_or(f64::NAN)
797 } else {
798 t.parse::<f64>().unwrap_or(f64::NAN)
799 }
800 }
801 Value::BigInt(b) => b.to_f64(),
802 Value::Object(o) => match &o.borrow().kind {
811 ObjKind::DateObj(ms) => *ms,
812 ObjKind::Array(_) => {
813 let s = self.to_js_string();
814 let t = s.trim();
815 if t.is_empty() {
816 0.0
817 } else {
818 t.parse::<f64>().unwrap_or(f64::NAN)
819 }
820 }
821 ObjKind::Proxy { target, .. } => Value::Object(target.clone()).to_number(),
827 _ => f64::NAN,
828 },
829 }
830 }
831
832 pub fn type_of(&self) -> &'static str {
834 match self {
835 Value::Undefined => "undefined",
836 Value::Null => "object",
837 Value::Bool(_) => "boolean",
838 Value::Number(_) => "number",
839 Value::Str(_) => "string",
840 Value::BigInt(_) => "bigint",
841 Value::Object(o) => {
842 if o.borrow().is_callable() {
843 "function"
844 } else {
845 "object"
846 }
847 }
848 }
849 }
850
851 pub fn to_js_string(&self) -> String {
853 let mut seen = Vec::new();
854 self.to_js_string_seen(&mut seen)
855 }
856
857 fn to_js_string_seen(&self, seen: &mut Vec<ObjRef>) -> String {
871 if seen.len() > 30 {
872 return String::new();
873 }
874 match self {
875 Value::Undefined => String::from("undefined"),
876 Value::Null => String::from("null"),
877 Value::Bool(b) => {
878 if *b {
879 String::from("true")
880 } else {
881 String::from("false")
882 }
883 }
884 Value::Number(n) => fmt_number(*n),
885 Value::Str(s) => (**s).clone(),
886 Value::BigInt(b) => b.to_decimal_string(),
887 Value::Object(o) => {
888 let b = o.borrow();
889 match &b.kind {
890 ObjKind::Array(items) => {
891 if seen.iter().any(|s| Rc::ptr_eq(s, o)) {
892 return String::new();
893 }
894 seen.push(o.clone());
895 let parts: Vec<String> = items
896 .iter()
897 .map(|v| match v {
898 Value::Undefined | Value::Null => String::new(),
899 _ => v.to_js_string_seen(seen),
900 })
901 .collect();
902 seen.pop();
903 parts.join(",")
904 }
905 ObjKind::Function(f) => format!("function {}() {{ [code] }}", f.name),
906 ObjKind::Native { name, .. } => {
907 format!("function {}() {{ [native code] }}", name)
908 }
909 ObjKind::Bound { .. } => String::from("function () { [native code] }"),
913 ObjKind::MapObj(_) => String::from("[object Map]"),
914 ObjKind::SetObj(_) => String::from("[object Set]"),
915 ObjKind::PromiseObj(_) => String::from("[object Promise]"),
916 ObjKind::Resolver { .. } => String::from("function () { [native code] }"),
917 ObjKind::Generator(_) => String::from("[object Generator]"),
918 ObjKind::RegExpObj(r) => {
919 let d = r.borrow();
920 format!("/{}/{}", d.re.source, d.re.flags)
921 }
922 ObjKind::Proxy { target, .. } => {
929 Value::Object(target.clone()).to_js_string_seen(seen)
930 }
931 ObjKind::DateObj(ms) => date_to_iso_string_repr(*ms),
935 _ => String::from("[object Object]"),
936 }
937 }
938 }
939 }
940
941 pub fn strict_eq(&self, other: &Value) -> bool {
943 match (self, other) {
944 (Value::Undefined, Value::Undefined) => true,
945 (Value::Null, Value::Null) => true,
946 (Value::Bool(a), Value::Bool(b)) => a == b,
947 (Value::Number(a), Value::Number(b)) => a == b,
948 (Value::Str(a), Value::Str(b)) => a == b,
949 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b) == core::cmp::Ordering::Equal,
950 (Value::Object(a), Value::Object(b)) => {
956 match (&a.borrow().kind, &b.borrow().kind) {
957 (ObjKind::DomElement(ia), ObjKind::DomElement(ib)) => ia == ib,
958 _ => Rc::ptr_eq(a, b),
959 }
960 }
961 _ => false,
962 }
963 }
964
965 pub fn same_ref(&self, other: &Value) -> bool {
967 match (self, other) {
968 (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
969 _ => self.strict_eq(other),
970 }
971 }
972}
973
974pub fn fmt_number(n: f64) -> String {
976 if n.is_nan() {
977 return String::from("NaN");
978 }
979 if n.is_infinite() {
980 return String::from(if n > 0.0 { "Infinity" } else { "-Infinity" });
981 }
982 if n == 0.0 {
983 return String::from("0");
984 }
985 if libm::trunc(n) == n && libm::fabs(n) < 1e21 {
987 return format!("{}", n as i64);
988 }
989 let mut s = format!("{}", n);
991 if s.contains('.') {
993 while s.ends_with('0') {
994 s.pop();
995 }
996 if s.ends_with('.') {
997 s.pop();
998 }
999 }
1000 s
1001}
1002
1003fn date_to_iso_string_repr(ms: f64) -> String {
1008 let total_ms = libm::floor(ms) as i64;
1009 let days = total_ms.div_euclid(86_400_000);
1010 let ms_of_day = total_ms.rem_euclid(86_400_000);
1011 let z = days + 719468;
1012 let era = (if z >= 0 { z } else { z - 146096 }) / 146097;
1013 let doe = z - era * 146097;
1014 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
1015 let y = yoe + era * 400;
1016 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
1017 let mp = (5 * doy + 2) / 153;
1018 let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
1019 let m = (if mp < 10 { mp + 3 } else { mp - 9 }) as u32;
1020 let y = if m <= 2 { y + 1 } else { y };
1021 let hh = (ms_of_day / 3_600_000) as u32;
1022 let mm = ((ms_of_day / 60_000) % 60) as u32;
1023 let ss = ((ms_of_day / 1000) % 60) as u32;
1024 let mms = (ms_of_day % 1000) as u32;
1025 format!(
1026 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
1027 y, m, d, hh, mm, ss, mms
1028 )
1029}