1use crate::host::{self, ops, with_host, FuncVal, JsObj};
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::DECLARE, b_declare);
15 vm.register_builtin(ops::DELNAME, b_delname);
16 vm.register_builtin(ops::GETATTR, b_getattr);
17 vm.register_builtin(ops::SETATTR, b_setattr);
18 vm.register_builtin(ops::GETITEM, b_getitem);
19 vm.register_builtin(ops::SETITEM, b_setitem);
20 vm.register_builtin(ops::DELITEM, b_delitem);
21 vm.register_builtin(ops::MKSTR, b_mkstr);
22 vm.register_builtin(ops::MKARR, b_mkarr);
23 vm.register_builtin(ops::MKOBJ, b_mkobj);
24 vm.register_builtin(ops::CALL, b_call);
25 vm.register_builtin(ops::CALL_METHOD, b_call_method);
26 vm.register_builtin(ops::CALL_VALUE, b_call_value);
27 vm.register_builtin(ops::NEW, b_new);
28 vm.register_builtin(ops::TRUTHY, b_truthy);
29 vm.register_builtin(ops::TOSTR, b_tostr);
30 vm.register_builtin(ops::MKFUNC, b_mkfunc);
31 vm.register_builtin(ops::GETITER, b_getiter);
32 vm.register_builtin(ops::FORITER, b_foriter);
33 vm.register_builtin(ops::FORIN_KEYS, b_forin_keys);
34 vm.register_builtin(ops::CONTAINS, b_contains);
35 vm.register_builtin(ops::SIG_RETURN, b_sig_return);
36 vm.register_builtin(ops::BINOP, b_binop);
37 vm.register_builtin(ops::UNARY, b_unary);
38 vm.register_builtin(ops::STRICT_EQ, b_strict_eq);
39 vm.register_builtin(ops::LOOSE_EQ, b_loose_eq);
40 vm.register_builtin(ops::TYPEOF, b_typeof);
41 vm.register_builtin(ops::LOAD_NULL, b_load_null);
42 vm.register_builtin(ops::THROW, b_throw);
43 vm.register_builtin(ops::TRY, b_try);
44 vm.register_builtin(ops::NULLISH, b_nullish);
45 vm.register_builtin(ops::UNPACK, b_unpack);
46 vm.register_builtin(ops::BUILD_ARGS, b_build_args);
47 vm.register_builtin(ops::THIS, b_this);
48 vm.register_builtin(ops::INSTANCEOF, b_instanceof);
49 vm.register_builtin(ops::DELPROP_NAME, b_delprop_name);
50 vm.register_builtin(ops::APPLY, b_apply);
51 vm.register_builtin(ops::APPLY_METHOD, b_apply_method);
52 vm.register_builtin(ops::OBJ_REST, b_obj_rest);
53 vm.register_builtin(ops::DIV, b_div);
54}
55
56fn b_div(vm: &mut VM, _: u8) -> Value {
62 let b = vm.pop();
63 let a = vm.pop();
64 let r = with_host(|h| h.arith(NumOp::Div, &a, &b));
65 finish(vm, r)
66}
67
68fn b_obj_rest(vm: &mut VM, _: u8) -> Value {
70 let excluded = vm.pop();
71 let obj = vm.pop();
72 let excl: Vec<String> = with_host(|h| h.iter_vec(&excluded))
73 .unwrap_or_default()
74 .iter()
75 .map(|v| with_host(|h| h.str_of(v)))
76 .collect();
77 with_host(|h| {
78 let props: IndexMap<String, Value> = match h.get(&obj) {
79 Some(JsObj::Object(m)) => m
80 .iter()
81 .filter(|(k, _)| !excl.contains(k))
82 .map(|(k, v)| (k.clone(), v.clone()))
83 .collect(),
84 _ => IndexMap::new(),
85 };
86 h.new_object(props)
87 })
88}
89
90fn pop_n(vm: &mut VM, n: usize) -> Vec<Value> {
93 let mut v = Vec::with_capacity(n);
94 for _ in 0..n {
95 v.push(vm.pop());
96 }
97 v.reverse();
98 v
99}
100
101fn sval(v: &Value) -> String {
103 if let Value::Str(s) = v {
104 return (**s).clone();
105 }
106 with_host(|h| h.as_str(v)).unwrap_or_default()
107}
108
109fn abort(vm: &mut VM, e: String) -> Value {
110 with_host(|h| h.error = Some(e));
111 vm.ip = vm.chunk.ops.len();
112 Value::Undef
113}
114
115fn finish(vm: &mut VM, r: Result<Value, String>) -> Value {
117 match r {
118 Ok(v) => {
119 if with_host(|h| h.error.is_some() || h.signal.is_some()) {
120 vm.ip = vm.chunk.ops.len();
121 }
122 v
123 }
124 Err(e) => abort(vm, e),
125 }
126}
127
128fn b_getlocal(vm: &mut VM, _: u8) -> Value {
131 let name = sval(&vm.pop());
132 if let Some(v) = with_host(|h| h.read_name(&name)) {
133 return v;
134 }
135 match name.as_str() {
137 "undefined" => return Value::Undef,
138 "NaN" => return Value::Float(f64::NAN),
139 "Infinity" => return Value::Float(f64::INFINITY),
140 "globalThis" => return with_host(|h| h.new_object(IndexMap::new())),
141 _ => {}
142 }
143 if is_namespace(&name) || is_known_builtin(&name) {
144 return with_host(|h| h.alloc(JsObj::Builtin(name.clone())));
145 }
146 abort(vm, host::ref_error(&name))
147}
148
149fn b_setlocal(vm: &mut VM, _: u8) -> Value {
150 let val = vm.pop();
151 let name = sval(&vm.pop());
152 with_host(|h| h.set_name(&name, val.clone()));
153 val
154}
155
156fn b_declare(vm: &mut VM, _: u8) -> Value {
157 let val = vm.pop();
158 let name = sval(&vm.pop());
159 with_host(|h| h.declare_name(&name, val.clone()));
160 val
161}
162
163fn b_delname(vm: &mut VM, _: u8) -> Value {
164 let name = sval(&vm.pop());
165 with_host(|h| h.del_name(&name));
166 Value::Bool(true)
167}
168
169fn b_this(_vm: &mut VM, _: u8) -> Value {
170 with_host(|h| h.current_this().unwrap_or(Value::Undef))
171}
172
173fn b_load_null(_vm: &mut VM, _: u8) -> Value {
174 with_host(|h| h.null())
175}
176
177fn b_getattr(vm: &mut VM, _: u8) -> Value {
180 let name = sval(&vm.pop());
181 let recv = vm.pop();
182 match get_property(&recv, &name) {
183 Ok(v) => v,
184 Err(e) => abort(vm, e),
185 }
186}
187
188fn get_property(recv: &Value, name: &str) -> Result<Value, String> {
190 if with_host(|h| h.is_nullish(recv)) {
191 return Err(host::type_error(&format!(
192 "Cannot read properties of {} (reading '{name}')",
193 with_host(|h| h.str_of(recv))
194 )));
195 }
196 let obj = with_host(|h| h.get(recv).cloned());
197 Ok(match obj {
198 Some(JsObj::Object(props)) => props.get(name).cloned().unwrap_or(Value::Undef),
199 Some(JsObj::Array(items)) => {
200 if name == "length" {
201 Value::Float(items.len() as f64)
202 } else if let Ok(i) = name.parse::<usize>() {
203 items.get(i).cloned().unwrap_or(Value::Undef)
204 } else if is_array_method(name) {
205 bound_method(recv, name)
206 } else {
207 Value::Undef
208 }
209 }
210 Some(JsObj::Str(s)) => {
211 if name == "length" {
212 Value::Float(s.chars().count() as f64)
213 } else if let Ok(i) = name.parse::<usize>() {
214 match s.chars().nth(i) {
215 Some(c) => with_host(|h| h.new_str(c.to_string())),
216 None => Value::Undef,
217 }
218 } else if is_string_method(name) {
219 bound_method(recv, name)
220 } else {
221 Value::Undef
222 }
223 }
224 Some(JsObj::Builtin(ns)) => namespace_property(&ns, name),
225 _ => {
226 if matches!(recv, Value::Float(_) | Value::Int(_)) && is_number_method(name) {
228 bound_method(recv, name)
229 } else {
230 Value::Undef
231 }
232 }
233 })
234}
235
236fn bound_method(recv: &Value, name: &str) -> Value {
237 with_host(|h| {
238 h.alloc(JsObj::BoundMethod {
239 recv: recv.clone(),
240 name: name.to_string(),
241 })
242 })
243}
244
245fn namespace_property(ns: &str, name: &str) -> Value {
248 let konst = match (ns, name) {
250 ("Math", "PI") => Some(std::f64::consts::PI),
251 ("Math", "E") => Some(std::f64::consts::E),
252 ("Math", "LN2") => Some(std::f64::consts::LN_2),
253 ("Math", "LN10") => Some(std::f64::consts::LN_10),
254 ("Math", "LOG2E") => Some(std::f64::consts::LOG2_E),
255 ("Math", "LOG10E") => Some(std::f64::consts::LOG10_E),
256 ("Math", "SQRT2") => Some(std::f64::consts::SQRT_2),
257 ("Math", "SQRT1_2") => Some(std::f64::consts::FRAC_1_SQRT_2),
258 ("Number", "MAX_SAFE_INTEGER") => Some(9007199254740991.0),
259 ("Number", "MIN_SAFE_INTEGER") => Some(-9007199254740991.0),
260 ("Number", "MAX_VALUE") => Some(f64::MAX),
261 ("Number", "MIN_VALUE") => Some(f64::MIN_POSITIVE),
262 ("Number", "EPSILON") => Some(f64::EPSILON),
263 ("Number", "POSITIVE_INFINITY") => Some(f64::INFINITY),
264 ("Number", "NEGATIVE_INFINITY") => Some(f64::NEG_INFINITY),
265 ("Number", "NaN") => Some(f64::NAN),
266 _ => None,
267 };
268 if let Some(k) = konst {
269 return Value::Float(k);
270 }
271 let qualified = format!("{ns}.{name}");
272 if is_known_builtin(&qualified) {
273 return with_host(|h| h.alloc(JsObj::Builtin(qualified)));
274 }
275 Value::Undef
276}
277
278fn b_setattr(vm: &mut VM, _: u8) -> Value {
279 let val = vm.pop();
280 let name = sval(&vm.pop());
281 let recv = vm.pop();
282 set_property(&recv, &name, val.clone());
283 val
284}
285
286fn set_property(recv: &Value, name: &str, val: Value) {
287 with_host(|h| match h.get_mut(recv) {
288 Some(JsObj::Object(props)) => {
289 props.insert(name.to_string(), val);
290 }
291 Some(JsObj::Array(items)) => {
292 if name == "length" {
293 let n = h_val_to_len(&val);
294 items.resize(n, Value::Undef);
295 } else if let Ok(i) = name.parse::<usize>() {
296 if i >= items.len() {
297 items.resize(i + 1, Value::Undef);
298 }
299 items[i] = val;
300 }
301 }
302 _ => {}
303 });
304}
305
306fn h_val_to_len(v: &Value) -> usize {
307 match v {
308 Value::Float(f) if f.is_finite() && *f >= 0.0 => *f as usize,
309 Value::Int(n) if *n >= 0 => *n as usize,
310 _ => 0,
311 }
312}
313
314fn b_getitem(vm: &mut VM, _: u8) -> Value {
315 let idx = vm.pop();
316 let recv = vm.pop();
317 let key = with_host(|h| h.str_of(&idx));
318 match get_property(&recv, &key) {
319 Ok(v) => v,
320 Err(e) => abort(vm, e),
321 }
322}
323
324fn b_setitem(vm: &mut VM, _: u8) -> Value {
325 let val = vm.pop();
326 let idx = vm.pop();
327 let recv = vm.pop();
328 let key = with_host(|h| h.str_of(&idx));
329 set_property(&recv, &key, val.clone());
330 val
331}
332
333fn b_delitem(vm: &mut VM, _: u8) -> Value {
334 let idx = vm.pop();
335 let recv = vm.pop();
336 let key = with_host(|h| h.str_of(&idx));
337 with_host(|h| match h.get_mut(&recv) {
338 Some(JsObj::Object(props)) => {
339 props.shift_remove(&key);
340 }
341 Some(JsObj::Array(items)) => {
342 if let Ok(i) = key.parse::<usize>() {
343 if i < items.len() {
344 items[i] = Value::Undef;
345 }
346 }
347 }
348 _ => {}
349 });
350 Value::Bool(true)
351}
352
353fn b_delprop_name(vm: &mut VM, _: u8) -> Value {
354 let name = sval(&vm.pop());
355 let recv = vm.pop();
356 with_host(|h| {
357 if let Some(JsObj::Object(props)) = h.get_mut(&recv) {
358 props.shift_remove(&name);
359 }
360 });
361 Value::Bool(true)
362}
363
364fn b_mkstr(vm: &mut VM, argc: u8) -> Value {
367 let parts = pop_n(vm, argc as usize);
368 let s: String = with_host(|h| parts.iter().map(|p| h.str_of(p)).collect());
369 with_host(|h| h.new_str(s))
370}
371
372fn b_mkarr(vm: &mut VM, argc: u8) -> Value {
373 let items = pop_n(vm, argc as usize);
374 with_host(|h| h.new_array(items))
375}
376
377fn b_mkobj(vm: &mut VM, argc: u8) -> Value {
378 let flat = pop_n(vm, argc as usize);
379 let mut props: IndexMap<String, Value> = IndexMap::new();
380 let mut i = 0;
381 while i + 2 < flat.len() || (i + 2 == flat.len() && flat.len() % 3 == 0 && i < flat.len()) {
382 if i + 2 >= flat.len() {
383 break;
384 }
385 let spread = matches!(flat[i], Value::Int(1));
386 if spread {
387 let src = flat[i + 1].clone();
388 let entries = with_host(|h| match h.get(&src) {
389 Some(JsObj::Object(m)) => m.iter().map(|(k, v)| (k.clone(), v.clone())).collect::<Vec<_>>(),
390 Some(JsObj::Array(items)) => items
391 .iter()
392 .enumerate()
393 .map(|(idx, v)| (idx.to_string(), v.clone()))
394 .collect::<Vec<_>>(),
395 _ => Vec::new(),
396 });
397 for (k, v) in entries {
398 props.insert(k, v);
399 }
400 } else {
401 let key = with_host(|h| h.str_of(&flat[i + 1]));
402 props.insert(key, flat[i + 2].clone());
403 }
404 i += 3;
405 }
406 with_host(|h| h.new_object(props))
407}
408
409fn b_mkfunc(vm: &mut VM, _: u8) -> Value {
410 let def_id = match vm.pop() {
411 Value::Int(n) => n as usize,
412 Value::Float(f) => f as usize,
413 _ => return abort(vm, "internal: MKFUNC id".into()),
414 };
415 let is_arrow = with_host(|h| h.funcs.get(def_id).map(|d| d.is_arrow).unwrap_or(false));
416 with_host(|h| {
417 let env = h.current_env_capture();
418 let this = h.current_this();
419 h.alloc(JsObj::Func(FuncVal {
420 def_id,
421 env: Some(env),
422 this,
423 is_arrow,
424 }))
425 })
426}
427
428fn b_truthy(vm: &mut VM, _: u8) -> Value {
431 let v = vm.pop();
432 Value::Bool(with_host(|h| h.truthy(&v)))
433}
434
435fn b_nullish(vm: &mut VM, _: u8) -> Value {
436 let v = vm.pop();
437 Value::Bool(with_host(|h| h.is_nullish(&v)))
438}
439
440fn b_tostr(vm: &mut VM, _: u8) -> Value {
441 let v = vm.pop();
442 with_host(|h| {
443 let s = h.str_of(&v);
444 h.new_str(s)
445 })
446}
447
448fn b_typeof(vm: &mut VM, _: u8) -> Value {
449 let v = vm.pop();
450 with_host(|h| {
451 let t = h.type_of(&v);
452 h.new_str(t)
453 })
454}
455
456fn b_strict_eq(vm: &mut VM, _: u8) -> Value {
457 let b = vm.pop();
458 let a = vm.pop();
459 Value::Bool(with_host(|h| h.strict_eq(&a, &b)))
460}
461
462fn b_loose_eq(vm: &mut VM, _: u8) -> Value {
463 let b = vm.pop();
464 let a = vm.pop();
465 Value::Bool(with_host(|h| h.loose_eq(&a, &b)))
466}
467
468fn b_instanceof(vm: &mut VM, _: u8) -> Value {
469 let _ctor = vm.pop();
470 let _obj = vm.pop();
471 Value::Bool(false)
473}
474
475fn b_binop(vm: &mut VM, _: u8) -> Value {
478 let b = vm.pop();
479 let a = vm.pop();
480 let tag = match vm.pop() {
481 Value::Int(n) => n,
482 _ => 0,
483 };
484 with_host(|h| h.bitwise(tag, &a, &b))
485}
486
487fn b_unary(vm: &mut VM, _: u8) -> Value {
488 let v = vm.pop();
489 let tag = match vm.pop() {
490 Value::Int(n) => n,
491 _ => 0,
492 };
493 with_host(|h| match tag {
494 host::unop::POS => Value::Float(h.to_number(&v)),
495 host::unop::BITNOT => {
496 let n = h.to_number(&v);
497 let i = if n.is_finite() { n.trunc() as i64 as i32 } else { 0 };
498 Value::Float(!i as f64)
499 }
500 _ => Value::Undef,
501 })
502}
503
504fn b_contains(vm: &mut VM, _: u8) -> Value {
507 let container = vm.pop();
508 let key = vm.pop();
509 let k = with_host(|h| h.str_of(&key));
510 Value::Bool(with_host(|h| match h.get(&container) {
511 Some(JsObj::Object(props)) => props.contains_key(&k),
512 Some(JsObj::Array(items)) => k.parse::<usize>().map(|i| i < items.len()).unwrap_or(false),
513 _ => false,
514 }))
515}
516
517fn b_sig_return(vm: &mut VM, _: u8) -> Value {
520 let v = vm.pop();
521 with_host(|h| h.signal = Some(host::Signal::Return(v.clone())));
522 vm.ip = vm.chunk.ops.len();
523 v
524}
525
526fn b_throw(vm: &mut VM, _: u8) -> Value {
527 let v = vm.pop();
528 let msg = with_host(|h| {
529 h.exc = Some(v.clone());
530 error_display(h, &v)
532 });
533 abort(vm, msg)
534}
535
536fn error_display(h: &host::JsHost, v: &Value) -> String {
537 if let Some(JsObj::Object(props)) = h.get(v) {
538 let name = props.get("name").map(|x| h.str_of(x)).unwrap_or_else(|| "Error".into());
539 if let Some(m) = props.get("message") {
540 return format!("Uncaught {name}: {}", h.str_of(m));
541 }
542 }
543 format!("Uncaught {}", h.str_of(v))
544}
545
546fn b_try(vm: &mut VM, _: u8) -> Value {
547 let id = match vm.pop() {
548 Value::Int(n) => n as usize,
549 _ => return abort(vm, "internal: TRY id".into()),
550 };
551 let td = match with_host(|h| h.try_def(id)) {
552 Some(t) => t,
553 None => return abort(vm, "internal: unknown try id".into()),
554 };
555 let mut pending: Option<String> = None;
556
557 let body_res = host::run_chunk_on(td.block.clone());
558 let signal_after = with_host(|h| h.signal.is_some());
559 if let Err(e) = body_res {
560 if signal_after {
561 pending = Some(e);
562 } else if let Some((bind, hbody)) = &td.handler {
563 let thrown = with_host(|h| h.exc.clone()).unwrap_or_else(|| {
565 with_host(|h| synth_error(h, &e))
566 });
567 with_host(|h| {
568 h.error = None;
569 h.exc = None;
570 });
571 if let Some(name) = bind {
572 with_host(|h| h.declare_name(name, thrown));
573 }
574 if let Err(e2) = host::run_chunk_on(hbody.clone()) {
575 pending = Some(e2);
576 }
577 } else {
578 pending = Some(e);
579 }
580 }
581
582 if let Some(fin) = &td.finalizer {
584 let sig_before = with_host(|h| h.signal.take());
585 match host::run_chunk_on(fin.clone()) {
586 Ok(_) => {
587 if with_host(|h| h.signal.is_none()) {
588 with_host(|h| h.signal = sig_before);
589 }
590 }
591 Err(e) => pending = Some(e),
592 }
593 }
594
595 if let Some(e) = pending {
596 return abort(vm, e);
597 }
598 Value::Undef
599}
600
601fn synth_error(h: &mut host::JsHost, e: &str) -> Value {
603 let (name, message) = match e.split_once(": ") {
604 Some((n, m)) => (n.to_string(), m.to_string()),
605 None => ("Error".to_string(), e.to_string()),
606 };
607 let mut props: IndexMap<String, Value> = IndexMap::new();
608 let nv = h.new_str(name);
609 let mv = h.new_str(message);
610 props.insert("name".into(), nv);
611 props.insert("message".into(), mv);
612 h.new_object(props)
613}
614
615fn b_getiter(vm: &mut VM, _: u8) -> Value {
618 let v = vm.pop();
619 match with_host(|h| h.iter_vec(&v)) {
620 Ok(items) => with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })),
621 Err(e) => abort(vm, e),
622 }
623}
624
625fn b_forin_keys(vm: &mut VM, _: u8) -> Value {
626 let v = vm.pop();
627 let keys = with_host(|h| h.enum_keys(&v));
628 with_host(|h| h.new_array(keys))
629}
630
631fn b_foriter(vm: &mut VM, _: u8) -> Value {
632 let it = match vm.stack.last() {
633 Some(v) => v.clone(),
634 None => return abort(vm, "internal: FORITER with empty stack".into()),
635 };
636 let next = with_host(|h| {
637 if let Some(JsObj::Iter { items, idx }) = h.get_mut(&it) {
638 if *idx < items.len() {
639 let v = items[*idx].clone();
640 *idx += 1;
641 return Some(v);
642 }
643 }
644 None
645 });
646 match next {
647 Some(v) => {
648 vm.push(v);
649 Value::Bool(true)
650 }
651 None => Value::Bool(false),
652 }
653}
654
655fn b_unpack(vm: &mut VM, _: u8) -> Value {
656 let star = match vm.pop() {
657 Value::Int(n) => n,
658 _ => -1,
659 };
660 let count = match vm.pop() {
661 Value::Int(n) => n as usize,
662 _ => 0,
663 };
664 let iterable = vm.pop();
665 let items = match with_host(|h| h.iter_vec(&iterable)) {
666 Ok(v) => v,
667 Err(e) => return abort(vm, e),
668 };
669 let ordered: Vec<Value> = if star < 0 {
670 (0..count).map(|i| items.get(i).cloned().unwrap_or(Value::Undef)).collect()
671 } else {
672 let si = star as usize;
673 let after = count.saturating_sub(si + 1);
674 let rest_end = items.len().saturating_sub(after).max(si);
675 let mut out: Vec<Value> = Vec::with_capacity(count);
676 for i in 0..si {
677 out.push(items.get(i).cloned().unwrap_or(Value::Undef));
678 }
679 let rest: Vec<Value> = items.get(si..rest_end).map(|s| s.to_vec()).unwrap_or_default();
680 out.push(with_host(|h| h.new_array(rest)));
681 for j in 0..after {
682 out.push(items.get(rest_end + j).cloned().unwrap_or(Value::Undef));
683 }
684 out
685 };
686 if ordered.is_empty() {
687 return Value::Undef;
688 }
689 for it in ordered[1..].iter().rev().cloned() {
690 vm.push(it);
691 }
692 ordered[0].clone()
693}
694
695fn b_build_args(vm: &mut VM, argc: u8) -> Value {
696 let flat = pop_n(vm, argc as usize);
697 let mut out = Vec::new();
698 let mut i = 0;
699 while i + 1 < flat.len() {
700 let spread = matches!(flat[i], Value::Int(1));
701 let val = flat[i + 1].clone();
702 if spread {
703 match with_host(|h| h.iter_vec(&val)) {
704 Ok(items) => out.extend(items),
705 Err(e) => return abort(vm, e),
706 }
707 } else {
708 out.push(val);
709 }
710 i += 2;
711 }
712 with_host(|h| h.new_array(out))
713}
714
715fn b_call(vm: &mut VM, argc: u8) -> Value {
718 let mut args = pop_n(vm, argc as usize);
719 let name = sval(&args.remove(0));
720 let r = host::call_named(&name, args);
721 finish(vm, r)
722}
723
724fn b_call_method(vm: &mut VM, argc: u8) -> Value {
725 let mut args = pop_n(vm, argc as usize);
726 let recv = args.remove(0);
727 let name = sval(&args.remove(0));
728 let r = host::call_method(&recv, &name, args);
729 finish(vm, r)
730}
731
732fn b_call_value(vm: &mut VM, argc: u8) -> Value {
733 let mut args = pop_n(vm, argc as usize);
734 let callable = args.remove(0);
735 let r = host::invoke(&callable, args, None);
736 finish(vm, r)
737}
738
739fn b_new(vm: &mut VM, argc: u8) -> Value {
740 let mut args = pop_n(vm, argc as usize);
741 let ctor = args.remove(0);
742 let r = host::construct(&ctor, args);
743 finish(vm, r)
744}
745
746fn b_apply(vm: &mut VM, _: u8) -> Value {
747 let args_arr = vm.pop();
748 let callable = vm.pop();
749 let args = with_host(|h| h.iter_vec(&args_arr)).unwrap_or_default();
750 let r = host::invoke(&callable, args, None);
751 finish(vm, r)
752}
753
754fn b_apply_method(vm: &mut VM, _: u8) -> Value {
755 let args_arr = vm.pop();
756 let name = sval(&vm.pop());
757 let recv = vm.pop();
758 let args = with_host(|h| h.iter_vec(&args_arr)).unwrap_or_default();
759 let r = host::call_method(&recv, &name, args);
760 finish(vm, r)
761}
762
763pub fn numeric_hook(op: NumOp, a: &Value, b: &Value) -> Result<Value, String> {
768 with_host(|h| h.arith(op, a, b))
769}
770
771fn is_namespace(name: &str) -> bool {
775 matches!(
776 name,
777 "console" | "Math" | "JSON" | "Object" | "Array" | "Number" | "String" | "Boolean"
778 )
779}
780
781const GLOBAL_FUNCS: &[&str] = &[
782 "parseInt",
783 "parseFloat",
784 "isNaN",
785 "isFinite",
786 "String",
787 "Number",
788 "Boolean",
789 "Array",
790 "Error",
791 "TypeError",
792 "RangeError",
793];
794
795const NS_METHODS: &[&str] = &[
796 "console.log",
797 "console.error",
798 "console.warn",
799 "console.info",
800 "console.debug",
801 "Math.floor",
802 "Math.ceil",
803 "Math.round",
804 "Math.trunc",
805 "Math.abs",
806 "Math.sign",
807 "Math.max",
808 "Math.min",
809 "Math.pow",
810 "Math.sqrt",
811 "Math.cbrt",
812 "Math.random",
813 "Math.hypot",
814 "Math.log",
815 "Math.log2",
816 "Math.log10",
817 "Math.exp",
818 "Math.sin",
819 "Math.cos",
820 "Math.tan",
821 "Math.atan",
822 "Math.atan2",
823 "Math.asin",
824 "Math.acos",
825 "JSON.stringify",
826 "JSON.parse",
827 "Object.keys",
828 "Object.values",
829 "Object.entries",
830 "Object.assign",
831 "Object.freeze",
832 "Object.fromEntries",
833 "Array.isArray",
834 "Array.from",
835 "Array.of",
836 "Number.isInteger",
837 "Number.isNaN",
838 "Number.isFinite",
839 "Number.isSafeInteger",
840 "Number.parseInt",
841 "Number.parseFloat",
842 "String.fromCharCode",
843];
844
845pub fn is_known_builtin(name: &str) -> bool {
846 GLOBAL_FUNCS.contains(&name) || NS_METHODS.contains(&name) || is_namespace(name)
847}
848
849pub fn call_builtin_function(name: &str, args: Vec<Value>) -> Result<Value, String> {
851 match name {
852 "console.log" | "console.info" | "console.debug" => {
853 print_line(&args, false);
854 Ok(Value::Undef)
855 }
856 "console.error" | "console.warn" => {
857 print_line(&args, true);
858 Ok(Value::Undef)
859 }
860 "parseInt" | "Number.parseInt" => Ok(Value::Float(parse_int(&args))),
861 "parseFloat" | "Number.parseFloat" => Ok(Value::Float(parse_float(&args))),
862 "isNaN" => Ok(Value::Bool(arg_num(&args, 0).is_nan())),
863 "isFinite" => Ok(Value::Bool(arg_num(&args, 0).is_finite())),
864 "Number.isInteger" => Ok(Value::Bool(is_integer(arg0(&args)))),
865 "Number.isSafeInteger" => Ok(Value::Bool(is_safe_integer(arg0(&args)))),
866 "Number.isNaN" => Ok(Value::Bool(matches!(arg0(&args), Value::Float(f) if f.is_nan()))),
867 "Number.isFinite" => Ok(Value::Bool(matches!(arg0(&args), Value::Float(f) if f.is_finite()) || matches!(arg0(&args), Value::Int(_)))),
868 "String" => Ok(with_host(|h| {
869 let s = if args.is_empty() { String::new() } else { h.str_of(&args[0]) };
870 h.new_str(s)
871 })),
872 "Number" => Ok(Value::Float(if args.is_empty() { 0.0 } else { with_host(|h| h.to_number(&args[0])) })),
873 "Boolean" => Ok(Value::Bool(with_host(|h| h.truthy(&arg0(&args))))),
874 "String.fromCharCode" => Ok(with_host(|h| {
875 let s: String = args
876 .iter()
877 .filter_map(|a| char::from_u32(h.to_number(a) as u32))
878 .collect();
879 h.new_str(s)
880 })),
881 "Array" | "Array.of" => Ok(with_host(|h| h.new_array(args))),
882 "Array.isArray" => Ok(Value::Bool(matches!(with_host(|h| h.get(&arg0(&args)).cloned()), Some(JsObj::Array(_))))),
883 "Array.from" => array_from(args),
884 "Object.keys" => object_keys(args, 0),
885 "Object.values" => object_keys(args, 1),
886 "Object.entries" => object_keys(args, 2),
887 "Object.assign" => object_assign(args),
888 "Object.freeze" => Ok(arg0(&args)),
889 "Object.fromEntries" => object_from_entries(args),
890 "JSON.stringify" => json_stringify(args),
891 "JSON.parse" => json_parse(args),
892 "Error" | "TypeError" | "RangeError" => Ok(make_error(name, &args)),
893 _ if name.starts_with("Math.") => math_fn(&name[5..], &args),
894 _ => Err(host::type_error(&format!("{name} is not a function"))),
895 }
896}
897
898pub fn construct_builtin(name: &str, args: Vec<Value>) -> Result<Value, String> {
900 match name {
901 "Array" => {
902 if args.len() == 1 {
904 if let Value::Float(f) = args[0] {
905 if f.fract() == 0.0 && f >= 0.0 {
906 return Ok(with_host(|h| h.new_array(vec![Value::Undef; f as usize])));
907 }
908 }
909 }
910 Ok(with_host(|h| h.new_array(args)))
911 }
912 "Object" => Ok(with_host(|h| h.new_object(IndexMap::new()))),
913 "Error" | "TypeError" | "RangeError" => Ok(make_error(name, &args)),
914 _ => Err(host::type_error(&format!("{name} is not a constructor"))),
915 }
916}
917
918fn make_error(name: &str, args: &[Value]) -> Value {
919 with_host(|h| {
920 let mut props: IndexMap<String, Value> = IndexMap::new();
921 let nv = h.new_str(name);
922 props.insert("name".into(), nv);
923 let msg = args.first().map(|a| h.str_of(a)).unwrap_or_default();
924 let mv = h.new_str(msg);
925 props.insert("message".into(), mv);
926 h.new_object(props)
927 })
928}
929
930fn print_line(args: &[Value], stderr: bool) {
931 let line: String = with_host(|h| {
932 args.iter().map(|a| h.console_format(a)).collect::<Vec<_>>().join(" ")
933 });
934 if stderr {
935 eprintln!("{line}");
936 } else {
937 println!("{line}");
938 }
939}
940
941fn arg0(args: &[Value]) -> Value {
942 args.first().cloned().unwrap_or(Value::Undef)
943}
944fn arg_num(args: &[Value], i: usize) -> f64 {
945 with_host(|h| h.to_number(&args.get(i).cloned().unwrap_or(Value::Undef)))
946}
947
948fn is_integer(v: Value) -> bool {
949 match v {
950 Value::Int(_) => true,
951 Value::Float(f) => f.is_finite() && f.fract() == 0.0,
952 _ => false,
953 }
954}
955fn is_safe_integer(v: Value) -> bool {
956 match v {
957 Value::Float(f) => f.is_finite() && f.fract() == 0.0 && f.abs() <= 9007199254740991.0,
958 Value::Int(_) => true,
959 _ => false,
960 }
961}
962
963fn parse_int(args: &[Value]) -> f64 {
964 let s = with_host(|h| h.str_of(&arg0(args)));
965 let radix = args.get(1).map(|r| with_host(|h| h.to_number(r)) as u32).filter(|r| (2..=36).contains(r));
966 let t = s.trim();
967 let (neg, digits) = match t.strip_prefix('-') {
968 Some(rest) => (true, rest),
969 None => (false, t.strip_prefix('+').unwrap_or(t)),
970 };
971 let (radix, digits) = match radix {
972 Some(16) => (16u32, digits.strip_prefix("0x").or_else(|| digits.strip_prefix("0X")).unwrap_or(digits)),
973 Some(r) => (r, digits),
974 None => {
975 if let Some(hex) = digits.strip_prefix("0x").or_else(|| digits.strip_prefix("0X")) {
976 (16, hex)
977 } else {
978 (10, digits)
979 }
980 }
981 };
982 let valid: String = digits.chars().take_while(|c| c.is_digit(radix)).collect();
983 if valid.is_empty() {
984 return f64::NAN;
985 }
986 let n = i64::from_str_radix(&valid, radix).map(|n| n as f64).unwrap_or(f64::NAN);
987 if neg {
988 -n
989 } else {
990 n
991 }
992}
993
994fn parse_float(args: &[Value]) -> f64 {
995 let s = with_host(|h| h.str_of(&arg0(args)));
996 let t = s.trim_start();
997 let inf_body = t.strip_prefix('+').or_else(|| t.strip_prefix('-')).unwrap_or(t);
999 if inf_body.starts_with("Infinity") {
1000 return if t.starts_with('-') {
1001 f64::NEG_INFINITY
1002 } else {
1003 f64::INFINITY
1004 };
1005 }
1006 let mut end = 0;
1008 let bytes = t.as_bytes();
1009 let mut seen_dot = false;
1010 let mut seen_e = false;
1011 for (i, &c) in bytes.iter().enumerate() {
1012 match c {
1013 b'0'..=b'9' => end = i + 1,
1014 b'+' | b'-' if i == 0 || bytes[i - 1] == b'e' || bytes[i - 1] == b'E' => end = i + 1,
1015 b'.' if !seen_dot && !seen_e => {
1016 seen_dot = true;
1017 end = i + 1;
1018 }
1019 b'e' | b'E' if !seen_e && i > 0 => {
1020 seen_e = true;
1021 end = i + 1;
1022 }
1023 _ => break,
1024 }
1025 }
1026 t[..end].parse::<f64>().unwrap_or(f64::NAN)
1027}
1028
1029fn math_fn(fname: &str, args: &[Value]) -> Result<Value, String> {
1030 let x = arg_num(args, 0);
1031 let r = match fname {
1032 "floor" => x.floor(),
1033 "ceil" => x.ceil(),
1034 "round" => {
1035 let r = (x + 0.5).floor();
1038 if r == 0.0 && x.is_sign_negative() {
1039 -0.0
1040 } else {
1041 r
1042 }
1043 }
1044 "trunc" => x.trunc(),
1045 "abs" => x.abs(),
1046 "sign" => {
1047 if x.is_nan() {
1048 f64::NAN
1049 } else if x > 0.0 {
1050 1.0
1051 } else if x < 0.0 {
1052 -1.0
1053 } else {
1054 x
1055 }
1056 }
1057 "sqrt" => x.sqrt(),
1058 "cbrt" => x.cbrt(),
1059 "exp" => x.exp(),
1060 "log" => x.ln(),
1061 "log2" => x.log2(),
1062 "log10" => x.log10(),
1063 "sin" => x.sin(),
1064 "cos" => x.cos(),
1065 "tan" => x.tan(),
1066 "asin" => x.asin(),
1067 "acos" => x.acos(),
1068 "atan" => x.atan(),
1069 "atan2" => x.atan2(arg_num(args, 1)),
1070 "pow" => x.powf(arg_num(args, 1)),
1071 "hypot" => {
1072 let xs: Vec<f64> = args.iter().map(|a| with_host(|h| h.to_number(a))).collect();
1075 let mut max = 0.0f64;
1076 for x in &xs {
1077 if x.abs() > max {
1078 max = x.abs();
1079 }
1080 }
1081 if xs.iter().any(|x| x.is_infinite()) {
1082 f64::INFINITY
1083 } else if max == 0.0 || !max.is_finite() {
1084 max
1085 } else {
1086 let s: f64 = xs.iter().map(|x| (x / max) * (x / max)).sum();
1087 max * s.sqrt()
1088 }
1089 }
1090 "random" => pseudo_random(),
1091 "max" => {
1092 if args.is_empty() {
1093 f64::NEG_INFINITY
1094 } else {
1095 let mut m = f64::NEG_INFINITY;
1096 for a in args {
1097 let n = with_host(|h| h.to_number(a));
1098 if n.is_nan() {
1099 return Ok(Value::Float(f64::NAN));
1100 }
1101 if n > m {
1102 m = n;
1103 }
1104 }
1105 m
1106 }
1107 }
1108 "min" => {
1109 if args.is_empty() {
1110 f64::INFINITY
1111 } else {
1112 let mut m = f64::INFINITY;
1113 for a in args {
1114 let n = with_host(|h| h.to_number(a));
1115 if n.is_nan() {
1116 return Ok(Value::Float(f64::NAN));
1117 }
1118 if n < m {
1119 m = n;
1120 }
1121 }
1122 m
1123 }
1124 }
1125 _ => return Err(host::type_error(&format!("Math.{fname} is not a function"))),
1126 };
1127 Ok(Value::Float(r))
1128}
1129
1130fn pseudo_random() -> f64 {
1133 use std::cell::Cell;
1134 thread_local!(static SEED: Cell<u64> = const { Cell::new(0x2545F4914F6CDD1D) });
1135 SEED.with(|s| {
1136 let mut x = s.get();
1137 x ^= x << 13;
1138 x ^= x >> 7;
1139 x ^= x << 17;
1140 s.set(x);
1141 (x >> 11) as f64 / (1u64 << 53) as f64
1142 })
1143}
1144
1145fn object_keys(args: Vec<Value>, mode: u8) -> Result<Value, String> {
1148 let v = arg0(&args);
1149 let entries: Vec<(String, Value)> = with_host(|h| match h.get(&v) {
1150 Some(JsObj::Object(props)) => props.iter().map(|(k, val)| (k.clone(), val.clone())).collect(),
1151 Some(JsObj::Array(items)) => items.iter().enumerate().map(|(i, val)| (i.to_string(), val.clone())).collect(),
1152 _ => Vec::new(),
1153 });
1154 Ok(with_host(|h| {
1155 let out: Vec<Value> = entries
1156 .into_iter()
1157 .map(|(k, val)| match mode {
1158 0 => h.new_str(k),
1159 1 => val,
1160 _ => {
1161 let ks = h.new_str(k);
1162 h.new_array(vec![ks, val])
1163 }
1164 })
1165 .collect();
1166 h.new_array(out)
1167 }))
1168}
1169
1170fn object_assign(args: Vec<Value>) -> Result<Value, String> {
1171 let target = arg0(&args);
1172 for src in args.iter().skip(1) {
1173 let entries: Vec<(String, Value)> = with_host(|h| match h.get(src) {
1174 Some(JsObj::Object(p)) => p.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
1175 _ => Vec::new(),
1176 });
1177 with_host(|h| {
1178 if let Some(JsObj::Object(p)) = h.get_mut(&target) {
1179 for (k, v) in entries {
1180 p.insert(k, v);
1181 }
1182 }
1183 });
1184 }
1185 Ok(target)
1186}
1187
1188fn object_from_entries(args: Vec<Value>) -> Result<Value, String> {
1189 let pairs = with_host(|h| h.iter_vec(&arg0(&args))).unwrap_or_default();
1190 let mut props: IndexMap<String, Value> = IndexMap::new();
1191 for p in pairs {
1192 let kv = with_host(|h| h.iter_vec(&p)).unwrap_or_default();
1193 let key = with_host(|h| h.str_of(&kv.first().cloned().unwrap_or(Value::Undef)));
1194 let val = kv.get(1).cloned().unwrap_or(Value::Undef);
1195 props.insert(key, val);
1196 }
1197 Ok(with_host(|h| h.new_object(props)))
1198}
1199
1200fn array_from(args: Vec<Value>) -> Result<Value, String> {
1201 let items = with_host(|h| h.iter_vec(&arg0(&args))).unwrap_or_default();
1202 if let Some(cb) = args.get(1).cloned() {
1203 let mut out = Vec::with_capacity(items.len());
1204 for (i, it) in items.into_iter().enumerate() {
1205 out.push(host::invoke(&cb, vec![it, Value::Float(i as f64)], None)?);
1206 }
1207 return Ok(with_host(|h| h.new_array(out)));
1208 }
1209 Ok(with_host(|h| h.new_array(items)))
1210}
1211
1212fn json_stringify(args: Vec<Value>) -> Result<Value, String> {
1215 let v = arg0(&args);
1216 let indent = match args.get(2) {
1217 Some(Value::Float(f)) => " ".repeat((*f as usize).min(10)),
1218 Some(other) => with_host(|h| h.as_str(other)).unwrap_or_default(),
1219 None => String::new(),
1220 };
1221 let s = with_host(|h| json_str(h, &v, &indent, 0));
1222 match s {
1223 Some(s) => Ok(with_host(|h| h.new_str(s))),
1224 None => Ok(Value::Undef),
1225 }
1226}
1227
1228fn json_str(h: &host::JsHost, v: &Value, indent: &str, depth: usize) -> Option<String> {
1229 match v {
1230 Value::Undef => None,
1231 Value::Bool(b) => Some(if *b { "true".into() } else { "false".into() }),
1232 Value::Int(n) => Some(n.to_string()),
1233 Value::Float(f) => Some(if f.is_finite() { host::fmt_number(*f) } else { "null".into() }),
1234 Value::Str(s) => Some(json_quote(s)),
1235 Value::Obj(_) => match h.get(v) {
1236 Some(JsObj::Str(s)) => Some(json_quote(s)),
1237 Some(JsObj::Null) => Some("null".into()),
1238 Some(JsObj::Func(_)) | Some(JsObj::Builtin(_)) | Some(JsObj::BoundMethod { .. }) => None,
1239 Some(JsObj::Array(items)) => {
1240 if items.is_empty() {
1241 return Some("[]".into());
1242 }
1243 let parts: Vec<String> = items
1244 .iter()
1245 .map(|x| json_str(h, x, indent, depth + 1).unwrap_or_else(|| "null".into()))
1246 .collect();
1247 Some(wrap(&parts, "[", "]", indent, depth))
1248 }
1249 Some(JsObj::Object(props)) => {
1250 let parts: Vec<String> = props
1251 .iter()
1252 .filter_map(|(k, val)| {
1253 json_str(h, val, indent, depth + 1).map(|vs| {
1254 let sep = if indent.is_empty() { ":" } else { ": " };
1255 format!("{}{sep}{vs}", json_quote(k))
1256 })
1257 })
1258 .collect();
1259 if parts.is_empty() {
1260 return Some("{}".into());
1261 }
1262 Some(wrap(&parts, "{", "}", indent, depth))
1263 }
1264 _ => Some("null".into()),
1265 },
1266 _ => Some("null".into()),
1267 }
1268}
1269
1270fn wrap(parts: &[String], open: &str, close: &str, indent: &str, depth: usize) -> String {
1271 if indent.is_empty() {
1272 format!("{open}{}{close}", parts.join(","))
1273 } else {
1274 let pad = indent.repeat(depth + 1);
1275 let pad_close = indent.repeat(depth);
1276 format!("{open}\n{pad}{}\n{pad_close}{close}", parts.join(&format!(",\n{pad}")))
1277 }
1278}
1279
1280fn json_quote(s: &str) -> String {
1281 let mut out = String::from("\"");
1282 for c in s.chars() {
1283 match c {
1284 '"' => out.push_str("\\\""),
1285 '\\' => out.push_str("\\\\"),
1286 '\n' => out.push_str("\\n"),
1287 '\t' => out.push_str("\\t"),
1288 '\r' => out.push_str("\\r"),
1289 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
1290 _ => out.push(c),
1291 }
1292 }
1293 out.push('"');
1294 out
1295}
1296
1297fn json_parse(args: Vec<Value>) -> Result<Value, String> {
1298 let s = with_host(|h| h.str_of(&arg0(&args)));
1299 let mut p = JsonParser { chars: s.chars().collect(), pos: 0 };
1300 p.skip_ws();
1301 let v = p.parse_value()?;
1302 p.skip_ws();
1303 Ok(v)
1304}
1305
1306struct JsonParser {
1307 chars: Vec<char>,
1308 pos: usize,
1309}
1310impl JsonParser {
1311 fn peek(&self) -> Option<char> {
1312 self.chars.get(self.pos).copied()
1313 }
1314 fn skip_ws(&mut self) {
1315 while matches!(self.peek(), Some(' ') | Some('\n') | Some('\t') | Some('\r')) {
1316 self.pos += 1;
1317 }
1318 }
1319 fn parse_value(&mut self) -> Result<Value, String> {
1320 self.skip_ws();
1321 match self.peek() {
1322 Some('{') => self.parse_object(),
1323 Some('[') => self.parse_array(),
1324 Some('"') => {
1325 let s = self.parse_string()?;
1326 Ok(with_host(|h| h.new_str(s)))
1327 }
1328 Some('t') | Some('f') => self.parse_bool(),
1329 Some('n') => {
1330 self.expect_lit("null")?;
1331 Ok(with_host(|h| h.null()))
1332 }
1333 Some(c) if c == '-' || c.is_ascii_digit() => self.parse_number(),
1334 _ => Err("SyntaxError: Unexpected token in JSON".into()),
1335 }
1336 }
1337 fn expect_lit(&mut self, lit: &str) -> Result<(), String> {
1338 for ch in lit.chars() {
1339 if self.peek() != Some(ch) {
1340 return Err("SyntaxError: Unexpected token in JSON".into());
1341 }
1342 self.pos += 1;
1343 }
1344 Ok(())
1345 }
1346 fn parse_bool(&mut self) -> Result<Value, String> {
1347 if self.peek() == Some('t') {
1348 self.expect_lit("true")?;
1349 Ok(Value::Bool(true))
1350 } else {
1351 self.expect_lit("false")?;
1352 Ok(Value::Bool(false))
1353 }
1354 }
1355 fn parse_number(&mut self) -> Result<Value, String> {
1356 let start = self.pos;
1357 while matches!(self.peek(), Some(c) if c.is_ascii_digit() || c == '-' || c == '+' || c == '.' || c == 'e' || c == 'E') {
1358 self.pos += 1;
1359 }
1360 let s: String = self.chars[start..self.pos].iter().collect();
1361 s.parse::<f64>().map(Value::Float).map_err(|_| "SyntaxError: bad number in JSON".into())
1362 }
1363 fn parse_string(&mut self) -> Result<String, String> {
1364 self.pos += 1; let mut out = String::new();
1366 loop {
1367 match self.peek() {
1368 None => return Err("SyntaxError: unterminated string in JSON".into()),
1369 Some('"') => {
1370 self.pos += 1;
1371 break;
1372 }
1373 Some('\\') => {
1374 self.pos += 1;
1375 match self.peek() {
1376 Some('n') => out.push('\n'),
1377 Some('t') => out.push('\t'),
1378 Some('r') => out.push('\r'),
1379 Some('"') => out.push('"'),
1380 Some('\\') => out.push('\\'),
1381 Some('/') => out.push('/'),
1382 Some('b') => out.push('\u{08}'),
1383 Some('f') => out.push('\u{0C}'),
1384 Some('u') => {
1385 let h: String = self.chars[self.pos + 1..(self.pos + 5).min(self.chars.len())].iter().collect();
1386 if let Ok(n) = u32::from_str_radix(&h, 16) {
1387 if let Some(ch) = char::from_u32(n) {
1388 out.push(ch);
1389 }
1390 }
1391 self.pos += 4;
1392 }
1393 _ => {}
1394 }
1395 self.pos += 1;
1396 }
1397 Some(c) => {
1398 out.push(c);
1399 self.pos += 1;
1400 }
1401 }
1402 }
1403 Ok(out)
1404 }
1405 fn parse_array(&mut self) -> Result<Value, String> {
1406 self.pos += 1; let mut items = Vec::new();
1408 self.skip_ws();
1409 if self.peek() == Some(']') {
1410 self.pos += 1;
1411 return Ok(with_host(|h| h.new_array(items)));
1412 }
1413 loop {
1414 items.push(self.parse_value()?);
1415 self.skip_ws();
1416 match self.peek() {
1417 Some(',') => {
1418 self.pos += 1;
1419 }
1420 Some(']') => {
1421 self.pos += 1;
1422 break;
1423 }
1424 _ => return Err("SyntaxError: bad array in JSON".into()),
1425 }
1426 }
1427 Ok(with_host(|h| h.new_array(items)))
1428 }
1429 fn parse_object(&mut self) -> Result<Value, String> {
1430 self.pos += 1; let mut props: IndexMap<String, Value> = IndexMap::new();
1432 self.skip_ws();
1433 if self.peek() == Some('}') {
1434 self.pos += 1;
1435 return Ok(with_host(|h| h.new_object(props)));
1436 }
1437 loop {
1438 self.skip_ws();
1439 let key = self.parse_string()?;
1440 self.skip_ws();
1441 if self.peek() != Some(':') {
1442 return Err("SyntaxError: expected ':' in JSON".into());
1443 }
1444 self.pos += 1;
1445 let val = self.parse_value()?;
1446 props.insert(key, val);
1447 self.skip_ws();
1448 match self.peek() {
1449 Some(',') => {
1450 self.pos += 1;
1451 }
1452 Some('}') => {
1453 self.pos += 1;
1454 break;
1455 }
1456 _ => return Err("SyntaxError: bad object in JSON".into()),
1457 }
1458 }
1459 Ok(with_host(|h| h.new_object(props)))
1460 }
1461}
1462
1463fn is_array_method(name: &str) -> bool {
1466 matches!(
1467 name,
1468 "push" | "pop" | "shift" | "unshift" | "map" | "filter" | "forEach" | "join" | "slice"
1469 | "indexOf" | "lastIndexOf" | "includes" | "reduce" | "concat" | "reverse" | "sort"
1470 | "find" | "findIndex" | "some" | "every" | "flat" | "fill" | "splice" | "keys"
1471 | "values" | "entries" | "flatMap" | "at" | "toString"
1472 )
1473}
1474fn is_string_method(name: &str) -> bool {
1475 matches!(
1476 name,
1477 "toUpperCase" | "toLowerCase" | "charAt" | "charCodeAt" | "codePointAt" | "indexOf"
1478 | "lastIndexOf" | "includes" | "slice" | "substring" | "substr" | "split" | "trim"
1479 | "trimStart" | "trimEnd" | "replace" | "replaceAll" | "repeat" | "startsWith"
1480 | "endsWith" | "padStart" | "padEnd" | "concat" | "at" | "toString" | "valueOf"
1481 )
1482}
1483fn is_number_method(name: &str) -> bool {
1484 matches!(name, "toFixed" | "toString" | "toPrecision" | "valueOf")
1485}
1486
1487pub fn call_type_method(recv: &Value, name: &str, args: Vec<Value>) -> Result<Value, String> {
1489 let obj = with_host(|h| h.get(recv).cloned());
1490 match obj {
1491 Some(JsObj::Array(_)) => array_method(recv, name, args),
1492 Some(JsObj::Str(s)) => string_method(&s, name, args),
1493 Some(JsObj::Object(props)) => {
1494 if let Some(f) = props.get(name).cloned() {
1495 host::invoke(&f, args, Some(recv.clone()))
1496 } else if name == "hasOwnProperty" {
1497 let k = with_host(|h| h.str_of(&arg0(&args)));
1498 Ok(Value::Bool(props.contains_key(&k)))
1499 } else if name == "toString" {
1500 Ok(with_host(|h| h.new_str("[object Object]")))
1501 } else {
1502 Err(host::type_error(&format!(
1503 "{} is not a function",
1504 name
1505 )))
1506 }
1507 }
1508 _ => {
1509 if let Value::Float(_) | Value::Int(_) = recv {
1511 return number_method(with_host(|h| h.to_number(recv)), name, args);
1512 }
1513 if let Some(s) = with_host(|h| h.as_str(recv)) {
1514 return string_method(&s, name, args);
1515 }
1516 Err(host::type_error(&format!("{} is not a function", name)))
1517 }
1518 }
1519}
1520
1521fn array_items(recv: &Value) -> Vec<Value> {
1522 with_host(|h| match h.get(recv) {
1523 Some(JsObj::Array(items)) => items.clone(),
1524 _ => Vec::new(),
1525 })
1526}
1527
1528fn array_method(recv: &Value, name: &str, args: Vec<Value>) -> Result<Value, String> {
1529 match name {
1530 "push" => {
1531 with_host(|h| {
1532 if let Some(JsObj::Array(items)) = h.get_mut(recv) {
1533 items.extend(args.iter().cloned());
1534 }
1535 });
1536 Ok(Value::Float(array_items(recv).len() as f64))
1537 }
1538 "pop" => Ok(with_host(|h| {
1539 if let Some(JsObj::Array(items)) = h.get_mut(recv) {
1540 items.pop().unwrap_or(Value::Undef)
1541 } else {
1542 Value::Undef
1543 }
1544 })),
1545 "shift" => Ok(with_host(|h| {
1546 if let Some(JsObj::Array(items)) = h.get_mut(recv) {
1547 if items.is_empty() {
1548 Value::Undef
1549 } else {
1550 items.remove(0)
1551 }
1552 } else {
1553 Value::Undef
1554 }
1555 })),
1556 "unshift" => {
1557 with_host(|h| {
1558 if let Some(JsObj::Array(items)) = h.get_mut(recv) {
1559 for (i, a) in args.iter().enumerate() {
1560 items.insert(i, a.clone());
1561 }
1562 }
1563 });
1564 Ok(Value::Float(array_items(recv).len() as f64))
1565 }
1566 "join" => {
1567 let sep = if args.is_empty() {
1568 ",".to_string()
1569 } else {
1570 with_host(|h| h.str_of(&args[0]))
1571 };
1572 let items = array_items(recv);
1573 let s = with_host(|h| {
1574 items
1575 .iter()
1576 .map(|x| match x {
1577 Value::Undef => String::new(),
1578 _ if h.is_null(x) => String::new(),
1579 _ => h.str_of(x),
1580 })
1581 .collect::<Vec<_>>()
1582 .join(&sep)
1583 });
1584 Ok(with_host(|h| h.new_str(s)))
1585 }
1586 "indexOf" => {
1587 let items = array_items(recv);
1588 let target = arg0(&args);
1589 let idx = with_host(|h| items.iter().position(|x| h.strict_eq(x, &target)));
1590 Ok(Value::Float(idx.map(|i| i as f64).unwrap_or(-1.0)))
1591 }
1592 "lastIndexOf" => {
1593 let items = array_items(recv);
1594 let target = arg0(&args);
1595 let idx = with_host(|h| items.iter().rposition(|x| h.strict_eq(x, &target)));
1596 Ok(Value::Float(idx.map(|i| i as f64).unwrap_or(-1.0)))
1597 }
1598 "includes" => {
1599 let items = array_items(recv);
1600 let target = arg0(&args);
1601 Ok(Value::Bool(with_host(|h| items.iter().any(|x| h.strict_eq(x, &target)))))
1602 }
1603 "slice" => {
1604 let items = array_items(recv);
1605 let (lo, hi) = slice_bounds(&args, items.len());
1606 Ok(with_host(|h| h.new_array(items[lo..hi].to_vec())))
1607 }
1608 "concat" => {
1609 let mut out = array_items(recv);
1610 for a in &args {
1611 match with_host(|h| h.get(a).cloned()) {
1612 Some(JsObj::Array(items)) => out.extend(items),
1613 _ => out.push(a.clone()),
1614 }
1615 }
1616 Ok(with_host(|h| h.new_array(out)))
1617 }
1618 "reverse" => {
1619 with_host(|h| {
1620 if let Some(JsObj::Array(items)) = h.get_mut(recv) {
1621 items.reverse();
1622 }
1623 });
1624 Ok(recv.clone())
1625 }
1626 "fill" => {
1627 let val = arg0(&args);
1628 with_host(|h| {
1629 if let Some(JsObj::Array(items)) = h.get_mut(recv) {
1630 for it in items.iter_mut() {
1631 *it = val.clone();
1632 }
1633 }
1634 });
1635 Ok(recv.clone())
1636 }
1637 "at" => {
1638 let items = array_items(recv);
1639 let mut i = arg_num(&args, 0) as i64;
1640 if i < 0 {
1641 i += items.len() as i64;
1642 }
1643 Ok(if i >= 0 && (i as usize) < items.len() {
1644 items[i as usize].clone()
1645 } else {
1646 Value::Undef
1647 })
1648 }
1649 "map" => {
1650 let items = array_items(recv);
1651 let cb = arg0(&args);
1652 let mut out = Vec::with_capacity(items.len());
1653 for (i, it) in items.iter().enumerate() {
1654 out.push(host::invoke(&cb, vec![it.clone(), Value::Float(i as f64), recv.clone()], None)?);
1655 }
1656 Ok(with_host(|h| h.new_array(out)))
1657 }
1658 "flatMap" => {
1659 let items = array_items(recv);
1660 let cb = arg0(&args);
1661 let mut out = Vec::new();
1662 for (i, it) in items.iter().enumerate() {
1663 let r = host::invoke(&cb, vec![it.clone(), Value::Float(i as f64), recv.clone()], None)?;
1664 match with_host(|h| h.get(&r).cloned()) {
1665 Some(JsObj::Array(inner)) => out.extend(inner),
1666 _ => out.push(r),
1667 }
1668 }
1669 Ok(with_host(|h| h.new_array(out)))
1670 }
1671 "filter" => {
1672 let items = array_items(recv);
1673 let cb = arg0(&args);
1674 let mut out = Vec::new();
1675 for (i, it) in items.iter().enumerate() {
1676 let keep = host::invoke(&cb, vec![it.clone(), Value::Float(i as f64), recv.clone()], None)?;
1677 if with_host(|h| h.truthy(&keep)) {
1678 out.push(it.clone());
1679 }
1680 }
1681 Ok(with_host(|h| h.new_array(out)))
1682 }
1683 "forEach" => {
1684 let items = array_items(recv);
1685 let cb = arg0(&args);
1686 for (i, it) in items.iter().enumerate() {
1687 host::invoke(&cb, vec![it.clone(), Value::Float(i as f64), recv.clone()], None)?;
1688 }
1689 Ok(Value::Undef)
1690 }
1691 "find" => {
1692 let items = array_items(recv);
1693 let cb = arg0(&args);
1694 for (i, it) in items.iter().enumerate() {
1695 let m = host::invoke(&cb, vec![it.clone(), Value::Float(i as f64), recv.clone()], None)?;
1696 if with_host(|h| h.truthy(&m)) {
1697 return Ok(it.clone());
1698 }
1699 }
1700 Ok(Value::Undef)
1701 }
1702 "findIndex" => {
1703 let items = array_items(recv);
1704 let cb = arg0(&args);
1705 for (i, it) in items.iter().enumerate() {
1706 let m = host::invoke(&cb, vec![it.clone(), Value::Float(i as f64), recv.clone()], None)?;
1707 if with_host(|h| h.truthy(&m)) {
1708 return Ok(Value::Float(i as f64));
1709 }
1710 }
1711 Ok(Value::Float(-1.0))
1712 }
1713 "some" => {
1714 let items = array_items(recv);
1715 let cb = arg0(&args);
1716 for (i, it) in items.iter().enumerate() {
1717 let m = host::invoke(&cb, vec![it.clone(), Value::Float(i as f64), recv.clone()], None)?;
1718 if with_host(|h| h.truthy(&m)) {
1719 return Ok(Value::Bool(true));
1720 }
1721 }
1722 Ok(Value::Bool(false))
1723 }
1724 "every" => {
1725 let items = array_items(recv);
1726 let cb = arg0(&args);
1727 for (i, it) in items.iter().enumerate() {
1728 let m = host::invoke(&cb, vec![it.clone(), Value::Float(i as f64), recv.clone()], None)?;
1729 if !with_host(|h| h.truthy(&m)) {
1730 return Ok(Value::Bool(false));
1731 }
1732 }
1733 Ok(Value::Bool(true))
1734 }
1735 "reduce" => {
1736 let items = array_items(recv);
1737 let cb = arg0(&args);
1738 let mut acc;
1739 let mut start = 0;
1740 if args.len() >= 2 {
1741 acc = args[1].clone();
1742 } else if !items.is_empty() {
1743 acc = items[0].clone();
1744 start = 1;
1745 } else {
1746 return Err(host::type_error("Reduce of empty array with no initial value"));
1747 }
1748 for (i, it) in items.iter().enumerate().skip(start) {
1749 acc = host::invoke(&cb, vec![acc, it.clone(), Value::Float(i as f64), recv.clone()], None)?;
1750 }
1751 Ok(acc)
1752 }
1753 "sort" => {
1754 let mut items = array_items(recv);
1755 let cmp = args.first().cloned();
1756 let mut err: Option<String> = None;
1758 for i in 1..items.len() {
1759 let mut j = i;
1760 while j > 0 {
1761 let order = match &cmp {
1762 Some(cb) => {
1763 match host::invoke(cb, vec![items[j - 1].clone(), items[j].clone()], None) {
1764 Ok(v) => with_host(|h| h.to_number(&v)),
1765 Err(e) => {
1766 err = Some(e);
1767 0.0
1768 }
1769 }
1770 }
1771 None => {
1772 let a = with_host(|h| h.str_of(&items[j - 1]));
1773 let b = with_host(|h| h.str_of(&items[j]));
1774 if a > b {
1775 1.0
1776 } else {
1777 -1.0
1778 }
1779 }
1780 };
1781 if err.is_some() {
1782 break;
1783 }
1784 if order > 0.0 {
1785 items.swap(j - 1, j);
1786 j -= 1;
1787 } else {
1788 break;
1789 }
1790 }
1791 if err.is_some() {
1792 break;
1793 }
1794 }
1795 if let Some(e) = err {
1796 return Err(e);
1797 }
1798 with_host(|h| {
1799 if let Some(JsObj::Array(a)) = h.get_mut(recv) {
1800 *a = items;
1801 }
1802 });
1803 Ok(recv.clone())
1804 }
1805 "flat" => {
1806 let items = array_items(recv);
1807 let mut out = Vec::new();
1808 for it in items {
1809 match with_host(|h| h.get(&it).cloned()) {
1810 Some(JsObj::Array(inner)) => out.extend(inner),
1811 _ => out.push(it),
1812 }
1813 }
1814 Ok(with_host(|h| h.new_array(out)))
1815 }
1816 "keys" => {
1817 let n = array_items(recv).len();
1818 let items: Vec<Value> = (0..n).map(|i| Value::Float(i as f64)).collect();
1819 Ok(with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })))
1820 }
1821 "values" => {
1822 let items = array_items(recv);
1823 Ok(with_host(|h| h.alloc(JsObj::Iter { items, idx: 0 })))
1824 }
1825 "entries" => {
1826 let items = array_items(recv);
1827 let pairs: Vec<Value> = items
1828 .into_iter()
1829 .enumerate()
1830 .map(|(i, v)| with_host(|h| h.new_array(vec![Value::Float(i as f64), v])))
1831 .collect();
1832 Ok(with_host(|h| h.alloc(JsObj::Iter { items: pairs, idx: 0 })))
1833 }
1834 "splice" => array_splice(recv, args),
1835 "toString" => {
1836 let s = with_host(|h| h.str_of(recv));
1837 Ok(with_host(|h| h.new_str(s)))
1838 }
1839 _ => Err(host::type_error(&format!("{name} is not a function"))),
1840 }
1841}
1842
1843fn array_splice(recv: &Value, args: Vec<Value>) -> Result<Value, String> {
1844 let len = array_items(recv).len();
1845 let start = {
1846 let s = arg_num(&args, 0);
1847 if s < 0.0 {
1848 ((len as f64 + s).max(0.0)) as usize
1849 } else {
1850 (s as usize).min(len)
1851 }
1852 };
1853 let delete = if args.len() >= 2 {
1854 (arg_num(&args, 1).max(0.0) as usize).min(len - start)
1855 } else {
1856 len - start
1857 };
1858 let inserts: Vec<Value> = args.iter().skip(2).cloned().collect();
1859 let removed = with_host(|h| {
1860 if let Some(JsObj::Array(items)) = h.get_mut(recv) {
1861 let removed: Vec<Value> = items.splice(start..start + delete, inserts).collect();
1862 removed
1863 } else {
1864 Vec::new()
1865 }
1866 });
1867 Ok(with_host(|h| h.new_array(removed)))
1868}
1869
1870fn slice_bounds(args: &[Value], len: usize) -> (usize, usize) {
1871 let norm = |v: f64| -> usize {
1872 if v < 0.0 {
1873 ((len as f64 + v).max(0.0)) as usize
1874 } else {
1875 (v as usize).min(len)
1876 }
1877 };
1878 let lo = if args.is_empty() || matches!(args[0], Value::Undef) {
1879 0
1880 } else {
1881 norm(arg_num(args, 0))
1882 };
1883 let hi = if args.len() < 2 || matches!(args[1], Value::Undef) {
1884 len
1885 } else {
1886 norm(arg_num(args, 1))
1887 };
1888 (lo, hi.max(lo))
1891}
1892
1893fn string_method(s: &str, name: &str, args: Vec<Value>) -> Result<Value, String> {
1894 let chars: Vec<char> = s.chars().collect();
1895 match name {
1896 "toUpperCase" => Ok(new_s(s.to_uppercase())),
1897 "toLowerCase" => Ok(new_s(s.to_lowercase())),
1898 "trim" => Ok(new_s(s.trim().to_string())),
1899 "trimStart" => Ok(new_s(s.trim_start().to_string())),
1900 "trimEnd" => Ok(new_s(s.trim_end().to_string())),
1901 "toString" | "valueOf" => Ok(new_s(s.to_string())),
1902 "charAt" => {
1903 let i = arg_num(&args, 0) as usize;
1904 Ok(new_s(chars.get(i).map(|c| c.to_string()).unwrap_or_default()))
1905 }
1906 "at" => {
1907 let mut i = arg_num(&args, 0) as i64;
1908 if i < 0 {
1909 i += chars.len() as i64;
1910 }
1911 if i >= 0 && (i as usize) < chars.len() {
1912 Ok(new_s(chars[i as usize].to_string()))
1913 } else {
1914 Ok(Value::Undef)
1915 }
1916 }
1917 "charCodeAt" | "codePointAt" => {
1918 let i = arg_num(&args, 0) as usize;
1919 match chars.get(i) {
1920 Some(c) => Ok(Value::Float(*c as u32 as f64)),
1921 None => Ok(Value::Float(f64::NAN)),
1922 }
1923 }
1924 "indexOf" => {
1925 let needle = with_host(|h| h.str_of(&arg0(&args)));
1926 Ok(Value::Float(byte_to_char_index(s, s.find(&needle))))
1927 }
1928 "lastIndexOf" => {
1929 let needle = with_host(|h| h.str_of(&arg0(&args)));
1930 Ok(Value::Float(byte_to_char_index(s, s.rfind(&needle))))
1931 }
1932 "includes" => {
1933 let needle = with_host(|h| h.str_of(&arg0(&args)));
1934 Ok(Value::Bool(s.contains(&needle)))
1935 }
1936 "startsWith" => {
1937 let needle = with_host(|h| h.str_of(&arg0(&args)));
1938 Ok(Value::Bool(s.starts_with(&needle)))
1939 }
1940 "endsWith" => {
1941 let needle = with_host(|h| h.str_of(&arg0(&args)));
1942 Ok(Value::Bool(s.ends_with(&needle)))
1943 }
1944 "slice" => {
1945 let (lo, hi) = slice_bounds(&args, chars.len());
1946 Ok(new_s(chars[lo..hi].iter().collect()))
1947 }
1948 "substring" => {
1949 let mut a = arg_num(&args, 0).max(0.0) as usize;
1950 let mut b = if args.len() < 2 || matches!(args[1], Value::Undef) {
1951 chars.len()
1952 } else {
1953 (arg_num(&args, 1).max(0.0) as usize).min(chars.len())
1954 };
1955 a = a.min(chars.len());
1956 if a > b {
1957 std::mem::swap(&mut a, &mut b);
1958 }
1959 Ok(new_s(chars[a..b].iter().collect()))
1960 }
1961 "substr" => {
1962 let len = chars.len() as i64;
1964 let mut start = arg_num(&args, 0) as i64;
1965 if start < 0 {
1966 start = (len + start).max(0);
1967 }
1968 let start = (start as usize).min(chars.len());
1969 let count = if args.len() >= 2 {
1970 arg_num(&args, 1).max(0.0) as usize
1971 } else {
1972 chars.len()
1973 };
1974 let end = (start + count).min(chars.len());
1975 Ok(new_s(chars[start..end].iter().collect()))
1976 }
1977 "repeat" => {
1978 let n = arg_num(&args, 0);
1979 if n < 0.0 || !n.is_finite() {
1980 return Err(host::type_error("Invalid count value"));
1981 }
1982 Ok(new_s(s.repeat(n as usize)))
1983 }
1984 "concat" => {
1985 let mut out = s.to_string();
1986 for a in &args {
1987 out.push_str(&with_host(|h| h.str_of(a)));
1988 }
1989 Ok(new_s(out))
1990 }
1991 "padStart" => Ok(new_s(pad(s, &args, true))),
1992 "padEnd" => Ok(new_s(pad(s, &args, false))),
1993 "replace" => {
1994 let from = with_host(|h| h.str_of(&arg0(&args)));
1995 let to = with_host(|h| h.str_of(&args.get(1).cloned().unwrap_or(Value::Undef)));
1996 Ok(new_s(s.replacen(&from, &to, 1)))
1997 }
1998 "replaceAll" => {
1999 let from = with_host(|h| h.str_of(&arg0(&args)));
2000 let to = with_host(|h| h.str_of(&args.get(1).cloned().unwrap_or(Value::Undef)));
2001 Ok(new_s(s.replace(&from, &to)))
2002 }
2003 "split" => {
2004 let parts: Vec<Value> = if args.is_empty() || matches!(args[0], Value::Undef) {
2005 vec![new_s(s.to_string())]
2006 } else {
2007 let sep = with_host(|h| h.str_of(&args[0]));
2008 if sep.is_empty() {
2009 chars.iter().map(|c| new_s(c.to_string())).collect()
2010 } else {
2011 s.split(&sep as &str).map(|p| new_s(p.to_string())).collect()
2012 }
2013 };
2014 Ok(with_host(|h| h.new_array(parts)))
2015 }
2016 _ => Err(host::type_error(&format!("{name} is not a function"))),
2017 }
2018}
2019
2020fn new_s(s: String) -> Value {
2021 with_host(|h| h.new_str(s))
2022}
2023
2024fn byte_to_char_index(s: &str, byte: Option<usize>) -> f64 {
2025 match byte {
2026 Some(b) => s[..b].chars().count() as f64,
2027 None => -1.0,
2028 }
2029}
2030
2031fn pad(s: &str, args: &[Value], start: bool) -> String {
2032 let target = arg_num(args, 0) as usize;
2033 let cur = s.chars().count();
2034 if cur >= target {
2035 return s.to_string();
2036 }
2037 let filler = if args.len() >= 2 {
2038 with_host(|h| h.str_of(&args[1]))
2039 } else {
2040 " ".to_string()
2041 };
2042 if filler.is_empty() {
2043 return s.to_string();
2044 }
2045 let need = target - cur;
2046 let fill_chars: Vec<char> = filler.chars().collect();
2047 let padding: String = (0..need).map(|i| fill_chars[i % fill_chars.len()]).collect();
2048 if start {
2049 format!("{padding}{s}")
2050 } else {
2051 format!("{s}{padding}")
2052 }
2053}
2054
2055fn number_method(n: f64, name: &str, args: Vec<Value>) -> Result<Value, String> {
2056 match name {
2057 "toFixed" => {
2058 let digits = arg_num(&args, 0).max(0.0) as usize;
2059 Ok(new_s(to_fixed(n, digits)))
2060 }
2061 "toString" => {
2062 let radix = args.first().map(|_| arg_num(&args, 0) as u32).unwrap_or(10);
2063 if radix == 10 || !(2..=36).contains(&radix) {
2064 Ok(new_s(host::fmt_number(n)))
2065 } else {
2066 Ok(new_s(to_radix(n, radix)))
2067 }
2068 }
2069 "toPrecision" => {
2070 if args.is_empty() {
2071 Ok(new_s(host::fmt_number(n)))
2072 } else {
2073 let p = arg_num(&args, 0) as usize;
2074 Ok(new_s(to_precision(n, p.max(1))))
2075 }
2076 }
2077 "valueOf" => Ok(Value::Float(n)),
2078 _ => Err(host::type_error(&format!("{name} is not a function"))),
2079 }
2080}
2081
2082fn to_fixed(n: f64, f: usize) -> String {
2091 if !n.is_finite() {
2092 return host::fmt_number(n);
2093 }
2094 if n.abs() >= 1e21 {
2096 return host::fmt_number(n);
2097 }
2098 let neg = n < 0.0;
2099 let full = format!("{:.*}", f + 25, n.abs());
2102 let mut body = round_decimal_string(&full, f);
2103 if neg {
2104 body.insert(0, '-'); }
2106 body
2107}
2108
2109fn round_decimal_string(s: &str, f: usize) -> String {
2112 let (int_part, frac_part) = s.split_once('.').unwrap_or((s, ""));
2113 let mut digits: Vec<u8> = int_part.bytes().chain(frac_part.bytes()).map(|b| b - b'0').collect();
2114 let point = int_part.len(); let keep = point + f; if digits.get(keep).map(|&d| d >= 5).unwrap_or(false) {
2119 let mut i = keep;
2120 loop {
2121 if i == 0 {
2122 digits.insert(0, 1);
2123 return assemble_decimal(&digits, point + 1, f);
2125 }
2126 i -= 1;
2127 if digits[i] == 9 {
2128 digits[i] = 0;
2129 } else {
2130 digits[i] += 1;
2131 break;
2132 }
2133 }
2134 }
2135 assemble_decimal(&digits, point, f)
2136}
2137
2138fn assemble_decimal(digits: &[u8], point: usize, f: usize) -> String {
2141 let int_str: String = digits[..point].iter().map(|d| (d + b'0') as char).collect();
2142 let int_str = int_str.trim_start_matches('0');
2143 let int_str = if int_str.is_empty() { "0" } else { int_str };
2144 if f == 0 {
2145 return int_str.to_string();
2146 }
2147 let frac: String = digits[point..point + f].iter().map(|d| (d + b'0') as char).collect();
2148 format!("{int_str}.{frac}")
2149}
2150
2151fn to_precision(n: f64, p: usize) -> String {
2156 if !n.is_finite() {
2157 return host::fmt_number(n);
2158 }
2159 if n == 0.0 {
2160 return if p == 1 {
2161 "0".into()
2162 } else {
2163 format!("0.{}", "0".repeat(p - 1))
2164 };
2165 }
2166 let neg = n < 0.0;
2167 let a = n.abs();
2168 let sci = format!("{a:.*e}", p - 1 + 25);
2172 let (mant, exp_str) = sci.split_once('e').expect("LowerExp always has 'e'");
2173 let mut e: i32 = exp_str.parse().expect("LowerExp exponent is an integer");
2174 let all: Vec<u8> = mant.chars().filter(|c| c.is_ascii_digit()).map(|c| c as u8 - b'0').collect();
2175 let mut s: String = all[..p].iter().map(|d| (d + b'0') as char).collect();
2176 if all.get(p).map(|&d| d >= 5).unwrap_or(false) {
2177 let mut d: Vec<u8> = all[..p].to_vec();
2180 let mut i = p;
2181 loop {
2182 if i == 0 {
2183 d.insert(0, 1);
2184 d.truncate(p);
2185 e += 1;
2186 break;
2187 }
2188 i -= 1;
2189 if d[i] == 9 {
2190 d[i] = 0;
2191 } else {
2192 d[i] += 1;
2193 break;
2194 }
2195 }
2196 s = d.iter().map(|x| (x + b'0') as char).collect();
2197 }
2198 let pp = p as i32;
2199
2200 let body = if e < -6 || e >= pp {
2201 let sign = if e >= 0 { '+' } else { '-' };
2203 let mag = e.abs();
2204 if p == 1 {
2205 format!("{s}e{sign}{mag}")
2206 } else {
2207 format!("{}.{}e{sign}{mag}", &s[..1], &s[1..])
2208 }
2209 } else if e >= 0 {
2210 let ip = (e + 1) as usize;
2212 if ip == p {
2213 s
2214 } else {
2215 format!("{}.{}", &s[..ip], &s[ip..])
2216 }
2217 } else {
2218 format!("0.{}{}", "0".repeat((-e - 1) as usize), s)
2220 };
2221 if neg {
2222 format!("-{body}")
2223 } else {
2224 body
2225 }
2226}
2227
2228fn to_radix(n: f64, radix: u32) -> String {
2229 if !n.is_finite() {
2230 return host::fmt_number(n);
2231 }
2232 let neg = n < 0.0;
2233 let mut i = n.abs().trunc() as u64;
2234 if i == 0 {
2235 return "0".into();
2236 }
2237 let digits = b"0123456789abcdefghijklmnopqrstuvwxyz";
2238 let mut out = Vec::new();
2239 while i > 0 {
2240 out.push(digits[(i % radix as u64) as usize]);
2241 i /= radix as u64;
2242 }
2243 if neg {
2244 out.push(b'-');
2245 }
2246 out.reverse();
2247 String::from_utf8(out).unwrap()
2248}