1use crate::host::{self, ops, with_host, FuncVal, JsObj, ObjKind};
7use fusevm::{NumOp, Value, VM};
8use indexmap::IndexMap;
9
10pub fn install(vm: &mut VM) {
12 vm.register_builtin(ops::GETLOCAL, b_getlocal);
13 vm.register_builtin(ops::SETLOCAL, b_setlocal);
14 vm.register_builtin(ops::SETLOCAL_STRICT, b_setlocal_strict);
15 vm.register_builtin(ops::DECLARE, b_declare);
16 vm.register_builtin(ops::DECLARE_CONST, b_declare_const);
17 vm.register_builtin(ops::MARK_HOLE, b_mark_hole);
18 vm.register_builtin(ops::DELNAME, b_delname);
19 vm.register_builtin(ops::GETATTR, b_getattr);
20 vm.register_builtin(ops::SETATTR, b_setattr);
21 vm.register_builtin(ops::GETITEM, b_getitem);
22 vm.register_builtin(ops::SETITEM, b_setitem);
23 vm.register_builtin(ops::DELITEM, b_delitem);
24 vm.register_builtin(ops::MKSTR, b_mkstr);
25 vm.register_builtin(ops::MKARR, b_mkarr);
26 vm.register_builtin(ops::MKOBJ, b_mkobj);
27 vm.register_builtin(ops::CALL, b_call);
28 vm.register_builtin(ops::CALL_METHOD, b_call_method);
29 vm.register_builtin(ops::CALL_VALUE, b_call_value);
30 vm.register_builtin(ops::NEW, b_new);
31 vm.register_builtin(ops::TRUTHY, b_truthy);
32 vm.register_builtin(ops::TOSTR, b_tostr);
33 vm.register_builtin(ops::MKFUNC, b_mkfunc);
34 vm.register_builtin(ops::GETITER, b_getiter);
35 vm.register_builtin(ops::FORITER, b_foriter);
36 vm.register_builtin(ops::FORIN_KEYS, b_forin_keys);
37 vm.register_builtin(ops::CONTAINS, b_contains);
38 vm.register_builtin(ops::SIG_RETURN, b_sig_return);
39 vm.register_builtin(ops::BINOP, b_binop);
40 vm.register_builtin(ops::UNARY, b_unary);
41 vm.register_builtin(ops::STRICT_EQ, b_strict_eq);
42 vm.register_builtin(ops::LOOSE_EQ, b_loose_eq);
43 vm.register_builtin(ops::TYPEOF, b_typeof);
44 vm.register_builtin(ops::LOAD_NULL, b_load_null);
45 vm.register_builtin(ops::THROW, b_throw);
46 vm.register_builtin(ops::TRY, b_try);
47 vm.register_builtin(ops::NULLISH, b_nullish);
48 vm.register_builtin(ops::UNPACK, b_unpack);
49 vm.register_builtin(ops::BUILD_ARGS, b_build_args);
50 vm.register_builtin(ops::THIS, b_this);
51 vm.register_builtin(ops::INSTANCEOF, b_instanceof);
52 vm.register_builtin(ops::DELPROP_NAME, b_delprop_name);
53 vm.register_builtin(ops::APPLY, b_apply);
54 vm.register_builtin(ops::APPLY_METHOD, b_apply_method);
55 vm.register_builtin(ops::OBJ_REST, b_obj_rest);
56 vm.register_builtin(ops::DIV, b_div);
57 vm.register_builtin(ops::POW, b_pow);
58 vm.register_builtin(ops::MKCLASS, b_mkclass);
59 vm.register_builtin(ops::DEF_MEMBER, b_def_member);
60 vm.register_builtin(ops::DEF_FIELD, b_def_field);
61 vm.register_builtin(ops::SUPER_CALL, b_super_call);
62 vm.register_builtin(ops::SUPER_GET, b_super_get);
63 vm.register_builtin(ops::YIELD, b_yield);
64 vm.register_builtin(ops::PROPKEY, b_propkey);
65 vm.register_builtin(ops::NEW_TARGET, b_new_target);
66 vm.register_builtin(ops::AWAIT, b_await);
67 vm.register_builtin(ops::DEF_ACCESSOR, b_def_accessor);
68 vm.register_builtin(ops::DBG_LINE, b_dbg_line);
69 vm.register_builtin(ops::MKBIGINT, b_mkbigint);
70 vm.register_builtin(ops::MKREGEX, b_mkregex);
71 vm.register_builtin(ops::TAG_TMPL, b_tag_tmpl);
72 vm.register_builtin(ops::GET_ASYNC_ITER, b_get_async_iter);
73 vm.register_builtin(ops::ASYNC_STEP, b_async_step);
74 vm.register_builtin(ops::NUM_STEP, b_num_step);
75 vm.register_builtin(ops::ITER_CLOSE, b_iter_close);
76 vm.register_builtin(ops::TYPEOF_NAME, b_typeof_name);
77 vm.register_builtin(ops::SIG_BREAK, b_sig_break);
78 vm.register_builtin(ops::SIG_CONTINUE, b_sig_continue);
79 vm.register_builtin(ops::SIG_UNWIND, b_sig_unwind);
80 vm.register_builtin(ops::PUSH_SCOPE, b_push_scope);
81 vm.register_builtin(ops::POP_SCOPE, b_pop_scope);
82 vm.register_builtin(ops::COPY_SCOPE, b_copy_scope);
83 vm.register_builtin(ops::DECLARE_VAR, b_declare_var);
84 vm.register_builtin(ops::NAMED_EVAL, b_named_eval);
85}
86
87pub(crate) fn close_iterator(it: &Value) -> Result<(), String> {
94 if with_host(|h| h.is_generator_val(it)) {
95 host::gen_return(it, Value::Undef)?;
96 return Ok(());
97 }
98 if matches!(with_host(|h| h.get(it).cloned()), Some(JsObj::Object(_))) {
99 if let Some(f) = with_host(|h| host::lookup_chain(h, it, "return")) {
100 if with_host(|h| host::is_callable(h, &f)) {
101 host::invoke(&f, Vec::new(), Some(it.clone()))?;
102 }
103 }
104 }
105 Ok(())
106}
107
108fn b_iter_close(vm: &mut VM, _: u8) -> Value {
109 let it = vm.pop();
110 match close_iterator(&it) {
113 Ok(()) => Value::Undef,
114 Err(e) => abort(vm, e),
115 }
116}
117
118fn b_num_step(vm: &mut VM, _: u8) -> Value {
123 let old = vm.pop();
124 let tag = match vm.pop() {
125 Value::Int(n) => n,
126 Value::Float(f) => f as i64,
127 _ => 1,
128 };
129 if with_host(|h| h.is_bigint_val(&old)) {
130 let b = with_host(|h| h.as_bigint(&old)).unwrap();
131 let old_n = with_host(|h| h.new_bigint(b.clone()));
132 let new = with_host(|h| h.new_bigint(b + num_bigint::BigInt::from(tag)));
133 vm.push(old_n);
134 new
135 } else {
136 let n = with_host(|h| h.to_number(&old));
137 vm.push(Value::Float(n));
138 Value::Float(n + tag as f64)
139 }
140}
141
142fn b_async_step(vm: &mut VM, _: u8) -> Value {
145 let iter = vm.pop();
146 let r = host::async_step(&iter);
147 finish(vm, r)
148}
149
150fn b_mkbigint(vm: &mut VM, _: u8) -> Value {
153 let digits = sval(&vm.pop());
154 match digits.parse::<num_bigint::BigInt>() {
155 Ok(b) => with_host(|h| h.new_bigint(b)),
156 Err(_) => abort(vm, host::type_error("invalid BigInt literal")),
157 }
158}
159
160fn b_tag_tmpl(vm: &mut VM, argc: u8) -> Value {
165 let mut all = pop_n(vm, argc as usize);
166 let int_of = |v: &Value| match v {
167 Value::Int(n) => *n as usize,
168 Value::Float(f) => *f as usize,
169 _ => 0,
170 };
171 let tag = all.remove(0);
172 let n = int_of(&all.remove(0));
173 let mcount = int_of(&all.remove(0));
174 let cooked: Vec<Value> = all.drain(0..n.min(all.len())).collect();
175 let raw: Vec<Value> = all.drain(0..n.min(all.len())).collect();
176 let values: Vec<Value> = all.drain(0..mcount.min(all.len())).collect();
177 let strings = with_host(|h| h.new_array(cooked));
180 let raw_arr = with_host(|h| h.new_array(raw));
181 with_host(|h| {
186 h.set_fn_prop(&strings, "raw", raw_arr);
187 h.set_prop_attrs(
188 &strings,
189 "raw",
190 host::PropAttrs {
191 writable: false,
192 enumerable: false,
193 configurable: false,
194 },
195 );
196 });
197 let mut call_args = vec![strings];
198 call_args.extend(values);
199 let r = host::invoke(&tag, call_args, None);
200 finish(vm, r)
201}
202
203fn b_get_async_iter(vm: &mut VM, _: u8) -> Value {
207 let src = vm.pop();
208 let r = host::get_async_iterator(&src);
209 finish(vm, r)
210}
211
212fn b_mkregex(vm: &mut VM, _: u8) -> Value {
216 let flags = sval(&vm.pop());
217 let pattern = sval(&vm.pop());
218 match crate::regexp::build_regexp(&pattern, &flags) {
219 Ok(v) => v,
220 Err(e) => abort(vm, e),
221 }
222}
223
224fn b_dbg_line(vm: &mut VM, _: u8) -> Value {
230 let line = match vm.pop() {
231 Value::Int(n) => n as u32,
232 _ => 0,
233 };
234 crate::dap::on_debug_line(line);
235 Value::Undef
236}
237
238fn b_def_accessor(vm: &mut VM, _: u8) -> Value {
241 let func = vm.pop();
242 let kind = match vm.pop() {
243 Value::Int(n) => n,
244 _ => 0,
245 };
246 let name = sval(&vm.pop());
247 let obj = vm.pop();
248 with_host(|h| {
249 if kind == host::member::SET {
250 h.set_accessor(&obj, &name, None, Some(func));
251 } else {
252 h.set_accessor(&obj, &name, Some(func), None);
253 }
254 });
255 obj
256}
257
258fn b_await(vm: &mut VM, _: u8) -> Value {
259 let v = vm.pop();
260 match host::await_value(v) {
261 Ok(r) => r,
262 Err(e) => abort(vm, e),
263 }
264}
265
266fn b_mkclass(vm: &mut VM, _: u8) -> Value {
269 let ctor = vm.pop();
270 let parent = vm.pop();
271 let name = sval(&vm.pop());
272 host::build_class(&name, parent, ctor)
273}
274
275fn b_def_member(vm: &mut VM, _: u8) -> Value {
276 let func = vm.pop();
277 let is_static = matches!(vm.pop(), Value::Bool(true));
278 let kind = match vm.pop() {
279 Value::Int(n) => n,
280 _ => 0,
281 };
282 let name = sval(&vm.pop());
283 let class_val = vm.pop();
284 host::define_member(&class_val, &name, kind, is_static, func);
285 class_val
286}
287
288fn b_def_field(vm: &mut VM, _: u8) -> Value {
289 let name_anon = matches!(vm.pop(), Value::Bool(true));
293 let thunk = vm.pop();
294 let name = sval(&vm.pop());
295 let class_val = vm.pop();
296 host::define_field(&class_val, &name, thunk, name_anon);
297 class_val
298}
299
300fn b_super_call(vm: &mut VM, argc: u8) -> Value {
303 let args = pop_n(vm, argc as usize);
304 let this = with_host(|h| h.current_this());
305 let this = match this {
306 Some(t) => t,
307 None => return abort(vm, host::type_error("'super' keyword unexpected here")),
308 };
309 let (parent, fields) = with_host(|h| h.super_context());
311 let (parent, fields) = match parent {
312 Some(p) => (p, fields),
313 None => return abort(vm, host::type_error("'super' keyword unexpected here")),
314 };
315 let nt = with_host(|h| h.current_new_target()).unwrap_or_else(|| this.clone());
316 let r = host::super_construct(&parent, args, &this, &nt);
317 if let Err(e) = r {
318 return abort(vm, e);
319 }
320 for (name, thunk, name_anon) in fields {
322 if let Err(e) = host::init_one_field(&this, &name, &thunk, name_anon) {
323 return abort(vm, e);
324 }
325 }
326 Value::Undef
327}
328
329fn b_super_get(vm: &mut VM, _: u8) -> Value {
331 let name = sval(&vm.pop());
332 match with_host(|h| h.super_resolve(&name)) {
333 host::SuperRef::Data(v) => v,
334 host::SuperRef::Getter(getter) => {
335 let this = with_host(|h| h.current_this());
336 match host::invoke(&getter, Vec::new(), this) {
337 Ok(v) => v,
338 Err(e) => abort(vm, e),
339 }
340 }
341 }
342}
343
344fn close_parked_iters(vm: &mut VM) {
352 let n = host::parked_iters(vm);
353 if n == 0 {
354 return;
355 }
356 let saved = with_host(|h| (h.signal.take(), h.error.take()));
361 for _ in 0..n {
362 let it = vm.pop();
363 let _ = close_iterator(&it);
364 }
365 with_host(|h| {
366 h.signal = saved.0;
367 h.error = saved.1;
368 });
369}
370
371fn b_yield(vm: &mut VM, _: u8) -> Value {
372 let v = vm.pop();
373 match host::gen_yield(v) {
374 Ok(sent) => {
375 if with_host(|h| h.error.is_some() || h.signal.is_some()) {
379 close_parked_iters(vm);
385 vm.ip = vm.chunk.ops.len();
386 }
387 sent
388 }
389 Err(e) => {
394 close_parked_iters(vm);
395 abort(vm, e)
396 }
397 }
398}
399
400fn b_propkey(vm: &mut VM, _: u8) -> Value {
409 let v = vm.pop();
410 match host::to_property_key(&v) {
411 Ok(k) => with_host(|h| h.new_str(k)),
412 Err(e) => abort(vm, e),
413 }
414}
415
416fn b_new_target(_vm: &mut VM, _: u8) -> Value {
417 with_host(|h| h.current_new_target().unwrap_or(Value::Undef))
418}
419
420fn b_div(vm: &mut VM, _: u8) -> Value {
431 let b = vm.pop();
432 let a = vm.pop();
433 let r = numeric_hook(NumOp::Div, &a, &b);
434 finish(vm, r)
435}
436
437fn b_pow(vm: &mut VM, _: u8) -> Value {
442 let b = vm.pop();
443 let a = vm.pop();
444 let r = numeric_hook(NumOp::Pow, &a, &b);
445 finish(vm, r)
446}
447
448fn b_obj_rest(vm: &mut VM, _: u8) -> Value {
450 let excluded = vm.pop();
451 let obj = vm.pop();
452 let excl: Vec<String> = with_host(|h| h.iter_vec(&excluded))
453 .unwrap_or_default()
454 .iter()
455 .map(|v| with_host(|h| h.str_of(v)))
456 .collect();
457 with_host(|h| {
458 let props: IndexMap<String, Value> = match h.get(&obj) {
459 Some(JsObj::Object(m)) => m
460 .iter()
461 .filter(|(k, _)| !excl.contains(k))
462 .map(|(k, v)| (k.clone(), v.clone()))
463 .collect(),
464 _ => IndexMap::new(),
465 };
466 h.new_object(props)
467 })
468}
469
470fn pop_n(vm: &mut VM, n: usize) -> Vec<Value> {
473 let mut v = Vec::with_capacity(n);
474 for _ in 0..n {
475 v.push(vm.pop());
476 }
477 v.reverse();
478 v
479}
480
481fn sval(v: &Value) -> String {
483 if let Value::Str(s) = v {
484 return (**s).clone();
485 }
486 with_host(|h| h.as_str(v)).unwrap_or_default()
487}
488
489fn sname(v: &Value) -> std::sync::Arc<String> {
495 match v {
496 Value::Str(s) => s.clone(),
497 _ => std::sync::Arc::new(sval(v)),
498 }
499}
500
501fn abort(vm: &mut VM, e: String) -> Value {
502 with_host(|h| h.error = Some(e));
503 vm.ip = vm.chunk.ops.len();
504 Value::Undef
505}
506
507fn finish(vm: &mut VM, r: Result<Value, String>) -> Value {
509 match r {
510 Ok(v) => {
511 if with_host(|h| h.error.is_some() || h.signal.is_some()) {
512 vm.ip = vm.chunk.ops.len();
513 }
514 v
515 }
516 Err(e) => abort(vm, e),
517 }
518}
519
520pub(crate) fn global_binding(name: &str) -> Option<Value> {
528 if let Some(v) = with_host(|h| h.read_name(name)) {
529 return Some(v);
530 }
531 match name {
533 "undefined" => return Some(Value::Undef),
534 "NaN" => return Some(Value::Float(f64::NAN)),
535 "Infinity" => return Some(Value::Float(f64::INFINITY)),
536 "globalThis" | "global" => return Some(with_host(|h| h.global_object())),
541 _ => {}
542 }
543 if is_namespace(name) || is_known_builtin(name) {
544 return Some(with_host(|h| h.alloc(JsObj::Builtin(name.to_string()))));
545 }
546 None
547}
548
549fn b_getlocal(vm: &mut VM, _: u8) -> Value {
550 let name = sname(&vm.pop());
551 match global_binding(&name) {
552 Some(v) => v,
553 None => abort(vm, host::ref_error(&name)),
554 }
555}
556
557const READONLY_GLOBALS: [&str; 3] = ["undefined", "NaN", "Infinity"];
561
562fn readonly_global_error(name: &str) -> String {
563 host::type_error(&format!(
564 "Cannot assign to read only property '{name}' of object '#<Object>'"
565 ))
566}
567
568fn b_setlocal(vm: &mut VM, _: u8) -> Value {
569 let val = vm.pop();
570 let name = sname(&vm.pop());
571 if READONLY_GLOBALS.contains(&name.as_str()) && !with_host(|h| h.has_name(&name)) {
575 return val;
576 }
577 if !with_host(|h| h.set_name(&name, val.clone())) {
580 return abort(vm, host::type_error("Assignment to constant variable."));
581 }
582 val
583}
584
585fn b_setlocal_strict(vm: &mut VM, _: u8) -> Value {
594 let val = vm.pop();
595 let name = sname(&vm.pop());
596 if !binding_exists(&name) {
597 return abort(vm, host::ref_error(&name));
598 }
599 if READONLY_GLOBALS.contains(&name.as_str()) && !with_host(|h| h.has_name(&name)) {
600 return abort(vm, readonly_global_error(&name));
601 }
602 if !with_host(|h| h.set_name(&name, val.clone())) {
603 return abort(vm, host::type_error("Assignment to constant variable."));
604 }
605 val
606}
607
608fn binding_exists(name: &str) -> bool {
613 if with_host(|h| h.has_name(name)) {
614 return true;
615 }
616 matches!(
617 name,
618 "undefined" | "NaN" | "Infinity" | "globalThis" | "global"
619 ) || is_namespace(name)
620 || is_known_builtin(name)
621}
622
623fn b_declare(vm: &mut VM, _: u8) -> Value {
624 let val = vm.pop();
625 let name = sname(&vm.pop());
626 with_host(|h| h.declare_name(&name, val.clone()));
627 val
628}
629
630fn b_declare_const(vm: &mut VM, _: u8) -> Value {
633 let val = vm.pop();
634 let name = sname(&vm.pop());
635 with_host(|h| h.declare_const_name(&name, val.clone()));
636 val
637}
638
639fn b_declare_var(vm: &mut VM, _: u8) -> Value {
642 let val = vm.pop();
643 let name = sname(&vm.pop());
644 with_host(|h| h.declare_var_name(&name, val.clone()));
645 val
646}
647
648fn b_push_scope(_: &mut VM, _: u8) -> Value {
649 with_host(|h| h.push_scope());
650 Value::Undef
651}
652
653fn b_pop_scope(_: &mut VM, _: u8) -> Value {
654 with_host(|h| h.pop_scope());
655 Value::Undef
656}
657
658fn b_copy_scope(_: &mut VM, _: u8) -> Value {
659 with_host(|h| h.copy_scope());
660 Value::Undef
661}
662
663fn b_delname(vm: &mut VM, _: u8) -> Value {
664 let name = sval(&vm.pop());
665 with_host(|h| h.del_name(&name));
666 Value::Bool(true)
667}
668
669fn b_this(_vm: &mut VM, _: u8) -> Value {
670 with_host(|h| h.current_this().unwrap_or(Value::Undef))
671}
672
673fn b_load_null(_vm: &mut VM, _: u8) -> Value {
674 with_host(|h| h.null())
675}
676
677fn b_getattr(vm: &mut VM, _: u8) -> Value {
680 let name = sval(&vm.pop());
681 let recv = vm.pop();
682 match get_property(&recv, &name) {
683 Ok(v) => v,
684 Err(e) => abort(vm, e),
685 }
686}
687
688fn peek<R>(recv: &Value, f: impl FnOnce(&JsObj) -> Option<R>) -> Option<R> {
698 with_host(|h| h.get(recv).and_then(f))
699}
700
701pub(crate) fn proxy_proto_link(recv: &Value, name: &str) -> Option<Value> {
708 with_host(|h| {
709 let mut cur = h.proto_of(recv);
710 for _ in 0..100 {
711 let p = cur?;
712 match h.get(&p) {
713 Some(JsObj::Proxy { .. }) => return Some(p),
714 Some(JsObj::Object(props)) if props.contains_key(name) => return None,
715 _ => {}
716 }
717 if h.own_accessor(&p, name).is_some() {
718 return None;
719 }
720 cur = h.proto_of(&p);
721 }
722 None
723 })
724}
725
726pub fn get_property(recv: &Value, name: &str) -> Result<Value, String> {
727 if name.starts_with('#') && !with_host(|h| h.has_private(recv, name)) {
731 return Err(private_brand_message(name, false));
732 }
733 get_property_recv(recv, name, recv)
734}
735
736pub fn private_brand_message(name: &str, writing: bool) -> String {
740 if with_host(|h| h.is_private_method(name)) {
741 if let Some(class) = with_host(|h| h.current_home_class_name()) {
742 return host::type_error(&format!("Receiver must be an instance of class {class}"));
743 }
744 }
745 let verb = if writing { "write" } else { "read" };
746 let prep = if writing { "to" } else { "from" };
747 host::type_error(&format!(
748 "Cannot {verb} private member {name} {prep} an object whose class did not declare it"
749 ))
750}
751
752pub fn get_property_recv(recv: &Value, name: &str, receiver: &Value) -> Result<Value, String> {
758 if let Some(v) = crate::proxy::get(recv, name, receiver)? {
762 return Ok(v);
763 }
764 if with_host(|h| h.is_nullish(recv)) {
765 return Err(host::type_error(&format!(
766 "Cannot read properties of {} (reading '{name}')",
767 with_host(|h| h.str_of(recv))
768 )));
769 }
770 if with_host(|h| h.is_global_object(recv)) {
776 let own = with_host(|h| match h.get(recv) {
777 Some(JsObj::Object(p)) => p.contains_key(name),
778 _ => false,
779 });
780 const CJS_WRAPPER_LOCALS: &[&str] = &[
784 "require",
785 "module",
786 "exports",
787 "__filename",
788 "__dirname",
789 "__cjs_require",
790 "__cjs_resolve",
791 ];
792 if !own && !CJS_WRAPPER_LOCALS.contains(&name) {
793 if let Some(v) = global_binding(name) {
794 return Ok(v);
795 }
796 }
797 }
798 if let Some((getter, _)) = with_host(|h| host::lookup_accessor(h, recv, name)) {
801 return match getter {
802 Some(g) => host::invoke(&g, Vec::new(), Some(receiver.clone())),
803 None => Ok(Value::Undef), };
805 }
806 if name == "@@toStringTag" && with_host(|h| host::lookup_chain(h, recv, name)).is_none() {
813 if let Some(tag) = with_host(|h| well_known_tag(h, recv)) {
814 return Ok(with_host(|h| h.new_str(tag)));
815 }
816 }
817 if name == "constructor" {
822 if let Some(v) = with_host(|h| {
823 match h.get(recv) {
824 Some(JsObj::Object(p)) => p.get("constructor").cloned(),
825 _ => None,
826 }
827 .or_else(|| host::lookup_chain(h, recv, "constructor"))
828 }) {
829 return Ok(v);
830 }
831 if let Some(cn) = with_host(|h| default_ctor_name(h, recv)) {
832 return Ok(with_host(|h| h.alloc(JsObj::Builtin(cn.to_string()))));
833 }
834 }
835 if name == "__proto__"
842 && !with_host(|h| h.has_null_proto(recv))
843 && peek(recv, |o| match o {
844 JsObj::Object(p) => Some(p.contains_key("__proto__")),
845 _ => Some(false),
846 }) != Some(true)
847 {
848 return Ok(prototype_of(recv));
849 }
850 let kind = with_host(|h| h.kind_of(recv));
851 Ok(match kind {
852 Some(ObjKind::Object) => {
853 let numeric = !name.is_empty() && name.bytes().all(|b| b.is_ascii_digit());
854 if numeric && crate::stdlib::native_tag(recv).as_deref() == Some("TypedArray") {
857 if let Some(v) = crate::stdlib::typedarray::elem_get(recv, name) {
858 return Ok(v);
859 }
860 }
861 if numeric
864 && peek(recv, |o| match o {
865 JsObj::Object(p) => Some(p.contains_key("@@bytes")),
866 _ => None,
867 })
868 .unwrap_or(false)
869 {
870 return Ok(crate::stdlib::buffer::byte_get(recv, name));
871 }
872 if let Some(v) = peek(recv, |o| match o {
873 JsObj::Object(p) => p.get(name).cloned(),
874 _ => None,
875 }) {
876 v
877 } else if let Some(link) = proxy_proto_link(recv, name) {
878 return Ok(crate::proxy::get(&link, name, recv)?.expect("link is a proxy"));
885 } else if let Some(v) = with_host(|h| host::lookup_chain(h, recv, name)) {
886 v
888 } else if crate::stdlib::native_tag(recv)
889 .map(|tag| crate::stdlib::instance_has_method(&tag, name))
890 .unwrap_or(false)
891 {
892 bound_method(recv, name)
895 } else if is_object_method(name) && !with_host(|h| h.has_null_proto(recv)) {
896 bound_method(recv, name)
901 } else {
902 Value::Undef
903 }
904 }
905 Some(ObjKind::Class) | Some(ObjKind::Func) | Some(ObjKind::BoundFunc) => {
906 function_property(recv, name)
907 }
908 Some(ObjKind::Symbol) => match name {
909 "description" => {
910 match peek(recv, |o| match o {
911 JsObj::Symbol { desc, .. } => desc.clone(),
912 _ => None,
913 }) {
914 Some(d) => with_host(|h| h.new_str(d)),
915 None => Value::Undef,
916 }
917 }
918 "toString" => bound_method(recv, name),
919 _ => Value::Undef,
920 },
921 Some(ObjKind::BigInt) => {
922 if matches!(
923 name,
924 "toString" | "valueOf" | "toLocaleString" | "constructor"
925 ) {
926 bound_method(recv, name)
927 } else {
928 Value::Undef
929 }
930 }
931 Some(ObjKind::RegExp) => {
932 let r = peek(recv, |o| match o {
936 JsObj::RegExp(r) => Some(r.clone()),
937 _ => None,
938 });
939 match r {
940 Some(r) => crate::regexp::regexp_property(&r, name).unwrap_or_else(|| {
941 if crate::regexp::is_regexp_method(name) {
942 bound_method(recv, name)
943 } else {
944 Value::Undef
945 }
946 }),
947 None => Value::Undef,
948 }
949 }
950 Some(ObjKind::Map) => {
953 let (len, weak) = peek(recv, |o| match o {
954 JsObj::Map { entries, weak } => Some((entries.len(), *weak)),
955 _ => None,
956 })
957 .unwrap_or((0, false));
958 match name {
959 "size" if !weak => Value::Float(len as f64),
960 "@@iterator" => bound_method(recv, name),
961 _ if is_map_method(name) => bound_method(recv, name),
962 _ => Value::Undef,
963 }
964 }
965 Some(ObjKind::Set) => {
966 let (len, weak) = peek(recv, |o| match o {
967 JsObj::Set { entries, weak } => Some((entries.len(), *weak)),
968 _ => None,
969 })
970 .unwrap_or((0, false));
971 match name {
972 "size" if !weak => Value::Float(len as f64),
973 "@@iterator" => bound_method(recv, name),
974 _ if is_set_method(name) => bound_method(recv, name),
975 _ => Value::Undef,
976 }
977 }
978 Some(ObjKind::Generator) => {
979 if is_generator_method(name) {
980 bound_method(recv, name)
981 } else {
982 Value::Undef
983 }
984 }
985 Some(ObjKind::Promise) => {
986 if matches!(name, "then" | "catch" | "finally") {
987 bound_method(recv, name)
988 } else {
989 Value::Undef
990 }
991 }
992 Some(ObjKind::Iter) => {
993 if matches!(name, "next" | "return" | "@@iterator") {
994 bound_method(recv, name)
995 } else {
996 Value::Undef
997 }
998 }
999 Some(ObjKind::Array) => {
1000 if name == "length" {
1001 let n = peek(recv, |o| match o {
1002 JsObj::Array(items) => Some(items.len()),
1003 _ => None,
1004 })
1005 .unwrap_or(0);
1006 Value::Float(n as f64)
1007 } else if let Ok(i) = name.parse::<usize>() {
1008 peek(recv, |o| match o {
1009 JsObj::Array(items) => items.get(i).cloned(),
1010 _ => None,
1011 })
1012 .unwrap_or(Value::Undef)
1013 } else if name == "@@iterator" || is_array_method(name) || is_object_method(name) {
1014 bound_method(recv, name)
1015 } else if let Some(v) = with_host(|h| h.fn_prop(recv, name)) {
1016 v
1019 } else {
1020 Value::Undef
1021 }
1022 }
1023 Some(ObjKind::Str) => {
1024 if name == "length" {
1026 let n = peek(recv, |o| match o {
1027 JsObj::Str(s) => Some(crate::utf16::len(s)),
1028 _ => None,
1029 })
1030 .unwrap_or(0);
1031 Value::Float(n as f64)
1032 } else if let Ok(i) = name.parse::<usize>() {
1033 match peek(recv, |o| match o {
1034 JsObj::Str(s) => crate::utf16::Units::of(s).unit_str(i),
1035 _ => None,
1036 }) {
1037 Some(c) => with_host(|h| h.new_str(c)),
1038 None => Value::Undef,
1039 }
1040 } else if name == "@@iterator" || is_string_method(name) {
1041 bound_method(recv, name)
1042 } else {
1043 Value::Undef
1044 }
1045 }
1046 Some(ObjKind::Builtin) => {
1047 let ns = peek(recv, |o| match o {
1048 JsObj::Builtin(ns) => Some(ns.clone()),
1049 _ => None,
1050 })
1051 .unwrap_or_default();
1052 namespace_property(&ns, name)
1053 }
1054 _ => {
1055 if matches!(recv, Value::Float(_) | Value::Int(_)) && is_number_method(name) {
1057 bound_method(recv, name)
1058 } else {
1059 Value::Undef
1060 }
1061 }
1062 })
1063}
1064
1065pub const REQUIRE_CACHE: &str = "__cjs_cache";
1070
1071fn default_ctor_name(h: &host::JsHost, recv: &Value) -> Option<&'static str> {
1077 match h.get(recv) {
1078 Some(JsObj::Array(_)) => Some("Array"),
1079 Some(JsObj::Object(props)) => {
1080 match props.get("@@native").map(|t| h.str_of(t)).as_deref() {
1086 Some("Buffer") => Some("Buffer"),
1087 Some("URL") => Some("URL"),
1088 Some("Date") => Some("Date"),
1089 Some("WeakRef") => Some("WeakRef"),
1090 Some("FinalizationRegistry") => Some("FinalizationRegistry"),
1091 Some("TextEncoder") => Some("TextEncoder"),
1092 Some("TextDecoder") => Some("TextDecoder"),
1093 Some("EventEmitter") => Some("EventEmitter"),
1094 Some("Timeout") => Some("Timeout"),
1095 Some("Immediate") => Some("Immediate"),
1096 _ => Some("Object"),
1097 }
1098 }
1099 Some(JsObj::Map { weak, .. }) => Some(if *weak { "WeakMap" } else { "Map" }),
1100 Some(JsObj::Set { weak, .. }) => Some(if *weak { "WeakSet" } else { "Set" }),
1101 Some(JsObj::Promise { .. }) => Some("Promise"),
1102 Some(JsObj::Str(_)) => Some("String"),
1103 Some(JsObj::Symbol { .. }) => Some("Symbol"),
1104 Some(JsObj::BigInt(_)) => Some("BigInt"),
1105 Some(JsObj::RegExp(_)) => Some("RegExp"),
1106 Some(JsObj::Iter { .. }) => Some("Iterator"),
1107 Some(JsObj::Func(_)) | Some(JsObj::Class(_)) | Some(JsObj::BoundFunc { .. }) => {
1108 Some("Function")
1109 }
1110 _ => match recv {
1111 Value::Float(_) | Value::Int(_) => Some("Number"),
1112 Value::Bool(_) => Some("Boolean"),
1113 _ => None,
1114 },
1115 }
1116}
1117
1118fn is_builtin_ctor(name: &str) -> bool {
1126 matches!(
1127 name,
1128 "Array"
1129 | "Object"
1130 | "Number"
1131 | "String"
1132 | "Boolean"
1133 | "Symbol"
1134 | "Function"
1135 | "Map"
1136 | "Set"
1137 | "WeakMap"
1138 | "WeakSet"
1139 | "Promise"
1140 | "BigInt"
1141 | "Iterator"
1142 | "RegExp"
1143 | "Date"
1144 | "ArrayBuffer"
1145 | "Uint8Array"
1146 | "Int8Array"
1147 | "Uint8ClampedArray"
1148 | "Int16Array"
1149 | "Uint16Array"
1150 | "Int32Array"
1151 | "Uint32Array"
1152 | "Float32Array"
1153 | "Float64Array"
1154 | "BigInt64Array"
1155 | "BigUint64Array"
1156 | "WeakRef"
1157 | "FinalizationRegistry"
1158 | "TextEncoder"
1159 | "TextDecoder"
1160 | "IncomingMessage"
1161 | "ServerResponse"
1162 | "EventEmitter"
1163 | "Buffer"
1164 | "URL"
1165 | "URLSearchParams"
1166 | "Timeout"
1167 | "Immediate"
1168 ) || host::ERROR_NAMES.contains(&name)
1169}
1170
1171fn bound_method(recv: &Value, name: &str) -> Value {
1172 with_host(|h| {
1173 h.alloc(JsObj::BoundMethod {
1174 recv: recv.clone(),
1175 name: name.to_string(),
1176 })
1177 })
1178}
1179
1180fn is_object_method(name: &str) -> bool {
1182 matches!(
1183 name,
1184 "hasOwnProperty"
1185 | "isPrototypeOf"
1186 | "propertyIsEnumerable"
1187 | "toString"
1188 | "toLocaleString"
1189 | "valueOf"
1190 | "constructor"
1191 )
1192}
1193
1194pub const OBJECT_PROTO_METHODS: &[&str] = &[
1198 "hasOwnProperty",
1199 "isPrototypeOf",
1200 "propertyIsEnumerable",
1201 "toString",
1202 "toLocaleString",
1203 "valueOf",
1204];
1205
1206pub fn is_object_builtin_method(name: &str) -> bool {
1207 matches!(
1208 name,
1209 "hasOwnProperty"
1210 | "isPrototypeOf"
1211 | "propertyIsEnumerable"
1212 | "toString"
1213 | "toLocaleString"
1214 | "valueOf"
1215 )
1216}
1217
1218pub fn object_builtin_method(recv: &Value, name: &str, args: Vec<Value>) -> Result<Value, String> {
1220 match name {
1221 "hasOwnProperty" => {
1222 let k = with_host(|h| h.property_key(&arg0(&args)));
1223 if with_host(|h| h.kind_of(recv)) == Some(ObjKind::Builtin) {
1226 return Ok(Value::Bool(has_property(recv, &k)?));
1227 }
1228 if with_host(|h| h.kind_of(recv)) == Some(ObjKind::Proxy) {
1232 let d = crate::proxy::get_own_descriptor(recv, &k)?.unwrap_or(Value::Undef);
1233 return Ok(Value::Bool(!matches!(d, Value::Undef)));
1234 }
1235 if let Some(hit) = crate::stdlib::typedarray::has_index(recv, &k) {
1240 return Ok(Value::Bool(hit));
1241 }
1242 let has = with_host(|h| match h.get(recv) {
1243 Some(JsObj::Object(p)) => p.contains_key(&k) || h.own_accessor(recv, &k).is_some(),
1244 Some(JsObj::Array(items)) => {
1245 k == "length"
1246 || k.parse::<usize>()
1247 .map(|i| i < items.len() && !h.is_hole(recv, i))
1248 .unwrap_or(false)
1249 }
1250 _ => false,
1251 });
1252 Ok(Value::Bool(has))
1253 }
1254 "isPrototypeOf" => {
1255 let target = arg0(&args);
1256 let mut cur = match crate::proxy::get_prototype_of(&target)? {
1262 Some(p) => Some(p).filter(|p| !with_host(|h| h.is_null(p))),
1263 None => with_host(|h| h.proto_of(&target)),
1264 };
1265 while let Some(p) = cur {
1266 if with_host(|h| h.strict_eq(&p, recv)) {
1267 return Ok(Value::Bool(true));
1268 }
1269 cur = with_host(|h| h.proto_of(&p));
1270 }
1271 Ok(Value::Bool(false))
1272 }
1273 "propertyIsEnumerable" => {
1274 let k = with_host(|h| h.str_of(&arg0(&args)));
1275 if with_host(|h| h.kind_of(recv)) == Some(ObjKind::Proxy) {
1279 let has = crate::proxy::own_enum_string_keys(recv)?.contains(&k);
1280 return Ok(Value::Bool(has));
1281 }
1282 let has = with_host(|h| h.own_enum_key_names(recv).contains(&k));
1283 Ok(Value::Bool(has))
1284 }
1285 "toString" => Ok(with_host(|h| {
1286 let s = h.str_of(recv);
1289 h.new_str(s)
1290 })),
1291 "toLocaleString" => {
1296 let v = host::call_method(recv, "toString", Vec::new())?;
1297 Ok(v)
1298 }
1299 "valueOf" => Ok(recv.clone()),
1300 _ => Err(host::type_error(&format!("{name} is not a function"))),
1301 }
1302}
1303
1304pub fn function_builtin_method(
1308 recv: &Value,
1309 name: &str,
1310 args: &[Value],
1311) -> Result<Option<Value>, String> {
1312 match name {
1313 "call" => {
1314 let this = args.first().cloned();
1315 let rest = args.get(1..).map(|s| s.to_vec()).unwrap_or_default();
1316 Ok(Some(host::invoke(recv, rest, this)?))
1317 }
1318 "apply" => {
1319 let this = args.first().cloned();
1320 let arr = args.get(1).cloned().unwrap_or(Value::Undef);
1321 let call_args = if matches!(arr, Value::Undef) || with_host(|h| h.is_null(&arr)) {
1322 Vec::new()
1323 } else {
1324 with_host(|h| h.iter_vec(&arr)).unwrap_or_default()
1325 };
1326 Ok(Some(host::invoke(recv, call_args, this)?))
1327 }
1328 "bind" => {
1329 let this = args.first().cloned().unwrap_or(Value::Undef);
1330 let pre = args.get(1..).map(|s| s.to_vec()).unwrap_or_default();
1331 Ok(Some(with_host(|h| {
1332 h.alloc(JsObj::BoundFunc {
1333 target: recv.clone(),
1334 this,
1335 args: pre,
1336 })
1337 })))
1338 }
1339 "toString" => Ok(Some(with_host(|h| {
1340 let s = h.str_of(recv);
1341 h.new_str(s)
1342 }))),
1343 _ => Ok(None),
1344 }
1345}
1346
1347fn is_function_method(name: &str) -> bool {
1348 matches!(name, "call" | "apply" | "bind" | "toString")
1349}
1350fn is_map_method(name: &str) -> bool {
1351 matches!(
1352 name,
1353 "get" | "set" | "has" | "delete" | "clear" | "forEach" | "keys" | "values" | "entries"
1354 )
1355}
1356fn is_set_method(name: &str) -> bool {
1357 matches!(
1358 name,
1359 "add" | "has" | "delete" | "clear" | "forEach" | "keys" | "values" | "entries"
1360 )
1361}
1362fn is_generator_method(name: &str) -> bool {
1363 matches!(name, "next" | "return" | "throw")
1364}
1365
1366fn function_property(recv: &Value, name: &str) -> Value {
1369 if with_host(|h| h.kind_of(recv)) == Some(ObjKind::Class) {
1371 if let Some(v) = with_host(|h| h.class_static(recv, name)) {
1372 return v;
1373 }
1374 if let Some(anc) = with_host(|h| h.class_builtin_ancestor(recv)) {
1379 if let Ok(v) = get_property(&anc, name) {
1380 if !matches!(v, Value::Undef) {
1381 return v;
1382 }
1383 }
1384 }
1385 } else if let Some(v) = with_host(|h| h.fn_prop(recv, name)) {
1386 return v;
1387 }
1388 if let Some(v) = with_host(|h| host::lookup_chain(h, recv, name)) {
1392 return v;
1393 }
1394 match name {
1395 "name" => with_host(|h| {
1396 let n = h.callable_name(recv);
1397 h.new_str(n)
1398 }),
1399 "length" => Value::Float(with_host(|h| h.func_arity(recv)) as f64),
1400 "prototype" => ensure_fn_prototype(recv),
1401 _ if is_function_method(name) => bound_method(recv, name),
1402 _ => Value::Undef,
1403 }
1404}
1405
1406fn ensure_fn_prototype(recv: &Value) -> Value {
1410 if let Some(p) = with_host(|h| h.fn_prop(recv, "prototype")) {
1411 return p;
1412 }
1413 if with_host(|h| h.kind_of(recv)) != Some(ObjKind::Func) {
1416 return Value::Undef;
1417 }
1418 if !with_host(|h| h.owns_prototype(recv)) {
1419 return Value::Undef;
1420 }
1421 with_host(|h| {
1422 let proto = h.new_object(IndexMap::new());
1423 if let Some(JsObj::Object(p)) = h.get_mut(&proto) {
1424 p.insert("constructor".to_string(), recv.clone());
1425 }
1426 h.hide_prop(&proto, "constructor");
1427 h.set_fn_prop(recv, "prototype", proto.clone());
1428 proto
1429 })
1430}
1431
1432pub fn namespace_property(ns: &str, name: &str) -> Value {
1435 if ns == REQUIRE_CACHE {
1439 return crate::module::cache_get(name).unwrap_or(Value::Undef);
1440 }
1441 if ns == "require" && name == "cache" {
1444 return with_host(|h| h.alloc(JsObj::Builtin(REQUIRE_CACHE.to_string())));
1445 }
1446 let konst = match (ns, name) {
1448 ("Math", "PI") => Some(std::f64::consts::PI),
1449 ("Math", "E") => Some(std::f64::consts::E),
1450 ("Math", "LN2") => Some(std::f64::consts::LN_2),
1451 ("Math", "LN10") => Some(std::f64::consts::LN_10),
1452 ("Math", "LOG2E") => Some(std::f64::consts::LOG2_E),
1453 ("Math", "LOG10E") => Some(std::f64::consts::LOG10_E),
1454 ("Math", "SQRT2") => Some(std::f64::consts::SQRT_2),
1455 ("Math", "SQRT1_2") => Some(std::f64::consts::FRAC_1_SQRT_2),
1456 ("Number", "MAX_SAFE_INTEGER") => Some(9007199254740991.0),
1457 ("Number", "MIN_SAFE_INTEGER") => Some(-9007199254740991.0),
1458 ("Number", "MAX_VALUE") => Some(f64::MAX),
1459 ("Number", "MIN_VALUE") => Some(f64::from_bits(1)),
1464 ("Number", "EPSILON") => Some(f64::EPSILON),
1465 ("Number", "POSITIVE_INFINITY") => Some(f64::INFINITY),
1466 ("Number", "NEGATIVE_INFINITY") => Some(f64::NEG_INFINITY),
1467 ("Number", "NaN") => Some(f64::NAN),
1468 _ => None,
1469 };
1470 if let Some(k) = konst {
1471 return Value::Float(k);
1472 }
1473 if name == "name" && is_builtin_ctor(ns) {
1477 return with_host(|h| h.new_str(ns.to_string()));
1478 }
1479 if ns == "Symbol" && host::WELL_KNOWN_SYMBOLS.contains(&name) {
1482 return with_host(|h| h.well_known_symbol(name));
1483 }
1484 if let Some(v) = crate::stdlib::constant(ns, name) {
1487 return v;
1488 }
1489 if name == "prototype" && is_builtin_ctor(ns) {
1494 if host::ERROR_NAMES.contains(&ns) {
1502 if let Some(p) = with_host(|h| {
1503 h.ensure_error_protos();
1504 host::error_proto_of(h, ns)
1505 }) {
1506 return p;
1507 }
1508 }
1509 if let Some(p) = with_host(|h| {
1514 h.ensure_native_protos();
1515 h.native_proto(ns)
1516 }) {
1517 return p;
1518 }
1519 let _ = ns;
1520 return with_host(|h| h.alloc(JsObj::Builtin(format!("{ns}.prototype"))));
1521 }
1522 if name == "prototype" {
1530 if let Some(p) = with_host(|h| h.ensure_ctor_proto(ns)) {
1531 return p;
1532 }
1533 }
1534 if let Some(ctor) = ns.strip_suffix(".prototype") {
1538 return with_host(|h| h.alloc(JsObj::Builtin(format!("@proto:{ctor}:{name}"))));
1539 }
1540 let qualified = format!("{ns}.{name}");
1541 if is_known_builtin(&qualified) {
1542 return with_host(|h| h.alloc(JsObj::Builtin(qualified)));
1543 }
1544 if let Some(v) = with_host(|h| h.builtin_static(ns, name)) {
1546 return v;
1547 }
1548 Value::Undef
1549}
1550
1551pub fn proto_method(recv: &Value, ctor_method: &str, args: Vec<Value>) -> Result<Value, String> {
1557 let (ctor, method) = ctor_method.split_once(':').unwrap_or(("", ctor_method));
1558 if ctor == "Error" && method == "toString" {
1561 let s = with_host(|h| h.error_to_string(recv)).unwrap_or_else(|| {
1562 with_host(|h| {
1563 let name = host::lookup_chain(h, recv, "name")
1564 .map(|n| h.str_of(&n))
1565 .unwrap_or_else(|| "Error".into());
1566 let msg = host::lookup_chain(h, recv, "message")
1567 .map(|m| h.str_of(&m))
1568 .unwrap_or_default();
1569 if msg.is_empty() {
1570 name
1571 } else {
1572 format!("{name}: {msg}")
1573 }
1574 })
1575 });
1576 return Ok(with_host(|h| h.new_str(s)));
1577 }
1578 if ctor == "Object" && method == "toString" {
1579 let tagged = with_host(|h| h.kind_of(recv)) == Some(ObjKind::Proxy)
1589 || with_host(|h| {
1590 host::lookup_chain(h, recv, "@@toStringTag").is_some()
1591 || host::lookup_accessor(h, recv, "@@toStringTag").is_some()
1592 });
1593 if tagged {
1594 let t = get_property(recv, "@@toStringTag")?;
1595 if let Some(s) = with_host(|h| h.as_str(&t)) {
1596 return Ok(with_host(|h| h.new_str(format!("[object {s}]"))));
1597 }
1598 }
1599 return Ok(with_host(|h| h.new_str(object_tag(h, recv))));
1600 }
1601 if ctor == "Object" && is_object_builtin_method(method) {
1605 return object_builtin_method(recv, method, args);
1606 }
1607 if ctor == "EventEmitter" {
1611 return crate::stdlib::events::instance_call(recv, method, args);
1612 }
1613 if ctor == "Buffer" && crate::stdlib::native_tag(recv).as_deref() == Some("Buffer") {
1618 return crate::stdlib::buffer::instance_call(recv, method, &args);
1619 }
1620 if ctor == "Uint8Array" || ctor == "TypedArray" {
1626 match crate::stdlib::native_tag(recv).as_deref() {
1627 Some("Buffer") => return crate::stdlib::buffer::instance_call(recv, method, &args),
1628 Some("TypedArray") => {
1629 return crate::stdlib::typedarray::instance_call(recv, method, &args)
1630 }
1631 _ => {}
1632 }
1633 }
1634 if ctor == "Array" && with_host(|h| h.kind_of(recv)) != Some(ObjKind::Array) {
1640 return array_generic(recv, method, args);
1641 }
1642 if crate::stdlib::native_tag(recv).as_deref() == Some(ctor) {
1648 return crate::stdlib::instance_call(ctor, recv, method, args);
1649 }
1650 host::call_method(recv, method, args)
1651}
1652
1653fn well_known_tag(h: &host::JsHost, v: &Value) -> Option<String> {
1666 let tag = object_brand(h, v);
1669 const NO_TAG: &[&str] = &[
1670 "Undefined",
1671 "Null",
1672 "Boolean",
1673 "Number",
1674 "String",
1675 "Array",
1676 "Function",
1677 "Object",
1678 "Date",
1679 "RegExp",
1680 "Error",
1681 ];
1682 if NO_TAG.contains(&tag.as_str()) {
1683 return None;
1684 }
1685 Some(tag)
1686}
1687
1688fn object_tag(h: &host::JsHost, v: &Value) -> String {
1694 format!("[object {}]", object_brand(h, v))
1695}
1696
1697fn object_brand(h: &host::JsHost, v: &Value) -> String {
1701 let tag: String = match v {
1702 Value::Undef => "Undefined".into(),
1703 Value::Bool(_) => "Boolean".into(),
1704 Value::Int(_) | Value::Float(_) => "Number".into(),
1705 Value::Str(_) => "String".into(),
1706 Value::Obj(_) => match h.get(v) {
1707 Some(JsObj::Null) => "Null".into(),
1708 Some(JsObj::Str(_)) => "String".into(),
1709 Some(JsObj::Array(_)) => "Array".into(),
1710 Some(JsObj::Proxy { target, .. }) => {
1716 let mut cur = target;
1717 for _ in 0..100 {
1718 match h.get(cur) {
1719 Some(JsObj::Proxy { target: t, .. }) => cur = t,
1720 _ => break,
1721 }
1722 }
1723 match h.get(cur) {
1724 Some(JsObj::Array(_)) => "Array".into(),
1725 _ => "Object".into(),
1726 }
1727 }
1728 Some(JsObj::Func(f)) => match h.funcs.get(f.def_id) {
1731 Some(d) if d.is_generator && d.is_async => "AsyncGeneratorFunction".into(),
1732 Some(d) if d.is_generator => "GeneratorFunction".into(),
1733 Some(d) if d.is_async => "AsyncFunction".into(),
1734 _ => "Function".into(),
1735 },
1736 Some(JsObj::Builtin(n)) if matches!(n.as_str(), "Math" | "JSON" | "Reflect") => {
1739 n.clone()
1740 }
1741 Some(JsObj::Class(_))
1742 | Some(JsObj::Builtin(_))
1743 | Some(JsObj::BoundFunc { .. })
1744 | Some(JsObj::BoundMethod { .. }) => "Function".into(),
1745 Some(JsObj::Generator { .. }) if h.is_async_gen_val(v) => "AsyncGenerator".into(),
1748 Some(JsObj::Generator { .. }) => "Generator".into(),
1749 Some(JsObj::RegExp(_)) => "RegExp".into(),
1750 Some(JsObj::Map { weak, .. }) => if *weak { "WeakMap" } else { "Map" }.into(),
1751 Some(JsObj::Set { weak, .. }) => if *weak { "WeakSet" } else { "Set" }.into(),
1752 Some(JsObj::Promise { .. }) => "Promise".into(),
1753 Some(JsObj::Symbol { .. }) => "Symbol".into(),
1754 Some(JsObj::BigInt(_)) => "BigInt".into(),
1755 Some(JsObj::Object(p)) => match p.get("@@native").map(|t| h.str_of(t)).as_deref() {
1758 Some("TypedArray") => p
1759 .get("@@kind")
1760 .map(|k| h.str_of(k))
1761 .unwrap_or_else(|| "Uint8Array".into()),
1762 Some("Buffer") => "Uint8Array".into(),
1763 Some(
1771 t @ ("ArrayBuffer"
1772 | "DataView"
1773 | "Date"
1774 | "WeakRef"
1775 | "FinalizationRegistry"
1776 | "TextEncoder"
1777 | "TextDecoder"
1778 | "URL"
1779 | "URLSearchParams"),
1780 ) => t.into(),
1781 _ if h.error_to_string(v).is_some() => "Error".into(),
1782 _ => "Object".into(),
1783 },
1784 _ => "Object".into(),
1785 },
1786 _ => "Object".into(),
1789 };
1790 tag
1791}
1792
1793fn b_setattr(vm: &mut VM, _: u8) -> Value {
1794 let val = vm.pop();
1795 let name = sval(&vm.pop());
1796 let recv = vm.pop();
1797 if let Err(e) = set_property(&recv, &name, val.clone()) {
1798 return abort(vm, e);
1799 }
1800 val
1801}
1802
1803fn b_named_eval(vm: &mut VM, _: u8) -> Value {
1815 let func = vm.pop();
1816 let kind = vm.pop().to_int();
1817 let key = vm.pop();
1818 let key = sval(&key);
1819 let base = match with_host(|h| h.symbol_of_key(&key)) {
1822 Some(sym) => match with_host(|h| h.get(&sym).cloned()) {
1823 Some(JsObj::Symbol {
1824 desc: Some(desc), ..
1825 }) => format!("[{desc}]"),
1826 _ => String::new(),
1827 },
1828 None => key,
1829 };
1830 let name = match kind {
1831 host::member::GET => format!("get {base}"),
1832 host::member::SET => format!("set {base}"),
1833 _ => base,
1834 };
1835 with_host(|h| {
1836 let s = h.new_str(name);
1837 h.set_fn_prop(&func, "name", s);
1838 });
1839 func
1840}
1841
1842pub fn set_property_pub(recv: &Value, name: &str, val: Value) -> Result<(), String> {
1845 set_property(recv, name, val)
1846}
1847
1848fn set_property(recv: &Value, name: &str, val: Value) -> Result<(), String> {
1849 if name.starts_with('#') && !with_host(|h| h.has_private(recv, name)) {
1853 return Err(private_brand_message(name, true));
1854 }
1855 if crate::proxy::set(recv, name, &val, recv)? {
1857 return Ok(());
1858 }
1859 if with_host(|h| h.is_global_object(recv)) && !name.starts_with("@@") {
1863 with_host(|h| h.set_name(name, val.clone()));
1864 }
1865 if name == "__proto__" && with_host(|h| h.kind_of(recv)) == Some(ObjKind::Object) {
1873 if with_host(|h| h.has_null_proto(recv)) {
1874 } else {
1876 let assignable =
1877 with_host(|h| h.is_null(&val) || matches!(h.kind_of(&val), Some(ObjKind::Object)));
1878 if assignable {
1879 with_host(|h| h.set_proto(recv, val));
1880 }
1881 return Ok(());
1882 }
1883 }
1884 if !with_host(|h| h.can_write_prop(recv, name)) {
1887 return Ok(());
1888 }
1889 if let Some((_, Some(setter))) = with_host(|h| host::lookup_accessor(h, recv, name)) {
1891 let _ = host::invoke(&setter, vec![val], Some(recv.clone()));
1892 return Ok(());
1893 }
1894 if let Some((Some(_), None)) = with_host(|h| host::lookup_accessor(h, recv, name)) {
1896 return Ok(());
1897 }
1898 if matches!(
1900 with_host(|h| h.kind_of(recv)),
1901 Some(ObjKind::Func) | Some(ObjKind::Class)
1902 ) {
1903 with_host(|h| h.set_fn_prop(recv, name, val));
1904 return Ok(());
1905 }
1906 if let Some(ns) = peek(recv, |o| match o {
1910 JsObj::Builtin(ns) => Some(ns.clone()),
1911 _ => None,
1912 }) {
1913 if ns == "process" && name == "exitCode" {
1919 return crate::stdlib::process::set_exit_code(&val);
1920 }
1921 with_host(|h| h.set_builtin_static(&ns, name, val));
1922 return Ok(());
1923 }
1924 if name == "lastIndex" {
1926 if let Some(n) = with_host(|h| match h.get(recv) {
1927 Some(JsObj::RegExp(_)) => Some(h.to_number(&val)),
1928 _ => None,
1929 }) {
1930 with_host(|h| {
1931 if let Some(JsObj::RegExp(r)) = h.get_mut(recv) {
1932 r.last_index = if n.is_finite() && n >= 0.0 {
1933 crate::utf16::U16Index::new(n as usize)
1934 } else {
1935 crate::utf16::U16Index::ZERO
1936 };
1937 }
1938 });
1939 return Ok(());
1940 }
1941 }
1942 if !name.is_empty() && name.bytes().all(|b| b.is_ascii_digit()) {
1944 let is_ta = crate::stdlib::native_tag(recv).as_deref() == Some("TypedArray");
1945 if is_ta && crate::stdlib::typedarray::elem_set(recv, name, &val)? {
1946 return Ok(());
1947 }
1948 if crate::stdlib::buffer::byte_set(recv, name, &val) {
1950 return Ok(());
1951 }
1952 }
1953 if with_host(|h| h.kind_of(recv)) == Some(ObjKind::Array)
1955 && name != "length"
1956 && name.parse::<usize>().is_err()
1957 {
1958 with_host(|h| h.set_fn_prop(recv, name, val));
1959 return Ok(());
1960 }
1961 let new_len = if name == "length" && with_host(|h| h.kind_of(recv)) == Some(ObjKind::Array) {
1965 Some(host::to_array_length(&val)?)
1966 } else {
1967 None
1968 };
1969 with_host(|h| match h.get_mut(recv) {
1970 Some(JsObj::Object(props)) => {
1971 let is_new = !props.contains_key(name);
1974 props.insert(name.to_string(), val);
1975 if is_new && host::array_index(name).is_some() {
1976 host::canonicalize_own_keys(props);
1977 }
1978 }
1979 Some(JsObj::Array(items)) => {
1980 if let Some(n) = new_len {
1981 let old = items.len();
1984 items.resize(n, Value::Undef);
1985 if n > old {
1986 h.mark_hole_range(recv, old..n);
1987 } else {
1988 h.truncate_holes(recv, n);
1989 }
1990 } else if let Ok(i) = name.parse::<usize>() {
1991 let old = items.len();
1993 if i >= old {
1994 items.resize(i + 1, Value::Undef);
1995 }
1996 items[i] = val;
1997 if i > old {
1998 h.mark_hole_range(recv, old..i);
1999 }
2000 h.clear_hole(recv, i);
2005 }
2006 }
2007 _ => {}
2008 });
2009 Ok(())
2010}
2011
2012fn b_getitem(vm: &mut VM, _: u8) -> Value {
2013 let idx = vm.pop();
2014 let recv = vm.pop();
2015 let key = match host::to_property_key(&idx) {
2016 Ok(k) => k,
2017 Err(e) => return abort(vm, e),
2018 };
2019 match get_property(&recv, &key) {
2020 Ok(v) => v,
2021 Err(e) => abort(vm, e),
2022 }
2023}
2024
2025fn b_setitem(vm: &mut VM, _: u8) -> Value {
2026 let val = vm.pop();
2027 let idx = vm.pop();
2028 let recv = vm.pop();
2029 let key = match host::to_property_key(&idx) {
2030 Ok(k) => k,
2031 Err(e) => return abort(vm, e),
2032 };
2033 if let Err(e) = set_property(&recv, &key, val.clone()) {
2034 return abort(vm, e);
2035 }
2036 val
2037}
2038
2039pub fn delete_property(recv: &Value, key: &str) -> Result<bool, String> {
2045 if let Some(b) = crate::proxy::delete(recv, key)? {
2048 return Ok(b);
2049 }
2050 if peek(recv, |o| match o {
2053 JsObj::Builtin(ns) => Some(ns == REQUIRE_CACHE),
2054 _ => None,
2055 }) == Some(true)
2056 {
2057 return Ok(crate::module::cache_delete(key));
2058 }
2059 if !with_host(|h| h.prop_attrs(recv, key).configurable) {
2060 return Ok(false);
2061 }
2062 with_host(|h| {
2063 let index = key.parse::<usize>();
2064 match h.get_mut(recv) {
2065 Some(JsObj::Object(props)) => {
2066 props.shift_remove(key);
2067 return;
2068 }
2069 Some(JsObj::Array(items)) => {
2070 if let Ok(i) = index {
2071 if i < items.len() {
2072 items[i] = Value::Undef;
2075 h.mark_hole(recv, i);
2076 }
2077 return;
2078 }
2079 }
2080 _ => {}
2081 }
2082 h.remove_fn_prop(recv, key);
2085 });
2086 Ok(true)
2087}
2088
2089fn b_delitem(vm: &mut VM, _: u8) -> Value {
2090 let idx = vm.pop();
2091 let recv = vm.pop();
2092 let key = match host::to_property_key(&idx) {
2096 Ok(k) => k,
2097 Err(e) => return abort(vm, e),
2098 };
2099 match delete_property(&recv, &key) {
2100 Ok(b) => Value::Bool(b),
2101 Err(e) => abort(vm, e),
2102 }
2103}
2104
2105fn b_delprop_name(vm: &mut VM, _: u8) -> Value {
2106 let name = sval(&vm.pop());
2107 let recv = vm.pop();
2108 match delete_property(&recv, &name) {
2109 Ok(b) => Value::Bool(b),
2110 Err(e) => abort(vm, e),
2111 }
2112}
2113
2114fn b_mkstr(vm: &mut VM, argc: u8) -> Value {
2117 let parts = pop_n(vm, argc as usize);
2118 let s: String = with_host(|h| parts.iter().map(|p| h.str_of(p)).collect());
2119 with_host(|h| h.new_str(s))
2120}
2121
2122fn b_mkarr(vm: &mut VM, argc: u8) -> Value {
2123 let items = pop_n(vm, argc as usize);
2124 with_host(|h| h.new_array(items))
2125}
2126
2127fn b_mark_hole(vm: &mut VM, _: u8) -> Value {
2132 let idx = vm.pop();
2133 let arr = vm.pop();
2134 let i = match idx {
2135 Value::Int(i) if i >= 0 => i as usize,
2136 _ => return Value::Undef,
2137 };
2138 with_host(|h| h.mark_hole(&arr, i));
2139 Value::Undef
2140}
2141
2142fn b_mkobj(vm: &mut VM, argc: u8) -> Value {
2143 let flat = pop_n(vm, argc as usize);
2144 let mut props: IndexMap<String, Value> = IndexMap::new();
2145 let mut proto_override: Option<Value> = None;
2147 let mut i = 0;
2148 while i + 2 < flat.len() || (i + 2 == flat.len() && flat.len() % 3 == 0 && i < flat.len()) {
2149 if i + 2 >= flat.len() {
2150 break;
2151 }
2152 if matches!(flat[i], Value::Int(2)) {
2158 let key = with_host(|h| h.str_of(&flat[i + 1]));
2159 props
2160 .entry(format!("{}{key}", host::ORD_MARKER))
2161 .or_insert(Value::Undef);
2162 i += 3;
2163 continue;
2164 }
2165 let spread = matches!(flat[i], Value::Int(1));
2166 if spread {
2167 let src = flat[i + 1].clone();
2168 if let Some(s) = with_host(|h| h.as_str(&src)) {
2177 for idx in 0..crate::utf16::len(&s) {
2178 if let Ok(ch) = get_property(&src, &idx.to_string()) {
2179 props.insert(idx.to_string(), ch);
2180 }
2181 }
2182 i += 3;
2183 continue;
2184 }
2185 let entries = host::own_enum_entries_deep(&src);
2190 for (k, v) in entries {
2191 props.insert(k, v);
2192 }
2193 for (k, v) in with_host(|h| h.own_symbol_entries(&src)) {
2196 props.insert(k, v);
2197 }
2198 } else {
2199 let key = with_host(|h| h.str_of(&flat[i + 1]));
2200 if key == "__proto__" {
2201 proto_override = Some(flat[i + 2].clone());
2202 } else {
2203 props.insert(key, flat[i + 2].clone());
2204 }
2205 }
2206 i += 3;
2207 }
2208 with_host(|h| {
2209 let o = h.new_object(props);
2210 if let Some(p) = proto_override {
2211 if matches!(p, Value::Obj(_)) {
2212 h.set_proto(&o, p);
2213 }
2214 }
2215 o
2216 })
2217}
2218
2219fn b_mkfunc(vm: &mut VM, _: u8) -> Value {
2220 let def_id = match vm.pop() {
2221 Value::Int(n) => n as usize,
2222 Value::Float(f) => f as usize,
2223 _ => return abort(vm, "internal: MKFUNC id".into()),
2224 };
2225 let (is_arrow, self_name) = with_host(|h| match h.funcs.get(def_id) {
2226 Some(d) => (
2227 d.is_arrow,
2228 (d.self_name && !d.name.is_empty()).then(|| d.name.clone()),
2229 ),
2230 None => (false, None),
2231 });
2232 with_host(|h| {
2233 let mut env = h.current_env_capture();
2234 let this = h.current_this();
2235 if self_name.is_some() {
2239 env = host::child_env(env);
2240 }
2241 let f = h.alloc(JsObj::Func(FuncVal {
2242 def_id,
2243 env: Some(env.clone()),
2244 this,
2245 is_arrow,
2246 home_class: None,
2247 }));
2248 if let Some(n) = self_name {
2249 env.borrow_mut().vars.insert(n, f.clone());
2250 }
2251 f
2252 })
2253}
2254
2255fn b_truthy(vm: &mut VM, _: u8) -> Value {
2258 let v = vm.pop();
2259 Value::Bool(with_host(|h| h.truthy(&v)))
2260}
2261
2262fn b_nullish(vm: &mut VM, _: u8) -> Value {
2263 let v = vm.pop();
2264 Value::Bool(with_host(|h| h.is_nullish(&v)))
2265}
2266
2267fn b_tostr(vm: &mut VM, _: u8) -> Value {
2268 let v = vm.pop();
2269 match host::to_string_value(&v) {
2272 Ok(s) => s,
2273 Err(e) => abort(vm, e),
2274 }
2275}
2276
2277fn b_typeof(vm: &mut VM, _: u8) -> Value {
2278 let v = vm.pop();
2279 with_host(|h| {
2280 let t = h.type_of(&v);
2281 h.new_str(t)
2282 })
2283}
2284
2285fn b_typeof_name(vm: &mut VM, _: u8) -> Value {
2288 let name = sval(&vm.pop());
2289 if let Some(v) = with_host(|h| h.read_name(&name)) {
2291 return with_host(|h| {
2292 let t = h.type_of(&v);
2293 h.new_str(t)
2294 });
2295 }
2296 let t = match name.as_str() {
2300 "undefined" => "undefined".to_string(),
2301 "NaN" | "Infinity" => "number".to_string(),
2302 "globalThis" | "global" => "object".to_string(),
2303 n if is_namespace(n) || is_known_builtin(n) => {
2304 let v = with_host(|h| h.alloc(JsObj::Builtin(name.clone())));
2305 with_host(|h| h.type_of(&v)).to_string()
2306 }
2307 _ => "undefined".to_string(), };
2309 with_host(|h| h.new_str(t))
2310}
2311
2312fn b_strict_eq(vm: &mut VM, _: u8) -> Value {
2313 let b = vm.pop();
2314 let a = vm.pop();
2315 Value::Bool(with_host(|h| h.strict_eq(&a, &b)))
2316}
2317
2318fn b_loose_eq(vm: &mut VM, _: u8) -> Value {
2319 let b = vm.pop();
2320 let a = vm.pop();
2321 let (a, b) = match with_host(|h| (host::is_primitive(h, &a), host::is_primitive(h, &b))) {
2325 (false, true) if coerces_against_object(&b) => match host::to_primitive(&a, "default") {
2326 Ok(p) => (p, b),
2327 Err(e) => return abort(vm, e),
2328 },
2329 (true, false) if coerces_against_object(&a) => match host::to_primitive(&b, "default") {
2330 Ok(p) => (a, p),
2331 Err(e) => return abort(vm, e),
2332 },
2333 _ => (a, b),
2334 };
2335 Value::Bool(with_host(|h| h.loose_eq(&a, &b)))
2336}
2337
2338fn b_instanceof(vm: &mut VM, _: u8) -> Value {
2339 let ctor = vm.pop();
2340 let obj = vm.pop();
2341 match host::instance_of(&obj, &ctor) {
2342 Ok(b) => Value::Bool(b),
2343 Err(e) => abort(vm, e),
2344 }
2345}
2346
2347fn b_binop(vm: &mut VM, _: u8) -> Value {
2350 let b = vm.pop();
2351 let a = vm.pop();
2352 let tag = match vm.pop() {
2353 Value::Int(n) => n,
2354 _ => 0,
2355 };
2356 let r = host::to_primitive(&a, "number")
2359 .and_then(|a| host::to_primitive(&b, "number").map(|b| (a, b)))
2360 .and_then(|(a, b)| with_host(|h| h.bitwise(tag, &a, &b)));
2361 finish(vm, r)
2362}
2363
2364fn b_unary(vm: &mut VM, _: u8) -> Value {
2365 let v = vm.pop();
2366 let tag = match vm.pop() {
2367 Value::Int(n) => n,
2368 _ => 0,
2369 };
2370 if with_host(|h| h.is_bigint_val(&v)) {
2373 return match tag {
2374 host::unop::POS => abort(
2375 vm,
2376 host::type_error("Cannot convert a BigInt value to a number"),
2377 ),
2378 host::unop::BITNOT => {
2379 let b = with_host(|h| h.as_bigint(&v)).unwrap();
2380 let r = -(b + num_bigint::BigInt::from(1));
2381 with_host(|h| h.new_bigint(r))
2382 }
2383 _ => Value::Undef,
2384 };
2385 }
2386 let n = match host::to_number_value(&v) {
2389 Ok(n) => n,
2390 Err(e) => return abort(vm, e),
2391 };
2392 match tag {
2393 host::unop::POS => Value::Float(n),
2394 host::unop::BITNOT => {
2395 let i = if n.is_finite() {
2396 n.trunc() as i64 as i32
2397 } else {
2398 0
2399 };
2400 Value::Float(!i as f64)
2401 }
2402 _ => Value::Undef,
2403 }
2404}
2405
2406fn b_contains(vm: &mut VM, _: u8) -> Value {
2409 let container = vm.pop();
2410 let key = vm.pop();
2411 if !matches!(container, Value::Obj(_)) {
2414 let (k, c) = with_host(|h| (h.property_key(&key), h.str_of(&container)));
2415 return abort(
2416 vm,
2417 host::type_error(&format!(
2418 "Cannot use 'in' operator to search for '{k}' in {c}"
2419 )),
2420 );
2421 }
2422 let k = with_host(|h| h.property_key(&key));
2423 match has_property(&container, &k) {
2424 Ok(b) => Value::Bool(b),
2425 Err(e) => abort(vm, e),
2426 }
2427}
2428
2429fn b_sig_return(vm: &mut VM, _: u8) -> Value {
2432 let v = vm.pop();
2433 with_host(|h| h.signal = Some(host::Signal::Return(v.clone())));
2434 vm.ip = vm.chunk.ops.len();
2435 v
2436}
2437
2438fn b_sig_break(vm: &mut VM, _: u8) -> Value {
2442 let label = sval(&vm.pop());
2443 let label = (!label.is_empty()).then_some(label);
2444 with_host(|h| h.signal = Some(host::Signal::Break(label)));
2445 vm.ip = vm.chunk.ops.len();
2446 Value::Undef
2447}
2448
2449fn b_sig_continue(vm: &mut VM, _: u8) -> Value {
2451 let label = sval(&vm.pop());
2452 let label = (!label.is_empty()).then_some(label);
2453 with_host(|h| h.signal = Some(host::Signal::Continue(label)));
2454 vm.ip = vm.chunk.ops.len();
2455 Value::Undef
2456}
2457
2458fn b_sig_unwind(vm: &mut VM, _: u8) -> Value {
2470 let cont_tag = sval(&vm.pop());
2471 let brk_tag = sval(&vm.pop());
2472 let sig = match with_host(|h| h.signal.clone()) {
2473 Some(s) => s,
2474 None => return Value::Int(host::unwind::NONE),
2475 };
2476 let propagate = |vm: &mut VM| {
2478 vm.ip = vm.chunk.ops.len();
2479 Value::Int(host::unwind::NONE)
2480 };
2481 match &sig {
2482 host::Signal::Return(_) => propagate(vm),
2483 host::Signal::Break(label) => {
2484 if brk_tag == host::unwind::NO_LOOP {
2485 return propagate(vm);
2486 }
2487 let mine = match label {
2488 None => true, Some(l) => brk_tag == *l,
2490 };
2491 if mine {
2492 with_host(|h| h.signal = None);
2493 }
2494 Value::Int(host::unwind::BREAK)
2497 }
2498 host::Signal::Continue(label) => {
2499 let mine = match label {
2500 None => cont_tag != host::unwind::NO_LOOP,
2503 Some(l) => cont_tag == *l,
2504 };
2505 if mine {
2506 with_host(|h| h.signal = None);
2507 return Value::Int(host::unwind::CONTINUE);
2508 }
2509 if brk_tag == host::unwind::NO_LOOP {
2510 return propagate(vm);
2511 }
2512 Value::Int(host::unwind::BREAK)
2515 }
2516 }
2517}
2518
2519fn b_throw(vm: &mut VM, _: u8) -> Value {
2520 let v = vm.pop();
2521 let msg = with_host(|h| {
2522 h.exc = Some(v.clone());
2523 error_display(h, &v)
2525 });
2526 abort(vm, msg)
2527}
2528
2529fn error_display(h: &host::JsHost, v: &Value) -> String {
2530 if let Some(JsObj::Object(props)) = h.get(v) {
2531 let name = props
2532 .get("name")
2533 .map(|x| h.str_of(x))
2534 .unwrap_or_else(|| "Error".into());
2535 if let Some(m) = props.get("message") {
2536 return format!("Uncaught {name}: {}", h.str_of(m));
2537 }
2538 }
2539 format!("Uncaught {}", h.str_of(v))
2540}
2541
2542fn b_try(vm: &mut VM, _: u8) -> Value {
2543 let id = match vm.pop() {
2544 Value::Int(n) => n as usize,
2545 _ => return abort(vm, "internal: TRY id".into()),
2546 };
2547 let (has_handler, catch_bind, has_finalizer) = match with_host(|h| h.try_shape(id)) {
2551 Some(t) => t,
2552 None => return abort(vm, "internal: unknown try id".into()),
2553 };
2554 let mut pending: Option<String> = None;
2555 let scope = with_host(|h| h.scope_snapshot());
2559
2560 with_host(|h| h.push_scope()); let body_res = host::run_chunk_keyed(host::try_key(id, 0), || {
2562 with_host(|h| h.try_chunk(id, 0)).expect("try block exists")
2563 });
2564 with_host(|h| h.restore_scope(scope.clone()));
2565 let signal_after = with_host(|h| h.signal.is_some());
2566 if let Err(e) = body_res {
2567 if signal_after {
2568 pending = Some(e);
2569 } else if has_handler {
2570 let thrown =
2572 with_host(|h| h.exc.clone()).unwrap_or_else(|| with_host(|h| synth_error(h, &e)));
2573 with_host(|h| {
2574 h.error = None;
2575 h.exc = None;
2576 });
2577 with_host(|h| h.push_scope());
2579 if let Some(name) = &catch_bind {
2580 with_host(|h| h.declare_name(name, thrown));
2581 }
2582 let hres = host::run_chunk_keyed(host::try_key(id, 1), || {
2583 with_host(|h| h.try_chunk(id, 1)).expect("handler exists")
2584 });
2585 with_host(|h| h.restore_scope(scope.clone()));
2586 if let Err(e2) = hres {
2587 pending = Some(e2);
2588 }
2589 } else {
2590 pending = Some(e);
2591 }
2592 }
2593
2594 if has_finalizer {
2596 let sig_before = with_host(|h| h.signal.take());
2597 with_host(|h| h.push_scope()); let fres = host::run_chunk_keyed(host::try_key(id, 2), || {
2599 with_host(|h| h.try_chunk(id, 2)).expect("finalizer exists")
2600 });
2601 with_host(|h| h.restore_scope(scope.clone()));
2602 match fres {
2603 Ok(_) => {
2604 if with_host(|h| h.signal.is_none()) {
2605 with_host(|h| h.signal = sig_before);
2608 } else {
2609 pending = None;
2614 with_host(|h| {
2615 h.error = None;
2616 h.exc = None;
2617 });
2618 }
2619 }
2620 Err(e) => pending = Some(e),
2621 }
2622 }
2623
2624 if let Some(e) = pending {
2625 return abort(vm, e);
2626 }
2627 Value::Undef
2628}
2629
2630pub(crate) fn synth_error(h: &mut host::JsHost, e: &str) -> Value {
2633 h.ensure_error_protos();
2634 let (head, rest) = match e.split_once(": ") {
2637 Some((n, m)) => (n, m.to_string()),
2638 None => ("", e.to_string()),
2639 };
2640 let (base, code) = match head.split_once(" [") {
2641 Some((n, c)) if c.ends_with(']') => (n, Some(c[..c.len() - 1].to_string())),
2642 _ => (head, None),
2643 };
2644 let (name, mut message) = if host::ERROR_NAMES.contains(&base) {
2645 (base.to_string(), rest)
2646 } else {
2647 ("Error".to_string(), e.to_string())
2648 };
2649 let mut code = code;
2655 let mut bracketed = code.is_some();
2658 if let Some(rest) = message.strip_prefix(host::CODE_MARK) {
2659 if let Some((c, m)) = rest.split_once('\u{1}') {
2660 code = Some(c.to_string());
2661 bracketed = false;
2662 message = m.to_string();
2663 }
2664 }
2665 let mut props: IndexMap<String, Value> = IndexMap::new();
2666 let mv = h.new_str(message.clone());
2667 props.insert("message".into(), mv);
2668 if let Some(c) = &code {
2669 let cv = h.new_str(c.clone());
2670 props.insert("code".into(), cv);
2671 if bracketed {
2672 props.insert("@@nodeError".into(), Value::Bool(true));
2675 }
2676 }
2677 let label = match (&code, bracketed) {
2678 (Some(c), true) => format!("{name} [{c}]"),
2679 _ => name.clone(),
2680 };
2681 let frames = h.stack_frames();
2682 let stack = if message.is_empty() {
2683 format!("{label}{frames}")
2684 } else {
2685 format!("{label}: {message}{frames}")
2686 };
2687 let sv = h.new_str(stack);
2688 props.insert("stack".into(), sv);
2689 for (k, v) in syscall_error_fields(&message) {
2695 let sv = match v {
2696 SysField::Str(s) => h.new_str(s),
2697 SysField::Num(n) => Value::Float(n),
2698 };
2699 props.insert(k.into(), sv);
2700 }
2701 let obj = h.new_object(props);
2702 if let Some(p) = host::error_proto_of(h, &name) {
2703 h.set_proto(&obj, p);
2704 }
2705 h.hide_prop(&obj, "message");
2708 h.hide_prop(&obj, "stack");
2709 obj
2710}
2711
2712enum SysField {
2713 Str(String),
2714 Num(f64),
2715}
2716
2717fn syscall_error_fields(message: &str) -> Vec<(&'static str, SysField)> {
2721 let (code, rest) = match message.split_once(": ") {
2722 Some((c, r))
2723 if c.len() >= 2
2724 && c.starts_with('E')
2725 && c.bytes()
2726 .all(|b| b.is_ascii_uppercase() || b.is_ascii_digit()) =>
2727 {
2728 (c, r)
2729 }
2730 _ => return Vec::new(),
2731 };
2732 let mut out: Vec<(&'static str, SysField)> = vec![
2733 ("errno", SysField::Num(errno_for(code))),
2734 ("code", SysField::Str(code.to_string())),
2735 ];
2736 if let Some((_, tail)) = rest.split_once(", ") {
2738 let (syscall, path) = match tail.split_once(" '") {
2739 Some((s, p)) => (s, p.strip_suffix('\'')),
2740 None => (tail, None),
2741 };
2742 out.push(("syscall", SysField::Str(syscall.to_string())));
2743 if let Some(p) = path {
2744 out.push(("path", SysField::Str(p.to_string())));
2745 }
2746 }
2747 out
2748}
2749
2750fn errno_for(code: &str) -> f64 {
2754 let n: i32 = match code {
2755 "ENOENT" => 2,
2756 "EACCES" => 13,
2757 "EEXIST" => 17,
2758 "ENOTDIR" => 20,
2759 "EISDIR" => 21,
2760 "EINVAL" => 22,
2761 "EPIPE" => 32,
2762 "ENOTEMPTY" => 66,
2763 _ => 5, };
2765 -f64::from(n)
2766}
2767
2768fn b_getiter(vm: &mut VM, _: u8) -> Value {
2771 let v = vm.pop();
2772 if with_host(|h| h.is_generator_val(&v)) {
2774 return v;
2775 }
2776 if with_host(|h| h.kind_of(&v)) == Some(ObjKind::Proxy) {
2779 return match crate::proxy::iterate(&v) {
2780 Ok(Some(items)) => with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })),
2781 Ok(None) => abort(vm, "internal: kind_of said Proxy".into()),
2782 Err(e) => abort(vm, e),
2783 };
2784 }
2785 if let Some(iter_fn) = with_host(|h| host::lookup_chain(h, &v, "@@iterator")) {
2787 if with_host(|h| host::is_callable(h, &iter_fn)) {
2788 return match host::invoke(&iter_fn, Vec::new(), Some(v.clone())) {
2789 Ok(it) => it,
2790 Err(e) => abort(vm, e),
2791 };
2792 }
2793 }
2794 match with_host(|h| h.iter_vec(&v)) {
2795 Ok(items) => with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })),
2796 Err(e) => abort(vm, e),
2797 }
2798}
2799
2800fn b_forin_keys(vm: &mut VM, _: u8) -> Value {
2801 let v = vm.pop();
2802 if with_host(|h| h.kind_of(&v)) == Some(ObjKind::Proxy) {
2806 return match crate::proxy::own_enum_string_keys(&v) {
2807 Ok(keys) => with_host(|h| {
2808 let out: Vec<Value> = keys.into_iter().map(|k| h.new_str(k)).collect();
2809 h.new_array(out)
2810 }),
2811 Err(e) => abort(vm, e),
2812 };
2813 }
2814 let keys = with_host(|h| h.enum_keys(&v));
2815 with_host(|h| h.new_array(keys))
2816}
2817
2818fn b_foriter(vm: &mut VM, _: u8) -> Value {
2819 let it = match vm.stack.last() {
2820 Some(v) => v.clone(),
2821 None => return abort(vm, "internal: FORITER with empty stack".into()),
2822 };
2823 let eager = with_host(|h| {
2825 if let Some(JsObj::Iter { items, idx }) = h.get_mut(&it) {
2826 if *idx < items.len() {
2827 let v = items[*idx].clone();
2828 *idx += 1;
2829 return Some(Some(v));
2830 }
2831 return Some(None);
2832 }
2833 None
2834 });
2835 if let Some(step) = eager {
2836 return match step {
2837 Some(v) => {
2838 vm.push(v);
2839 Value::Bool(true)
2840 }
2841 None => Value::Bool(false),
2842 };
2843 }
2844 if with_host(|h| h.is_generator_val(&it)) {
2846 return match host::gen_resume(&it, Value::Undef) {
2847 Ok(host::GenStep::Yield(v)) => {
2848 vm.push(v);
2849 Value::Bool(true)
2850 }
2851 Ok(host::GenStep::Done(_)) => Value::Bool(false),
2852 Err(e) => abort(vm, e),
2853 };
2854 }
2855 match host::call_method(&it, "next", Vec::new()) {
2857 Ok(step) => {
2858 let done = get_property(&step, "done")
2859 .map(|d| with_host(|h| h.truthy(&d)))
2860 .unwrap_or(true);
2861 if done {
2862 Value::Bool(false)
2863 } else {
2864 match get_property(&step, "value") {
2865 Ok(v) => {
2866 vm.push(v);
2867 Value::Bool(true)
2868 }
2869 Err(e) => abort(vm, e),
2870 }
2871 }
2872 }
2873 Err(e) => abort(vm, e),
2874 }
2875}
2876
2877fn b_unpack(vm: &mut VM, _: u8) -> Value {
2878 let star = match vm.pop() {
2879 Value::Int(n) => n,
2880 _ => -1,
2881 };
2882 let count = match vm.pop() {
2883 Value::Int(n) => n as usize,
2884 _ => 0,
2885 };
2886 let iterable = vm.pop();
2887 let items = match host::iter_all(&iterable) {
2888 Ok(v) => v,
2889 Err(e) => return abort(vm, e),
2890 };
2891 let ordered: Vec<Value> = if star < 0 {
2892 (0..count)
2893 .map(|i| items.get(i).cloned().unwrap_or(Value::Undef))
2894 .collect()
2895 } else {
2896 let si = star as usize;
2897 let after = count.saturating_sub(si + 1);
2898 let rest_end = items.len().saturating_sub(after).max(si);
2899 let mut out: Vec<Value> = Vec::with_capacity(count);
2900 for i in 0..si {
2901 out.push(items.get(i).cloned().unwrap_or(Value::Undef));
2902 }
2903 let rest: Vec<Value> = items
2904 .get(si..rest_end)
2905 .map(|s| s.to_vec())
2906 .unwrap_or_default();
2907 out.push(with_host(|h| h.new_array(rest)));
2908 for j in 0..after {
2909 out.push(items.get(rest_end + j).cloned().unwrap_or(Value::Undef));
2910 }
2911 out
2912 };
2913 if ordered.is_empty() {
2914 return Value::Undef;
2915 }
2916 for it in ordered[1..].iter().rev().cloned() {
2917 vm.push(it);
2918 }
2919 ordered[0].clone()
2920}
2921
2922fn b_build_args(vm: &mut VM, argc: u8) -> Value {
2923 let flat = pop_n(vm, argc as usize);
2924 let mut out = Vec::new();
2925 let mut holes: rustc_hash::FxHashSet<usize> = rustc_hash::FxHashSet::default();
2931 let mut i = 0;
2932 while i + 1 < flat.len() {
2933 let val = flat[i + 1].clone();
2934 match flat[i] {
2935 Value::Int(1) => match host::iter_all(&val) {
2936 Ok(items) => out.extend(items),
2937 Err(e) => return abort(vm, e),
2938 },
2939 Value::Int(2) => {
2940 holes.insert(out.len());
2941 out.push(Value::Undef);
2942 }
2943 _ => out.push(val),
2944 }
2945 i += 2;
2946 }
2947 with_host(|h| {
2948 let arr = h.new_array(out);
2949 h.install_holes(&arr, holes);
2950 arr
2951 })
2952}
2953
2954fn b_call(vm: &mut VM, argc: u8) -> Value {
2957 let mut args = pop_n(vm, argc as usize);
2958 let name = sval(&args.remove(0));
2959 let r = host::call_named(&name, args);
2960 let r = r.map_err(|e| {
2964 let shown = global_binding(&name)
2965 .map(|v| with_host(|h| h.str_of(&v)))
2966 .unwrap_or_default();
2967 host::name_call_site(vm, &shown, e)
2968 });
2969 finish(vm, r)
2970}
2971
2972fn b_call_method(vm: &mut VM, argc: u8) -> Value {
2973 let mut args = pop_n(vm, argc as usize);
2974 let recv = args.remove(0);
2975 let name = sval(&args.remove(0));
2976 let r = host::call_method(&recv, &name, args);
2977 let r = r.map_err(|e| host::name_call_site(vm, &name, e));
2981 finish(vm, r)
2982}
2983
2984fn b_call_value(vm: &mut VM, argc: u8) -> Value {
2985 let mut args = pop_n(vm, argc as usize);
2986 let callable = args.remove(0);
2987 let r = host::invoke(&callable, args, None);
2988 let r = r.map_err(|e| {
2992 let shown = with_host(|h| h.str_of(&callable));
2993 host::name_call_site(vm, &shown, e)
2994 });
2995 finish(vm, r)
2996}
2997
2998fn b_new(vm: &mut VM, argc: u8) -> Value {
2999 let mut args = pop_n(vm, argc as usize);
3000 let ctor = args.remove(0);
3001 let r = host::construct(&ctor, args);
3002 let r = r.map_err(|e| {
3005 let shown = with_host(|h| h.str_of(&ctor));
3006 host::name_call_site(vm, &shown, e)
3007 });
3008 finish(vm, r)
3009}
3010
3011fn b_apply(vm: &mut VM, _: u8) -> Value {
3012 let args_arr = vm.pop();
3013 let callable = vm.pop();
3014 let args = host::iter_all(&args_arr).unwrap_or_default();
3015 let r = host::invoke(&callable, args, None);
3016 finish(vm, r)
3017}
3018
3019fn b_apply_method(vm: &mut VM, _: u8) -> Value {
3020 let args_arr = vm.pop();
3021 let name = sval(&vm.pop());
3022 let recv = vm.pop();
3023 let args = host::iter_all(&args_arr).unwrap_or_default();
3024 let r = host::call_method(&recv, &name, args);
3025 finish(vm, r)
3026}
3027
3028pub fn numeric_hook(op: NumOp, a: &Value, b: &Value) -> Result<Value, String> {
3039 use NumOp::*;
3040 let (a, b) = match op {
3041 Eq | Ne => {
3044 let (pa, pb) = with_host(|h| (host::is_primitive(h, a), host::is_primitive(h, b)));
3045 match (pa, pb) {
3046 (false, true) if coerces_against_object(b) => {
3047 (host::to_primitive(a, "default")?, b.clone())
3048 }
3049 (true, false) if coerces_against_object(a) => {
3050 (a.clone(), host::to_primitive(b, "default")?)
3051 }
3052 _ => (a.clone(), b.clone()),
3053 }
3054 }
3055 Add => (
3058 host::to_primitive(a, "default")?,
3059 host::to_primitive(b, "default")?,
3060 ),
3061 _ => (
3062 host::to_primitive(a, "number")?,
3063 host::to_primitive(b, "number")?,
3064 ),
3065 };
3066 reject_symbol_operand(op, &a, &b)?;
3067 with_host(|h| h.arith(op, &a, &b))
3068}
3069
3070fn reject_symbol_operand(op: NumOp, a: &Value, b: &Value) -> Result<(), String> {
3081 use NumOp::*;
3082 if matches!(op, Eq | Ne) {
3083 return Ok(());
3084 }
3085 let (sym, concat) = with_host(|h| {
3086 let is_sym = |v: &Value| matches!(h.get(v), Some(JsObj::Symbol { .. }));
3087 let is_str =
3088 |v: &Value| matches!(v, Value::Str(_)) || matches!(h.get(v), Some(JsObj::Str(_)));
3089 (is_sym(a) || is_sym(b), is_str(a) || is_str(b))
3090 });
3091 if !sym {
3092 return Ok(());
3093 }
3094 Err(host::type_error(if matches!(op, Add) && concat {
3095 "Cannot convert a Symbol value to a string"
3096 } else {
3097 "Cannot convert a Symbol value to a number"
3098 }))
3099}
3100
3101fn coerces_against_object(v: &Value) -> bool {
3106 match v {
3107 Value::Undef => false,
3108 Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::Str(_) => true,
3109 _ => with_host(|h| !h.is_null(v)),
3110 }
3111}
3112
3113fn is_namespace(name: &str) -> bool {
3117 matches!(
3118 name,
3119 "console"
3120 | "Math"
3121 | "JSON"
3122 | "Object"
3123 | "Array"
3124 | "Number"
3125 | "String"
3126 | "Boolean"
3127 | "Symbol"
3128 | "Reflect"
3129 | "Promise"
3130 | "process"
3131 | "Buffer"
3132 | "URL"
3133 | "URLSearchParams"
3134 )
3135}
3136
3137const GLOBAL_FUNCS: &[&str] = &[
3138 "parseInt",
3139 "parseFloat",
3140 "isNaN",
3141 "isFinite",
3142 "encodeURIComponent",
3143 "decodeURIComponent",
3144 "encodeURI",
3145 "decodeURI",
3146 "escape",
3149 "unescape",
3150 "eval",
3151 "String",
3152 "Number",
3153 "Boolean",
3154 "Array",
3155 "Object",
3156 "Function",
3157 "Symbol",
3158 "Map",
3159 "Set",
3160 "WeakMap",
3161 "WeakSet",
3162 "Promise",
3163 "Error",
3164 "TypeError",
3165 "RangeError",
3166 "SyntaxError",
3167 "ReferenceError",
3168 "EvalError",
3169 "URIError",
3170 "AggregateError",
3171 "BigInt",
3172 "RegExp",
3173 "Date",
3174 "ArrayBuffer",
3175 "Uint8Array",
3176 "Int8Array",
3177 "Uint8ClampedArray",
3178 "Int16Array",
3179 "Uint16Array",
3180 "Int32Array",
3181 "Uint32Array",
3182 "Float32Array",
3183 "Float64Array",
3184 "BigInt64Array",
3185 "BigUint64Array",
3186 "WeakRef",
3187 "FinalizationRegistry",
3188 "TextEncoder",
3189 "TextDecoder",
3190 "fetch",
3192 "Headers",
3193 "Request",
3194 "Response",
3195 "Blob",
3196 "File",
3197 "FormData",
3198 "AbortController",
3199 "AbortSignal",
3200 "queueMicrotask",
3201 "setTimeout",
3202 "setInterval",
3203 "setImmediate",
3204 "clearTimeout",
3205 "clearInterval",
3206 "clearImmediate",
3207 "structuredClone",
3208 "Proxy",
3209 "require",
3210 "__cjs_require",
3213 "__cjs_resolve",
3214 "__cjs_cache",
3215];
3216
3217const NS_METHODS: &[&str] = &[
3218 "console.log",
3219 "console.error",
3220 "console.warn",
3221 "console.info",
3222 "console.debug",
3223 "Math.floor",
3224 "Math.ceil",
3225 "Math.round",
3226 "Math.trunc",
3227 "Math.abs",
3228 "Math.sign",
3229 "Math.max",
3230 "Math.min",
3231 "Math.pow",
3232 "Math.sqrt",
3233 "Math.cbrt",
3234 "Math.random",
3235 "Math.hypot",
3236 "Math.clz32",
3237 "Math.fround",
3238 "Math.imul",
3239 "Math.sinh",
3240 "Math.cosh",
3241 "Math.tanh",
3242 "Math.asinh",
3243 "Math.acosh",
3244 "Math.atanh",
3245 "Math.log1p",
3246 "Math.expm1",
3247 "Math.log",
3248 "Math.log2",
3249 "Math.log10",
3250 "Math.exp",
3251 "Math.sin",
3252 "Math.cos",
3253 "Math.tan",
3254 "Math.atan",
3255 "Math.atan2",
3256 "Math.asin",
3257 "Math.acos",
3258 "JSON.stringify",
3259 "JSON.parse",
3260 "Object.keys",
3261 "Object.values",
3262 "Object.entries",
3263 "Object.assign",
3264 "Object.freeze",
3265 "Object.is",
3266 "Object.fromEntries",
3267 "Object.getPrototypeOf",
3268 "Object.setPrototypeOf",
3269 "Object.create",
3270 "Object.getOwnPropertyNames",
3271 "Object.getOwnPropertySymbols",
3272 "Object.defineProperty",
3273 "Object.getOwnPropertyDescriptor",
3274 "Object.getOwnPropertyDescriptors",
3275 "Object.defineProperties",
3276 "Object.isFrozen",
3277 "Object.isSealed",
3278 "Object.seal",
3279 "Object.preventExtensions",
3280 "Object.isExtensible",
3281 "Object.hasOwn",
3282 "Object.groupBy",
3283 "Array.isArray",
3284 "Array.from",
3285 "Array.fromAsync",
3286 "Array.of",
3287 "Number.isInteger",
3288 "Number.isNaN",
3289 "Number.isFinite",
3290 "Number.isSafeInteger",
3291 "Number.parseInt",
3292 "Number.parseFloat",
3293 "String.fromCharCode",
3294 "String.fromCodePoint",
3295 "String.raw",
3296 "Symbol.for",
3297 "Symbol.keyFor",
3298 "BigInt.asIntN",
3299 "BigInt.asUintN",
3300 "Proxy.revocable",
3301 "Reflect.ownKeys",
3302 "Reflect.has",
3303 "Reflect.get",
3304 "Reflect.set",
3305 "Reflect.getPrototypeOf",
3306 "Reflect.setPrototypeOf",
3307 "Reflect.getOwnPropertyDescriptor",
3308 "Reflect.defineProperty",
3309 "Reflect.deleteProperty",
3310 "Reflect.apply",
3311 "Reflect.construct",
3312 "Reflect.isExtensible",
3313 "Reflect.preventExtensions",
3314 "Promise.resolve",
3315 "Promise.reject",
3316 "Promise.all",
3317 "Promise.allSettled",
3318 "Promise.race",
3319 "Promise.any",
3320 "Promise.withResolvers",
3321 "Map.groupBy",
3322 "Response.json",
3323 "Response.error",
3324 "Response.redirect",
3325 "AbortSignal.abort",
3326 "AbortSignal.timeout",
3327 "process.nextTick",
3328 "Error.captureStackTrace",
3329 "require.resolve",
3330];
3331
3332pub fn is_known_builtin(name: &str) -> bool {
3333 GLOBAL_FUNCS.contains(&name)
3334 || NS_METHODS.contains(&name)
3335 || is_namespace(name)
3336 || crate::stdlib::is_method(name)
3337}
3338
3339pub fn dynamic_function(src: &str) -> Result<Value, String> {
3356 let f = crate::eval_in_global_scope(&format!("({src})"))?;
3357 with_host(|h| {
3358 let s = h.new_str(src.to_string());
3359 h.set_fn_prop(&f, "@@source", s);
3360 });
3361 Ok(f)
3362}
3363
3364pub fn function_ctor(args: &[Value]) -> Result<Value, String> {
3380 let parts: Vec<String> = args.iter().map(|a| with_host(|h| h.str_of(a))).collect();
3381 let (params, body) = match parts.split_last() {
3382 Some((body, params)) => (params.join(","), body.clone()),
3383 None => (String::new(), String::new()),
3384 };
3385 dynamic_function(&format!("function anonymous({params}\n) {{\n{body}\n}}"))
3386}
3387
3388pub fn eval_source(arg: Option<&Value>, direct: bool) -> Result<Value, String> {
3397 let v = arg.cloned().unwrap_or(Value::Undef);
3398 let is_string =
3399 matches!(v, Value::Str(_)) || with_host(|h| matches!(h.get(&v), Some(JsObj::Str(_))));
3400 if !is_string {
3401 return Ok(v);
3402 }
3403 let src = with_host(|h| h.str_of(&v));
3404 let chunk = crate::load_merged(crate::compile_completion(&src)?);
3405 if direct {
3406 host::run_chunk_on(chunk)
3407 } else {
3408 host::run_chunk_in_global_scope(chunk)
3409 }
3410}
3411
3412pub fn call_builtin_function(name: &str, args: Vec<Value>) -> Result<Value, String> {
3414 if name == "require" {
3417 let spec = with_host(|h| h.str_of(&arg0(&args)));
3418 return crate::module::require(&spec, &crate::module::entry_dir());
3419 }
3420 if name == "__cjs_require" {
3423 let spec = with_host(|h| h.str_of(&arg0(&args)));
3424 let from = with_host(|h| h.str_of(args.get(1).unwrap_or(&Value::Undef)));
3425 return crate::module::require(&spec, std::path::Path::new(&from));
3426 }
3427 if name == "require.resolve" {
3429 let spec = with_host(|h| h.str_of(&arg0(&args)));
3430 if crate::stdlib::resolve(&spec).is_some() {
3431 return Ok(with_host(|h| h.new_str(spec)));
3432 }
3433 return match crate::module::resolve(&spec, &crate::module::entry_dir()) {
3434 Some(p) => Ok(with_host(|h| h.new_str(p.to_string_lossy().to_string()))),
3435 None => Err(crate::host::plain_coded_error(
3436 "Error",
3437 "MODULE_NOT_FOUND",
3438 &format!("Cannot find module '{spec}'"),
3439 )),
3440 };
3441 }
3442 if name == "__cjs_resolve" {
3445 let spec = with_host(|h| h.str_of(&arg0(&args)));
3446 let from = with_host(|h| h.str_of(args.get(1).unwrap_or(&Value::Undef)));
3447 if crate::stdlib::resolve(&spec).is_some() {
3448 return Ok(with_host(|h| h.new_str(spec)));
3449 }
3450 return match crate::module::resolve(&spec, std::path::Path::new(&from)) {
3451 Some(p) => Ok(with_host(|h| h.new_str(p.to_string_lossy().to_string()))),
3452 None => Err(crate::host::plain_coded_error(
3453 "Error",
3454 "MODULE_NOT_FOUND",
3455 &format!("Cannot find module '{spec}'"),
3456 )),
3457 };
3458 }
3459 if name == "Error.captureStackTrace" {
3464 let target = arg0(&args);
3465 let prep = with_host(|h| h.builtin_static("Error", "prepareStackTrace"));
3466 let stack = match prep {
3467 Some(f)
3468 if matches!(
3469 with_host(|h| h.get(&f).cloned()),
3470 Some(JsObj::Func(_)) | Some(JsObj::Builtin(_)) | Some(JsObj::BoundFunc { .. })
3471 ) =>
3472 {
3473 let sites = crate::module::callsite_stack(10)?;
3474 host::invoke(&f, vec![target.clone(), sites], None)?
3475 }
3476 _ => with_host(|h| h.new_str("")),
3477 };
3478 let _ = set_property(&target, "stack", stack);
3479 return Ok(Value::Undef);
3480 }
3481 if let Some(r) = crate::stdlib::call(name, &args) {
3483 return r;
3484 }
3485 match name {
3486 "console.log" | "console.info" | "console.debug" => {
3487 print_line(&args, false);
3488 Ok(Value::Undef)
3489 }
3490 "console.error" | "console.warn" => {
3491 print_line(&args, true);
3492 Ok(Value::Undef)
3493 }
3494 "parseInt" | "Number.parseInt" => Ok(Value::Float(parse_int(&args))),
3495 "parseFloat" | "Number.parseFloat" => Ok(Value::Float(parse_float(&args))),
3496 "isNaN" => Ok(Value::Bool(arg_num(&args, 0).is_nan())),
3497 "isFinite" => Ok(Value::Bool(arg_num(&args, 0).is_finite())),
3498 "encodeURIComponent" => uri_encode(&with_host(|h| h.str_of(&arg0(&args))), false),
3499 "encodeURI" => uri_encode(&with_host(|h| h.str_of(&arg0(&args))), true),
3500 "decodeURIComponent" => uri_decode(&with_host(|h| h.str_of(&arg0(&args))), false),
3501 "decodeURI" => uri_decode(&with_host(|h| h.str_of(&arg0(&args))), true),
3502 "escape" => legacy_escape(&with_host(|h| h.str_of(&arg0(&args)))),
3503 "unescape" => legacy_unescape(&with_host(|h| h.str_of(&arg0(&args)))),
3504 "eval" => eval_source(args.first(), false),
3509 "Function" => function_ctor(&args),
3513 "Buffer" => {
3523 crate::stdlib::process::emit_deprecation_warning(
3524 "DEP0005",
3525 "Buffer() is deprecated due to security and usability issues. \
3526 Please use the Buffer.alloc(), Buffer.allocUnsafe(), or \
3527 Buffer.from() methods instead.",
3528 );
3529 crate::stdlib::construct("Buffer", &args)
3530 .unwrap_or_else(|| Err(host::type_error("Buffer is not a function")))
3531 }
3532 "Number.isInteger" => Ok(Value::Bool(is_integer(arg0(&args)))),
3533 "Number.isSafeInteger" => Ok(Value::Bool(is_safe_integer(arg0(&args)))),
3534 "Number.isNaN" => Ok(Value::Bool(
3535 matches!(arg0(&args), Value::Float(f) if f.is_nan()),
3536 )),
3537 "Number.isFinite" => Ok(Value::Bool(
3538 matches!(arg0(&args), Value::Float(f) if f.is_finite())
3539 || matches!(arg0(&args), Value::Int(_)),
3540 )),
3541 "String" => {
3542 if args.is_empty() {
3543 Ok(with_host(|h| h.new_str("")))
3544 } else {
3545 host::string_ctor_value(&args[0])
3548 }
3549 }
3550 "Number" => Ok(Value::Float(if args.is_empty() {
3551 0.0
3552 } else {
3553 host::to_number_value(&args[0])?
3555 })),
3556 "BigInt" => bigint_ctor(&arg0(&args)),
3557 "RegExp" => regexp_ctor(&args),
3558 "BigInt.asIntN" | "BigInt.asUintN" => bigint_as_n(name.ends_with("asUintN"), &args),
3559 "Boolean" => Ok(Value::Bool(with_host(|h| h.truthy(&arg0(&args))))),
3560 "String.fromCharCode" => Ok(with_host(|h| {
3564 let units: Vec<u16> = args
3565 .iter()
3566 .map(|a| crate::utf16::to_uint16(h.to_number(a)))
3567 .collect();
3568 let s = crate::utf16::to_string_lossy(&units);
3569 h.new_str(s)
3570 })),
3571 "String.fromCodePoint" => {
3574 let mut s = String::new();
3575 for a in &args {
3576 let n = with_host(|h| h.to_number(a));
3577 let cp = if n.is_finite() && n.trunc() == n && (0.0..=0x10FFFF as f64).contains(&n)
3578 {
3579 char::from_u32(n as u32)
3580 } else {
3581 None
3582 };
3583 match cp {
3584 Some(c) => s.push(c),
3585 None => {
3586 return Err(format!(
3587 "RangeError: Invalid code point {}",
3588 with_host(|h| h.str_of(a))
3589 ))
3590 }
3591 }
3592 }
3593 Ok(new_s(s))
3594 }
3595 "String.raw" => string_raw(&args),
3596 "Array" => construct_builtin("Array", args),
3598 "Array.of" => Ok(with_host(|h| h.new_array(args))),
3599 "Array.isArray" => {
3602 let v = arg0(&args);
3603 let subject = crate::proxy::ultimate_target(&v).unwrap_or(v);
3604 Ok(Value::Bool(matches!(
3605 with_host(|h| h.get(&subject).cloned()),
3606 Some(JsObj::Array(_))
3607 )))
3608 }
3609 "Array.from" => array_from(args),
3610 "Array.fromAsync" => array_from_async(args),
3611 "Object" => Ok(object_call(args)),
3612 "Object.keys" => object_keys(args, 0),
3613 "Object.values" => object_keys(args, 1),
3614 "Object.entries" => object_keys(args, 2),
3615 "Object.assign" => object_assign(args),
3616 "Object.freeze" => {
3617 let v = arg0(&args);
3618 with_host(|h| h.seal_object(&v, true));
3619 Ok(v)
3620 }
3621 "Object.seal" => {
3622 let v = arg0(&args);
3623 with_host(|h| h.seal_object(&v, false));
3624 Ok(v)
3625 }
3626 "Object.preventExtensions" => {
3627 let v = arg0(&args);
3628 if crate::proxy::prevent_extensions(&v)? {
3629 return Ok(v);
3630 }
3631 with_host(|h| h.prevent_extensions(&v));
3632 Ok(v)
3633 }
3634 "Object.isFrozen" => Ok(Value::Bool(with_host(|h| h.is_sealed(&arg0(&args), true)))),
3635 "Object.isSealed" => Ok(Value::Bool(with_host(|h| h.is_sealed(&arg0(&args), false)))),
3636 "Object.isExtensible" => {
3637 let v = arg0(&args);
3638 match crate::proxy::is_extensible(&v)? {
3639 Some(b) => Ok(Value::Bool(b)),
3640 None => Ok(Value::Bool(with_host(|h| h.is_extensible(&v)))),
3641 }
3642 }
3643 "Object.is" => {
3646 let a = arg0(&args);
3647 let b = args.get(1).cloned().unwrap_or(Value::Undef);
3648 let num = |v: &Value| match v {
3649 Value::Int(n) => Some(*n as f64),
3650 Value::Float(f) => Some(*f),
3651 _ => None,
3652 };
3653 let r = match (num(&a), num(&b)) {
3654 (Some(x), Some(y)) => {
3655 if x.is_nan() && y.is_nan() {
3656 true
3657 } else if x == 0.0 && y == 0.0 {
3658 x.is_sign_negative() == y.is_sign_negative()
3659 } else {
3660 x == y
3661 }
3662 }
3663 _ => with_host(|h| h.strict_eq(&a, &b)),
3664 };
3665 Ok(Value::Bool(r))
3666 }
3667 "Object.fromEntries" => object_from_entries(args),
3668 "Object.getPrototypeOf" | "Reflect.getPrototypeOf" => {
3671 let v = arg0(&args);
3672 match crate::proxy::get_prototype_of(&v)? {
3673 Some(p) => Ok(p),
3674 None => Ok(prototype_of(&v)),
3675 }
3676 }
3677 "Object.setPrototypeOf" => {
3678 let obj = arg0(&args);
3679 let proto = args.get(1).cloned().unwrap_or(Value::Undef);
3680 if with_host(|h| h.kind_of(&obj)) == Some(ObjKind::Proxy) {
3681 reject_bad_prototype(&proto)?;
3682 crate::proxy::set_prototype_of(&obj, &proto)?;
3683 return Ok(obj);
3684 }
3685 if with_host(|h| matches!(obj, Value::Undef) || h.is_null(&obj)) {
3691 return Err(host::type_error(
3692 "Object.setPrototypeOf called on null or undefined",
3693 ));
3694 }
3695 reject_bad_prototype(&proto)?;
3696 if with_host(|h| is_object_like(h, &obj)) {
3697 let cur = prototype_of(&obj);
3704 let same = with_host(|h| h.strict_eq(&cur, &proto));
3705 if !same && !with_host(|h| h.is_extensible(&obj)) {
3706 return Err(host::type_error("#<Object> is not extensible"));
3707 }
3708 with_host(|h| h.set_proto(&obj, proto));
3709 }
3710 Ok(obj)
3711 }
3712 "Object.create" => object_create(args),
3713 "Object.getOwnPropertyNames" => object_keys(args, 3),
3714 "Object.getOwnPropertySymbols" => {
3715 let v = arg0(&args);
3716 require_object_coercible(&v)?;
3717 let syms = proxy_or_own_symbol_keys(&v)?;
3718 Ok(with_host(|h| h.new_array(syms)))
3719 }
3720 "Object.hasOwn" => {
3722 let obj = arg0(&args);
3723 let key = args.get(1).cloned().unwrap_or(Value::Undef);
3724 object_builtin_method(&obj, "hasOwnProperty", vec![key])
3725 }
3726 "Object.defineProperty" => object_define_property(args),
3727 "Object.getOwnPropertyDescriptor" => object_get_own_descriptor(args),
3728 "Object.getOwnPropertyDescriptors" => object_get_own_descriptors(args),
3729 "Object.defineProperties" => object_define_properties(args),
3730 "Object.groupBy" => object_group_by(args),
3733 "Symbol" => Ok(with_host(|h| {
3734 let desc = args
3735 .first()
3736 .filter(|a| !matches!(a, Value::Undef))
3737 .map(|a| h.str_of(a));
3738 h.new_symbol(desc)
3739 })),
3740 "Symbol.for" => Ok(with_host(|h| {
3741 let key = h.str_of(&arg0(&args));
3742 h.symbol_for(&key)
3743 })),
3744 "Symbol.keyFor" => Ok(with_host(|h| h.symbol_registry_key(&arg0(&args)))),
3749 "Map" | "WeakMap" | "Set" | "WeakSet" | "Promise" => construct_builtin(name, args),
3750 "Proxy" => Err(host::type_error("Constructor Proxy requires 'new'")),
3752 "Proxy.revocable" => crate::proxy::revocable(&args),
3753 "Reflect.ownKeys" => {
3759 let v = arg0(&args);
3760 let names = object_keys(args, 3)?;
3761 let syms = proxy_or_own_symbol_keys(&v)?;
3762 if syms.is_empty() {
3763 return Ok(names);
3764 }
3765 let mut all = with_host(|h| h.iter_vec(&names)).unwrap_or_default();
3766 all.extend(syms);
3767 Ok(with_host(|h| h.new_array(all)))
3768 }
3769 "Reflect.getOwnPropertyDescriptor" => object_get_own_descriptor(args),
3770 "Reflect.defineProperty" => {
3771 object_define_property(args)?;
3772 Ok(Value::Bool(true))
3773 }
3774 "Reflect.deleteProperty" => {
3775 let obj = arg0(&args);
3776 let k = with_host(|h| h.property_key(&args.get(1).cloned().unwrap_or(Value::Undef)));
3777 Ok(Value::Bool(delete_property(&obj, &k)?))
3778 }
3779 "Reflect.setPrototypeOf" => {
3780 let obj = arg0(&args);
3781 let p = args.get(1).cloned().unwrap_or(Value::Undef);
3782 if with_host(|h| h.kind_of(&obj)) == Some(ObjKind::Proxy) {
3783 crate::proxy::set_prototype_of(&obj, &p)?;
3784 return Ok(Value::Bool(true));
3785 }
3786 with_host(|h| h.set_proto(&obj, p));
3787 Ok(Value::Bool(true))
3788 }
3789 "Reflect.isExtensible" => {
3790 let v = arg0(&args);
3791 match crate::proxy::is_extensible(&v)? {
3792 Some(b) => Ok(Value::Bool(b)),
3793 None => Ok(Value::Bool(with_host(|h| h.is_extensible(&v)))),
3794 }
3795 }
3796 "Reflect.preventExtensions" => {
3797 let v = arg0(&args);
3798 if crate::proxy::prevent_extensions(&v)? {
3799 return Ok(Value::Bool(true));
3800 }
3801 with_host(|h| h.prevent_extensions(&v));
3802 Ok(Value::Bool(true))
3803 }
3804 "Reflect.apply" => {
3806 let f = arg0(&args);
3807 let this = args.get(1).cloned();
3808 let list = with_host(|h| h.iter_vec(&args.get(2).cloned().unwrap_or(Value::Undef)))
3809 .unwrap_or_default();
3810 host::invoke(&f, list, this.filter(|t| !with_host(|h| h.is_nullish(t))))
3811 }
3812 "Reflect.construct" => {
3813 let f = arg0(&args);
3814 let list = with_host(|h| h.iter_vec(&args.get(1).cloned().unwrap_or(Value::Undef)))
3815 .unwrap_or_default();
3816 host::construct(&f, list)
3817 }
3818 "Reflect.has" => {
3819 let obj = arg0(&args);
3820 let k = with_host(|h| h.property_key(&args.get(1).cloned().unwrap_or(Value::Undef)));
3821 Ok(Value::Bool(has_property(&obj, &k)?))
3822 }
3823 "Reflect.get" => {
3826 let obj = arg0(&args);
3827 let k = with_host(|h| h.property_key(&args.get(1).cloned().unwrap_or(Value::Undef)));
3828 let receiver = args.get(2).cloned().unwrap_or_else(|| obj.clone());
3829 get_property_recv(&obj, &k, &receiver)
3830 }
3831 "Reflect.set" => {
3832 let obj = arg0(&args);
3833 let k = with_host(|h| h.property_key(&args.get(1).cloned().unwrap_or(Value::Undef)));
3834 let v = args.get(2).cloned().unwrap_or(Value::Undef);
3835 let _ = set_property(&obj, &k, v);
3836 Ok(Value::Bool(true))
3837 }
3838 "JSON.stringify" => json_stringify(args),
3839 "JSON.parse" => json_parse(args),
3840 "structuredClone" => Ok(deep_clone(&arg0(&args))),
3841 "fetch" => crate::stdlib::fetch::fetch(&args),
3842 _ if name.starts_with("@@aborttimeout:") => {
3845 let idx: u32 = name["@@aborttimeout:".len()..].parse().unwrap_or(0);
3846 crate::stdlib::fetch::fire_timeout_abort(idx)
3847 }
3848 "queueMicrotask" | "process.nextTick" => {
3849 let cb = arg0(&args);
3850 let rest = args.get(1..).map(|s| s.to_vec()).unwrap_or_default();
3851 enqueue_microtask(name == "process.nextTick", cb, rest);
3852 Ok(Value::Undef)
3853 }
3854 "setTimeout" | "setInterval" | "setImmediate" => Ok(schedule_timer(name, args)),
3855 "clearTimeout" | "clearInterval" | "clearImmediate" => {
3856 clear_timer(&arg0(&args));
3857 Ok(Value::Undef)
3858 }
3859 "Promise.resolve" => promise_resolve(arg0(&args)),
3860 "Promise.reject" => promise_reject(arg0(&args)),
3861 "Promise.all" => promise_all(args, AllMode::All),
3862 "Promise.allSettled" => promise_all(args, AllMode::AllSettled),
3863 "Promise.race" => promise_race(args, false),
3864 "Promise.any" => promise_race(args, true),
3865 "Promise.withResolvers" => promise_with_resolvers(),
3868 "Map.groupBy" => map_group_by(args),
3871 n if host::ERROR_NAMES.contains(&n) => Ok(make_error(name, &args)),
3872 _ if name.starts_with("Math.") => math_fn(&name[5..], &args),
3873 _ if name.starts_with("@@presolve:") => {
3875 let id: u32 = name[11..].parse().unwrap_or(0);
3876 host::resolve_promise_val(id, arg0(&args));
3877 Ok(Value::Undef)
3878 }
3879 _ if name.starts_with("@@preject:") => {
3880 let id: u32 = name[10..].parse().unwrap_or(0);
3881 host::reject_promise_val(id, arg0(&args));
3882 Ok(Value::Undef)
3883 }
3884 _ if name.starts_with("@@prevoke:") => {
3887 let i: u32 = name[10..].parse().unwrap_or(0);
3888 Ok(crate::proxy::revoke(i))
3889 }
3890 _ if name.starts_with("@@finpass:") => {
3891 let i: u32 = name[10..].parse().unwrap_or(0);
3893 let cb = Value::Obj(i);
3894 host::invoke(&cb, Vec::new(), None)?;
3895 Ok(arg0(&args))
3896 }
3897 _ if name.starts_with("@@finthrow:") => {
3898 let i: u32 = name[11..].parse().unwrap_or(0);
3900 let cb = Value::Obj(i);
3901 host::invoke(&cb, Vec::new(), None)?;
3902 let reason = arg0(&args);
3903 with_host(|h| h.exc = Some(reason.clone()));
3904 Err(with_host(|h| error_string(h, &reason)))
3905 }
3906 _ => Err(host::type_error(&format!("{name} is not a function"))),
3907 }
3908}
3909
3910fn bigint_convert_error(v: &Value) -> String {
3917 let shown = with_host(|h| h.str_of(v));
3918 host::type_error(&format!("Cannot convert {shown} to a BigInt"))
3919}
3920
3921fn bigint_ctor(v: &Value) -> Result<Value, String> {
3922 use num_bigint::BigInt;
3923 let big = match v {
3924 Value::Bool(b) => BigInt::from(*b as i64),
3925 Value::Int(n) => BigInt::from(*n),
3926 Value::Float(f) => {
3927 if !f.is_finite() || f.fract() != 0.0 {
3928 let disp = with_host(|h| h.str_of(v));
3929 return Err(format!(
3930 "RangeError: The number {disp} cannot be converted to a BigInt because it is not an integer"
3931 ));
3932 }
3933 match BigInt::parse_bytes(format!("{f:.0}").as_bytes(), 10) {
3942 Some(b) => b,
3943 None => return Err(bigint_convert_error(v)),
3944 }
3945 }
3946 Value::Str(s) => match host::parse_bigint_str(s) {
3947 Some(b) => b,
3948 None => return Err(format!("SyntaxError: Cannot convert {s} to a BigInt")),
3949 },
3950 Value::Obj(_) => match with_host(|h| h.get(v).cloned()) {
3951 Some(JsObj::BigInt(b)) => b,
3952 Some(JsObj::Str(s)) => match host::parse_bigint_str(&s) {
3953 Some(b) => b,
3954 None => return Err(format!("SyntaxError: Cannot convert {s} to a BigInt")),
3955 },
3956 _ => return Err(bigint_convert_error(v)),
3957 },
3958 _ => return Err(bigint_convert_error(v)),
3959 };
3960 Ok(with_host(|h| h.new_bigint(big)))
3961}
3962
3963fn regexp_ctor(args: &[Value]) -> Result<Value, String> {
3966 let (source, existing_flags) = match with_host(|h| h.get(&arg0(args)).cloned()) {
3967 Some(JsObj::RegExp(r)) => (r.source.clone(), Some(r.flags.clone())),
3968 _ => {
3969 let a0 = arg0(args);
3970 let src = if matches!(a0, Value::Undef) {
3971 String::new()
3972 } else {
3973 with_host(|h| h.str_of(&a0))
3974 };
3975 (src, None)
3976 }
3977 };
3978 let flags = match args.get(1) {
3979 Some(v) if !matches!(v, Value::Undef) => with_host(|h| h.str_of(v)),
3980 _ => existing_flags.unwrap_or_default(),
3981 };
3982 let src = if source.is_empty() {
3984 "(?:)".to_string()
3985 } else {
3986 source
3987 };
3988 crate::regexp::build_regexp(&src, &flags)
3989}
3990
3991fn bigint_as_n(unsigned: bool, args: &[Value]) -> Result<Value, String> {
3994 use num_bigint::BigInt;
3995 use num_traits::Signed;
3996 let bits = with_host(|h| h.to_number(&arg0(args))) as i64;
3997 if bits < 0 {
3998 return Err("RangeError: Invalid value: not (convertible to) a safe integer".into());
3999 }
4000 let x = match with_host(|h| h.as_bigint(&args.get(1).cloned().unwrap_or(Value::Undef))) {
4001 Some(b) => b,
4002 None => return Err(host::type_error("Cannot convert to a BigInt")),
4003 };
4004 let bits = bits as u32;
4005 if bits == 0 {
4006 return Ok(with_host(|h| h.new_bigint(BigInt::from(0))));
4007 }
4008 let modulus = BigInt::from(1) << bits; let mut r = &x % &modulus;
4011 if r.is_negative() {
4012 r += &modulus;
4013 }
4014 if !unsigned {
4015 let half = BigInt::from(1) << (bits - 1);
4016 if r >= half {
4017 r -= &modulus;
4018 }
4019 }
4020 Ok(with_host(|h| h.new_bigint(r)))
4021}
4022
4023fn string_raw(args: &[Value]) -> Result<Value, String> {
4026 let call_site = arg0(args);
4027 let raw = get_property(&call_site, "raw")?;
4028 let raws = with_host(|h| h.iter_vec(&raw)).unwrap_or_default();
4029 let mut out = String::new();
4030 for (i, r) in raws.iter().enumerate() {
4031 out.push_str(&with_host(|h| h.str_of(r)));
4032 if i + 1 < raws.len() {
4033 if let Some(sub) = args.get(i + 1) {
4034 out.push_str(&with_host(|h| h.str_of(sub)));
4035 }
4036 }
4037 }
4038 Ok(with_host(|h| h.new_str(out)))
4039}
4040
4041fn object_call(args: Vec<Value>) -> Value {
4044 let a = arg0(&args);
4045 if matches!(
4046 with_host(|h| h.get(&a).cloned()),
4047 Some(JsObj::Object(_)) | Some(JsObj::Array(_))
4048 ) {
4049 a
4050 } else {
4051 with_host(|h| h.new_object(IndexMap::new()))
4052 }
4053}
4054
4055pub fn construct_builtin(name: &str, args: Vec<Value>) -> Result<Value, String> {
4057 if let Some(r) = crate::stdlib::construct(name, &args) {
4059 return r;
4060 }
4061 match name {
4062 "Array" => {
4063 if args.len() == 1 {
4069 if let Value::Float(_) | Value::Int(_) = args[0] {
4070 let n = host::to_array_length(&args[0])?;
4071 return Ok(with_host(|h| {
4074 let a = h.new_array(vec![Value::Undef; n]);
4075 h.mark_hole_range(&a, 0..n);
4076 a
4077 }));
4078 }
4079 }
4080 Ok(with_host(|h| h.new_array(args)))
4081 }
4082 "Object" => Ok(object_call(args)),
4083 "Map" | "WeakMap" => {
4084 let weak = name == "WeakMap";
4085 let m = with_host(|h| {
4086 h.alloc(JsObj::Map {
4087 entries: indexmap::IndexMap::new(),
4088 weak,
4089 })
4090 });
4091 if let Some(init) = args
4092 .first()
4093 .filter(|a| !matches!(a, Value::Undef) && !with_host(|h| h.is_null(a)))
4094 {
4095 let pairs = host::iter_all(init)?;
4096 for p in pairs {
4097 let kv = host::iter_all(&p)?;
4098 let k = kv.first().cloned().unwrap_or(Value::Undef);
4099 let v = kv.get(1).cloned().unwrap_or(Value::Undef);
4100 map_method(&m, "set", vec![k, v])?;
4101 }
4102 }
4103 Ok(m)
4104 }
4105 "Set" | "WeakSet" => {
4106 let weak = name == "WeakSet";
4107 let s = with_host(|h| {
4108 h.alloc(JsObj::Set {
4109 entries: indexmap::IndexMap::new(),
4110 weak,
4111 })
4112 });
4113 if let Some(init) = args
4114 .first()
4115 .filter(|a| !matches!(a, Value::Undef) && !with_host(|h| h.is_null(a)))
4116 {
4117 let vals = host::iter_all(init)?;
4118 for v in vals {
4119 set_method(&s, "add", vec![v])?;
4120 }
4121 }
4122 Ok(s)
4123 }
4124 "Promise" => new_promise(arg0(&args)),
4125 "Proxy" => crate::proxy::create(&args),
4126 "Function" => function_ctor(&args),
4131 "RegExp" => regexp_ctor(&args),
4132 "BigInt" => Err(host::type_error("BigInt is not a constructor")),
4133 "Error" => Ok(make_error(name, &args)),
4134 n if host::ERROR_NAMES.contains(&n) => Ok(make_error(name, &args)),
4135 _ => Err(host::type_error(&format!("{name} is not a constructor"))),
4136 }
4137}
4138
4139fn make_error(name: &str, args: &[Value]) -> Value {
4140 let agg = name == "AggregateError";
4143 let (errors, args) = if agg {
4144 (
4145 Some(args.first().cloned().unwrap_or(Value::Undef)),
4146 args.get(1..).unwrap_or(&[]),
4147 )
4148 } else {
4149 (None, args)
4150 };
4151 with_host(|h| {
4152 h.ensure_error_protos();
4153 let mut props: IndexMap<String, Value> = IndexMap::new();
4154 let msg = args
4155 .first()
4156 .filter(|a| !matches!(a, Value::Undef))
4157 .map(|a| h.str_of(a));
4158 if let Some(m) = &msg {
4159 let mv = h.new_str(m.clone());
4160 props.insert("message".into(), mv);
4161 }
4162 let frames = h.stack_frames();
4165 let stack = match &msg {
4166 Some(m) if !m.is_empty() => format!("{name}: {m}{frames}"),
4167 _ => format!("{name}{frames}"),
4168 };
4169 let sv = h.new_str(stack);
4170 props.insert("stack".into(), sv);
4171 if let Some(errs) = errors {
4172 let items = h.iter_vec(&errs).unwrap_or_default();
4174 let arr = h.new_array(items);
4175 props.insert("errors".into(), arr);
4176 }
4177 let opts = args.get(1);
4180 if let Some(cause) = opts.and_then(|o| match h.get(o) {
4181 Some(JsObj::Object(p)) => p.get("cause").cloned(),
4182 _ => None,
4183 }) {
4184 props.insert("cause".into(), cause);
4185 }
4186 let e = h.new_object(props);
4187 if let Some(p) = host::error_proto_of(h, name) {
4188 h.set_proto(&e, p);
4189 }
4190 for k in ["message", "stack", "errors", "cause"] {
4194 h.hide_prop(&e, k);
4195 }
4196 e
4197 })
4198}
4199
4200fn print_line(args: &[Value], stderr: bool) {
4201 let line: String = crate::stdlib::util::format(args);
4204 with_host(|h| h.write_out(&format!("{line}\n"), stderr));
4205}
4206
4207fn arg0(args: &[Value]) -> Value {
4208 args.first().cloned().unwrap_or(Value::Undef)
4209}
4210fn arg_num(args: &[Value], i: usize) -> f64 {
4211 with_host(|h| h.to_number(&args.get(i).cloned().unwrap_or(Value::Undef)))
4212}
4213
4214fn is_integer(v: Value) -> bool {
4215 match v {
4216 Value::Int(_) => true,
4217 Value::Float(f) => f.is_finite() && f.fract() == 0.0,
4218 _ => false,
4219 }
4220}
4221fn is_safe_integer(v: Value) -> bool {
4222 match v {
4223 Value::Float(f) => f.is_finite() && f.fract() == 0.0 && f.abs() <= 9007199254740991.0,
4224 Value::Int(_) => true,
4225 _ => false,
4226 }
4227}
4228
4229fn uri_encode(s: &str, uri: bool) -> Result<Value, String> {
4233 const UNRESERVED: &[u8] =
4235 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.!~*'()";
4236 const RESERVED: &[u8] = b";,/?:@&=+$#";
4238 let mut out = String::with_capacity(s.len());
4239 for &b in s.as_bytes() {
4240 if UNRESERVED.contains(&b) || (uri && RESERVED.contains(&b)) {
4241 out.push(b as char);
4242 } else {
4243 out.push('%');
4244 out.push(
4245 char::from_digit((b >> 4) as u32, 16)
4246 .unwrap()
4247 .to_ascii_uppercase(),
4248 );
4249 out.push(
4250 char::from_digit((b & 0xf) as u32, 16)
4251 .unwrap()
4252 .to_ascii_uppercase(),
4253 );
4254 }
4255 }
4256 Ok(with_host(|h| h.new_str(out)))
4257}
4258
4259fn uri_decode(s: &str, uri: bool) -> Result<Value, String> {
4263 const RESERVED: &[u8] = b";,/?:@&=+$#";
4264 let bytes = s.as_bytes();
4265 let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
4266 let mut i = 0;
4267 while i < bytes.len() {
4268 if bytes[i] == b'%' {
4269 if i + 2 >= bytes.len() {
4270 return Err("URIError: URI malformed".into());
4271 }
4272 let hi = (bytes[i + 1] as char).to_digit(16);
4273 let lo = (bytes[i + 2] as char).to_digit(16);
4274 match (hi, lo) {
4275 (Some(h), Some(l)) => {
4276 let byte = (h * 16 + l) as u8;
4277 if uri && RESERVED.contains(&byte) {
4279 out.extend_from_slice(&bytes[i..i + 3]);
4280 } else {
4281 out.push(byte);
4282 }
4283 i += 3;
4284 }
4285 _ => return Err("URIError: URI malformed".into()),
4286 }
4287 } else {
4288 out.push(bytes[i]);
4289 i += 1;
4290 }
4291 }
4292 match String::from_utf8(out) {
4293 Ok(decoded) => Ok(with_host(|h| h.new_str(decoded))),
4294 Err(_) => Err("URIError: URI malformed".into()),
4295 }
4296}
4297
4298fn legacy_escape(s: &str) -> Result<Value, String> {
4309 const KEEP: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./";
4310 let mut out = String::with_capacity(s.len());
4311 for u in s.encode_utf16() {
4312 if u < 0x100 {
4313 if KEEP.contains(&(u as u8)) {
4314 out.push(u as u8 as char);
4315 } else {
4316 out.push_str(&format!("%{u:02X}"));
4317 }
4318 } else {
4319 out.push_str(&format!("%u{u:04X}"));
4320 }
4321 }
4322 Ok(with_host(|h| h.new_str(out)))
4323}
4324
4325fn legacy_unescape(s: &str) -> Result<Value, String> {
4333 let b = s.as_bytes();
4334 let hex = |i: usize, n: usize| -> Option<u16> {
4335 if i + n > b.len() {
4336 return None;
4337 }
4338 let mut v: u16 = 0;
4339 for &c in &b[i..i + n] {
4340 v = v.checked_mul(16)? + (c as char).to_digit(16)? as u16;
4341 }
4342 Some(v)
4343 };
4344 let units: Vec<u16> = s.encode_utf16().collect();
4345 let mut out: Vec<u16> = Vec::with_capacity(units.len());
4346 let mut i = 0;
4347 while i < b.len() {
4348 if b[i] == b'%' {
4351 if let Some(u) = hex(i + 1, 2) {
4352 out.push(u);
4353 i += 3;
4354 continue;
4355 }
4356 if b.get(i + 1) == Some(&b'u') {
4357 if let Some(u) = hex(i + 2, 4) {
4358 out.push(u);
4359 i += 6;
4360 continue;
4361 }
4362 }
4363 }
4364 let c = s[i..].chars().next().unwrap_or('%');
4365 let mut buf = [0u16; 2];
4366 out.extend_from_slice(c.encode_utf16(&mut buf));
4367 i += c.len_utf8();
4368 }
4369 Ok(with_host(|h| {
4370 h.new_str(crate::utf16::to_string_lossy(&out))
4371 }))
4372}
4373
4374fn parse_int(args: &[Value]) -> f64 {
4375 let s = with_host(|h| h.str_of(&arg0(args)));
4376 let radix_arg = args
4380 .get(1)
4381 .map(|r| with_host(|h| host::to_int32(h.to_number(r))));
4382 let radix = match radix_arg {
4383 Some(0) | None => None,
4384 Some(r) if (2..=36).contains(&r) => Some(r as u32),
4385 Some(_) => return f64::NAN,
4386 };
4387 let t = crate::utf16::js_trim_start(&s);
4388 let (neg, digits) = match t.strip_prefix('-') {
4389 Some(rest) => (true, rest),
4390 None => (false, t.strip_prefix('+').unwrap_or(t)),
4391 };
4392 let (radix, digits) = match radix {
4393 Some(16) => (
4394 16u32,
4395 digits
4396 .strip_prefix("0x")
4397 .or_else(|| digits.strip_prefix("0X"))
4398 .unwrap_or(digits),
4399 ),
4400 Some(r) => (r, digits),
4401 None => {
4402 if let Some(hex) = digits
4403 .strip_prefix("0x")
4404 .or_else(|| digits.strip_prefix("0X"))
4405 {
4406 (16, hex)
4407 } else {
4408 (10, digits)
4409 }
4410 }
4411 };
4412 let valid: String = digits.chars().take_while(|c| c.is_digit(radix)).collect();
4413 if valid.is_empty() {
4414 return f64::NAN;
4415 }
4416 let n = if radix == 10 {
4422 valid.parse::<f64>().unwrap_or(f64::NAN)
4427 } else {
4428 let mut n = 0.0f64;
4429 for c in valid.chars() {
4430 n = n * radix as f64 + c.to_digit(radix).unwrap_or(0) as f64;
4431 }
4432 n
4433 };
4434 if neg {
4435 -n
4436 } else {
4437 n
4438 }
4439}
4440
4441fn parse_float(args: &[Value]) -> f64 {
4442 let s = with_host(|h| h.str_of(&arg0(args)));
4443 let t = crate::utf16::js_trim_start(&s);
4444 let inf_body = t
4446 .strip_prefix('+')
4447 .or_else(|| t.strip_prefix('-'))
4448 .unwrap_or(t);
4449 if inf_body.starts_with("Infinity") {
4450 return if t.starts_with('-') {
4451 f64::NEG_INFINITY
4452 } else {
4453 f64::INFINITY
4454 };
4455 }
4456 let mut end = 0;
4463 let bytes = t.as_bytes();
4464 let mut seen_dot = false;
4465 let mut seen_e = false;
4466 let mut digits_before_dot = false;
4467 for (i, &c) in bytes.iter().enumerate() {
4468 match c {
4469 b'0'..=b'9' => {
4470 if !seen_dot && !seen_e {
4471 digits_before_dot = true;
4472 }
4473 end = i + 1;
4474 }
4475 b'+' | b'-' if i == 0 || bytes[i - 1] == b'e' || bytes[i - 1] == b'E' => {}
4478 b'.' if !seen_dot && !seen_e => {
4480 seen_dot = true;
4481 if digits_before_dot {
4482 end = i + 1;
4483 }
4484 }
4485 b'e' | b'E' if !seen_e && end > 0 => seen_e = true,
4486 _ => break,
4487 }
4488 }
4489 if end == 0 {
4490 return f64::NAN;
4491 }
4492 t[..end].parse::<f64>().unwrap_or(f64::NAN)
4493}
4494
4495pub(crate) fn js_pow(base: f64, exp: f64) -> f64 {
4501 if exp == 0.0 {
4502 return 1.0;
4503 }
4504 if base.is_nan() || exp.is_nan() {
4505 return f64::NAN;
4506 }
4507 if base.abs() == 1.0 && exp.is_infinite() {
4508 return f64::NAN;
4509 }
4510 base.powf(exp)
4511}
4512
4513fn math_fn(fname: &str, args: &[Value]) -> Result<Value, String> {
4514 if fname != "random"
4520 && args
4521 .iter()
4522 .any(|a| with_host(|h| matches!(h.get(a), Some(JsObj::BigInt(_)))))
4523 {
4524 return Err(host::type_error(
4525 "Cannot convert a BigInt value to a number",
4526 ));
4527 }
4528 let x = arg_num(args, 0);
4529 let r = match fname {
4530 "floor" => x.floor(),
4531 "ceil" => x.ceil(),
4532 "round" => {
4541 if !x.is_finite() || x == 0.0 {
4542 x
4543 } else if x > 0.0 && x < 0.5 {
4544 0.0
4545 } else if (-0.5..0.0).contains(&x) {
4546 -0.0
4547 } else {
4548 let f = x.floor();
4551 if x - f >= 0.5 {
4552 f + 1.0
4553 } else {
4554 f
4555 }
4556 }
4557 }
4558 "trunc" => x.trunc(),
4559 "abs" => x.abs(),
4560 "sign" => {
4561 if x.is_nan() {
4562 f64::NAN
4563 } else if x > 0.0 {
4564 1.0
4565 } else if x < 0.0 {
4566 -1.0
4567 } else {
4568 x
4569 }
4570 }
4571 "sqrt" => x.sqrt(),
4572 "cbrt" => x.cbrt(),
4573 "exp" => x.exp(),
4574 "log" => x.ln(),
4575 "log2" => x.log2(),
4576 "log10" => x.log10(),
4577 "sin" => x.sin(),
4578 "cos" => x.cos(),
4579 "tan" => x.tan(),
4580 "asin" => x.asin(),
4581 "acos" => x.acos(),
4582 "atan" => x.atan(),
4583 "atan2" => x.atan2(arg_num(args, 1)),
4584 "pow" => js_pow(x, arg_num(args, 1)),
4590 "sinh" => x.sinh(),
4592 "cosh" => x.cosh(),
4593 "tanh" => x.tanh(),
4594 "asinh" => x.asinh(),
4595 "acosh" => x.acosh(),
4596 "atanh" => x.atanh(),
4597 "log1p" => x.ln_1p(),
4598 "expm1" => x.exp_m1(),
4599 "imul" => (host::to_int32(x).wrapping_mul(host::to_int32(arg_num(args, 1)))) as f64,
4602 "hypot" => {
4603 let xs: Vec<f64> = args.iter().map(|a| with_host(|h| h.to_number(a))).collect();
4606 let mut max = 0.0f64;
4607 for x in &xs {
4608 if x.abs() > max {
4609 max = x.abs();
4610 }
4611 }
4612 if xs.iter().any(|x| x.is_infinite()) {
4613 f64::INFINITY
4614 } else if max == 0.0 || !max.is_finite() {
4615 max
4616 } else {
4617 let s: f64 = xs.iter().map(|x| (x / max) * (x / max)).sum();
4618 max * s.sqrt()
4619 }
4620 }
4621 "random" => pseudo_random(),
4622 "max" => {
4623 if args.is_empty() {
4624 f64::NEG_INFINITY
4625 } else {
4626 let mut m = f64::NEG_INFINITY;
4627 for a in args {
4628 let n = with_host(|h| h.to_number(a));
4629 if n.is_nan() {
4630 return Ok(Value::Float(f64::NAN));
4631 }
4632 if n > m || (n == m && n == 0.0 && n.is_sign_positive()) {
4636 m = n;
4637 }
4638 }
4639 m
4640 }
4641 }
4642 "min" => {
4643 if args.is_empty() {
4644 f64::INFINITY
4645 } else {
4646 let mut m = f64::INFINITY;
4647 for a in args {
4648 let n = with_host(|h| h.to_number(a));
4649 if n.is_nan() {
4650 return Ok(Value::Float(f64::NAN));
4651 }
4652 if n < m || (n == m && n == 0.0 && n.is_sign_negative()) {
4655 m = n;
4656 }
4657 }
4658 m
4659 }
4660 }
4661 "clz32" => {
4663 let u = if x.is_finite() {
4664 x.trunc().rem_euclid(4294967296.0) as u32
4665 } else {
4666 0
4667 };
4668 u.leading_zeros() as f64
4669 }
4670 "fround" => (x as f32) as f64,
4672 _ => return Err(host::type_error(&format!("Math.{fname} is not a function"))),
4673 };
4674 Ok(Value::Float(r))
4675}
4676
4677fn pseudo_random() -> f64 {
4680 use std::cell::Cell;
4681 thread_local!(static SEED: Cell<u64> = const { Cell::new(0x2545F4914F6CDD1D) });
4682 SEED.with(|s| {
4683 let mut x = s.get();
4684 x ^= x << 13;
4685 x ^= x >> 7;
4686 x ^= x << 17;
4687 s.set(x);
4688 (x >> 11) as f64 / (1u64 << 53) as f64
4689 })
4690}
4691
4692fn object_keys(args: Vec<Value>, mode: u8) -> Result<Value, String> {
4695 let v = arg0(&args);
4696 require_object_coercible(&v)?;
4697 if with_host(|h| h.kind_of(&v)) == Some(ObjKind::Proxy) {
4701 if mode == 3 {
4702 let keys = crate::proxy::own_keys(&v)?.unwrap_or_default();
4703 return Ok(with_host(|h| {
4704 let out: Vec<Value> = keys
4705 .into_iter()
4706 .filter(|k| !host::is_symbol_key(k))
4707 .map(|k| h.new_str(k))
4708 .collect();
4709 h.new_array(out)
4710 }));
4711 }
4712 let entries = crate::proxy::own_enum_entries(&v)?;
4713 return Ok(with_host(|h| {
4714 let out: Vec<Value> = entries
4715 .into_iter()
4716 .map(|(k, val)| match mode {
4717 0 => h.new_str(k),
4718 1 => val,
4719 _ => {
4720 let ks = h.new_str(k);
4721 h.new_array(vec![ks, val])
4722 }
4723 })
4724 .collect();
4725 h.new_array(out)
4726 }));
4727 }
4728 if let Some(JsObj::Builtin(ns)) = with_host(|h| h.get(&v).cloned()) {
4731 if let Some(names) = builtin_proto_method_names(&ns) {
4732 return Ok(with_host(|h| {
4733 let out: Vec<Value> = names
4734 .iter()
4735 .map(|name| match mode {
4736 1 => h.alloc(JsObj::Builtin(format!(
4737 "@proto:{}:{name}",
4738 ns.trim_end_matches(".prototype")
4739 ))),
4740 2 => {
4741 let ks = h.new_str(*name);
4742 let val = h.alloc(JsObj::Builtin(format!(
4743 "@proto:{}:{name}",
4744 ns.trim_end_matches(".prototype")
4745 )));
4746 h.new_array(vec![ks, val])
4747 }
4748 _ => h.new_str(*name),
4749 })
4750 .collect();
4751 h.new_array(out)
4752 }));
4753 }
4754 let mut names = crate::stdlib::namespace_keys(&ns);
4758 if names.is_empty() && mode == 3 {
4763 let prefix = format!("{ns}.");
4764 names = NS_METHODS
4765 .iter()
4766 .filter_map(|q| q.strip_prefix(&prefix))
4767 .map(|m| m.to_string())
4768 .collect();
4769 }
4770 if !names.is_empty() {
4771 let entries: Vec<(String, Value)> = names
4772 .into_iter()
4773 .map(|k| {
4774 let val = namespace_property(&ns, &k);
4775 (k, val)
4776 })
4777 .collect();
4778 return Ok(with_host(|h| {
4779 let out: Vec<Value> = entries
4780 .into_iter()
4781 .map(|(k, val)| match mode {
4782 1 => val,
4783 2 => {
4784 let ks = h.new_str(k);
4785 h.new_array(vec![ks, val])
4786 }
4787 _ => h.new_str(k),
4788 })
4789 .collect();
4790 h.new_array(out)
4791 }));
4792 }
4793 }
4794 let entries: Vec<(String, Value)> = with_host(|h| {
4797 if mode == 3 {
4798 return h
4801 .own_key_names(&v, false)
4802 .into_iter()
4803 .map(|k| (k, Value::Undef))
4804 .collect();
4805 }
4806 Vec::new()
4807 });
4808 let entries = if mode == 3 {
4809 entries
4810 } else {
4811 host::own_enum_entries_deep(&v)
4812 };
4813 Ok(with_host(|h| {
4814 let out: Vec<Value> = entries
4815 .into_iter()
4816 .map(|(k, val)| match mode {
4817 0 | 3 => h.new_str(k),
4818 1 => val,
4819 _ => {
4820 let ks = h.new_str(k);
4821 h.new_array(vec![ks, val])
4822 }
4823 })
4824 .collect();
4825 h.new_array(out)
4826 }))
4827}
4828
4829fn object_assign(args: Vec<Value>) -> Result<Value, String> {
4830 let target = arg0(&args);
4831 require_object_coercible(&target)?;
4834 for src in args.iter().skip(1) {
4835 let entries = host::own_enum_entries_deep(src);
4838 let syms = with_host(|h| h.own_symbol_entries(src));
4839 let filled = with_host(|h| {
4842 if let Some(JsObj::Object(p)) = h.get_mut(&target) {
4843 for (k, v) in entries.iter().cloned().chain(syms.iter().cloned()) {
4844 p.insert(k, v);
4845 }
4846 host::canonicalize_own_keys(p);
4847 return true;
4848 }
4849 false
4850 });
4851 if !filled {
4858 for (k, v) in entries.into_iter().chain(syms) {
4859 set_property(&target, &k, v)?;
4860 }
4861 }
4862 }
4863 Ok(target)
4864}
4865
4866fn object_from_entries(args: Vec<Value>) -> Result<Value, String> {
4867 let pairs = with_host(|h| h.iter_vec(&arg0(&args))).unwrap_or_default();
4868 let mut props: IndexMap<String, Value> = IndexMap::new();
4869 for p in pairs {
4870 let kv = with_host(|h| h.iter_vec(&p)).unwrap_or_default();
4871 let key = with_host(|h| h.str_of(&kv.first().cloned().unwrap_or(Value::Undef)));
4872 let val = kv.get(1).cloned().unwrap_or(Value::Undef);
4873 props.insert(key, val);
4874 }
4875 Ok(with_host(|h| h.new_object(props)))
4876}
4877
4878fn object_group_by(args: Vec<Value>) -> Result<Value, String> {
4882 let items = host::iter_all(&arg0(&args))?;
4883 let cb = args.get(1).cloned().unwrap_or(Value::Undef);
4884 let mut groups: IndexMap<String, Vec<Value>> = IndexMap::new();
4885 for (i, item) in items.into_iter().enumerate() {
4886 let key_v = host::invoke(&cb, vec![item.clone(), Value::Float(i as f64)], None)?;
4887 let key = with_host(|h| h.property_key(&key_v));
4888 groups.entry(key).or_default().push(item);
4889 }
4890 let props: IndexMap<String, Value> = with_host(|h| {
4891 groups
4892 .into_iter()
4893 .map(|(k, v)| (k, h.new_array(v)))
4894 .collect()
4895 });
4896 let obj = with_host(|h| h.new_object(props));
4897 with_host(|h| {
4899 let nv = h.null();
4900 h.set_proto(&obj, nv);
4901 });
4902 Ok(obj)
4903}
4904
4905fn map_group_by(args: Vec<Value>) -> Result<Value, String> {
4908 let items = host::iter_all(&arg0(&args))?;
4909 let cb = args.get(1).cloned().unwrap_or(Value::Undef);
4910 let m = with_host(|h| {
4911 h.alloc(JsObj::Map {
4912 entries: IndexMap::new(),
4913 weak: false,
4914 })
4915 });
4916 for (i, item) in items.into_iter().enumerate() {
4917 let key_v = host::invoke(&cb, vec![item.clone(), Value::Float(i as f64)], None)?;
4918 let existing = map_method(&m, "get", vec![key_v.clone()])?;
4919 if matches!(existing, Value::Undef) {
4920 let arr = with_host(|h| h.new_array(vec![item]));
4921 map_method(&m, "set", vec![key_v, arr])?;
4922 } else {
4923 with_host(|h| {
4924 if let Some(JsObj::Array(a)) = h.get_mut(&existing) {
4925 a.push(item);
4926 }
4927 });
4928 }
4929 }
4930 Ok(m)
4931}
4932
4933fn array_from_async(args: Vec<Value>) -> Result<Value, String> {
4949 thread_local! {
4950 static IMPL: std::cell::RefCell<Option<Value>> = const { std::cell::RefCell::new(None) };
4951 }
4952 const SRC: &str = "(async function (items, mapFn, thisArg) {\n\
4953 const out = []; let i = 0;\n\
4954 const step = async (v) => { const a = await v; out.push(mapFn ? await mapFn.call(thisArg, a, i) : a); i++; };\n\
4955 const iterable = items != null && (typeof items[Symbol.asyncIterator] === 'function'\n\
4956 || typeof items[Symbol.iterator] === 'function' || typeof items.next === 'function');\n\
4957 if (iterable) {\n\
4958 for await (const v of items) { out.push(mapFn ? await mapFn.call(thisArg, v, i) : v); i++; }\n\
4959 return out;\n\
4960 }\n\
4961 const len = items == null ? 0 : (Math.trunc(Number(items.length)) || 0);\n\
4962 while (i < len) { await step(items[i]); }\n\
4963 return out;\n\
4964 })";
4965 let f = IMPL.with(|c| c.borrow().clone());
4966 let f = match f {
4967 Some(f) => f,
4968 None => {
4969 let f = crate::eval_in_global_scope(SRC)?;
4970 IMPL.with(|c| *c.borrow_mut() = Some(f.clone()));
4971 f
4972 }
4973 };
4974 host::invoke(&f, args, None)
4975}
4976
4977fn array_from(args: Vec<Value>) -> Result<Value, String> {
4978 let src = arg0(&args);
4981 let items = match host::iter_all(&src) {
4982 Ok(v) => v,
4983 Err(_) => array_like_items(&src),
4984 };
4985 if let Some(cb) = args.get(1).cloned() {
4986 let mut out = Vec::with_capacity(items.len());
4987 for (i, it) in items.into_iter().enumerate() {
4988 out.push(host::invoke(&cb, vec![it, Value::Float(i as f64)], None)?);
4989 }
4990 return Ok(with_host(|h| h.new_array(out)));
4991 }
4992 Ok(with_host(|h| h.new_array(items)))
4993}
4994
4995fn array_like_items(src: &Value) -> Vec<Value> {
4997 let len = get_property(src, "length")
4998 .ok()
4999 .map(|l| with_host(|h| h.to_number(&l)))
5000 .unwrap_or(0.0);
5001 if !len.is_finite() || len <= 0.0 {
5002 return Vec::new();
5003 }
5004 (0..len as usize)
5005 .map(|i| get_property(src, &i.to_string()).unwrap_or(Value::Undef))
5006 .collect()
5007}
5008
5009fn json_stringify(args: Vec<Value>) -> Result<Value, String> {
5012 let replacer = args
5016 .get(1)
5017 .filter(|r| with_host(|h| host::is_callable(h, r)))
5018 .cloned();
5019 let root = arg0(&args);
5027 let wrapper = with_host(|h| {
5028 let mut m: IndexMap<String, Value> = IndexMap::new();
5029 m.insert(String::new(), root.clone());
5030 h.new_object(m)
5031 });
5032 let v = apply_to_json(&wrapper, "", &root, &mut Vec::new(), replacer.as_ref())?;
5033 if with_host(|h| json_has_bigint(h, &v)) {
5036 return Err(host::type_error("Do not know how to serialize a BigInt"));
5037 }
5038 let indent = match args.get(2) {
5039 Some(Value::Float(f)) => " ".repeat((*f as usize).min(10)),
5040 Some(other) => with_host(|h| h.as_str(other)).unwrap_or_default(),
5041 None => String::new(),
5042 };
5043 let keys: Option<Vec<String>> = args.get(1).and_then(|r| {
5045 with_host(|h| match h.get(r) {
5046 Some(JsObj::Array(items)) => {
5047 Some(items.iter().map(|k| h.str_of(k)).collect::<Vec<_>>())
5048 }
5049 _ => None,
5050 })
5051 });
5052 let s = with_host(|h| json_str(h, &v, &indent, 0, keys.as_deref()));
5053 match s {
5054 Some(s) => Ok(with_host(|h| h.new_str(s))),
5055 None => Ok(Value::Undef),
5056 }
5057}
5058
5059fn apply_to_json(
5074 holder: &Value,
5075 key: &str,
5076 v: &Value,
5077 path: &mut Vec<Value>,
5078 rep: Option<&Value>,
5079) -> Result<Value, String> {
5080 let mut v = v.clone();
5081 if matches!(v, Value::Obj(_)) {
5082 let tag = crate::stdlib::native_tag(&v);
5083 let has_to_json = with_host(|h| match host::lookup_chain(h, &v, "toJSON") {
5084 Some(f) => host::is_callable(h, &f),
5085 None => false,
5086 }) || tag
5087 .as_deref()
5088 .map(crate::stdlib::has_to_json)
5089 .unwrap_or(false);
5090 if has_to_json {
5091 let k = with_host(|h| h.new_str(key.to_string()));
5092 v = host::call_method(&v, "toJSON", vec![k])?;
5093 }
5094 }
5095 if let Some(rep) = rep {
5096 let k = with_host(|h| h.new_str(key.to_string()));
5097 v = host::invoke(rep, vec![k, v.clone()], Some(holder.clone()))?;
5098 }
5099 json_walk_children(&v, path, rep)
5100}
5101
5102fn json_visible_key(k: &str) -> bool {
5106 !k.starts_with("@@") && !k.starts_with('#')
5107}
5108
5109fn json_walk_children(
5112 v: &Value,
5113 path: &mut Vec<Value>,
5114 rep: Option<&Value>,
5115) -> Result<Value, String> {
5116 if !matches!(v, Value::Obj(_)) {
5117 return Ok(v.clone());
5118 }
5119 if with_host(|h| path.iter().any(|p| h.strict_eq(p, v))) {
5121 return Err(host::type_error("Converting circular structure to JSON"));
5122 }
5123 if with_host(|h| h.kind_of(v)) == Some(ObjKind::Proxy) {
5127 let snap = crate::proxy::json_snapshot(v)?;
5128 path.push(v.clone());
5129 let out = json_walk_children(&snap, path, rep);
5130 path.pop();
5131 return out;
5132 }
5133 let obj = with_host(|h| h.get(v).cloned());
5134 path.push(v.clone());
5135 let out = (|| match obj {
5136 Some(JsObj::Array(items)) => {
5137 let mut out = Vec::with_capacity(items.len());
5138 let mut changed = false;
5139 for (i, it) in items.iter().enumerate() {
5140 let nv = apply_to_json(v, &i.to_string(), it, path, rep)?;
5141 changed |= !with_host(|h| h.strict_eq(&nv, it));
5142 out.push(nv);
5143 }
5144 if changed {
5147 Ok(with_host(|h| h.new_array(out)))
5148 } else {
5149 Ok(v.clone())
5150 }
5151 }
5152 Some(JsObj::Object(props)) => {
5153 let has_accessor = with_host(|h| {
5158 h.own_accessor_keys(v)
5159 .iter()
5160 .any(|k| h.prop_attrs(v, k).enumerable)
5161 });
5162 if has_accessor {
5163 let mut next: IndexMap<String, Value> = IndexMap::new();
5164 for (k, val) in host::own_enum_entries_deep(v) {
5165 let nv = if json_visible_key(&k) {
5166 apply_to_json(v, &k, &val, path, rep)?
5167 } else {
5168 val
5169 };
5170 next.insert(k, nv);
5171 }
5172 return Ok(with_host(|h| h.new_object(next)));
5173 }
5174 let mut next: IndexMap<String, Value> = IndexMap::new();
5177 let mut changed = false;
5178 for (k, val) in &props {
5179 let nv = if json_visible_key(k) {
5180 apply_to_json(v, k, val, path, rep)?
5181 } else {
5182 val.clone()
5183 };
5184 changed |= !with_host(|h| h.strict_eq(&nv, val));
5185 next.insert(k.clone(), nv);
5186 }
5187 if changed {
5188 Ok(with_host(|h| {
5189 let o = h.new_object(next);
5190 h.copy_prop_attrs(v, &o);
5191 o
5192 }))
5193 } else {
5194 Ok(v.clone())
5195 }
5196 }
5197 _ => Ok(v.clone()),
5198 })();
5199 path.pop();
5200 out
5201}
5202
5203fn json_has_bigint(h: &host::JsHost, v: &Value) -> bool {
5206 match h.get(v) {
5207 Some(JsObj::BigInt(_)) => true,
5208 Some(JsObj::Array(items)) => items.iter().any(|x| json_has_bigint(h, x)),
5209 Some(JsObj::Object(props)) => props
5210 .iter()
5211 .filter(|(k, _)| !k.starts_with("@@") && !k.starts_with('#'))
5212 .any(|(_, val)| json_has_bigint(h, val)),
5213 _ => false,
5214 }
5215}
5216
5217fn json_str(
5218 h: &host::JsHost,
5219 v: &Value,
5220 indent: &str,
5221 depth: usize,
5222 keys: Option<&[String]>,
5223) -> Option<String> {
5224 let sep = if indent.is_empty() { ":" } else { ": " };
5225 match v {
5226 Value::Undef => None,
5227 Value::Bool(b) => Some(if *b { "true".into() } else { "false".into() }),
5228 Value::Int(n) => Some(n.to_string()),
5229 Value::Float(f) => Some(if f.is_finite() {
5230 host::fmt_number(*f)
5231 } else {
5232 "null".into()
5233 }),
5234 Value::Str(s) => Some(json_quote(s)),
5235 Value::Obj(_) => match h.get(v) {
5236 Some(JsObj::Str(s)) => Some(json_quote(s)),
5237 Some(JsObj::Null) => Some("null".into()),
5238 Some(JsObj::Map { .. }) | Some(JsObj::Set { .. }) => Some("{}".into()),
5240 Some(JsObj::Func(_))
5242 | Some(JsObj::Builtin(_))
5243 | Some(JsObj::BoundMethod { .. })
5244 | Some(JsObj::BoundFunc { .. })
5245 | Some(JsObj::Class(_))
5246 | Some(JsObj::Symbol { .. })
5247 | Some(JsObj::Generator { .. }) => None,
5248 Some(JsObj::Array(items)) => {
5249 if items.is_empty() {
5250 return Some("[]".into());
5251 }
5252 let parts: Vec<String> = items
5253 .iter()
5254 .map(|x| {
5255 json_str(h, x, indent, depth + 1, keys).unwrap_or_else(|| "null".into())
5256 })
5257 .collect();
5258 Some(wrap(&parts, "[", "]", indent, depth))
5259 }
5260 Some(JsObj::Object(props)) => {
5261 let parts: Vec<String> = match keys {
5263 Some(allow) => allow
5264 .iter()
5265 .filter_map(|k| {
5266 props.get(k).and_then(|val| {
5267 json_str(h, val, indent, depth + 1, keys)
5268 .map(|vs| format!("{}{sep}{vs}", json_quote(k)))
5269 })
5270 })
5271 .collect(),
5272 None => h
5273 .own_enum_entries(v)
5274 .iter()
5275 .filter_map(|(k, val)| {
5276 json_str(h, val, indent, depth + 1, keys)
5277 .map(|vs| format!("{}{sep}{vs}", json_quote(k)))
5278 })
5279 .collect(),
5280 };
5281 if parts.is_empty() {
5282 return Some("{}".into());
5283 }
5284 Some(wrap(&parts, "{", "}", indent, depth))
5285 }
5286 _ => Some("null".into()),
5287 },
5288 _ => Some("null".into()),
5289 }
5290}
5291
5292fn wrap(parts: &[String], open: &str, close: &str, indent: &str, depth: usize) -> String {
5293 if indent.is_empty() {
5294 format!("{open}{}{close}", parts.join(","))
5295 } else {
5296 let pad = indent.repeat(depth + 1);
5297 let pad_close = indent.repeat(depth);
5298 format!(
5299 "{open}\n{pad}{}\n{pad_close}{close}",
5300 parts.join(&format!(",\n{pad}"))
5301 )
5302 }
5303}
5304
5305fn json_quote(s: &str) -> String {
5306 let mut out = String::from("\"");
5307 for c in s.chars() {
5308 match c {
5309 '"' => out.push_str("\\\""),
5310 '\\' => out.push_str("\\\\"),
5311 '\n' => out.push_str("\\n"),
5312 '\t' => out.push_str("\\t"),
5313 '\r' => out.push_str("\\r"),
5314 '\u{8}' => out.push_str("\\b"),
5321 '\u{c}' => out.push_str("\\f"),
5322 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
5323 _ => out.push(c),
5324 }
5325 }
5326 out.push('"');
5327 out
5328}
5329
5330fn json_parse(args: Vec<Value>) -> Result<Value, String> {
5331 let s = with_host(|h| h.str_of(&arg0(&args)));
5332 let mut p = JsonParser {
5333 chars: s.chars().collect(),
5334 pos: 0,
5335 };
5336 p.skip_ws();
5337 if p.peek().is_none() {
5338 return Err("SyntaxError: Unexpected end of JSON input".into());
5339 }
5340 let v = p.parse_value()?;
5341 let value_end = p.pos;
5342 p.skip_ws();
5343 if let Some(c) = p.peek() {
5346 let after_number = value_end > 0
5353 && p.pos == value_end
5354 && p.chars[value_end - 1].is_ascii_digit()
5355 && c.is_ascii_digit();
5356 return Err(if after_number {
5357 p.err_at("Unexpected number", p.pos)
5358 } else {
5359 p.err_trailing(p.pos)
5360 });
5361 }
5362 if let Some(reviver) = args
5364 .get(1)
5365 .filter(|r| with_host(|h| host::is_callable(h, r)))
5366 .cloned()
5367 {
5368 return json_revive("", v, &reviver);
5369 }
5370 Ok(v)
5371}
5372
5373fn json_revive(key: &str, val: Value, reviver: &Value) -> Result<Value, String> {
5376 match with_host(|h| h.get(&val).cloned()) {
5377 Some(JsObj::Array(items)) => {
5378 for i in 0..items.len() {
5379 let elem = with_host(|h| match h.get(&val) {
5380 Some(JsObj::Array(it)) => it[i].clone(),
5381 _ => Value::Undef,
5382 });
5383 let nv = json_revive(&i.to_string(), elem, reviver)?;
5384 with_host(|h| {
5385 if let Some(JsObj::Array(it)) = h.get_mut(&val) {
5386 it[i] = nv;
5387 }
5388 });
5389 }
5390 }
5391 Some(JsObj::Object(props)) => {
5392 let keys: Vec<String> = props
5393 .keys()
5394 .filter(|k| !k.starts_with("@@"))
5395 .cloned()
5396 .collect();
5397 for k in keys {
5398 let elem = with_host(|h| match h.get(&val) {
5399 Some(JsObj::Object(p)) => p.get(&k).cloned().unwrap_or(Value::Undef),
5400 _ => Value::Undef,
5401 });
5402 let nv = json_revive(&k, elem, reviver)?;
5403 with_host(|h| {
5404 if let Some(JsObj::Object(p)) = h.get_mut(&val) {
5405 if matches!(nv, Value::Undef) {
5406 p.shift_remove(&k);
5407 } else {
5408 p.insert(k.clone(), nv);
5409 }
5410 }
5411 });
5412 }
5413 }
5414 _ => {}
5415 }
5416 let kv = with_host(|h| h.new_str(key.to_string()));
5417 host::invoke(reviver, vec![kv, val], None)
5418}
5419
5420struct JsonParser {
5421 chars: Vec<char>,
5422 pos: usize,
5423}
5424impl JsonParser {
5425 fn peek(&self) -> Option<char> {
5426 self.chars.get(self.pos).copied()
5427 }
5428
5429 fn at(&self, pos: usize) -> String {
5433 let mut line = 1usize;
5434 let mut col = 1usize;
5435 for c in &self.chars[..pos.min(self.chars.len())] {
5436 if *c == '\n' {
5437 line += 1;
5438 col = 1;
5439 } else {
5440 col += 1;
5441 }
5442 }
5443 format!(" at position {pos} (line {line} column {col})")
5444 }
5445
5446 fn err_at(&self, what: &str, pos: usize) -> String {
5448 format!("SyntaxError: {what} in JSON{}", self.at(pos))
5449 }
5450
5451 fn err_trailing(&self, pos: usize) -> String {
5453 format!(
5454 "SyntaxError: Unexpected non-whitespace character after JSON{}",
5455 self.at(pos)
5456 )
5457 }
5458
5459 fn err_token(&self, pos: usize) -> String {
5464 const MAX_WHOLE: usize = 20;
5465 const CONTEXT: usize = 10;
5466 let len = self.chars.len();
5467 let Some(c) = self.chars.get(pos) else {
5468 return "SyntaxError: Unexpected end of JSON input".into();
5469 };
5470 let whole: String = self.chars.iter().collect();
5473 if matches!(
5474 whole.as_str(),
5475 "undefined" | "NaN" | "Infinity" | "-Infinity"
5476 ) {
5477 return format!("SyntaxError: \"{whole}\" is not valid JSON");
5478 }
5479 let snippet = if len <= MAX_WHOLE {
5480 format!("\"{whole}\"")
5481 } else {
5482 let start = pos.saturating_sub(CONTEXT);
5483 let end = (pos + CONTEXT).min(len);
5484 let body: String = self.chars[start..end].iter().collect();
5485 let head = if start > 0 { "..." } else { "" };
5486 let tail = if end < len { "..." } else { "" };
5487 format!("{head}\"{body}\"{tail}")
5488 };
5489 format!("SyntaxError: Unexpected token '{c}', {snippet} is not valid JSON")
5490 }
5491
5492 fn skip_ws(&mut self) {
5493 while matches!(
5494 self.peek(),
5495 Some(' ') | Some('\n') | Some('\t') | Some('\r')
5496 ) {
5497 self.pos += 1;
5498 }
5499 }
5500 fn parse_value(&mut self) -> Result<Value, String> {
5501 self.skip_ws();
5502 match self.peek() {
5503 Some('{') => self.parse_object(),
5504 Some('[') => self.parse_array(),
5505 Some('"') => {
5506 let s = self.parse_string()?;
5507 Ok(with_host(|h| h.new_str(s)))
5508 }
5509 Some('t') | Some('f') => self.parse_bool(),
5510 Some('n') => {
5511 self.expect_lit("null")?;
5512 Ok(with_host(|h| h.null()))
5513 }
5514 Some(c) if c == '-' || c.is_ascii_digit() => self.parse_number(),
5515 None => Err("SyntaxError: Unexpected end of JSON input".into()),
5516 _ => Err(self.err_token(self.pos)),
5517 }
5518 }
5519 fn expect_lit(&mut self, lit: &str) -> Result<(), String> {
5520 for ch in lit.chars() {
5521 match self.peek() {
5522 Some(c) if c == ch => self.pos += 1,
5523 None => return Err("SyntaxError: Unexpected end of JSON input".into()),
5526 _ => return Err(self.err_token(self.pos)),
5527 }
5528 }
5529 Ok(())
5530 }
5531 fn parse_bool(&mut self) -> Result<Value, String> {
5532 if self.peek() == Some('t') {
5533 self.expect_lit("true")?;
5534 Ok(Value::Bool(true))
5535 } else {
5536 self.expect_lit("false")?;
5537 Ok(Value::Bool(false))
5538 }
5539 }
5540 fn parse_number(&mut self) -> Result<Value, String> {
5545 let start = self.pos;
5546 if self.peek() == Some('-') {
5547 self.pos += 1;
5548 if !matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
5549 return Err(self.err_at("No number after minus sign", self.pos));
5550 }
5551 }
5552 if self.peek() == Some('0') {
5553 self.pos += 1;
5554 } else {
5555 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
5556 self.pos += 1;
5557 }
5558 }
5559 if self.peek() == Some('.') {
5560 self.pos += 1;
5561 if !matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
5562 return Err(self.err_at("Unterminated fractional number", self.pos));
5563 }
5564 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
5565 self.pos += 1;
5566 }
5567 }
5568 if matches!(self.peek(), Some('e') | Some('E')) {
5569 self.pos += 1;
5570 if matches!(self.peek(), Some('+') | Some('-')) {
5571 self.pos += 1;
5572 }
5573 if !matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
5574 return Err(self.err_at("Exponent part is missing a number", self.pos));
5575 }
5576 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
5577 self.pos += 1;
5578 }
5579 }
5580 let s: String = self.chars[start..self.pos].iter().collect();
5581 s.parse::<f64>()
5582 .map(Value::Float)
5583 .map_err(|_| self.err_at("Unexpected number", start))
5584 }
5585 fn parse_string(&mut self) -> Result<String, String> {
5586 self.pos += 1; let mut out = String::new();
5588 loop {
5589 match self.peek() {
5590 None => return Err(self.err_at("Unterminated string", self.pos)),
5591 Some('"') => {
5592 self.pos += 1;
5593 break;
5594 }
5595 Some('\\') => {
5596 self.pos += 1;
5597 match self.peek() {
5598 Some('n') => out.push('\n'),
5599 Some('t') => out.push('\t'),
5600 Some('r') => out.push('\r'),
5601 Some('"') => out.push('"'),
5602 Some('\\') => out.push('\\'),
5603 Some('/') => out.push('/'),
5604 Some('b') => out.push('\u{08}'),
5605 Some('f') => out.push('\u{0C}'),
5606 Some('u') => {
5607 let h: String = self.chars
5608 [self.pos + 1..(self.pos + 5).min(self.chars.len())]
5609 .iter()
5610 .collect();
5611 if let Ok(n) = u32::from_str_radix(&h, 16) {
5612 if let Some(ch) = char::from_u32(n) {
5613 out.push(ch);
5614 }
5615 }
5616 self.pos += 4;
5617 }
5618 _ => {}
5619 }
5620 self.pos += 1;
5621 }
5622 Some(c) if (c as u32) < 0x20 => {
5625 return Err(self.err_at("Bad control character in string literal", self.pos))
5626 }
5627 Some(c) => {
5628 out.push(c);
5629 self.pos += 1;
5630 }
5631 }
5632 }
5633 Ok(out)
5634 }
5635 fn parse_array(&mut self) -> Result<Value, String> {
5636 self.pos += 1; let mut items = Vec::new();
5638 self.skip_ws();
5639 if self.peek() == Some(']') {
5640 self.pos += 1;
5641 return Ok(with_host(|h| h.new_array(items)));
5642 }
5643 loop {
5644 items.push(self.parse_value()?);
5645 self.skip_ws();
5646 match self.peek() {
5647 Some(',') => {
5648 self.pos += 1;
5649 }
5650 Some(']') => {
5651 self.pos += 1;
5652 break;
5653 }
5654 _ => return Err(self.err_at("Expected ',' or ']' after array element", self.pos)),
5655 }
5656 }
5657 Ok(with_host(|h| h.new_array(items)))
5658 }
5659 fn parse_object(&mut self) -> Result<Value, String> {
5660 self.pos += 1; let mut props: IndexMap<String, Value> = IndexMap::new();
5662 self.skip_ws();
5663 if self.peek() == Some('}') {
5664 self.pos += 1;
5665 return Ok(with_host(|h| h.new_object(props)));
5666 }
5667 loop {
5668 self.skip_ws();
5669 if self.peek() != Some('"') {
5670 return Err(if props.is_empty() {
5674 self.err_at("Expected property name or '}'", self.pos)
5675 } else {
5676 self.err_at("Expected double-quoted property name", self.pos)
5677 });
5678 }
5679 let key = self.parse_string()?;
5680 self.skip_ws();
5681 if self.peek() != Some(':') {
5682 return Err(match self.peek() {
5683 None => "SyntaxError: Unexpected end of JSON input".into(),
5684 _ => self.err_at("Expected ':' after property name", self.pos),
5685 });
5686 }
5687 self.pos += 1;
5688 let val = self.parse_value()?;
5689 props.insert(key, val);
5690 self.skip_ws();
5691 match self.peek() {
5692 Some(',') => {
5693 self.pos += 1;
5694 }
5695 Some('}') => {
5696 self.pos += 1;
5697 break;
5698 }
5699 _ => return Err(self.err_at("Expected ',' or '}' after property value", self.pos)),
5700 }
5701 }
5702 Ok(with_host(|h| h.new_object(props)))
5703 }
5704}
5705
5706fn is_array_method(name: &str) -> bool {
5709 matches!(
5710 name,
5711 "push"
5712 | "pop"
5713 | "shift"
5714 | "unshift"
5715 | "map"
5716 | "filter"
5717 | "forEach"
5718 | "join"
5719 | "slice"
5720 | "indexOf"
5721 | "lastIndexOf"
5722 | "includes"
5723 | "reduce"
5724 | "concat"
5725 | "reverse"
5726 | "sort"
5727 | "find"
5728 | "findIndex"
5729 | "some"
5730 | "every"
5731 | "flat"
5732 | "fill"
5733 | "splice"
5734 | "keys"
5735 | "values"
5736 | "entries"
5737 | "flatMap"
5738 | "at"
5739 | "toString"
5740 | "reduceRight"
5741 | "findLast"
5742 | "findLastIndex"
5743 | "copyWithin"
5744 )
5745}
5746fn is_string_method(name: &str) -> bool {
5747 matches!(
5748 name,
5749 "toUpperCase"
5750 | "toLowerCase"
5751 | "charAt"
5752 | "charCodeAt"
5753 | "codePointAt"
5754 | "indexOf"
5755 | "lastIndexOf"
5756 | "includes"
5757 | "slice"
5758 | "substring"
5759 | "substr"
5760 | "split"
5761 | "trim"
5762 | "trimStart"
5763 | "trimEnd"
5764 | "replace"
5765 | "replaceAll"
5766 | "repeat"
5767 | "startsWith"
5768 | "endsWith"
5769 | "padStart"
5770 | "padEnd"
5771 | "concat"
5772 | "at"
5773 | "toString"
5774 | "toLocaleString"
5775 | "valueOf"
5776 | "match"
5777 | "matchAll"
5778 | "search"
5779 | "normalize"
5780 | "localeCompare"
5781 | "toLocaleUpperCase"
5782 | "toLocaleLowerCase"
5783 | "isWellFormed"
5784 | "toWellFormed"
5785 )
5786}
5787
5788fn is_regexp_arg(v: &Value) -> bool {
5790 with_host(|h| h.kind_of(v)) == Some(ObjKind::RegExp)
5791}
5792
5793fn replace_str_fn(s: &str, pat: &str, repl: &Value, all: bool) -> Result<String, String> {
5796 if pat.is_empty() {
5797 return Ok(s.to_string());
5798 }
5799 let mut out = String::new();
5800 let mut rest = s;
5801 let mut base = 0usize;
5802 while let Some(pos) = rest.find(pat) {
5803 out.push_str(&rest[..pos]);
5804 let offset = base + pos;
5805 let m = with_host(|h| h.new_str(pat.to_string()));
5806 let str_arg = with_host(|h| h.new_str(s.to_string()));
5807 let r = host::invoke(repl, vec![m, Value::Float(offset as f64), str_arg], None)?;
5808 out.push_str(&with_host(|h| h.str_of(&r)));
5809 let consumed = pos + pat.len();
5810 base += consumed;
5811 rest = &rest[consumed..];
5812 if !all {
5813 break;
5814 }
5815 }
5816 out.push_str(rest);
5817 Ok(out)
5818}
5819fn is_number_method(name: &str) -> bool {
5820 matches!(
5821 name,
5822 "toFixed" | "toExponential" | "toString" | "toPrecision" | "toLocaleString" | "valueOf"
5823 )
5824}
5825
5826pub fn call_type_method(recv: &Value, name: &str, args: Vec<Value>) -> Result<Value, String> {
5828 if name == "valueOf"
5833 && matches!(
5834 with_host(|h| h.kind_of(recv)),
5835 Some(
5836 ObjKind::Array
5837 | ObjKind::Map
5838 | ObjKind::Set
5839 | ObjKind::Generator
5840 | ObjKind::Promise
5841 | ObjKind::Iter
5842 | ObjKind::RegExp
5843 )
5844 )
5845 {
5846 return Ok(recv.clone());
5847 }
5848 match with_host(|h| h.kind_of(recv)) {
5851 Some(ObjKind::Array) => array_method(recv, name, args),
5852 Some(ObjKind::Str) => {
5853 let s = peek(recv, |o| match o {
5856 JsObj::Str(s) => Some(s.clone()),
5857 _ => None,
5858 })
5859 .unwrap_or_default();
5860 string_method(&s, name, args)
5861 }
5862 Some(ObjKind::Map) => map_method(recv, name, args),
5863 Some(ObjKind::Set) => set_method(recv, name, args),
5864 Some(ObjKind::Generator) => generator_method(recv, name, args),
5865 Some(ObjKind::Promise) => promise_method(recv, name, args),
5866 Some(ObjKind::Iter) => iter_method(recv, name, args),
5867 Some(ObjKind::Symbol) => symbol_method(recv, name, args),
5868 Some(ObjKind::BigInt) => {
5869 let b = peek(recv, |o| match o {
5870 JsObj::BigInt(b) => Some(b.clone()),
5871 _ => None,
5872 })
5873 .unwrap_or_default();
5874 bigint_method(&b, name, args)
5875 }
5876 Some(ObjKind::RegExp) => crate::regexp::regexp_method(recv, name, args),
5877 Some(ObjKind::Func) | Some(ObjKind::Class) | Some(ObjKind::BoundFunc) => {
5878 match function_builtin_method(recv, name, &args)? {
5879 Some(v) => Ok(v),
5880 None => Err(host::type_error(&format!("{name} is not a function"))),
5881 }
5882 }
5883 Some(ObjKind::Object) => {
5884 if let Some(f) = peek(recv, |o| match o {
5885 JsObj::Object(p) => p.get(name).cloned(),
5886 _ => None,
5887 }) {
5888 host::invoke(&f, args, Some(recv.clone()))
5889 } else if name == "hasOwnProperty" {
5890 let k = with_host(|h| h.str_of(&arg0(&args)));
5891 let has = peek(recv, |o| match o {
5892 JsObj::Object(p) => Some(p.contains_key(&k)),
5893 _ => None,
5894 })
5895 .unwrap_or(false);
5896 Ok(Value::Bool(has))
5897 } else if name == "toString" {
5898 Ok(with_host(|h| h.new_str("[object Object]")))
5899 } else {
5900 Err(host::type_error(&format!("{} is not a function", name)))
5901 }
5902 }
5903 _ => {
5904 if let Value::Float(_) | Value::Int(_) = recv {
5906 return number_method(with_host(|h| h.to_number(recv)), name, args);
5907 }
5908 if let Some(s) = with_host(|h| h.as_str(recv)) {
5909 return string_method(&s, name, args);
5910 }
5911 if let Value::Bool(b) = recv {
5918 return match name {
5919 "toString" | "toLocaleString" => {
5920 Ok(new_s(if *b { "true" } else { "false" }.to_string()))
5921 }
5922 "valueOf" => Ok(Value::Bool(*b)),
5923 _ => Err(host::type_error(&format!("{name} is not a function"))),
5924 };
5925 }
5926 Err(host::type_error(&format!("{} is not a function", name)))
5927 }
5928 }
5929}
5930
5931fn array_items(recv: &Value) -> Vec<Value> {
5935 with_host(|h| match h.get(recv) {
5936 Some(JsObj::Array(items)) => items.clone(),
5937 _ => Vec::new(),
5938 })
5939}
5940
5941fn hole_set(recv: &Value) -> rustc_hash::FxHashSet<usize> {
5951 with_host(|h| h.hole_indices(recv)).into_iter().collect()
5952}
5953
5954fn array_len(recv: &Value) -> usize {
5956 peek(recv, |o| match o {
5957 JsObj::Array(items) => Some(items.len()),
5958 _ => None,
5959 })
5960 .unwrap_or(0)
5961}
5962
5963fn array_method(recv: &Value, name: &str, args: Vec<Value>) -> Result<Value, String> {
5964 array_method_on(recv, recv, name, args)
5965}
5966
5967const ARRAY_MUTATORS: &[&str] = &[
5970 "push",
5971 "pop",
5972 "shift",
5973 "unshift",
5974 "splice",
5975 "sort",
5976 "reverse",
5977 "fill",
5978 "copyWithin",
5979];
5980
5981fn array_generic(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
5994 let len = match get_property(recv, "length") {
5995 Ok(v) => host::to_array_length(&v).unwrap_or(0),
5996 Err(_) => 0,
5997 };
5998 let dense = with_host(|h| h.as_str(recv)).is_some();
6002 let mut items = Vec::with_capacity(len);
6003 let mut holes: rustc_hash::FxHashSet<usize> = rustc_hash::FxHashSet::default();
6004 for i in 0..len {
6005 let k = i.to_string();
6006 if dense || has_property(recv, &k)? {
6007 items.push(get_property(recv, &k)?);
6008 } else {
6009 holes.insert(i);
6010 items.push(Value::Undef);
6011 }
6012 }
6013 let tmp = with_host(|h| {
6014 let a = h.new_array(items);
6015 h.install_holes(&a, holes);
6016 a
6017 });
6018 let out = array_method_on(&tmp, recv, method, args)?;
6019 if ARRAY_MUTATORS.contains(&method) {
6020 let result = with_host(|h| match h.get(&tmp) {
6021 Some(JsObj::Array(items)) => items.clone(),
6022 _ => Vec::new(),
6023 });
6024 for (i, v) in result.iter().enumerate() {
6025 set_property(recv, &i.to_string(), v.clone())?;
6026 }
6027 set_property(recv, "length", Value::Float(result.len() as f64))?;
6028 }
6029 Ok(out)
6030}
6031
6032fn array_method_on(
6039 recv: &Value,
6040 this_value: &Value,
6041 name: &str,
6042 args: Vec<Value>,
6043) -> Result<Value, String> {
6044 match name {
6045 "push" => {
6046 let len = with_host(|h| {
6049 if let Some(JsObj::Array(items)) = h.get_mut(recv) {
6050 items.extend(args.iter().cloned());
6051 items.len()
6052 } else {
6053 0
6054 }
6055 });
6056 Ok(Value::Float(len as f64))
6057 }
6058 "pop" => Ok(with_host(|h| {
6059 let popped = if let Some(JsObj::Array(items)) = h.get_mut(recv) {
6060 items.pop().unwrap_or(Value::Undef)
6061 } else {
6062 Value::Undef
6063 };
6064 let len = match h.get(recv) {
6065 Some(JsObj::Array(items)) => items.len(),
6066 _ => 0,
6067 };
6068 h.truncate_holes(recv, len);
6069 popped
6070 })),
6071 "shift" => Ok(with_host(|h| {
6072 let shifted = if let Some(JsObj::Array(items)) = h.get_mut(recv) {
6073 if items.is_empty() {
6074 Value::Undef
6075 } else {
6076 items.remove(0)
6077 }
6078 } else {
6079 Value::Undef
6080 };
6081 h.remap_holes(recv, |i| i.checked_sub(1));
6082 shifted
6083 })),
6084 "unshift" => {
6085 with_host(|h| {
6086 if let Some(JsObj::Array(items)) = h.get_mut(recv) {
6087 for (i, a) in args.iter().enumerate() {
6088 items.insert(i, a.clone());
6089 }
6090 }
6091 let n = args.len();
6092 h.remap_holes(recv, |i| Some(i + n));
6093 });
6094 Ok(Value::Float(array_len(recv) as f64))
6095 }
6096 "join" => {
6097 let sep = if args.is_empty() {
6098 ",".to_string()
6099 } else {
6100 with_host(|h| h.str_of(&args[0]))
6101 };
6102 join_array(recv, &sep)
6103 }
6104 "toLocaleString" => {
6109 if !host::join_stack_push(recv) {
6112 return Ok(with_host(|h| h.new_str(String::new())));
6113 }
6114 let items = array_items(recv);
6115 let mut parts: Vec<String> = Vec::with_capacity(items.len());
6116 for it in &items {
6117 if with_host(|h| h.is_nullish(it)) {
6118 parts.push(String::new());
6119 continue;
6120 }
6121 let v = match host::call_method(it, "toLocaleString", Vec::new()) {
6122 Ok(v) => v,
6123 Err(e) => {
6124 host::join_stack_pop();
6125 return Err(e);
6126 }
6127 };
6128 parts.push(with_host(|h| h.str_of(&v)));
6129 }
6130 host::join_stack_pop();
6131 Ok(with_host(|h| h.new_str(parts.join(","))))
6132 }
6133 "indexOf" => {
6137 let items = array_items(recv);
6138 let holes = hole_set(recv);
6139 let target = arg0(&args);
6140 let idx = with_host(|h| {
6141 items
6142 .iter()
6143 .enumerate()
6144 .position(|(i, x)| !holes.contains(&i) && h.strict_eq(x, &target))
6145 });
6146 Ok(Value::Float(idx.map(|i| i as f64).unwrap_or(-1.0)))
6147 }
6148 "lastIndexOf" => {
6149 let items = array_items(recv);
6150 let holes = hole_set(recv);
6151 let target = arg0(&args);
6152 let idx = with_host(|h| {
6153 items
6154 .iter()
6155 .enumerate()
6156 .rposition(|(i, x)| !holes.contains(&i) && h.strict_eq(x, &target))
6157 });
6158 Ok(Value::Float(idx.map(|i| i as f64).unwrap_or(-1.0)))
6159 }
6160 "includes" => {
6161 let items = array_items(recv);
6163 let target = arg0(&args);
6164 let tnan = matches!(target, Value::Float(f) if f.is_nan());
6165 Ok(Value::Bool(with_host(|h| {
6166 items.iter().any(|x| {
6167 (tnan && matches!(x, Value::Float(f) if f.is_nan())) || h.strict_eq(x, &target)
6168 })
6169 })))
6170 }
6171 "slice" => {
6172 let items = array_items(recv);
6173 let (lo, hi) = slice_bounds(&args, items.len());
6174 Ok(with_host(|h| {
6175 let out = h.new_array(items[lo..hi].to_vec());
6176 h.copy_holes(recv, &out, |i| (i >= lo && i < hi).then(|| i - lo));
6177 out
6178 }))
6179 }
6180 "concat" => {
6181 let mut out = array_items(recv);
6182 let mut holes = hole_set(recv);
6185 let mut sources: Vec<(Value, usize)> = Vec::new();
6186 for a in &args {
6187 match with_host(|h| h.get(a).cloned()) {
6188 Some(JsObj::Array(items)) => {
6189 sources.push((a.clone(), out.len()));
6190 out.extend(items);
6191 }
6192 _ => out.push(a.clone()),
6193 }
6194 }
6195 for (src, base) in sources {
6196 holes.extend(
6197 with_host(|h| h.hole_indices(&src))
6198 .into_iter()
6199 .map(|i| i + base),
6200 );
6201 }
6202 Ok(with_host(|h| {
6203 let arr = h.new_array(out);
6204 h.install_holes(&arr, holes);
6205 arr
6206 }))
6207 }
6208 "reverse" => {
6209 let len = array_len(recv);
6210 with_host(|h| {
6211 if let Some(JsObj::Array(items)) = h.get_mut(recv) {
6212 items.reverse();
6213 }
6214 h.remap_holes(recv, |i| Some(len - 1 - i));
6215 });
6216 Ok(this_value.clone())
6217 }
6218 "fill" => {
6219 let val = arg0(&args);
6221 let len = array_len(recv) as i64;
6222 let norm =
6223 |v: i64| -> usize { (if v < 0 { (len + v).max(0) } else { v.min(len) }) as usize };
6224 let start = if args.len() >= 2 {
6225 norm(arg_num(&args, 1) as i64)
6226 } else {
6227 0
6228 };
6229 let end = if args.len() >= 3 {
6230 norm(arg_num(&args, 2) as i64)
6231 } else {
6232 len as usize
6233 };
6234 with_host(|h| {
6235 if let Some(JsObj::Array(items)) = h.get_mut(recv) {
6236 for it in items.iter_mut().take(end).skip(start) {
6237 *it = val.clone();
6238 }
6239 }
6240 h.remap_holes(recv, |i| (i < start || i >= end).then_some(i));
6242 });
6243 Ok(this_value.clone())
6244 }
6245 "copyWithin" => {
6246 let items = array_items(recv);
6248 let len = items.len() as i64;
6249 let norm =
6250 |v: i64| -> usize { (if v < 0 { (len + v).max(0) } else { v.min(len) }) as usize };
6251 let target = norm(arg_num(&args, 0) as i64);
6252 let start = if args.len() >= 2 {
6253 norm(arg_num(&args, 1) as i64)
6254 } else {
6255 0
6256 };
6257 let end = if args.len() >= 3 {
6258 norm(arg_num(&args, 2) as i64)
6259 } else {
6260 len as usize
6261 };
6262 let slice: Vec<Value> = items[start..end.max(start)].to_vec();
6263 let copied = slice.len();
6264 let src_holes = hole_set(recv);
6268 with_host(|h| {
6269 if let Some(JsObj::Array(a)) = h.get_mut(recv) {
6270 for (k, v) in slice.into_iter().enumerate() {
6271 if target + k < a.len() {
6272 a[target + k] = v;
6273 }
6274 }
6275 }
6276 let len = len as usize;
6277 let mut holes: rustc_hash::FxHashSet<usize> = src_holes
6278 .iter()
6279 .copied()
6280 .filter(|i| *i < target || *i >= (target + copied).min(len))
6281 .collect();
6282 for k in 0..copied {
6283 if target + k < len && src_holes.contains(&(start + k)) {
6284 holes.insert(target + k);
6285 }
6286 }
6287 h.install_holes(recv, holes);
6288 });
6289 Ok(this_value.clone())
6290 }
6291 "at" => {
6292 let items = array_items(recv);
6293 let mut i = arg_num(&args, 0) as i64;
6294 if i < 0 {
6295 i += items.len() as i64;
6296 }
6297 Ok(if i >= 0 && (i as usize) < items.len() {
6298 items[i as usize].clone()
6299 } else {
6300 Value::Undef
6301 })
6302 }
6303 "map" => {
6307 let items = array_items(recv);
6308 let holes = hole_set(recv);
6309 let cb = arg0(&args);
6310 let mut out = Vec::with_capacity(items.len());
6311 for (i, it) in items.iter().enumerate() {
6312 if holes.contains(&i) {
6313 out.push(Value::Undef);
6314 continue;
6315 }
6316 out.push(host::invoke(
6317 &cb,
6318 vec![it.clone(), Value::Float(i as f64), this_value.clone()],
6319 None,
6320 )?);
6321 }
6322 Ok(with_host(|h| {
6323 let arr = h.new_array(out);
6324 h.install_holes(&arr, holes);
6325 arr
6326 }))
6327 }
6328 "flatMap" => {
6329 let items = array_items(recv);
6330 let cb = arg0(&args);
6331 let holes = hole_set(recv);
6332 let mut out = Vec::new();
6333 for (i, it) in items.iter().enumerate() {
6334 if holes.contains(&i) {
6335 continue;
6336 }
6337 let r = host::invoke(
6338 &cb,
6339 vec![it.clone(), Value::Float(i as f64), this_value.clone()],
6340 None,
6341 )?;
6342 match with_host(|h| h.get(&r).cloned()) {
6343 Some(JsObj::Array(inner)) => out.extend(inner),
6344 _ => out.push(r),
6345 }
6346 }
6347 Ok(with_host(|h| h.new_array(out)))
6348 }
6349 "filter" => {
6350 let items = array_items(recv);
6351 let holes = hole_set(recv);
6352 let cb = arg0(&args);
6353 let mut out = Vec::new();
6354 for (i, it) in items.iter().enumerate() {
6355 if holes.contains(&i) {
6356 continue;
6357 }
6358 let keep = host::invoke(
6359 &cb,
6360 vec![it.clone(), Value::Float(i as f64), this_value.clone()],
6361 None,
6362 )?;
6363 if with_host(|h| h.truthy(&keep)) {
6364 out.push(it.clone());
6365 }
6366 }
6367 Ok(with_host(|h| h.new_array(out)))
6368 }
6369 "forEach" => {
6370 let items = array_items(recv);
6371 let holes = hole_set(recv);
6372 let cb = arg0(&args);
6373 for (i, it) in items.iter().enumerate() {
6374 if holes.contains(&i) {
6375 continue;
6376 }
6377 host::invoke(
6378 &cb,
6379 vec![it.clone(), Value::Float(i as f64), this_value.clone()],
6380 None,
6381 )?;
6382 }
6383 Ok(Value::Undef)
6384 }
6385 "find" => {
6386 let items = array_items(recv);
6387 let cb = arg0(&args);
6388 for (i, it) in items.iter().enumerate() {
6389 let m = host::invoke(
6390 &cb,
6391 vec![it.clone(), Value::Float(i as f64), this_value.clone()],
6392 None,
6393 )?;
6394 if with_host(|h| h.truthy(&m)) {
6395 return Ok(it.clone());
6396 }
6397 }
6398 Ok(Value::Undef)
6399 }
6400 "findIndex" => {
6401 let items = array_items(recv);
6402 let cb = arg0(&args);
6403 for (i, it) in items.iter().enumerate() {
6404 let m = host::invoke(
6405 &cb,
6406 vec![it.clone(), Value::Float(i as f64), this_value.clone()],
6407 None,
6408 )?;
6409 if with_host(|h| h.truthy(&m)) {
6410 return Ok(Value::Float(i as f64));
6411 }
6412 }
6413 Ok(Value::Float(-1.0))
6414 }
6415 "some" => {
6416 let items = array_items(recv);
6417 let holes = hole_set(recv);
6418 let cb = arg0(&args);
6419 for (i, it) in items.iter().enumerate() {
6420 if holes.contains(&i) {
6421 continue;
6422 }
6423 let m = host::invoke(
6424 &cb,
6425 vec![it.clone(), Value::Float(i as f64), this_value.clone()],
6426 None,
6427 )?;
6428 if with_host(|h| h.truthy(&m)) {
6429 return Ok(Value::Bool(true));
6430 }
6431 }
6432 Ok(Value::Bool(false))
6433 }
6434 "every" => {
6435 let items = array_items(recv);
6436 let holes = hole_set(recv);
6437 let cb = arg0(&args);
6438 for (i, it) in items.iter().enumerate() {
6439 if holes.contains(&i) {
6440 continue;
6441 }
6442 let m = host::invoke(
6443 &cb,
6444 vec![it.clone(), Value::Float(i as f64), this_value.clone()],
6445 None,
6446 )?;
6447 if !with_host(|h| h.truthy(&m)) {
6448 return Ok(Value::Bool(false));
6449 }
6450 }
6451 Ok(Value::Bool(true))
6452 }
6453 "reduce" => {
6454 let items = array_items(recv);
6455 let holes = hole_set(recv);
6456 let cb = arg0(&args);
6457 let mut acc;
6458 let mut start = 0;
6459 if args.len() >= 2 {
6460 acc = args[1].clone();
6461 } else {
6462 match (0..items.len()).find(|i| !holes.contains(i)) {
6465 Some(i) => {
6466 acc = items[i].clone();
6467 start = i + 1;
6468 }
6469 None => {
6470 return Err(host::type_error(
6471 "Reduce of empty array with no initial value",
6472 ))
6473 }
6474 }
6475 }
6476 for (i, it) in items.iter().enumerate().skip(start) {
6477 if holes.contains(&i) {
6478 continue;
6479 }
6480 acc = host::invoke(
6481 &cb,
6482 vec![acc, it.clone(), Value::Float(i as f64), this_value.clone()],
6483 None,
6484 )?;
6485 }
6486 Ok(acc)
6487 }
6488 "reduceRight" => {
6489 let items = array_items(recv);
6490 let holes = hole_set(recv);
6491 let cb = arg0(&args);
6492 let n = items.len();
6493 let mut acc;
6494 let mut i = n; if args.len() >= 2 {
6496 acc = args[1].clone();
6497 } else {
6498 match (0..n).rev().find(|i| !holes.contains(i)) {
6499 Some(k) => {
6500 acc = items[k].clone();
6501 i = k;
6502 }
6503 None => {
6504 return Err(host::type_error(
6505 "Reduce of empty array with no initial value",
6506 ))
6507 }
6508 }
6509 }
6510 while i > 0 {
6511 i -= 1;
6512 if holes.contains(&i) {
6513 continue;
6514 }
6515 acc = host::invoke(
6516 &cb,
6517 vec![
6518 acc,
6519 items[i].clone(),
6520 Value::Float(i as f64),
6521 this_value.clone(),
6522 ],
6523 None,
6524 )?;
6525 }
6526 Ok(acc)
6527 }
6528 "findLast" => {
6529 let items = array_items(recv);
6530 let cb = arg0(&args);
6531 for i in (0..items.len()).rev() {
6532 let m = host::invoke(
6533 &cb,
6534 vec![items[i].clone(), Value::Float(i as f64), this_value.clone()],
6535 None,
6536 )?;
6537 if with_host(|h| h.truthy(&m)) {
6538 return Ok(items[i].clone());
6539 }
6540 }
6541 Ok(Value::Undef)
6542 }
6543 "findLastIndex" => {
6544 let items = array_items(recv);
6545 let cb = arg0(&args);
6546 for i in (0..items.len()).rev() {
6547 let m = host::invoke(
6548 &cb,
6549 vec![items[i].clone(), Value::Float(i as f64), this_value.clone()],
6550 None,
6551 )?;
6552 if with_host(|h| h.truthy(&m)) {
6553 return Ok(Value::Float(i as f64));
6554 }
6555 }
6556 Ok(Value::Float(-1.0))
6557 }
6558 "sort" => {
6562 let all = array_items(recv);
6563 let holes = hole_set(recv);
6564 let mut items: Vec<Value> = all
6565 .iter()
6566 .enumerate()
6567 .filter(|(i, _)| !holes.contains(i))
6568 .map(|(_, v)| v.clone())
6569 .collect();
6570 sort_values(&mut items, args.first())?;
6571 let present = items.len();
6572 items.resize(all.len(), Value::Undef);
6573 with_host(|h| {
6574 if let Some(JsObj::Array(a)) = h.get_mut(recv) {
6575 *a = items;
6576 }
6577 h.install_holes(recv, (present..all.len()).collect());
6578 });
6579 Ok(this_value.clone())
6580 }
6581 "toSorted" => {
6583 let mut items = array_items(recv);
6584 sort_values(&mut items, args.first())?;
6585 Ok(with_host(|h| h.new_array(items)))
6586 }
6587 "toReversed" => {
6588 let mut items = array_items(recv);
6589 items.reverse();
6590 Ok(with_host(|h| h.new_array(items)))
6591 }
6592 "toSpliced" => {
6593 let mut items = array_items(recv);
6594 let len = items.len();
6595 let start = {
6596 let s = arg_num(&args, 0);
6597 if s < 0.0 {
6598 ((len as f64 + s).max(0.0)) as usize
6599 } else {
6600 (s as usize).min(len)
6601 }
6602 };
6603 let delete = if args.len() >= 2 {
6604 (arg_num(&args, 1).max(0.0) as usize).min(len - start)
6605 } else if args.is_empty() {
6606 0
6607 } else {
6608 len - start
6609 };
6610 let inserts: Vec<Value> = args.iter().skip(2).cloned().collect();
6611 items.splice(start..start + delete, inserts);
6612 Ok(with_host(|h| h.new_array(items)))
6613 }
6614 "with" => {
6615 let mut items = array_items(recv);
6616 let len = items.len() as i64;
6617 let rel = arg_num(&args, 0) as i64;
6618 let idx = if rel < 0 { len + rel } else { rel };
6619 if idx < 0 || idx >= len {
6620 return Err(host::range_error(&format!("Invalid index : {rel}")));
6621 }
6622 items[idx as usize] = args.get(1).cloned().unwrap_or(Value::Undef);
6623 Ok(with_host(|h| h.new_array(items)))
6624 }
6625 "flat" => {
6626 let raw = if args.is_empty() {
6629 1.0
6630 } else {
6631 arg_num(&args, 0)
6632 };
6633 let depth = if raw.is_nan() {
6634 0.0
6635 } else if raw.is_infinite() {
6636 raw
6637 } else {
6638 raw.trunc()
6639 };
6640 let mut out = Vec::new();
6641 flatten_into(recv, depth, &mut out)?;
6642 Ok(with_host(|h| h.new_array(out)))
6643 }
6644 "keys" => {
6645 let n = array_len(recv);
6646 let items: Vec<Value> = (0..n).map(|i| Value::Float(i as f64)).collect();
6647 Ok(with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })))
6648 }
6649 "values" | "@@iterator" => {
6650 let items = array_items(recv);
6651 Ok(with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })))
6652 }
6653 "entries" => {
6654 let items = array_items(recv);
6655 let pairs: Vec<Value> = items
6656 .into_iter()
6657 .enumerate()
6658 .map(|(i, v)| with_host(|h| h.new_array(vec![Value::Float(i as f64), v])))
6659 .collect();
6660 Ok(with_host(|h| {
6661 h.alloc(JsObj::Iter {
6662 items: pairs,
6663 idx: 0,
6664 })
6665 }))
6666 }
6667 "splice" => array_splice(recv, args),
6668 "toString" => join_array(recv, ","),
6673 _ if is_object_builtin_method(name) => object_builtin_method(recv, name, args),
6678 _ => Err(host::type_error(&format!("{name} is not a function"))),
6679 }
6680}
6681
6682fn join_array(recv: &Value, sep: &str) -> Result<Value, String> {
6689 if !host::join_stack_push(recv) {
6690 return Ok(with_host(|h| h.new_str(String::new())));
6691 }
6692 let parts = join_parts(&array_items(recv));
6693 host::join_stack_pop();
6694 let s = parts?.join(sep);
6695 Ok(with_host(|h| h.new_str(s)))
6696}
6697
6698fn join_parts(items: &[Value]) -> Result<Vec<String>, String> {
6708 let fast = with_host(|h| {
6709 items
6710 .iter()
6711 .map(|x| match x {
6712 Value::Undef => Some(String::new()),
6713 _ if h.is_null(x) => Some(String::new()),
6714 _ if matches!(h.get(x), Some(JsObj::Symbol { .. })) => None,
6718 _ if host::is_primitive(h, x) => Some(h.str_of(x)),
6719 _ => None,
6720 })
6721 .collect::<Vec<_>>()
6722 });
6723 if fast.iter().all(Option::is_some) {
6724 return Ok(fast.into_iter().flatten().collect());
6725 }
6726 let mut out = Vec::with_capacity(items.len());
6727 for (x, p) in items.iter().zip(fast) {
6728 match p {
6729 Some(s) => out.push(s),
6730 None => {
6731 let s = host::to_string_value(x)?;
6732 out.push(with_host(|h| h.str_of(&s)));
6733 }
6734 }
6735 }
6736 Ok(out)
6737}
6738
6739pub(crate) fn sort_values(items: &mut [Value], cmp: Option<&Value>) -> Result<(), String> {
6750 let cmp = match cmp {
6754 Some(Value::Undef) => None,
6755 Some(v) if !with_host(|h| host::is_callable(h, v)) => {
6756 let shown = with_host(|h| h.inspect(v));
6757 return Err(host::type_error(&format!(
6758 "The comparison function must be either a function or undefined: {shown}"
6759 )));
6760 }
6761 other => other,
6762 };
6763 let mut defined = 0;
6770 for i in 0..items.len() {
6771 if !matches!(items[i], Value::Undef) {
6772 items.swap(defined, i);
6773 defined += 1;
6774 }
6775 }
6776 merge_sort(&mut items[..defined], cmp)
6777}
6778
6779fn sort_compare(a: &Value, b: &Value, cmp: Option<&Value>) -> Result<f64, String> {
6783 match cmp {
6784 Some(cb) => {
6785 let v = host::invoke(cb, vec![a.clone(), b.clone()], None)?;
6786 Ok(with_host(|h| h.to_number(&v)))
6787 }
6788 None => {
6789 let x = with_host(|h| h.str_of(a));
6793 let y = with_host(|h| h.str_of(b));
6794 if crate::utf16::cmp_units(&x, &y) == std::cmp::Ordering::Greater {
6795 Ok(1.0)
6796 } else {
6797 Ok(-1.0)
6798 }
6799 }
6800 }
6801}
6802
6803fn merge_sort(items: &mut [Value], cmp: Option<&Value>) -> Result<(), String> {
6807 let n = items.len();
6808 if n < 2 {
6809 return Ok(());
6810 }
6811 let mut src = items.to_vec();
6812 let mut dst = src.clone();
6813 let mut width = 1;
6814 while width < n {
6815 let mut lo = 0;
6816 while lo < n {
6817 let mid = (lo + width).min(n);
6818 let hi = (lo + 2 * width).min(n);
6819 merge(&src[lo..mid], &src[mid..hi], &mut dst[lo..hi], cmp)?;
6820 lo = hi;
6821 }
6822 std::mem::swap(&mut src, &mut dst);
6823 width *= 2;
6824 }
6825 items.clone_from_slice(&src);
6826 Ok(())
6827}
6828
6829fn merge(
6833 left: &[Value],
6834 right: &[Value],
6835 out: &mut [Value],
6836 cmp: Option<&Value>,
6837) -> Result<(), String> {
6838 let (mut i, mut j, mut k) = (0, 0, 0);
6839 while i < left.len() && j < right.len() {
6840 if sort_compare(&left[i], &right[j], cmp)? > 0.0 {
6841 out[k] = right[j].clone();
6842 j += 1;
6843 } else {
6844 out[k] = left[i].clone();
6845 i += 1;
6846 }
6847 k += 1;
6848 }
6849 for v in left[i..].iter().chain(&right[j..]) {
6850 out[k] = v.clone();
6851 k += 1;
6852 }
6853 Ok(())
6854}
6855
6856fn flatten_into(src: &Value, depth: f64, out: &mut Vec<Value>) -> Result<(), String> {
6868 if host::stack_exhausted() {
6869 return Err(host::stack_overflow_error());
6870 }
6871 let items = array_items(src);
6872 let holes = hole_set(src);
6873 for (i, it) in items.into_iter().enumerate() {
6874 if holes.contains(&i) {
6875 continue;
6876 }
6877 let nested = depth > 0.0 && with_host(|h| h.kind_of(&it)) == Some(ObjKind::Array);
6878 if nested {
6879 flatten_into(&it, depth - 1.0, out)?;
6880 } else {
6881 out.push(it);
6882 }
6883 }
6884 Ok(())
6885}
6886
6887fn array_splice(recv: &Value, args: Vec<Value>) -> Result<Value, String> {
6888 let len = array_len(recv);
6889 let start = {
6890 let s = arg_num(&args, 0);
6891 if s < 0.0 {
6892 ((len as f64 + s).max(0.0)) as usize
6893 } else {
6894 (s as usize).min(len)
6895 }
6896 };
6897 let delete = if args.len() >= 2 {
6898 (arg_num(&args, 1).max(0.0) as usize).min(len - start)
6899 } else {
6900 len - start
6901 };
6902 let inserts: Vec<Value> = args.iter().skip(2).cloned().collect();
6903 let inserted = inserts.len();
6904 let holes = hole_set(recv);
6907 let removed = with_host(|h| {
6908 if let Some(JsObj::Array(items)) = h.get_mut(recv) {
6909 let removed: Vec<Value> = items.splice(start..start + delete, inserts).collect();
6910 removed
6911 } else {
6912 Vec::new()
6913 }
6914 });
6915 Ok(with_host(|h| {
6916 h.install_holes(
6917 recv,
6918 holes
6919 .iter()
6920 .filter_map(|&i| {
6921 if i < start {
6922 Some(i)
6923 } else if i < start + delete {
6924 None
6925 } else {
6926 Some(i - delete + inserted)
6927 }
6928 })
6929 .collect(),
6930 );
6931 let out = h.new_array(removed);
6932 h.install_holes(
6933 &out,
6934 holes
6935 .iter()
6936 .filter(|&&i| i >= start && i < start + delete)
6937 .map(|&i| i - start)
6938 .collect(),
6939 );
6940 out
6941 }))
6942}
6943
6944fn slice_bounds(args: &[Value], len: usize) -> (usize, usize) {
6945 let norm = |v: f64| -> usize {
6946 if v < 0.0 {
6947 ((len as f64 + v).max(0.0)) as usize
6948 } else {
6949 (v as usize).min(len)
6950 }
6951 };
6952 let lo = if args.is_empty() || matches!(args[0], Value::Undef) {
6953 0
6954 } else {
6955 norm(arg_num(args, 0))
6956 };
6957 let hi = if args.len() < 2 || matches!(args[1], Value::Undef) {
6958 len
6959 } else {
6960 norm(arg_num(args, 1))
6961 };
6962 (lo, hi.max(lo))
6965}
6966
6967fn string_method(s: &str, name: &str, args: Vec<Value>) -> Result<Value, String> {
6968 let u = crate::utf16::Units::of(s);
6972 match name {
6973 "@@iterator" => {
6978 let items: Vec<Value> = s.chars().map(|c| new_s(c.to_string())).collect();
6979 Ok(with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })))
6980 }
6981 "toUpperCase" => Ok(new_s(s.to_uppercase())),
6982 "toLowerCase" => Ok(new_s(s.to_lowercase())),
6983 "toLocaleString" => Ok(new_s(s.to_string())),
6996 "toLocaleUpperCase" => Ok(new_s(s.to_uppercase())),
6997 "toLocaleLowerCase" => Ok(new_s(s.to_lowercase())),
6998 "localeCompare" => {
7001 let other = with_host(|h| h.str_of(&arg0(&args)));
7002 let (la, lb) = (s.to_lowercase(), other.to_lowercase());
7003 let r = match la.cmp(&lb) {
7004 std::cmp::Ordering::Less => -1.0,
7005 std::cmp::Ordering::Greater => 1.0,
7006 std::cmp::Ordering::Equal => {
7007 let mut t = 0.0;
7008 for (ca, cb) in s.chars().zip(other.chars()) {
7009 if ca != cb {
7010 t = if ca.is_lowercase() { -1.0 } else { 1.0 };
7011 break;
7012 }
7013 }
7014 t
7015 }
7016 };
7017 Ok(Value::Float(r))
7018 }
7019 "normalize" => {
7029 use unicode_normalization::UnicodeNormalization;
7030 let form = match args.first() {
7031 Some(v) if !matches!(v, Value::Undef) => with_host(|h| h.str_of(v)),
7032 _ => "NFC".to_string(),
7033 };
7034 let out = match form.as_str() {
7035 "NFC" => s.nfc().collect::<String>(),
7036 "NFD" => s.nfd().collect::<String>(),
7037 "NFKC" => s.nfkc().collect::<String>(),
7038 "NFKD" => s.nfkd().collect::<String>(),
7039 _ => {
7040 return Err(host::range_error(
7041 "The normalization form should be one of NFC, NFD, NFKC, NFKD.",
7042 ))
7043 }
7044 };
7045 Ok(new_s(out))
7046 }
7047 "isWellFormed" => Ok(Value::Bool(true)),
7056 "toWellFormed" => Ok(new_s(s.to_string())),
7057 "trim" => Ok(new_s(crate::utf16::js_trim(s).to_string())),
7059 "trimStart" => Ok(new_s(crate::utf16::js_trim_start(s).to_string())),
7060 "trimEnd" => Ok(new_s(crate::utf16::js_trim_end(s).to_string())),
7061 "toString" | "valueOf" => Ok(new_s(s.to_string())),
7062 "charAt" => {
7063 let at = unit_pos(arg_num(&args, 0)).and_then(|i| u.unit_str(i));
7064 Ok(new_s(at.unwrap_or_default()))
7065 }
7066 "at" => {
7067 let n = arg_num(&args, 0);
7068 let i = if n.is_nan() {
7071 Some(0i64)
7072 } else if n.is_finite() {
7073 let i = n.trunc() as i64;
7074 Some(if i < 0 { i + u.len() as i64 } else { i })
7075 } else {
7076 None
7077 };
7078 match i
7079 .and_then(|i| usize::try_from(i).ok())
7080 .and_then(|i| u.unit_str(i))
7081 {
7082 Some(c) => Ok(new_s(c)),
7083 None => Ok(Value::Undef),
7084 }
7085 }
7086 "charCodeAt" => {
7093 let unit = unit_pos(arg_num(&args, 0)).and_then(|i| u.unit(i));
7094 Ok(Value::Float(unit.map(f64::from).unwrap_or(f64::NAN)))
7095 }
7096 "codePointAt" => match unit_pos(arg_num(&args, 0)).and_then(|i| u.code_point(i)) {
7097 Some(cp) => Ok(Value::Float(f64::from(cp))),
7098 None => Ok(Value::Undef),
7099 },
7100 "indexOf" => {
7104 let needle = needle_units(&args);
7105 let from = clamp_pos(arg_num(&args, 1), u.len());
7106 Ok(Value::Float(
7107 search_from(u.as_slice(), needle.as_slice(), from)
7108 .map(|i| i as f64)
7109 .unwrap_or(-1.0),
7110 ))
7111 }
7112 "lastIndexOf" => {
7113 let needle = needle_units(&args);
7114 let n = arg_num(&args, 1);
7116 let upto = if n.is_nan() {
7117 u.len()
7118 } else {
7119 clamp_pos(n, u.len())
7120 };
7121 Ok(Value::Float(
7122 search_last(u.as_slice(), needle.as_slice(), upto)
7123 .map(|i| i as f64)
7124 .unwrap_or(-1.0),
7125 ))
7126 }
7127 "includes" => {
7128 let needle = needle_units(&args);
7129 let from = clamp_pos(arg_num(&args, 1), u.len());
7130 Ok(Value::Bool(
7131 search_from(u.as_slice(), needle.as_slice(), from).is_some(),
7132 ))
7133 }
7134 "startsWith" => {
7135 let needle = needle_units(&args);
7136 let from = clamp_pos(arg_num(&args, 1), u.len());
7137 Ok(Value::Bool(
7138 u.as_slice()[from..].starts_with(needle.as_slice()),
7139 ))
7140 }
7141 "endsWith" => {
7142 let needle = needle_units(&args);
7143 let end = if args.len() < 2 || matches!(args[1], Value::Undef) {
7145 u.len()
7146 } else {
7147 clamp_pos(arg_num(&args, 1), u.len())
7148 };
7149 Ok(Value::Bool(
7150 u.as_slice()[..end].ends_with(needle.as_slice()),
7151 ))
7152 }
7153 "slice" => {
7154 let (lo, hi) = slice_bounds(&args, u.len());
7155 Ok(new_s(u.slice(lo, hi)))
7156 }
7157 "substring" => {
7158 let mut a = arg_num(&args, 0).max(0.0) as usize;
7159 let mut b = if args.len() < 2 || matches!(args[1], Value::Undef) {
7160 u.len()
7161 } else {
7162 (arg_num(&args, 1).max(0.0) as usize).min(u.len())
7163 };
7164 a = a.min(u.len());
7165 if a > b {
7166 std::mem::swap(&mut a, &mut b);
7167 }
7168 Ok(new_s(u.slice(a, b)))
7169 }
7170 "substr" => {
7171 let len = u.len() as i64;
7173 let mut start = arg_num(&args, 0) as i64;
7174 if start < 0 {
7175 start = (len + start).max(0);
7176 }
7177 let start = (start as usize).min(u.len());
7178 let count = if args.len() >= 2 {
7179 arg_num(&args, 1).max(0.0) as usize
7180 } else {
7181 u.len()
7182 };
7183 let end = start.saturating_add(count).min(u.len());
7184 Ok(new_s(u.slice(start, end)))
7185 }
7186 "repeat" => {
7187 let n = arg_num(&args, 0);
7188 if n < 0.0 || !n.is_finite() {
7191 return Err(host::range_error(&format!(
7192 "Invalid count value: {}",
7193 host::fmt_number(n)
7194 )));
7195 }
7196 if n * crate::utf16::len(s) as f64 > host::MAX_STRING_LENGTH as f64 {
7201 return Err(host::invalid_string_length());
7202 }
7203 Ok(new_s(s.repeat(n as usize)))
7204 }
7205 "concat" => {
7206 let mut out = s.to_string();
7207 for a in &args {
7208 out.push_str(&with_host(|h| h.str_of(a)));
7209 }
7210 Ok(new_s(out))
7211 }
7212 "padStart" => Ok(new_s(pad(s, &args, true)?)),
7213 "padEnd" => Ok(new_s(pad(s, &args, false)?)),
7214 "match" => crate::regexp::str_match(s, &arg0(&args)),
7217 "matchAll" => crate::regexp::str_match_all(s, &arg0(&args)),
7218 "search" => {
7219 if is_regexp_arg(&arg0(&args)) {
7220 crate::regexp::str_search(s, &arg0(&args))
7221 } else {
7222 let needle = with_host(|h| h.str_of(&arg0(&args)));
7226 Ok(Value::Float(byte_to_unit_index(s, s.find(&needle))))
7227 }
7228 }
7229 "replace" => {
7230 let pat = arg0(&args);
7231 let repl = args.get(1).cloned().unwrap_or(Value::Undef);
7232 if is_regexp_arg(&pat) {
7233 crate::regexp::str_replace_regex(s, &pat, &repl, false)
7234 } else if with_host(|h| host::is_callable(h, &repl)) {
7235 Ok(new_s(replace_str_fn(
7236 s,
7237 &with_host(|h| h.str_of(&pat)),
7238 &repl,
7239 false,
7240 )?))
7241 } else {
7242 let from = with_host(|h| h.str_of(&pat));
7243 let to = with_host(|h| h.str_of(&repl));
7244 Ok(new_s(s.replacen(&from, &to, 1)))
7245 }
7246 }
7247 "replaceAll" => {
7248 let pat = arg0(&args);
7249 let repl = args.get(1).cloned().unwrap_or(Value::Undef);
7250 if is_regexp_arg(&pat) {
7251 crate::regexp::str_replace_regex(s, &pat, &repl, true)
7252 } else if with_host(|h| host::is_callable(h, &repl)) {
7253 Ok(new_s(replace_str_fn(
7254 s,
7255 &with_host(|h| h.str_of(&pat)),
7256 &repl,
7257 true,
7258 )?))
7259 } else {
7260 let from = with_host(|h| h.str_of(&pat));
7261 let to = with_host(|h| h.str_of(&repl));
7262 Ok(new_s(s.replace(&from, &to)))
7263 }
7264 }
7265 "split" => {
7266 if is_regexp_arg(&arg0(&args)) {
7267 let limit = args
7268 .get(1)
7269 .filter(|v| !matches!(v, Value::Undef))
7270 .map(|v| with_host(|h| h.to_number(v)) as usize);
7271 return crate::regexp::str_split_regex(s, &arg0(&args), limit);
7272 }
7273 let mut parts: Vec<Value> = if args.is_empty() || matches!(args[0], Value::Undef) {
7274 vec![new_s(s.to_string())]
7275 } else {
7276 let sep = with_host(|h| h.str_of(&args[0]));
7277 if sep.is_empty() {
7278 (0..u.len())
7281 .filter_map(|i| u.unit_str(i))
7282 .map(new_s)
7283 .collect()
7284 } else {
7285 s.split(&sep as &str)
7286 .map(|p| new_s(p.to_string()))
7287 .collect()
7288 }
7289 };
7290 if let Some(lim) = args.get(1).filter(|v| !matches!(v, Value::Undef)) {
7292 let n = with_host(|h| h.to_number(lim));
7293 if n.is_finite() && n >= 0.0 {
7294 parts.truncate(n as usize);
7295 }
7296 }
7297 Ok(with_host(|h| h.new_array(parts)))
7298 }
7299 _ => Err(host::type_error(&format!("{name} is not a function"))),
7300 }
7301}
7302
7303fn new_s(s: String) -> Value {
7304 with_host(|h| h.new_str(s))
7305}
7306
7307fn clamp_pos(n: f64, len: usize) -> usize {
7310 if n.is_nan() || n <= 0.0 {
7311 0
7312 } else if n >= len as f64 {
7313 len
7314 } else {
7315 n.trunc() as usize
7316 }
7317}
7318
7319fn unit_pos(n: f64) -> Option<usize> {
7323 if n.is_nan() {
7324 Some(0)
7325 } else if n < 0.0 || !n.is_finite() {
7326 None
7327 } else {
7328 Some(n.trunc() as usize)
7329 }
7330}
7331
7332fn needle_units(args: &[Value]) -> crate::utf16::Units {
7335 crate::utf16::Units::of(&with_host(|h| h.str_of(&arg0(args))))
7336}
7337
7338fn search_from(hay: &[u16], needle: &[u16], from: usize) -> Option<usize> {
7341 if needle.is_empty() {
7342 return Some(from.min(hay.len()));
7343 }
7344 if needle.len() > hay.len() {
7345 return None;
7346 }
7347 (from..=hay.len().saturating_sub(needle.len())).find(|&i| &hay[i..i + needle.len()] == needle)
7348}
7349
7350fn search_last(hay: &[u16], needle: &[u16], upto: usize) -> Option<usize> {
7352 if needle.is_empty() {
7353 return Some(upto.min(hay.len()));
7354 }
7355 if needle.len() > hay.len() {
7356 return None;
7357 }
7358 let last = hay.len() - needle.len();
7359 (0..=upto.min(last))
7360 .rev()
7361 .find(|&i| &hay[i..i + needle.len()] == needle)
7362}
7363
7364fn byte_to_unit_index(s: &str, byte: Option<usize>) -> f64 {
7367 match byte {
7368 Some(b) => crate::utf16::index_of_byte(s, b).get() as f64,
7369 None => -1.0,
7370 }
7371}
7372
7373fn pad(s: &str, args: &[Value], start: bool) -> Result<String, String> {
7374 let target_f = arg_num(args, 0);
7375 let target = if target_f.is_finite() && target_f > 0.0 {
7376 target_f as usize
7377 } else {
7378 0
7379 };
7380 let cur = crate::utf16::len(s);
7383 if cur >= target {
7384 return Ok(s.to_string());
7385 }
7386 let filler = if args.len() >= 2 {
7387 with_host(|h| h.str_of(&args[1]))
7388 } else {
7389 " ".to_string()
7390 };
7391 if filler.is_empty() {
7392 return Ok(s.to_string());
7393 }
7394 if target_f > host::MAX_STRING_LENGTH as f64 {
7398 return Err(host::invalid_string_length());
7399 }
7400 let need = target - cur;
7401 let fill = crate::utf16::Units::of(&filler);
7402 let units: Vec<u16> = (0..need)
7406 .filter_map(|i| fill.unit(i % fill.len()))
7407 .collect();
7408 let padding = crate::utf16::to_string_lossy(&units);
7409 Ok(if start {
7410 format!("{padding}{s}")
7411 } else {
7412 format!("{s}{padding}")
7413 })
7414}
7415
7416const RADIX_RANGE: &str = "toString() radix argument must be between 2 and 36";
7421
7422fn bigint_method(b: &num_bigint::BigInt, name: &str, args: Vec<Value>) -> Result<Value, String> {
7424 match name {
7425 "toString" => {
7426 let radix = match args.first() {
7427 None | Some(Value::Undef) => 10,
7428 Some(_) => {
7429 let t = arg_num(&args, 0).trunc();
7430 if !(2.0..=36.0).contains(&t) {
7431 return Err(host::range_error(RADIX_RANGE));
7432 }
7433 t as u32
7434 }
7435 };
7436 Ok(new_s(b.to_str_radix(radix)))
7437 }
7438 "toLocaleString" => {
7444 let digits = b.magnitude().to_string();
7445 let sign = if b.sign() == num_bigint::Sign::Minus {
7446 "-"
7447 } else {
7448 ""
7449 };
7450 Ok(new_s(format!("{sign}{}", group_thousands(&digits))))
7451 }
7452 "valueOf" => Ok(with_host(|h| h.new_bigint(b.clone()))),
7453 _ => Err(host::type_error(&format!("{name} is not a function"))),
7454 }
7455}
7456
7457fn number_method(n: f64, name: &str, args: Vec<Value>) -> Result<Value, String> {
7458 match name {
7459 "toFixed" => {
7460 let digits = arg_num(&args, 0);
7461 if !(0.0..=100.0).contains(&digits.trunc()) {
7462 return Err(host::range_error(
7463 "toFixed() digits argument must be between 0 and 100",
7464 ));
7465 }
7466 Ok(new_s(to_fixed(n, digits as usize)))
7467 }
7468 "toExponential" => {
7469 let f = match args.first() {
7471 None | Some(Value::Undef) => None,
7472 Some(_) => {
7473 let d = arg_num(&args, 0).trunc();
7474 if !(0.0..=100.0).contains(&d) {
7475 return Err(host::range_error(
7476 "toExponential() argument must be between 0 and 100",
7477 ));
7478 }
7479 Some(d as usize)
7480 }
7481 };
7482 Ok(new_s(to_exponential(n, f)))
7483 }
7484 "toString" => {
7485 let radix = match args.first() {
7489 None | Some(Value::Undef) => 10,
7490 Some(_) => {
7491 let r = arg_num(&args, 0);
7492 let t = r.trunc();
7493 if !(2.0..=36.0).contains(&t) {
7494 return Err(host::range_error(RADIX_RANGE));
7495 }
7496 t as u32
7497 }
7498 };
7499 if radix == 10 {
7500 Ok(new_s(host::fmt_number(n)))
7501 } else {
7502 Ok(new_s(to_radix(n, radix)))
7503 }
7504 }
7505 "toPrecision" => {
7506 match args.first() {
7508 None | Some(Value::Undef) => Ok(new_s(host::fmt_number(n))),
7509 Some(_) => {
7510 let p = arg_num(&args, 0).trunc();
7511 if !(1.0..=100.0).contains(&p) {
7512 return Err(host::range_error(
7513 "toPrecision() argument must be between 1 and 100",
7514 ));
7515 }
7516 Ok(new_s(to_precision(n, p as usize)))
7517 }
7518 }
7519 }
7520 "toLocaleString" => Ok(new_s(to_locale_string(n))),
7521 "valueOf" => Ok(Value::Float(n)),
7522 _ => Err(host::type_error(&format!("{name} is not a function"))),
7523 }
7524}
7525
7526fn to_locale_string(n: f64) -> String {
7533 if n.is_nan() {
7534 return "NaN".to_string();
7535 }
7536 if n.is_infinite() {
7537 return if n < 0.0 { "-∞" } else { "∞" }.to_string();
7538 }
7539 let neg = n.is_sign_negative();
7540 let fixed = expand_exponential(&to_fixed(n.abs(), 3));
7551 let trimmed = match fixed.split_once('.') {
7552 Some(_) => fixed.trim_end_matches('0').trim_end_matches('.'),
7553 None => fixed.as_str(),
7554 };
7555 let (int_part, frac_part) = match trimmed.split_once('.') {
7556 Some((i, f)) => (i, Some(f)),
7557 None => (trimmed, None),
7558 };
7559 let mut out = String::new();
7560 if neg {
7561 out.push('-'); }
7563 out.push_str(&group_thousands(int_part));
7564 if let Some(f) = frac_part {
7565 out.push('.');
7566 out.push_str(f);
7567 }
7568 out
7569}
7570
7571fn expand_exponential(s: &str) -> String {
7577 let Some((mantissa, exp)) = s.split_once(['e', 'E']) else {
7578 return s.to_string();
7579 };
7580 let Ok(exp) = exp.trim_start_matches('+').parse::<i32>() else {
7581 return s.to_string();
7582 };
7583 if exp <= 0 {
7584 return s.to_string();
7585 }
7586 let (int_digits, frac_digits) = match mantissa.split_once('.') {
7587 Some((i, f)) => (i.to_string(), f.to_string()),
7588 None => (mantissa.to_string(), String::new()),
7589 };
7590 let mut digits = int_digits;
7591 digits.push_str(&frac_digits);
7592 let zeros = exp as usize - frac_digits.len().min(exp as usize);
7595 digits.push_str(&"0".repeat(zeros));
7596 digits
7597}
7598
7599fn group_thousands(int_part: &str) -> String {
7601 let bytes = int_part.as_bytes();
7602 let n = bytes.len();
7603 let mut out = String::with_capacity(n + n / 3);
7604 for (i, &b) in bytes.iter().enumerate() {
7605 if i > 0 && (n - i) % 3 == 0 {
7606 out.push(',');
7607 }
7608 out.push(b as char);
7609 }
7610 out
7611}
7612
7613fn to_fixed(n: f64, f: usize) -> String {
7622 if !n.is_finite() {
7623 return host::fmt_number(n);
7624 }
7625 if n.abs() >= 1e21 {
7627 return host::fmt_number(n);
7628 }
7629 let neg = n < 0.0;
7630 let full = format!("{:.*}", f + 25, n.abs());
7633 let mut body = round_decimal_string(&full, f);
7634 if neg {
7635 body.insert(0, '-'); }
7637 body
7638}
7639
7640fn round_decimal_string(s: &str, f: usize) -> String {
7643 let (int_part, frac_part) = s.split_once('.').unwrap_or((s, ""));
7644 let mut digits: Vec<u8> = int_part
7645 .bytes()
7646 .chain(frac_part.bytes())
7647 .map(|b| b - b'0')
7648 .collect();
7649 let point = int_part.len(); let keep = point + f; if digits.get(keep).map(|&d| d >= 5).unwrap_or(false) {
7654 let mut i = keep;
7655 loop {
7656 if i == 0 {
7657 digits.insert(0, 1);
7658 return assemble_decimal(&digits, point + 1, f);
7660 }
7661 i -= 1;
7662 if digits[i] == 9 {
7663 digits[i] = 0;
7664 } else {
7665 digits[i] += 1;
7666 break;
7667 }
7668 }
7669 }
7670 assemble_decimal(&digits, point, f)
7671}
7672
7673fn assemble_decimal(digits: &[u8], point: usize, f: usize) -> String {
7676 let int_str: String = digits[..point].iter().map(|d| (d + b'0') as char).collect();
7677 let int_str = int_str.trim_start_matches('0');
7678 let int_str = if int_str.is_empty() { "0" } else { int_str };
7679 if f == 0 {
7680 return int_str.to_string();
7681 }
7682 let frac: String = digits[point..point + f]
7683 .iter()
7684 .map(|d| (d + b'0') as char)
7685 .collect();
7686 format!("{int_str}.{frac}")
7687}
7688
7689fn round_significant(a: f64, p: usize) -> (String, i32) {
7695 let sci = format!("{a:.*e}", p - 1 + 25);
7696 let (mant, exp_str) = sci.split_once('e').expect("LowerExp always has 'e'");
7697 let mut e: i32 = exp_str.parse().expect("LowerExp exponent is an integer");
7698 let all: Vec<u8> = mant
7699 .chars()
7700 .filter(|c| c.is_ascii_digit())
7701 .map(|c| c as u8 - b'0')
7702 .collect();
7703 let mut s: String = all[..p].iter().map(|d| (d + b'0') as char).collect();
7704 if all.get(p).map(|&d| d >= 5).unwrap_or(false) {
7705 let mut d: Vec<u8> = all[..p].to_vec();
7708 let mut i = p;
7709 loop {
7710 if i == 0 {
7711 d.insert(0, 1);
7712 d.truncate(p);
7713 e += 1;
7714 break;
7715 }
7716 i -= 1;
7717 if d[i] == 9 {
7718 d[i] = 0;
7719 } else {
7720 d[i] += 1;
7721 break;
7722 }
7723 }
7724 s = d.iter().map(|x| (x + b'0') as char).collect();
7725 }
7726 (s, e)
7727}
7728
7729fn to_exponential(n: f64, f: Option<usize>) -> String {
7735 if !n.is_finite() {
7736 return host::fmt_number(n);
7737 }
7738 let neg = n < 0.0;
7739 let a = n.abs();
7740 let (s, e) = if a == 0.0 {
7741 ("0".repeat(f.unwrap_or(0) + 1), 0)
7743 } else {
7744 match f {
7745 Some(f) => round_significant(a, f + 1),
7746 None => {
7747 let sci = format!("{a:e}");
7749 let (mant, exp_str) = sci.split_once('e').expect("LowerExp always has 'e'");
7750 let digits: String = mant.chars().filter(|c| c.is_ascii_digit()).collect();
7751 let trimmed = digits.trim_end_matches('0');
7752 let digits = if trimmed.is_empty() { "0" } else { trimmed };
7753 (digits.to_string(), exp_str.parse().unwrap_or(0))
7754 }
7755 }
7756 };
7757 let sign = if e >= 0 { '+' } else { '-' };
7758 let mag = e.abs();
7759 let body = if s.len() == 1 {
7760 format!("{s}e{sign}{mag}")
7761 } else {
7762 format!("{}.{}e{sign}{mag}", &s[..1], &s[1..])
7763 };
7764 if neg {
7765 format!("-{body}")
7766 } else {
7767 body
7768 }
7769}
7770
7771fn to_precision(n: f64, p: usize) -> String {
7776 if !n.is_finite() {
7777 return host::fmt_number(n);
7778 }
7779 if n == 0.0 {
7780 return if p == 1 {
7781 "0".into()
7782 } else {
7783 format!("0.{}", "0".repeat(p - 1))
7784 };
7785 }
7786 let neg = n < 0.0;
7787 let (s, e) = round_significant(n.abs(), p);
7788 let pp = p as i32;
7789
7790 let body = if e < -6 || e >= pp {
7791 let sign = if e >= 0 { '+' } else { '-' };
7793 let mag = e.abs();
7794 if p == 1 {
7795 format!("{s}e{sign}{mag}")
7796 } else {
7797 format!("{}.{}e{sign}{mag}", &s[..1], &s[1..])
7798 }
7799 } else if e >= 0 {
7800 let ip = (e + 1) as usize;
7802 if ip == p {
7803 s
7804 } else {
7805 format!("{}.{}", &s[..ip], &s[ip..])
7806 }
7807 } else {
7808 format!("0.{}{}", "0".repeat((-e - 1) as usize), s)
7810 };
7811 if neg {
7812 format!("-{body}")
7813 } else {
7814 body
7815 }
7816}
7817
7818fn to_radix(n: f64, radix: u32) -> String {
7824 if !n.is_finite() {
7825 return host::fmt_number(n);
7826 }
7827 let digits = b"0123456789abcdefghijklmnopqrstuvwxyz";
7828 let rf = radix as f64;
7829 let neg = n < 0.0;
7830 let value = n.abs();
7831
7832 let mut integer = value.floor();
7833 let mut fraction = value - integer;
7834
7835 let mut frac: Vec<u8> = Vec::new();
7837 let mut delta = 0.5 * (next_up(value) - value);
7839 delta = delta.max(next_up(0.0));
7840 if fraction >= delta {
7841 loop {
7842 fraction *= rf;
7844 delta *= rf;
7845 let digit = fraction as usize;
7846 frac.push(digits[digit]);
7847 fraction -= digit as f64;
7848 if (fraction > 0.5 || (fraction == 0.5 && (digit & 1) == 1)) && fraction + delta > 1.0 {
7850 loop {
7852 match frac.pop() {
7853 None => {
7854 integer += 1.0;
7856 break;
7857 }
7858 Some(c) => {
7859 let d = if c > b'9' {
7860 (c - b'a' + 10) as u32
7861 } else {
7862 (c - b'0') as u32
7863 };
7864 if d + 1 < radix {
7865 frac.push(digits[(d + 1) as usize]);
7866 break;
7867 }
7868 }
7870 }
7871 }
7872 break;
7873 }
7874 if fraction < delta {
7875 break;
7876 }
7877 }
7878 }
7879
7880 let mut int_out: Vec<u8> = Vec::new();
7882 while v8_exponent(integer / rf) > 0 {
7884 integer /= rf;
7885 int_out.push(b'0');
7886 }
7887 loop {
7888 let remainder = integer % rf;
7889 int_out.push(digits[remainder as usize]);
7890 integer = (integer - remainder) / rf;
7891 if integer <= 0.0 {
7892 break;
7893 }
7894 }
7895 int_out.reverse();
7896
7897 let mut out: Vec<u8> = Vec::new();
7898 if neg {
7899 out.push(b'-');
7900 }
7901 out.extend_from_slice(&int_out);
7902 if !frac.is_empty() {
7903 out.push(b'.');
7904 out.extend_from_slice(&frac);
7905 }
7906 String::from_utf8(out).unwrap()
7907}
7908
7909fn next_up(x: f64) -> f64 {
7911 f64::from_bits(x.to_bits() + 1)
7912}
7913
7914fn v8_exponent(x: f64) -> i32 {
7917 let biased = ((x.to_bits() >> 52) & 0x7ff) as i32;
7918 if biased == 0 {
7919 -1074 } else {
7921 biased - 1075
7922 }
7923}
7924
7925fn normalize_zero_key(v: Value) -> Value {
7932 match v {
7933 Value::Float(f) if f == 0.0 && f.is_sign_negative() => Value::Float(0.0),
7934 other => other,
7935 }
7936}
7937
7938fn map_method(recv: &Value, name: &str, args: Vec<Value>) -> Result<Value, String> {
7939 match name {
7940 "get" => {
7941 let key = with_host(|h| host::map_key(h, &arg0(&args)));
7942 Ok(with_host(|h| match h.get(recv) {
7943 Some(JsObj::Map { entries, .. }) => entries
7944 .get(&key)
7945 .map(|(_, v)| v.clone())
7946 .unwrap_or(Value::Undef),
7947 _ => Value::Undef,
7948 }))
7949 }
7950 "set" => {
7951 let kv = normalize_zero_key(arg0(&args));
7952 let vv = args.get(1).cloned().unwrap_or(Value::Undef);
7953 reject_non_object_weak_key(recv, &kv, "WeakMap")?;
7954 let key = with_host(|h| host::map_key(h, &kv));
7955 with_host(|h| {
7956 if let Some(JsObj::Map { entries, .. }) = h.get_mut(recv) {
7957 entries.insert(key, (kv, vv));
7958 }
7959 });
7960 Ok(recv.clone())
7961 }
7962 "has" => {
7963 let key = with_host(|h| host::map_key(h, &arg0(&args)));
7964 Ok(Value::Bool(with_host(
7965 |h| matches!(h.get(recv), Some(JsObj::Map { entries, .. }) if entries.contains_key(&key)),
7966 )))
7967 }
7968 "delete" => {
7969 let key = with_host(|h| host::map_key(h, &arg0(&args)));
7970 Ok(Value::Bool(with_host(|h| match h.get_mut(recv) {
7971 Some(JsObj::Map { entries, .. }) => entries.shift_remove(&key).is_some(),
7972 _ => false,
7973 })))
7974 }
7975 "clear" => {
7976 with_host(|h| {
7977 if let Some(JsObj::Map { entries, .. }) = h.get_mut(recv) {
7978 entries.clear();
7979 }
7980 });
7981 Ok(Value::Undef)
7982 }
7983 "forEach" => {
7984 let cb = arg0(&args);
7985 let pairs: Vec<(Value, Value)> = with_host(|h| match h.get(recv) {
7986 Some(JsObj::Map { entries, .. }) => entries.values().cloned().collect(),
7987 _ => Vec::new(),
7988 });
7989 for (k, v) in pairs {
7990 host::invoke(&cb, vec![v, k, recv.clone()], None)?;
7991 }
7992 Ok(Value::Undef)
7993 }
7994 "keys" | "values" | "entries" | "@@iterator" => {
7995 let items: Vec<Value> = with_host(|h| {
7996 let pairs: Vec<(Value, Value)> = match h.get(recv) {
7997 Some(JsObj::Map { entries, .. }) => entries.values().cloned().collect(),
7998 _ => Vec::new(),
7999 };
8000 pairs
8001 .into_iter()
8002 .map(|(k, v)| match name {
8003 "keys" => k,
8004 "values" => v,
8005 _ => h.new_array(vec![k, v]), })
8007 .collect()
8008 });
8009 Ok(with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })))
8010 }
8011 _ => Err(host::type_error(&format!("map.{name} is not a function"))),
8012 }
8013}
8014
8015fn reject_non_object_weak_key(recv: &Value, key: &Value, kind: &str) -> Result<(), String> {
8018 let weak = with_host(|h| {
8019 matches!(
8020 h.get(recv),
8021 Some(JsObj::Map { weak: true, .. }) | Some(JsObj::Set { weak: true, .. })
8022 )
8023 });
8024 if !weak {
8025 return Ok(());
8026 }
8027 let is_object = with_host(|h| match key {
8028 Value::Obj(_) => !h.is_null(key) && h.as_str(key).is_none() && h.as_bigint(key).is_none(),
8029 _ => false,
8030 });
8031 if is_object {
8032 return Ok(());
8033 }
8034 Err(host::type_error(if kind == "WeakMap" {
8035 "Invalid value used as weak map key"
8036 } else {
8037 "Invalid value used in weak set"
8038 }))
8039}
8040
8041fn set_method(recv: &Value, name: &str, args: Vec<Value>) -> Result<Value, String> {
8042 match name {
8043 "add" => {
8044 let vv = normalize_zero_key(arg0(&args));
8045 reject_non_object_weak_key(recv, &vv, "WeakSet")?;
8046 let key = with_host(|h| host::map_key(h, &vv));
8047 with_host(|h| {
8048 if let Some(JsObj::Set { entries, .. }) = h.get_mut(recv) {
8049 entries.insert(key, vv);
8050 }
8051 });
8052 Ok(recv.clone())
8053 }
8054 "has" => {
8055 let key = with_host(|h| host::map_key(h, &arg0(&args)));
8056 Ok(Value::Bool(with_host(
8057 |h| matches!(h.get(recv), Some(JsObj::Set { entries, .. }) if entries.contains_key(&key)),
8058 )))
8059 }
8060 "delete" => {
8061 let key = with_host(|h| host::map_key(h, &arg0(&args)));
8062 Ok(Value::Bool(with_host(|h| match h.get_mut(recv) {
8063 Some(JsObj::Set { entries, .. }) => entries.shift_remove(&key).is_some(),
8064 _ => false,
8065 })))
8066 }
8067 "clear" => {
8068 with_host(|h| {
8069 if let Some(JsObj::Set { entries, .. }) = h.get_mut(recv) {
8070 entries.clear();
8071 }
8072 });
8073 Ok(Value::Undef)
8074 }
8075 "forEach" => {
8076 let cb = arg0(&args);
8077 let vals: Vec<Value> = with_host(|h| match h.get(recv) {
8078 Some(JsObj::Set { entries, .. }) => entries.values().cloned().collect(),
8079 _ => Vec::new(),
8080 });
8081 for v in vals {
8082 host::invoke(&cb, vec![v.clone(), v, recv.clone()], None)?;
8083 }
8084 Ok(Value::Undef)
8085 }
8086 "keys" | "values" | "entries" | "@@iterator" => {
8087 let items: Vec<Value> = with_host(|h| {
8088 let vals: Vec<Value> = match h.get(recv) {
8089 Some(JsObj::Set { entries, .. }) => entries.values().cloned().collect(),
8090 _ => Vec::new(),
8091 };
8092 if name == "entries" {
8093 vals.into_iter()
8094 .map(|v| h.new_array(vec![v.clone(), v]))
8095 .collect()
8096 } else {
8097 vals
8098 }
8099 });
8100 Ok(with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })))
8101 }
8102 _ => Err(host::type_error(&format!("set.{name} is not a function"))),
8103 }
8104}
8105
8106fn generator_method(recv: &Value, name: &str, args: Vec<Value>) -> Result<Value, String> {
8107 if host::is_async_generator(recv) {
8111 return match name {
8117 "next" => Ok(host::async_gen_enqueue(
8118 recv,
8119 host::GenReq::Next(arg0(&args)),
8120 )),
8121 "return" => Ok(host::async_gen_enqueue(
8122 recv,
8123 host::GenReq::Return(arg0(&args)),
8124 )),
8125 "throw" => Ok(host::async_gen_enqueue(
8126 recv,
8127 host::GenReq::Throw(arg0(&args)),
8128 )),
8129 "@@asyncIterator" => Ok(recv.clone()),
8130 _ => Err(host::type_error(&format!(
8131 "asyncGenerator.{name} is not a function"
8132 ))),
8133 };
8134 }
8135 match name {
8136 "next" => {
8137 let send = arg0(&args);
8138 match host::gen_resume(recv, send)? {
8139 host::GenStep::Yield(v) => Ok(iter_result(v, false)),
8140 host::GenStep::Done(v) => Ok(iter_result(v, true)),
8141 }
8142 }
8143 "return" => {
8144 match host::gen_return(recv, arg0(&args))? {
8147 host::GenStep::Yield(v) => Ok(iter_result(v, false)),
8148 host::GenStep::Done(v) => Ok(iter_result(v, true)),
8149 }
8150 }
8151 "throw" => {
8152 match host::gen_throw(recv, arg0(&args))? {
8156 host::GenStep::Yield(v) => Ok(iter_result(v, false)),
8157 host::GenStep::Done(v) => Ok(iter_result(v, true)),
8158 }
8159 }
8160 _ => Err(host::type_error(&format!(
8161 "generator.{name} is not a function"
8162 ))),
8163 }
8164}
8165
8166fn iter_result(value: Value, done: bool) -> Value {
8168 with_host(|h| {
8169 let mut m: IndexMap<String, Value> = IndexMap::new();
8170 m.insert("value".into(), value);
8171 m.insert("done".into(), Value::Bool(done));
8172 h.new_object(m)
8173 })
8174}
8175
8176fn iter_method(recv: &Value, name: &str, args: Vec<Value>) -> Result<Value, String> {
8179 match name {
8180 "next" => {
8181 let step = with_host(|h| {
8182 if let Some(JsObj::Iter { items, idx }) = h.get_mut(recv) {
8183 if *idx < items.len() {
8184 let v = items[*idx].clone();
8185 *idx += 1;
8186 return Some(v);
8187 }
8188 }
8189 None
8190 });
8191 Ok(match step {
8192 Some(v) => iter_result(v, false),
8193 None => iter_result(Value::Undef, true),
8194 })
8195 }
8196 "return" => {
8197 with_host(|h| {
8199 if let Some(JsObj::Iter { items, idx }) = h.get_mut(recv) {
8200 *idx = items.len();
8201 }
8202 });
8203 Ok(iter_result(arg0(&args), true))
8204 }
8205 "@@iterator" => Ok(recv.clone()),
8207 _ => Err(host::type_error(&format!(
8208 "iterator.{name} is not a function"
8209 ))),
8210 }
8211}
8212
8213fn symbol_method(recv: &Value, name: &str, _args: Vec<Value>) -> Result<Value, String> {
8214 match name {
8215 "toString" => Ok(with_host(|h| {
8216 let s = h.str_of(recv);
8217 h.new_str(s)
8218 })),
8219 _ => Err(host::type_error(&format!(
8220 "symbol.{name} is not a function"
8221 ))),
8222 }
8223}
8224
8225fn object_create(args: Vec<Value>) -> Result<Value, String> {
8228 let proto = arg0(&args);
8229 reject_bad_prototype(&proto)?;
8235 let obj = with_host(|h| h.new_object(IndexMap::new()));
8236 with_host(|h| h.set_proto(&obj, proto));
8238 if let Some(descs) = args.get(1).filter(|d| !matches!(d, Value::Undef)) {
8240 let entries: Vec<(String, Value)> = with_host(|h| match h.get(descs) {
8241 Some(JsObj::Object(p)) => p.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
8242 _ => Vec::new(),
8243 });
8244 for (k, d) in entries {
8245 apply_descriptor(&obj, &k, &d);
8246 }
8247 }
8248 Ok(obj)
8249}
8250
8251fn builtin_proto_method_names(ns: &str) -> Option<&'static [&'static str]> {
8255 match ns {
8256 "EventEmitter.prototype" => Some(crate::stdlib::events::METHODS),
8257 _ => None,
8258 }
8259}
8260
8261fn proxy_or_own_symbol_keys(v: &Value) -> Result<Vec<Value>, String> {
8265 if let Some(keys) = crate::proxy::own_keys(v)? {
8266 return Ok(keys
8267 .iter()
8268 .filter(|k| host::is_symbol_key(k))
8269 .map(|k| crate::proxy::key_value(k))
8270 .collect());
8271 }
8272 Ok(with_host(|h| h.own_symbol_keys(v)))
8273}
8274
8275pub fn define_property_pub(obj: &Value, key: Value, desc: Value) -> Result<Value, String> {
8277 object_define_property(vec![obj.clone(), key, desc])
8278}
8279
8280pub fn own_descriptor_pub(obj: &Value, key: Value) -> Result<Value, String> {
8282 object_get_own_descriptor(vec![obj.clone(), key])
8283}
8284
8285fn object_define_property(args: Vec<Value>) -> Result<Value, String> {
8286 let obj = arg0(&args);
8287 if with_host(|h| h.kind_of(&obj)) == Some(ObjKind::Proxy) {
8290 let key = with_host(|h| h.property_key(&args.get(1).cloned().unwrap_or(Value::Undef)));
8291 let desc = args.get(2).cloned().unwrap_or(Value::Undef);
8292 if !with_host(|h| is_object_like(h, &desc)) {
8293 return Err(host::type_error(&format!(
8294 "Property description must be an object: {}",
8295 with_host(|h| h.str_of(&desc))
8296 )));
8297 }
8298 crate::proxy::define_property(&obj, &key, &desc)?;
8299 return Ok(obj);
8300 }
8301 if !with_host(|h| is_object_like(h, &obj)) {
8304 return Err(host::type_error(
8305 "Object.defineProperty called on non-object",
8306 ));
8307 }
8308 let desc = args.get(2).cloned().unwrap_or(Value::Undef);
8309 if !with_host(|h| is_object_like(h, &desc)) {
8310 return Err(host::type_error(&format!(
8311 "Property description must be an object: {}",
8312 with_host(|h| h.str_of(&desc))
8313 )));
8314 }
8315 let key = with_host(|h| h.property_key(&args.get(1).cloned().unwrap_or(Value::Undef)));
8316 apply_descriptor(&obj, &key, &desc);
8317 Ok(obj)
8318}
8319
8320fn is_object_like(h: &host::JsHost, v: &Value) -> bool {
8324 matches!(v, Value::Obj(_)) && !h.is_null(v) && !host::is_primitive(h, v)
8325}
8326
8327fn require_object_coercible(v: &Value) -> Result<(), String> {
8334 if with_host(|h| matches!(v, Value::Undef) || h.is_null(v)) {
8335 return Err(host::type_error(
8336 "Cannot convert undefined or null to object",
8337 ));
8338 }
8339 Ok(())
8340}
8341
8342fn reject_bad_prototype(proto: &Value) -> Result<(), String> {
8347 if with_host(|h| h.is_null(proto) || is_object_like(h, proto)) {
8348 return Ok(());
8349 }
8350 Err(host::type_error(&format!(
8351 "Object prototype may only be an Object or null: {}",
8352 with_host(|h| h.str_of(proto))
8353 )))
8354}
8355
8356fn apply_descriptor(obj: &Value, key: &str, desc: &Value) {
8364 let (value, get, set, attrs) = with_host(|h| match h.get(desc) {
8365 Some(JsObj::Object(p)) => {
8366 let flag = |n: &str| p.get(n).map(|v| h.truthy(v)).unwrap_or(false);
8367 (
8368 p.get("value").cloned(),
8369 p.get("get").cloned(),
8370 p.get("set").cloned(),
8371 host::PropAttrs {
8372 writable: flag("writable"),
8373 enumerable: flag("enumerable"),
8374 configurable: flag("configurable"),
8375 },
8376 )
8377 }
8378 _ => (None, None, None, host::PropAttrs::default()),
8379 });
8380 with_host(|h| h.set_prop_attrs(obj, key, attrs));
8381 if get.is_some() || set.is_some() {
8382 with_host(|h| h.set_accessor(obj, key, get, set));
8383 } else if let Some(v) = value {
8384 if matches!(
8387 with_host(|h| h.get(obj).cloned()),
8388 Some(JsObj::Func(_)) | Some(JsObj::Class(_))
8389 ) {
8390 with_host(|h| h.set_fn_prop(obj, key, v));
8391 } else if let (Some(ObjKind::Array), Ok(i)) =
8392 (with_host(|h| h.kind_of(obj)), key.parse::<usize>())
8393 {
8394 with_host(|h| {
8400 let old = match h.get(obj) {
8401 Some(JsObj::Array(items)) => items.len(),
8402 _ => 0,
8403 };
8404 if let Some(JsObj::Array(items)) = h.get_mut(obj) {
8405 if i >= old {
8406 items.resize(i + 1, Value::Undef);
8407 }
8408 items[i] = v;
8409 }
8410 if i > old {
8411 h.mark_hole_range(obj, old..i);
8412 }
8413 h.clear_hole(obj, i);
8414 });
8415 } else {
8416 with_host(|h| {
8417 if let Some(JsObj::Object(p)) = h.get_mut(obj) {
8418 p.insert(key.to_string(), v);
8419 host::canonicalize_own_keys(p);
8420 }
8421 });
8422 }
8423 }
8424}
8425
8426fn object_define_properties(args: Vec<Value>) -> Result<Value, String> {
8428 let obj = arg0(&args);
8429 let descs = args.get(1).cloned().unwrap_or(Value::Undef);
8430 let entries: Vec<(String, Value)> = with_host(|h| match h.get(&descs) {
8431 Some(JsObj::Object(p)) => p.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
8432 _ => Vec::new(),
8433 });
8434 for (k, d) in entries {
8435 apply_descriptor(&obj, &k, &d);
8436 }
8437 Ok(obj)
8438}
8439
8440fn object_get_own_descriptor(args: Vec<Value>) -> Result<Value, String> {
8441 let obj = arg0(&args);
8442 require_object_coercible(&obj)?;
8443 let key = with_host(|h| h.property_key(&args.get(1).cloned().unwrap_or(Value::Undef)));
8444 if with_host(|h| h.kind_of(&obj)) == Some(ObjKind::Proxy) {
8445 return Ok(crate::proxy::get_own_descriptor(&obj, &key)?.unwrap_or(Value::Undef));
8446 }
8447 if let Some(JsObj::Builtin(ns)) = with_host(|h| h.get(&obj).cloned()) {
8450 if let Some(names) = builtin_proto_method_names(&ns) {
8451 if names.contains(&key.as_str()) {
8452 return Ok(with_host(|h| {
8453 let thunk = h.alloc(JsObj::Builtin(format!(
8454 "@proto:{}:{key}",
8455 ns.trim_end_matches(".prototype")
8456 )));
8457 let mut m: IndexMap<String, Value> = IndexMap::new();
8458 m.insert("value".into(), thunk);
8459 m.insert("writable".into(), Value::Bool(true));
8460 m.insert("enumerable".into(), Value::Bool(true));
8461 m.insert("configurable".into(), Value::Bool(true));
8462 h.new_object(m)
8463 }));
8464 }
8465 }
8466 }
8467 if let Some((get, set)) = with_host(|h| h.own_accessor(&obj, &key)) {
8469 return Ok(with_host(|h| {
8470 let a = h.prop_attrs(&obj, &key);
8471 let mut m: IndexMap<String, Value> = IndexMap::new();
8472 m.insert("get".into(), get.unwrap_or(Value::Undef));
8473 m.insert("set".into(), set.unwrap_or(Value::Undef));
8474 m.insert("enumerable".into(), Value::Bool(a.enumerable));
8475 m.insert("configurable".into(), Value::Bool(a.configurable));
8476 h.new_object(m)
8477 }));
8478 }
8479 let val = with_host(|h| match h.get(&obj) {
8480 Some(JsObj::Object(p))
8484 if p.get("@@native").map(|t| h.str_of(t)).as_deref() == Some("Buffer") =>
8485 {
8486 match (
8487 p.get("@@bytes").and_then(|b| h.get(b)),
8488 key.parse::<usize>(),
8489 ) {
8490 (Some(JsObj::Array(items)), Ok(i)) => items.get(i).cloned(),
8491 _ => None,
8492 }
8493 }
8494 Some(JsObj::Object(p)) => p.get(&key).cloned(),
8495 Some(JsObj::Array(items)) => match key.parse::<usize>() {
8498 Ok(i) if h.is_hole(&obj, i) => None,
8500 Ok(i) => items.get(i).cloned(),
8501 Err(_) if key == "length" => Some(Value::Float(items.len() as f64)),
8502 Err(_) => h.fn_prop(&obj, &key),
8503 },
8504 Some(JsObj::Func(_)) | Some(JsObj::Class(_)) => h.fn_prop(&obj, &key),
8506 _ => None,
8507 });
8508 match val {
8509 Some(v) => Ok(with_host(|h| {
8510 let a = h.prop_attrs(&obj, &key);
8511 let mut m: IndexMap<String, Value> = IndexMap::new();
8512 m.insert("value".into(), v);
8513 m.insert("writable".into(), Value::Bool(a.writable));
8514 m.insert("enumerable".into(), Value::Bool(a.enumerable));
8515 m.insert("configurable".into(), Value::Bool(a.configurable));
8516 h.new_object(m)
8517 })),
8518 None => Ok(Value::Undef),
8519 }
8520}
8521
8522fn object_get_own_descriptors(args: Vec<Value>) -> Result<Value, String> {
8527 let obj = arg0(&args);
8528 let names = object_keys(vec![obj.clone()], 3)?;
8529 let keys: Vec<String> = with_host(|h| match h.get(&names) {
8530 Some(JsObj::Array(items)) => items.iter().map(|k| h.str_of(k)).collect(),
8531 _ => Vec::new(),
8532 });
8533 let mut out: IndexMap<String, Value> = IndexMap::new();
8534 for k in keys {
8535 let ks = with_host(|h| h.new_str(k.clone()));
8536 let d = object_get_own_descriptor(vec![obj.clone(), ks])?;
8537 if !matches!(d, Value::Undef) {
8538 out.insert(k, d);
8539 }
8540 }
8541 Ok(with_host(|h| h.new_object(out)))
8542}
8543
8544pub fn has_property(obj: &Value, key: &str) -> Result<bool, String> {
8547 if let Some(b) = crate::proxy::has(obj, key)? {
8548 return Ok(b);
8549 }
8550 Ok(has_property_ordinary(obj, key))
8551}
8552
8553fn has_property_ordinary(obj: &Value, key: &str) -> bool {
8555 if let Some(JsObj::Builtin(ns)) = with_host(|h| h.get(obj).cloned()) {
8561 return !matches!(namespace_property(&ns, key), Value::Undef);
8562 }
8563 if crate::stdlib::typedarray::has_index(obj, key) == Some(true) {
8569 return true;
8570 }
8571 if with_host(|h| host::lookup_chain(h, obj, key)).is_some() {
8572 return true;
8573 }
8574 if with_host(|h| host::lookup_accessor(h, obj, key)).is_some() {
8575 return true;
8576 }
8577 with_host(|h| match h.get(obj) {
8578 Some(JsObj::Object(p)) => p.contains_key(key),
8579 Some(JsObj::Array(items)) => {
8580 key == "length"
8581 || key
8582 .parse::<usize>()
8583 .map(|i| i < items.len() && !h.is_hole(obj, i))
8584 .unwrap_or(false)
8585 || h.fn_prop(obj, key).is_some()
8588 }
8589 Some(JsObj::Func(_)) | Some(JsObj::Class(_)) => h.fn_prop(obj, key).is_some(),
8590 _ => false,
8591 })
8592}
8593
8594pub(crate) fn deep_clone(v: &Value) -> Value {
8601 deep_clone_seen(v, &mut std::collections::HashMap::new())
8602}
8603
8604fn deep_clone_seen(v: &Value, seen: &mut std::collections::HashMap<u32, Value>) -> Value {
8605 let idx = match v {
8606 Value::Obj(i) => *i,
8607 _ => return v.clone(),
8608 };
8609 if let Some(done) = seen.get(&idx) {
8610 return done.clone();
8611 }
8612 match with_host(|h| h.get(v).cloned()) {
8613 Some(JsObj::Array(items)) => {
8614 let out = with_host(|h| h.new_array(Vec::new()));
8617 seen.insert(idx, out.clone());
8618 let cloned: Vec<Value> = items.iter().map(|x| deep_clone_seen(x, seen)).collect();
8619 with_host(|h| {
8620 if let Some(JsObj::Array(a)) = h.get_mut(&out) {
8621 *a = cloned;
8622 }
8623 h.copy_holes(v, &out, Some);
8626 });
8627 out
8628 }
8629 Some(JsObj::Object(props)) => {
8630 let out = with_host(|h| h.new_object(IndexMap::new()));
8631 seen.insert(idx, out.clone());
8632 let cloned: IndexMap<String, Value> = props
8633 .iter()
8634 .map(|(k, val)| (k.clone(), deep_clone_seen(val, seen)))
8635 .collect();
8636 with_host(|h| {
8637 if let Some(JsObj::Object(p)) = h.get_mut(&out) {
8638 *p = cloned;
8639 }
8640 if let Some(p) = h.proto_of(v) {
8643 h.set_proto(&out, p);
8644 }
8645 h.copy_prop_attrs(v, &out);
8646 });
8647 out
8648 }
8649 Some(JsObj::Map { entries, weak }) => {
8651 let out = with_host(|h| {
8652 h.alloc(JsObj::Map {
8653 entries: IndexMap::new(),
8654 weak,
8655 })
8656 });
8657 seen.insert(idx, out.clone());
8658 let pairs: Vec<(Value, Value)> = entries.values().cloned().collect();
8659 for (k, val) in pairs {
8660 let ck = deep_clone_seen(&k, seen);
8661 let cv = deep_clone_seen(&val, seen);
8662 let _ = map_method(&out, "set", vec![ck, cv]);
8663 }
8664 out
8665 }
8666 Some(JsObj::Set { entries, weak }) => {
8667 let out = with_host(|h| {
8668 h.alloc(JsObj::Set {
8669 entries: IndexMap::new(),
8670 weak,
8671 })
8672 });
8673 seen.insert(idx, out.clone());
8674 let vals: Vec<Value> = entries.values().cloned().collect();
8675 for x in vals {
8676 let cx = deep_clone_seen(&x, seen);
8677 let _ = set_method(&out, "add", vec![cx]);
8678 }
8679 out
8680 }
8681 _ => v.clone(),
8685 }
8686}
8687
8688pub fn error_string(h: &host::JsHost, v: &Value) -> String {
8693 if let Some(JsObj::Object(props)) = h.get(v) {
8694 let name = props
8695 .get("name")
8696 .map(|x| h.str_of(x))
8697 .or_else(|| host::lookup_chain(h, v, "name").map(|x| h.str_of(&x)))
8698 .unwrap_or_else(|| "Error".into());
8699 if let Some(m) = props.get("message") {
8700 return format!("{name}: {}", h.str_of(m));
8701 }
8702 return name;
8703 }
8704 h.str_of(v)
8705}
8706
8707fn make_builtin(name: String) -> Value {
8708 with_host(|h| h.alloc(JsObj::Builtin(name)))
8709}
8710
8711pub fn prototype_of(v: &Value) -> Value {
8720 if matches!(with_host(|h| h.get(v).cloned()), Some(JsObj::Builtin(ref n)) if n == "Buffer") {
8725 return with_host(|h| h.alloc(JsObj::Builtin("Uint8Array".into())));
8726 }
8727 if let Some(JsObj::Class(c)) = with_host(|h| h.get(v).cloned()) {
8735 if let Some(parent) = c.parent {
8736 return parent;
8737 }
8738 }
8739 if with_host(|h| h.has_null_proto(v)) {
8741 return with_host(|h| h.null());
8742 }
8743 if let Some(p) = with_host(|h| h.proto_of(v)) {
8744 return p;
8745 }
8746 with_host(|h| {
8751 h.ensure_native_protos();
8752 match default_ctor_name(h, v) {
8753 Some("Object") => h.object_proto(),
8754 Some(c) => h.alloc(JsObj::Builtin(format!("{c}.prototype"))),
8755 None => h.null(),
8756 }
8757 })
8758}
8759
8760fn new_promise(executor: Value) -> Result<Value, String> {
8763 let p = with_host(|h| h.new_promise());
8764 let id = with_host(|h| h.promise_id(&p).unwrap());
8765 let res = make_builtin(format!("@@presolve:{id}"));
8766 let rej = make_builtin(format!("@@preject:{id}"));
8767 if let Err(e) = host::invoke(&executor, vec![res, rej], None) {
8768 let ev = host::take_exc_or_error(&e);
8770 host::reject_promise_val(id, ev);
8771 }
8772 Ok(p)
8773}
8774
8775fn promise_resolve(v: Value) -> Result<Value, String> {
8776 Ok(host::promise_of(&v))
8777}
8778fn promise_reject(v: Value) -> Result<Value, String> {
8779 let p = with_host(|h| h.new_promise());
8780 let id = with_host(|h| h.promise_id(&p).unwrap());
8781 host::reject_promise_val(id, v);
8782 Ok(p)
8783}
8784
8785fn promise_with_resolvers() -> Result<Value, String> {
8789 let p = with_host(|h| h.new_promise());
8790 let id = with_host(|h| h.promise_id(&p).unwrap());
8791 let resolve = make_builtin(format!("@@presolve:{id}"));
8792 let reject = make_builtin(format!("@@preject:{id}"));
8793 let mut props: IndexMap<String, Value> = IndexMap::new();
8794 props.insert("promise".into(), p);
8795 props.insert("resolve".into(), resolve);
8796 props.insert("reject".into(), reject);
8797 Ok(with_host(|h| h.new_object(props)))
8798}
8799
8800#[derive(Clone, Copy)]
8801enum AllMode {
8802 All,
8803 AllSettled,
8804}
8805
8806fn promise_all(args: Vec<Value>, mode: AllMode) -> Result<Value, String> {
8808 let items = host::iter_all(&arg0(&args))?;
8809 let result = with_host(|h| h.new_promise());
8810 let rid = with_host(|h| h.promise_id(&result).unwrap());
8811 let n = items.len();
8812 if n == 0 {
8813 let empty = with_host(|h| h.new_array(Vec::new()));
8814 host::resolve_promise_val(rid, empty);
8815 return Ok(result);
8816 }
8817 let slots = std::rc::Rc::new(std::cell::RefCell::new(vec![Value::Undef; n]));
8819 let remaining = std::rc::Rc::new(std::cell::RefCell::new(n));
8820 for (i, it) in items.into_iter().enumerate() {
8821 let ap = host::promise_of(&it);
8822 let aid = with_host(|h| h.promise_id(&ap).unwrap());
8823 let slots = slots.clone();
8824 let remaining = remaining.clone();
8825 host::subscribe_native(
8826 aid,
8827 Box::new(move |state, val| {
8828 let settled = match mode {
8829 AllMode::All => {
8830 if state == host::PromiseState::Rejected {
8831 host::reject_promise_val(rid, val);
8832 return Ok(());
8833 }
8834 val
8835 }
8836 AllMode::AllSettled => with_host(|h| {
8837 let mut m: IndexMap<String, Value> = IndexMap::new();
8838 if state == host::PromiseState::Rejected {
8839 m.insert("status".into(), h.new_str("rejected"));
8840 m.insert("reason".into(), val);
8841 } else {
8842 m.insert("status".into(), h.new_str("fulfilled"));
8843 m.insert("value".into(), val);
8844 }
8845 h.new_object(m)
8846 }),
8847 };
8848 slots.borrow_mut()[i] = settled;
8849 let mut r = remaining.borrow_mut();
8850 *r -= 1;
8851 if *r == 0 {
8852 let arr = with_host(|h| h.new_array(slots.borrow().clone()));
8853 host::resolve_promise_val(rid, arr);
8854 }
8855 Ok(())
8856 }),
8857 );
8858 }
8859 Ok(result)
8860}
8861
8862fn promise_race(args: Vec<Value>, any: bool) -> Result<Value, String> {
8864 let items = host::iter_all(&arg0(&args))?;
8865 let result = with_host(|h| h.new_promise());
8866 let rid = with_host(|h| h.promise_id(&result).unwrap());
8867 let n = items.len();
8868 let errors = std::rc::Rc::new(std::cell::RefCell::new(vec![Value::Undef; n]));
8869 let remaining = std::rc::Rc::new(std::cell::RefCell::new(n));
8870 for (i, it) in items.into_iter().enumerate() {
8871 let ap = host::promise_of(&it);
8872 let aid = with_host(|h| h.promise_id(&ap).unwrap());
8873 let errors = errors.clone();
8874 let remaining = remaining.clone();
8875 host::subscribe_native(
8876 aid,
8877 Box::new(move |state, val| {
8878 if any {
8879 if state == host::PromiseState::Fulfilled {
8880 host::resolve_promise_val(rid, val);
8881 } else {
8882 errors.borrow_mut()[i] = val;
8883 let mut r = remaining.borrow_mut();
8884 *r -= 1;
8885 if *r == 0 {
8886 let reasons = with_host(|h| h.new_array(errors.borrow().clone()));
8888 let msg = with_host(|h| h.new_str("All promises were rejected"));
8889 let agg = make_error("AggregateError", &[reasons, msg]);
8890 host::reject_promise_val(rid, agg);
8891 }
8892 }
8893 } else if state == host::PromiseState::Rejected {
8894 host::reject_promise_val(rid, val);
8895 } else {
8896 host::resolve_promise_val(rid, val);
8897 }
8898 Ok(())
8899 }),
8900 );
8901 }
8902 Ok(result)
8903}
8904
8905fn promise_method(recv: &Value, name: &str, args: Vec<Value>) -> Result<Value, String> {
8907 match name {
8908 "then" => Ok(host::promise_then(
8909 recv,
8910 args.first().cloned().unwrap_or(Value::Undef),
8911 args.get(1).cloned().unwrap_or(Value::Undef),
8912 )),
8913 "catch" => Ok(host::promise_then(
8914 recv,
8915 Value::Undef,
8916 args.first().cloned().unwrap_or(Value::Undef),
8917 )),
8918 "finally" => {
8919 let cb = arg0(&args);
8920 let i = match cb {
8921 Value::Obj(i) => i,
8922 _ => 0,
8923 };
8924 let pass = make_builtin(format!("@@finpass:{i}"));
8925 let throw = make_builtin(format!("@@finthrow:{i}"));
8926 Ok(host::promise_then(recv, pass, throw))
8927 }
8928 _ => Err(host::type_error(&format!(
8929 "promise.{name} is not a function"
8930 ))),
8931 }
8932}
8933
8934fn enqueue_microtask(next_tick: bool, cb: Value, args: Vec<Value>) {
8935 with_host(|h| {
8936 if next_tick {
8937 h.queue_nexttick(cb, args);
8938 } else {
8939 h.queue_micro(cb, args);
8940 }
8941 });
8942}
8943
8944fn schedule_timer(name: &str, args: Vec<Value>) -> Value {
8952 let cb = arg0(&args);
8953 let delay = if name == "setImmediate" {
8954 -1.0 } else {
8956 args.get(1)
8957 .map(|d| with_host(|h| h.to_number(d)))
8958 .unwrap_or(0.0)
8959 .max(0.0)
8960 };
8961 let extra = if name == "setImmediate" {
8962 args.get(1..).map(|s| s.to_vec()).unwrap_or_default()
8963 } else {
8964 args.get(2..).map(|s| s.to_vec()).unwrap_or_default()
8965 };
8966 let interval = (name == "setInterval").then(|| delay.max(1.0));
8969 let id = with_host(|h| h.add_timer(delay, cb, extra, interval));
8970 let tag = if name == "setImmediate" {
8971 "Immediate"
8972 } else {
8973 "Timeout"
8974 };
8975 crate::stdlib::timers::new_handle(id, tag)
8976}
8977
8978fn clear_timer(v: &Value) {
8981 let id =
8982 crate::stdlib::timers::handle_id(v).unwrap_or_else(|| with_host(|h| h.to_number(v)) as u64);
8983 with_host(|h| h.cancel_timer(id));
8984}