1use crate::host::HostFunction;
2use crate::object::object::JSObject;
3use crate::runtime::context::JSContext;
4use crate::value::JSValue;
5
6fn to_integer_index(val: &JSValue, ctx: &mut JSContext) -> i64 {
7 if val.is_int() {
8 val.get_int()
9 } else if val.is_float() {
10 val.get_float().trunc() as i64
11 } else if val.is_undefined() || val.is_null() {
12 0
13 } else if val.is_bool() {
14 if val.get_bool() { 1 } else { 0 }
15 } else if val.is_string() {
16 let s = ctx.get_atom_str(val.get_atom());
17 match s.trim().parse::<f64>() {
18 Ok(v) if !v.is_nan() => v.trunc() as i64,
19 _ => 0,
20 }
21 } else {
22 0
23 }
24}
25
26fn create_builtin_function(ctx: &mut JSContext, name: &str) -> JSValue {
27 let arity = ctx.get_builtin_arity(name).unwrap_or(1);
28 let mut func = crate::object::function::JSFunction::new_builtin(ctx.intern(name), arity);
29 func.set_builtin_marker(ctx, name);
30 let ptr = Box::into_raw(Box::new(func)) as usize;
31 ctx.runtime_mut().gc_heap_mut().track_function(ptr);
32 JSValue::new_function(ptr)
33}
34
35pub fn js_float_to_string(f: f64) -> String {
36 if f.is_infinite() {
37 return if f.is_sign_positive() {
38 "Infinity".to_string()
39 } else {
40 "-Infinity".to_string()
41 };
42 }
43 if f.is_nan() {
44 return "NaN".to_string();
45 }
46 format!("{}", f)
47}
48
49fn this_to_string(ctx: &mut JSContext, this: &JSValue) -> Option<String> {
50 if this.is_string() {
51 return Some(ctx.get_atom_str(this.get_atom()).to_string());
52 } if this.is_undefined() || this.is_null() {
53 let mut err = JSObject::new();
54 err.set(
55 ctx.common_atoms.name,
56 JSValue::new_string(ctx.intern("TypeError")),
57 );
58 err.set(
59 ctx.common_atoms.message,
60 JSValue::new_string(ctx.intern("this is not coercible")),
61 );
62 if let Some(proto) = ctx.get_type_error_prototype() {
63 err.prototype = Some(proto);
64 }
65 let ptr = Box::into_raw(Box::new(err)) as usize;
66 ctx.runtime_mut().gc_heap_mut().track(ptr);
67 ctx.pending_exception = Some(JSValue::new_object(ptr));
68 return None;
69 }
70 if this.is_bool() {
71 return Some(if this.get_bool() {
72 "true".to_string()
73 } else {
74 "false".to_string()
75 });
76 }
77 if this.is_int() {
78 return Some(format!("{}", this.get_int()));
79 }
80 if this.is_float() {
81 return Some(js_float_to_string(this.get_float()));
82 }
83 if this.is_object() {
84 let obj = this.as_object();
85 if let Some(v) = obj.get(ctx.common_atoms.__value__) {
86 if v.is_string() {
87 return Some(ctx.get_atom_str(v.get_atom()).to_string());
88 }
89 if v.is_int() {
90 return Some(format!("{}", v.get_int()));
91 }
92 if v.is_float() {
93 return Some(js_float_to_string(v.get_float()));
94 }
95 if v.is_bool() {
96 return Some(if v.get_bool() {
97 "true".to_string()
98 } else {
99 "false".to_string()
100 });
101 }
102 }
103 if let Some(vm_ptr) = ctx.get_register_vm_ptr() {
104 let ts_fn = obj.get(ctx.common_atoms.to_string);
105 if let Some(ts) = ts_fn {
106 if ts.is_function() {
107 let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
108 if let Ok(r) = vm.call_function_with_this(ctx, ts, *this, &[]) {
109 if r.is_string() {
110 return Some(ctx.get_atom_str(r.get_atom()).to_string());
111 }
112 if r.is_int() {
113 return Some(format!("{}", r.get_int()));
114 }
115 if r.is_float() {
116 return Some(js_float_to_string(r.get_float()));
117 }
118 if r.is_bool() {
119 return Some(if r.get_bool() {
120 "true".to_string()
121 } else {
122 "false".to_string()
123 });
124 }
125 }
126 }
127 }
128 }
129 return Some("[object Object]".to_string());
130 }
131 Some(format!("{}", this.to_number()))
132}
133
134fn require_string_coercible(ctx: &mut JSContext, this: &JSValue) -> Option<String> {
135 if this.is_undefined() || this.is_null() {
136 let mut err = JSObject::new();
137 err.set(
138 ctx.common_atoms.name,
139 JSValue::new_string(ctx.intern("TypeError")),
140 );
141 err.set(
142 ctx.common_atoms.message,
143 JSValue::new_string(ctx.intern("this is not coercible")),
144 );
145 if let Some(proto) = ctx.get_type_error_prototype() {
146 err.prototype = Some(proto);
147 }
148 let ptr = Box::into_raw(Box::new(err)) as usize;
149 ctx.runtime_mut().gc_heap_mut().track(ptr);
150 ctx.pending_exception = Some(JSValue::new_object(ptr));
151 return None;
152 }
153 if this.is_string() {
154 return Some(ctx.get_atom_str(this.get_atom()).to_string());
155 }
156 if this.is_bool() {
157 return Some(if this.get_bool() {
158 "true".to_string()
159 } else {
160 "false".to_string()
161 });
162 }
163 if this.is_int() {
164 return Some(format!("{}", this.get_int()));
165 }
166 if this.is_float() {
167 return Some(js_float_to_string(this.get_float()));
168 }
169 if this.is_object() {
170 return this_to_string(ctx, this);
171 }
172 Some(ctx.get_atom_str(this.get_atom()).to_string())
173}
174
175pub fn init_string(ctx: &mut JSContext) {
176 fn set_ne(
177 obj: &mut crate::object::object::JSObject,
178 key: crate::runtime::atom::Atom,
179 val: crate::value::JSValue,
180 ) {
181 obj.define_property(
182 key,
183 crate::object::object::PropertyDescriptor {
184 value: Some(val),
185 writable: true,
186 enumerable: false,
187 configurable: true,
188 get: None,
189 set: None,
190 },
191 );
192 }
193
194 let string_atom = ctx.common_atoms.string;
195
196 let mut string_func = crate::object::function::JSFunction::new_builtin(string_atom, 1);
197 string_func.set_builtin_marker(ctx, "string_constructor");
198
199 string_func.base.define_property(
200 ctx.intern("fromCharCode"),
201 crate::object::object::PropertyDescriptor {
202 value: Some(create_builtin_function(ctx, "string_fromCharCode")),
203 writable: true,
204 enumerable: false,
205 configurable: true,
206 get: None,
207 set: None,
208 },
209 );
210 string_func.base.define_property(
211 ctx.intern("fromCodePoint"),
212 crate::object::object::PropertyDescriptor {
213 value: Some(create_builtin_function(ctx, "string_fromCodePoint")),
214 writable: true,
215 enumerable: false,
216 configurable: true,
217 get: None,
218 set: None,
219 },
220 );
221 string_func.base.define_property(
222 ctx.intern("raw"),
223 crate::object::object::PropertyDescriptor {
224 value: Some(create_builtin_function(ctx, "string_raw")),
225 writable: true,
226 enumerable: false,
227 configurable: true,
228 get: None,
229 set: None,
230 },
231 );
232
233 let string_ptr = Box::into_raw(Box::new(string_func)) as usize;
234 ctx.runtime_mut().gc_heap_mut().track_function(string_ptr);
235 let string_value = JSValue::new_function(string_ptr);
236 let global = ctx.global();
237 if global.is_object() {
238 let global_obj = global.as_object_mut();
239 global_obj.define_property(
240 string_atom,
241 crate::object::object::PropertyDescriptor {
242 value: Some(string_value),
243 writable: true,
244 enumerable: false,
245 configurable: true,
246 get: None,
247 set: None,
248 },
249 );
250 }
251
252 let proto_atom = ctx.intern("StringPrototype");
253 let mut proto_obj = JSObject::new();
254 set_ne(&mut proto_obj, ctx.intern("constructor"), string_value);
255 set_ne(
256 &mut proto_obj,
257 ctx.intern("charAt"),
258 create_builtin_function(ctx, "string_charAt"),
259 );
260 set_ne(
261 &mut proto_obj,
262 ctx.intern("charCodeAt"),
263 create_builtin_function(ctx, "string_charCodeAt"),
264 );
265 set_ne(
266 &mut proto_obj,
267 ctx.intern("concat"),
268 create_builtin_function(ctx, "string_concat"),
269 );
270 set_ne(
271 &mut proto_obj,
272 ctx.intern("indexOf"),
273 create_builtin_function(ctx, "string_indexOf"),
274 );
275 set_ne(
276 &mut proto_obj,
277 ctx.intern("lastIndexOf"),
278 create_builtin_function(ctx, "string_lastIndexOf"),
279 );
280 set_ne(
281 &mut proto_obj,
282 ctx.intern("localeCompare"),
283 create_builtin_function(ctx, "string_localeCompare"),
284 );
285 set_ne(
286 &mut proto_obj,
287 ctx.intern("normalize"),
288 create_builtin_function(ctx, "string_normalize"),
289 );
290 set_ne(
291 &mut proto_obj,
292 ctx.intern("slice"),
293 create_builtin_function(ctx, "string_slice"),
294 );
295 set_ne(
296 &mut proto_obj,
297 ctx.intern("substring"),
298 create_builtin_function(ctx, "string_substring"),
299 );
300 set_ne(
301 &mut proto_obj,
302 ctx.intern("toString"),
303 create_builtin_function(ctx, "string_toString"),
304 );
305 set_ne(
306 &mut proto_obj,
307 ctx.intern("valueOf"),
308 create_builtin_function(ctx, "string_toString"),
309 );
310 set_ne(
311 &mut proto_obj,
312 ctx.intern("toLowerCase"),
313 create_builtin_function(ctx, "string_toLowerCase"),
314 );
315 set_ne(
316 &mut proto_obj,
317 ctx.intern("toUpperCase"),
318 create_builtin_function(ctx, "string_toUpperCase"),
319 );
320 set_ne(
321 &mut proto_obj,
322 ctx.intern("toLocaleLowerCase"),
323 create_builtin_function(ctx, "string_toLocaleLowerCase"),
324 );
325 set_ne(
326 &mut proto_obj,
327 ctx.intern("toLocaleUpperCase"),
328 create_builtin_function(ctx, "string_toLocaleUpperCase"),
329 );
330 set_ne(
331 &mut proto_obj,
332 ctx.intern("split"),
333 create_builtin_function(ctx, "string_split"),
334 );
335 set_ne(
336 &mut proto_obj,
337 ctx.common_atoms.length,
338 create_builtin_function(ctx, "string_length"),
339 );
340 set_ne(
341 &mut proto_obj,
342 ctx.intern("trim"),
343 create_builtin_function(ctx, "string_trim"),
344 );
345 set_ne(
346 &mut proto_obj,
347 ctx.intern("trimStart"),
348 create_builtin_function(ctx, "string_trimStart"),
349 );
350 set_ne(
351 &mut proto_obj,
352 ctx.intern("trimLeft"),
353 create_builtin_function(ctx, "string_trimStart"),
354 );
355 set_ne(
356 &mut proto_obj,
357 ctx.intern("trimEnd"),
358 create_builtin_function(ctx, "string_trimEnd"),
359 );
360 set_ne(
361 &mut proto_obj,
362 ctx.intern("trimRight"),
363 create_builtin_function(ctx, "string_trimEnd"),
364 );
365 set_ne(
366 &mut proto_obj,
367 ctx.intern("startsWith"),
368 create_builtin_function(ctx, "string_startsWith"),
369 );
370 set_ne(
371 &mut proto_obj,
372 ctx.intern("endsWith"),
373 create_builtin_function(ctx, "string_endsWith"),
374 );
375 set_ne(
376 &mut proto_obj,
377 ctx.intern("repeat"),
378 create_builtin_function(ctx, "string_repeat"),
379 );
380 set_ne(
381 &mut proto_obj,
382 ctx.intern("includes"),
383 create_builtin_function(ctx, "string_includes"),
384 );
385 set_ne(
386 &mut proto_obj,
387 ctx.intern("startsWith"),
388 create_builtin_function(ctx, "string_starts_with"),
389 );
390 set_ne(
391 &mut proto_obj,
392 ctx.intern("endsWith"),
393 create_builtin_function(ctx, "string_ends_with"),
394 );
395 set_ne(
396 &mut proto_obj,
397 ctx.intern("replace"),
398 create_builtin_function(ctx, "string_replace"),
399 );
400 set_ne(
401 &mut proto_obj,
402 ctx.intern("padStart"),
403 create_builtin_function(ctx, "string_padStart"),
404 );
405 set_ne(
406 &mut proto_obj,
407 ctx.intern("padEnd"),
408 create_builtin_function(ctx, "string_padEnd"),
409 );
410 set_ne(
411 &mut proto_obj,
412 ctx.intern("replaceAll"),
413 create_builtin_function(ctx, "string_replaceAll"),
414 );
415 set_ne(
416 &mut proto_obj,
417 ctx.intern("search"),
418 create_builtin_function(ctx, "string_search"),
419 );
420 set_ne(
421 &mut proto_obj,
422 ctx.intern("match"),
423 create_builtin_function(ctx, "string_match"),
424 );
425 set_ne(
426 &mut proto_obj,
427 ctx.intern("at"),
428 create_builtin_function(ctx, "string_at"),
429 );
430 set_ne(
431 &mut proto_obj,
432 ctx.intern("isWellFormed"),
433 create_builtin_function(ctx, "string_isWellFormed"),
434 );
435 set_ne(
436 &mut proto_obj,
437 ctx.intern("toWellFormed"),
438 create_builtin_function(ctx, "string_toWellFormed"),
439 );
440 set_ne(
441 &mut proto_obj,
442 ctx.intern("codePointAt"),
443 create_builtin_function(ctx, "string_codePointAt"),
444 );
445 set_ne(
446 &mut proto_obj,
447 ctx.intern("matchAll"),
448 create_builtin_function(ctx, "string_matchAll"),
449 );
450 set_ne(
451 &mut proto_obj,
452 ctx.intern("substr"),
453 create_builtin_function(ctx, "string_substr"),
454 );
455
456 if let Some(obj_proto_ptr) = ctx.get_object_prototype() {
457 proto_obj.prototype = Some(obj_proto_ptr);
458 }
459
460 let proto_ptr = Box::into_raw(Box::new(proto_obj)) as usize;
461 ctx.runtime_mut().gc_heap_mut().track(proto_ptr);
462
463 ctx.set_string_prototype(proto_ptr);
464 let proto_value = JSValue::new_object(proto_ptr);
465
466 let string_func_ptr = string_ptr as *mut crate::object::function::JSFunction;
467 unsafe {
468 (*string_func_ptr)
469 .base
470 .set(ctx.common_atoms.prototype, proto_value);
471 }
472
473 if global.is_object() {
474 let global_obj = global.as_object_mut();
475 global_obj.set(proto_atom, proto_value);
476 }
477}
478
479pub fn register_builtins(ctx: &mut JSContext) {
480 ctx.register_builtin(
481 "string_constructor",
482 HostFunction::ctor("String", 1, string_constructor),
483 );
484 ctx.register_builtin(
485 "string_charAt",
486 HostFunction::method("charAt", 1, string_char_at),
487 );
488 ctx.register_builtin(
489 "string_charCodeAt",
490 HostFunction::method("charCodeAt", 1, string_char_code_at),
491 );
492 ctx.register_builtin(
493 "string_concat",
494 HostFunction::method("concat", 1, string_concat),
495 );
496 ctx.register_builtin(
497 "string_indexOf",
498 HostFunction::method("indexOf", 1, string_index_of),
499 );
500 ctx.register_builtin(
501 "string_lastIndexOf",
502 HostFunction::method("lastIndexOf", 1, string_last_index_of),
503 );
504 ctx.register_builtin(
505 "string_localeCompare",
506 HostFunction::method("localeCompare", 1, string_locale_compare),
507 );
508 ctx.register_builtin(
509 "string_normalize",
510 HostFunction::method("normalize", 0, string_normalize),
511 );
512 ctx.register_builtin(
513 "string_slice",
514 HostFunction::method("slice", 2, string_slice),
515 );
516 ctx.register_builtin(
517 "string_substring",
518 HostFunction::method("substring", 2, string_substring),
519 );
520 ctx.register_builtin(
521 "string_toString",
522 HostFunction::method("toString", 0, string_to_string),
523 );
524 ctx.register_builtin(
525 "string_toLowerCase",
526 HostFunction::method("toLowerCase", 0, string_to_lower_case),
527 );
528 ctx.register_builtin(
529 "string_toUpperCase",
530 HostFunction::method("toUpperCase", 0, string_to_upper_case),
531 );
532 ctx.register_builtin(
533 "string_toLocaleLowerCase",
534 HostFunction::method("toLocaleLowerCase", 0, string_to_locale_lower_case),
535 );
536 ctx.register_builtin(
537 "string_toLocaleUpperCase",
538 HostFunction::method("toLocaleUpperCase", 0, string_to_locale_upper_case),
539 );
540 ctx.register_builtin(
541 "string_split",
542 HostFunction::method("split", 1, string_split),
543 );
544 ctx.register_builtin(
545 "string_length",
546 HostFunction::method("length", 0, string_length),
547 );
548 ctx.register_builtin(
549 "string_fromCharCode",
550 HostFunction::new("fromCharCode", 1, string_fromcharcode),
551 );
552 ctx.register_builtin(
553 "string_fromCodePoint",
554 HostFunction::new("fromCodePoint", 1, string_fromcodepoint),
555 );
556 ctx.register_builtin("string_raw", HostFunction::new("raw", 1, string_raw));
557 ctx.register_builtin("string_trim", HostFunction::method("trim", 0, string_trim));
558 ctx.register_builtin(
559 "string_trimStart",
560 HostFunction::method("trimStart", 0, string_trim_start),
561 );
562 ctx.register_builtin(
563 "string_trimEnd",
564 HostFunction::method("trimEnd", 0, string_trim_end),
565 );
566 ctx.register_builtin(
567 "string_startsWith",
568 HostFunction::method("startsWith", 1, string_starts_with),
569 );
570 ctx.register_builtin(
571 "string_endsWith",
572 HostFunction::method("endsWith", 1, string_ends_with),
573 );
574 ctx.register_builtin(
575 "string_repeat",
576 HostFunction::method("repeat", 1, string_repeat),
577 );
578 ctx.register_builtin(
579 "string_includes",
580 HostFunction::method("includes", 1, string_includes),
581 );
582 ctx.register_builtin(
583 "string_starts_with",
584 HostFunction::method("startsWith", 1, string_starts_with),
585 );
586 ctx.register_builtin(
587 "string_ends_with",
588 HostFunction::method("endsWith", 1, string_ends_with),
589 );
590 ctx.register_builtin(
591 "string_replace",
592 HostFunction::method("replace", 2, string_replace),
593 );
594 ctx.register_builtin(
595 "string_padStart",
596 HostFunction::method("padStart", 1, string_pad_start),
597 );
598 ctx.register_builtin(
599 "string_padEnd",
600 HostFunction::method("padEnd", 1, string_pad_end),
601 );
602 ctx.register_builtin(
603 "string_replaceAll",
604 HostFunction::method("replaceAll", 2, string_replace_all),
605 );
606 ctx.register_builtin(
607 "string_search",
608 HostFunction::method("search", 1, string_search),
609 );
610 ctx.register_builtin(
611 "string_match",
612 HostFunction::method("match", 1, string_match),
613 );
614 ctx.register_builtin("string_at", HostFunction::method("at", 1, string_at));
615 ctx.register_builtin(
616 "string_isWellFormed",
617 HostFunction::method("isWellFormed", 0, string_is_well_formed),
618 );
619 ctx.register_builtin(
620 "string_toWellFormed",
621 HostFunction::method("toWellFormed", 0, string_to_well_formed),
622 );
623 ctx.register_builtin(
624 "string_codePointAt",
625 HostFunction::method("codePointAt", 1, string_code_point_at),
626 );
627 ctx.register_builtin(
628 "string_matchAll",
629 HostFunction::method("matchAll", 1, string_match_all),
630 );
631 ctx.register_builtin(
632 "string_substr",
633 HostFunction::method("substr", 2, string_substr),
634 );
635}
636
637fn string_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
638 let s = if args.is_empty() {
639 JSValue::new_string(ctx.intern(""))
640 } else {
641 let val = &args[0];
642 if val.is_string() {
643 val.clone()
644 } else if val.is_int() {
645 JSValue::new_string(ctx.intern(&val.get_int().to_string()))
646 } else if val.is_float() {
647 JSValue::new_string(ctx.intern(&val.get_float().to_string()))
648 } else if val.is_bigint() {
649 let obj = unsafe { crate::value::JSValue::object_from_ptr(val.get_ptr()) };
650 let n = obj.get_bigint_value();
651 JSValue::new_string(ctx.intern(&n.to_string()))
652 } else if val.is_bool() {
653 JSValue::new_string(ctx.intern(&val.get_bool().to_string()))
654 } else if val.is_null() {
655 JSValue::new_string(ctx.common_atoms.null)
656 } else if val.is_undefined() {
657 JSValue::new_string(ctx.common_atoms.undefined)
658 } else if val.is_symbol() {
659 let desc_atom = val.get_atom();
660 if desc_atom == crate::builtins::symbol::NO_DESCRIPTION {
661 JSValue::new_string(ctx.intern("Symbol()"))
662 } else {
663 let desc = ctx.get_atom_str(desc_atom);
664 JSValue::new_string(ctx.intern(&format!("Symbol({})", desc)))
665 }
666 } else if val.is_object() {
667 JSValue::new_string(ctx.intern("[object Object]"))
668 } else {
669 JSValue::new_string(ctx.intern(""))
670 }
671 };
672 s
673}
674
675fn char_count(s: &str) -> usize {
676 s.chars().count()
677}
678
679fn byte_index_for_char_pos(s: &str, pos: usize) -> usize {
680 if pos == 0 {
681 return 0;
682 }
683 match s.char_indices().nth(pos) {
684 Some((idx, _)) => idx,
685 None => s.len(),
686 }
687}
688
689fn string_char_at(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
690 if args.is_empty() {
691 return JSValue::new_string(ctx.intern(""));
692 }
693 let s = match require_string_coercible(ctx, &args[0]) {
694 Some(s) => s,
695 None => return JSValue::undefined(),
696 };
697 let atom = ctx.intern(&s);
698
699 let index = if args.len() > 1 {
700 let idx = to_integer_index(&args[1], ctx);
701 if idx < 0 { 0usize } else { idx as usize }
702 } else {
703 0usize
704 };
705
706 if index >= ctx.string_char_count(atom) {
707 return JSValue::new_string(ctx.intern(""));
708 }
709
710 match ctx.string_char_code_at(atom, index) {
711 Some(code) => {
712 let c = char::from_u32(code).unwrap_or('\0');
713 let mut buf = [0u8; 4];
714 let s = c.encode_utf8(&mut buf);
715 JSValue::new_string(ctx.intern(s))
716 }
717 None => JSValue::new_string(ctx.intern("")),
718 }
719}
720
721fn string_char_code_at(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
722 if args.is_empty() {
723 return JSValue::new_float(f64::NAN);
724 }
725 let s = match require_string_coercible(ctx, &args[0]) {
726 Some(s) => s,
727 None => return JSValue::undefined(),
728 };
729 let atom = ctx.intern(&s);
730
731 let index = if args.len() > 1 {
732 let v = args[1];
733 if v.is_int() {
734 let i = v.get_int();
735 if i < 0 {
736 return JSValue::new_float(f64::NAN);
737 }
738 i as usize
739 } else if v.is_float() {
740 let f = v.get_float();
741 if f < 0.0 || f.is_nan() || f.is_infinite() {
742 return JSValue::new_float(f64::NAN);
743 }
744 f as usize
745 } else {
746 0
747 }
748 } else {
749 0
750 };
751
752 match ctx.string_char_code_at(atom, index) {
753 Some(code) => JSValue::new_int(code as i64),
754 None => JSValue::new_float(f64::NAN),
755 }
756}
757
758fn string_concat(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
759 if args.is_empty() {
760 return JSValue::new_string(ctx.intern(""));
761 }
762 let mut result = match require_string_coercible(ctx, &args[0]) {
763 Some(s) => s,
764 None => return JSValue::undefined(),
765 };
766
767 for arg in args.iter().skip(1) {
768 if arg.is_string() {
769 result.push_str(ctx.get_atom_str(arg.get_atom()));
770 } else if arg.is_int() {
771 result.push_str(&arg.get_int().to_string());
772 } else if arg.is_float() {
773 result.push_str(&arg.get_float().to_string());
774 } else if arg.is_bool() {
775 result.push_str(&arg.get_bool().to_string());
776 }
777 }
778
779 JSValue::new_string(ctx.intern(&result))
780}
781
782fn string_index_of(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
783 if args.len() < 2 {
784 return JSValue::new_int(-1);
785 }
786 let s = match require_string_coercible(ctx, &args[0]) {
787 Some(s) => s,
788 None => return JSValue::undefined(),
789 };
790
791 let search = if args[1].is_string() {
792 ctx.get_atom_str(args[1].get_atom()).to_string()
793 } else {
794 return JSValue::new_int(-1);
795 };
796
797 let from_index = if args.len() > 2 {
798 let p = &args[2];
799 let idx = if p.is_int() {
800 p.get_int()
801 } else if p.is_float() {
802 p.get_float().trunc() as i64
803 } else if p.is_undefined() || p.is_null() {
804 0
805 } else if p.is_bool() {
806 if p.get_bool() { 1 } else { 0 }
807 } else if p.is_string() {
808 let ps = ctx.get_atom_str(p.get_atom());
809 match ps.trim().parse::<f64>() {
810 Ok(v) if !v.is_nan() => v.trunc() as i64,
811 _ => 0,
812 }
813 } else {
814 0
815 };
816 if idx < 0 { 0usize } else { idx as usize }
817 } else {
818 0usize
819 };
820
821 let s_char_len = char_count(&s);
822 if from_index > s_char_len {
823 return JSValue::new_int(-1);
824 }
825
826 let from_byte = byte_index_for_char_pos(&s, from_index);
827
828 match s[from_byte..].find(&search) {
829 Some(byte_pos) => {
830 let byte_idx = from_byte + byte_pos;
831 let char_idx = s[..byte_idx].chars().count();
832 JSValue::new_int(char_idx as i64)
833 }
834 None => JSValue::new_int(-1),
835 }
836}
837
838fn string_last_index_of(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
839 if args.len() < 2 {
840 return JSValue::new_int(-1);
841 }
842 let s = match require_string_coercible(ctx, &args[0]) {
843 Some(s) => s,
844 None => return JSValue::undefined(),
845 };
846
847 let search = if args[1].is_string() {
848 ctx.get_atom_str(args[1].get_atom()).to_string()
849 } else {
850 return JSValue::new_int(-1);
851 };
852
853 match s.rfind(&search) {
854 Some(byte_pos) => {
855 let char_idx = s[..byte_pos].chars().count();
856 JSValue::new_int(char_idx as i64)
857 }
858 None => JSValue::new_int(-1),
859 }
860}
861
862fn string_locale_compare(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
863 let this_s = match require_string_coercible(ctx, &args[0]) {
864 Some(s) => s,
865 None => return JSValue::undefined(),
866 };
867 let cmp_s = if args.len() > 1 {
868 match require_string_coercible(ctx, &args[1]) {
869 Some(s) => s,
870 None => return JSValue::undefined(),
871 }
872 } else {
873 "undefined".to_string()
874 };
875 let res = match this_s.cmp(&cmp_s) {
876 std::cmp::Ordering::Less => -1,
877 std::cmp::Ordering::Equal => 0,
878 std::cmp::Ordering::Greater => 1,
879 };
880 JSValue::new_int(res)
881}
882
883fn string_normalize(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
884 let this_s = match require_string_coercible(ctx, &args[0]) {
885 Some(s) => s,
886 None => return JSValue::undefined(),
887 };
888 if args.len() > 1 && !args[1].is_undefined() {
889 let form_str = match require_string_coercible(ctx, &args[1]) {
890 Some(s) => s,
891 None => return JSValue::undefined(),
892 };
893 let valid = matches!(form_str.as_str(), "NFC" | "NFD" | "NFKC" | "NFKD");
894 if !valid {
895 let mut err = JSObject::new();
896 err.set(
897 ctx.common_atoms.name,
898 JSValue::new_string(ctx.intern("RangeError")),
899 );
900 err.set(
901 ctx.common_atoms.message,
902 JSValue::new_string(ctx.intern("Normalization form is invalid")),
903 );
904 if let Some(proto) = ctx.get_range_error_prototype() {
905 err.prototype = Some(proto);
906 }
907 let ptr = Box::into_raw(Box::new(err)) as usize;
908 ctx.runtime_mut().gc_heap_mut().track(ptr);
909 ctx.pending_exception = Some(JSValue::new_object(ptr));
910 return JSValue::undefined();
911 }
912 }
913 JSValue::new_string(ctx.intern(&this_s))
914}
915
916fn string_substring(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
917 if args.is_empty() {
918 return JSValue::new_string(ctx.intern(""));
919 }
920 let s = match require_string_coercible(ctx, &args[0]) {
921 Some(s) => s,
922 None => return JSValue::undefined(),
923 };
924
925 let len = char_count(&s);
926 let start = if args.len() > 1 {
927 let v = to_integer_index(&args[1], ctx).max(0) as usize;
928 v.min(len)
929 } else {
930 0
931 };
932
933 let end = if args.len() > 2 {
934 let v = to_integer_index(&args[2], ctx).max(0) as usize;
935 v.min(len)
936 } else {
937 len
938 };
939
940 let (start, end) = if start > end {
941 (end, start)
942 } else {
943 (start, end)
944 };
945
946 let start_b = byte_index_for_char_pos(&s, start);
947 let end_b = byte_index_for_char_pos(&s, end);
948 JSValue::new_string(ctx.intern(&s[start_b..end_b]))
949}
950
951fn string_slice(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
952 if args.is_empty() {
953 return JSValue::new_string(ctx.intern(""));
954 }
955 let s = match require_string_coercible(ctx, &args[0]) {
956 Some(s) => s,
957 None => return JSValue::undefined(),
958 };
959
960 let len = char_count(&s);
961 let start = if args.len() > 1 {
962 let v = to_integer_index(&args[1], ctx);
963 if v < 0 {
964 (len as i64 + v).max(0) as usize
965 } else {
966 (v as usize).min(len)
967 }
968 } else {
969 0
970 };
971
972 let end = if args.len() > 2 {
973 let v = to_integer_index(&args[2], ctx);
974 if v < 0 {
975 (len as i64 + v).max(0) as usize
976 } else {
977 (v as usize).min(len)
978 }
979 } else {
980 len
981 };
982
983 if start >= end {
984 return JSValue::new_string(ctx.intern(""));
985 }
986
987 let start_b = byte_index_for_char_pos(&s, start);
988 let end_b = byte_index_for_char_pos(&s, end);
989 JSValue::new_string(ctx.intern(&s[start_b..end_b]))
990}
991
992fn string_to_string(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
993 if args.is_empty() {
994 return JSValue::new_string(ctx.intern(""));
995 }
996 match require_string_coercible(ctx, &args[0]) {
997 Some(s) => JSValue::new_string(ctx.intern(&s)),
998 None => JSValue::undefined(),
999 }
1000}
1001
1002fn string_to_lower_case(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1003 if args.is_empty() {
1004 return JSValue::new_string(ctx.intern(""));
1005 }
1006 let s = match require_string_coercible(ctx, &args[0]) {
1007 Some(s) => s.to_lowercase(),
1008 None => return JSValue::undefined(),
1009 };
1010
1011 JSValue::new_string(ctx.intern(&s))
1012}
1013
1014fn string_to_upper_case(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1015 if args.is_empty() {
1016 return JSValue::new_string(ctx.intern(""));
1017 }
1018 let s = match require_string_coercible(ctx, &args[0]) {
1019 Some(s) => s.to_uppercase(),
1020 None => return JSValue::undefined(),
1021 };
1022
1023 JSValue::new_string(ctx.intern(&s))
1024}
1025
1026fn string_to_locale_lower_case(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1027 string_to_lower_case(ctx, args)
1028}
1029
1030fn string_to_locale_upper_case(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1031 string_to_upper_case(ctx, args)
1032}
1033
1034fn string_split(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1035 if args.is_empty() {
1036 let mut result = JSObject::new_array();
1037 if let Some(p) = ctx.get_array_prototype() {
1038 result.set_prototype_raw(p);
1039 }
1040 result.set(ctx.common_atoms.length, JSValue::new_int(0));
1041 let ptr = Box::into_raw(Box::new(result)) as usize;
1042 return JSValue::new_object(ptr);
1043 }
1044 let s = match require_string_coercible(ctx, &args[0]) {
1045 Some(s) => s,
1046 None => return JSValue::undefined(),
1047 };
1048 let s_atom = ctx.intern(&s);
1049
1050 let length_atom = ctx.common_atoms.length;
1051
1052 if args.len() > 1 && args[1].is_object() {
1053 let sep_obj = args[1].as_object();
1054 let pattern_atom = ctx.common_atoms.__pattern__;
1055 if sep_obj.get(pattern_atom).is_some() {
1056 let limit = if args.len() > 2 && args[2].is_int() {
1057 args[2].get_int() as usize
1058 } else {
1059 usize::MAX
1060 };
1061
1062 let mut parts: Vec<String> = Vec::new();
1063 let compiled_re = args[1]
1064 .as_object()
1065 .get_compiled_regex()
1066 .map(|re| re as *const crate::regexp::Regex);
1067 if let Some(re_ptr) = compiled_re {
1068 let re = unsafe { &*re_ptr };
1069 let mut last = 0usize;
1070 let mut search_start = 0usize;
1071 loop {
1072 if parts.len() >= limit {
1073 break;
1074 }
1075 if search_start > s.len() {
1076 break;
1077 }
1078 let sub = &s[search_start..];
1079 if let Some(m) = re.find(sub) {
1080 let match_start = search_start + m.start();
1081 let match_end = search_start + m.end();
1082 if match_end == search_start && match_start == last {
1083 search_start += s[search_start..]
1084 .chars()
1085 .next()
1086 .map(|c| c.len_utf8())
1087 .unwrap_or(1);
1088 continue;
1089 }
1090 parts.push(s[last..match_start].to_string());
1091
1092 for cap in m.iter().skip(1) {
1093 parts.push(cap.unwrap_or("").to_string());
1094 }
1095 last = match_end;
1096 search_start = match_end;
1097 if match_end == match_start {
1098 search_start += s[search_start..]
1099 .chars()
1100 .next()
1101 .map(|c| c.len_utf8())
1102 .unwrap_or(1);
1103 }
1104 } else {
1105 break;
1106 }
1107 }
1108 if parts.len() < limit {
1109 parts.push(s[last..].to_string());
1110 }
1111 } else {
1112 parts.push(s);
1113 }
1114
1115 let mut result = JSObject::new_array();
1116 if let Some(p) = ctx.get_array_prototype() {
1117 result.set_prototype_raw(p);
1118 }
1119 for (i, part) in parts.iter().enumerate() {
1120 let key = ctx.int_atom_mut(i);
1121 let part_atom = ctx.intern(part);
1122 result.set(key, JSValue::new_string(part_atom));
1123 }
1124 result.set(length_atom, JSValue::new_int(parts.len() as i64));
1125 let ptr = Box::into_raw(Box::new(result)) as usize;
1126 return JSValue::new_object(ptr);
1127 }
1128 }
1129
1130 let sep_atom = if args.len() > 1 && args[1].is_string() {
1131 Some(args[1].get_atom())
1132 } else if args.len() > 1 && !args[1].is_undefined() {
1133 let s = match this_to_string(ctx, &args[1]) {
1134 Some(s) => s,
1135 None => return JSValue::undefined(),
1136 };
1137 Some(ctx.intern(&s))
1138 } else {
1139 None
1140 };
1141
1142 let s_owned = ctx.get_atom_str(s_atom).to_string();
1143 let s = s_owned.as_str();
1144
1145 if args.len() <= 1 || args[1].is_undefined() {
1146 let limit = if args.len() > 2 && !args[2].is_undefined() {
1147 to_integer_index(&args[2], ctx).max(0) as usize
1148 } else {
1149 usize::MAX
1150 };
1151 if limit == 0 {
1152 let mut result = JSObject::new_array();
1153 if let Some(p) = ctx.get_array_prototype() {
1154 result.set_prototype_raw(p);
1155 }
1156 result.set(length_atom, JSValue::new_int(0));
1157 let ptr = Box::into_raw(Box::new(result)) as usize;
1158 return JSValue::new_object(ptr);
1159 }
1160 let mut result = JSObject::new_array();
1161 if let Some(p) = ctx.get_array_prototype() {
1162 result.set_prototype_raw(p);
1163 }
1164 result.set(ctx.int_atom_mut(0), JSValue::new_string(s_atom));
1165 result.set(length_atom, JSValue::new_int(1));
1166 let ptr = Box::into_raw(Box::new(result)) as usize;
1167 return JSValue::new_object(ptr);
1168 }
1169
1170 let limit = if args.len() > 2 && !args[2].is_undefined() {
1171 to_integer_index(&args[2], ctx).max(0) as usize
1172 } else {
1173 usize::MAX
1174 };
1175
1176 let separator = sep_atom
1177 .map(|a| ctx.get_atom_str(a).to_string())
1178 .unwrap_or_default();
1179 let s = s.to_string();
1180
1181 if limit == 0 {
1182 let mut result = JSObject::new_array();
1183 if let Some(p) = ctx.get_array_prototype() {
1184 result.set_prototype_raw(p);
1185 }
1186 result.set(length_atom, JSValue::new_int(0));
1187 let ptr = Box::into_raw(Box::new(result)) as usize;
1188 return JSValue::new_object(ptr);
1189 }
1190
1191 let mut result = JSObject::new_array();
1192 if let Some(p) = ctx.get_array_prototype() {
1193 result.set_prototype_raw(p);
1194 }
1195
1196 if separator.is_empty() {
1197 let chars: Vec<char> = s.chars().collect();
1198 let count = chars.len().min(limit);
1199 for i in 0..count {
1200 let key = ctx.int_atom_mut(i);
1201 let c_str = chars[i].to_string();
1202 let c_atom = ctx.intern(&c_str);
1203 result.set(key, JSValue::new_string(c_atom));
1204 }
1205 result.set(length_atom, JSValue::new_int(count as i64));
1206 } else {
1207 let parts: Vec<String> = s.split(separator.as_str()).map(|s| s.to_string()).collect();
1208 let count = parts.len().min(limit);
1209 for i in 0..count {
1210 let key = ctx.int_atom_mut(i);
1211 let part_atom = ctx.intern(&parts[i]);
1212 result.set(key, JSValue::new_string(part_atom));
1213 }
1214 result.set(length_atom, JSValue::new_int(count as i64));
1215 }
1216
1217 let ptr = Box::into_raw(Box::new(result)) as usize;
1218 JSValue::new_object(ptr)
1219}
1220
1221fn string_length(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1222 if args.is_empty() {
1223 return JSValue::new_int(0);
1224 }
1225 let s = match require_string_coercible(ctx, &args[0]) {
1226 Some(s) => s,
1227 None => return JSValue::undefined(),
1228 };
1229 JSValue::new_int(char_count(&s) as i64)
1230}
1231
1232fn from_str_or_hex(s: &str) -> u32 {
1233 let s = s.trim();
1234 if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
1235 u32::from_str_radix(hex, 16).unwrap_or(0)
1236 } else {
1237 s.parse::<f64>().unwrap_or(0.0) as u32
1238 }
1239}
1240
1241fn string_fromcharcode(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1242 let mut units: Vec<u16> = Vec::with_capacity(args.len());
1243 for arg in args {
1244 let n = match crate::builtins::global::js_to_number_value(ctx, arg) {
1245 Ok(n) => n,
1246 Err(()) => return JSValue::undefined(),
1247 };
1248 let unit = to_uint16(n);
1249 units.push(unit);
1250 }
1251 let result: String = String::from_utf16_lossy(&units);
1252 JSValue::new_string(ctx.intern(&result))
1253}
1254
1255fn to_uint16(n: f64) -> u16 {
1256 if n.is_nan() || n.is_infinite() {
1257 return 0;
1258 }
1259 let i = n.trunc();
1260 let modulo = ((i.round() as i128).rem_euclid(65536)) as u16;
1261 modulo
1262}
1263
1264fn string_fromcodepoint(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1265 let mut result = String::new();
1266 for arg in args {
1267 let cp = if arg.is_int() {
1268 arg.get_int() as f64
1269 } else if arg.is_float() {
1270 arg.get_float()
1271 } else if arg.is_undefined() || arg.is_null() {
1272 0.0
1273 } else if arg.is_bool() {
1274 if arg.get_bool() { 1.0 } else { 0.0 }
1275 } else {
1276 let mut err = crate::object::object::JSObject::new();
1277 err.set(
1278 ctx.common_atoms.name,
1279 JSValue::new_string(ctx.intern("TypeError")),
1280 );
1281 err.set(
1282 ctx.common_atoms.message,
1283 JSValue::new_string(ctx.intern("Invalid code point")),
1284 );
1285 if let Some(proto) = ctx.get_type_error_prototype() {
1286 err.prototype = Some(proto);
1287 }
1288 let ptr = Box::into_raw(Box::new(err)) as usize;
1289 ctx.runtime_mut().gc_heap_mut().track(ptr);
1290 ctx.pending_exception = Some(JSValue::new_object(ptr));
1291 return JSValue::undefined();
1292 };
1293 if cp.is_nan() || cp < 0.0 || cp > 1114111.0 || cp != cp.floor() {
1294 let mut err = crate::object::object::JSObject::new();
1295 err.set(
1296 ctx.common_atoms.name,
1297 JSValue::new_string(ctx.intern("RangeError")),
1298 );
1299 err.set(
1300 ctx.common_atoms.message,
1301 JSValue::new_string(ctx.intern("Invalid code point")),
1302 );
1303 if let Some(proto) = ctx.get_range_error_prototype() {
1304 err.prototype = Some(proto);
1305 }
1306 let ptr = Box::into_raw(Box::new(err)) as usize;
1307 ctx.runtime_mut().gc_heap_mut().track(ptr);
1308 ctx.pending_exception = Some(JSValue::new_object(ptr));
1309 return JSValue::undefined();
1310 }
1311 let code = cp as u32;
1312 if let Some(c) = char::from_u32(code) {
1313 result.push(c);
1314 } else if code >= 0xD800 && code <= 0xDFFF {
1315 result.push('\u{FFFD}');
1316 } else {
1317 result.push('\u{FFFD}');
1318 }
1319 }
1320 JSValue::new_string(ctx.intern(&result))
1321}
1322
1323fn string_trim(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1324 if args.is_empty() {
1325 return JSValue::new_string(ctx.intern(""));
1326 }
1327 let s = match require_string_coercible(ctx, &args[0]) {
1328 Some(s) => s,
1329 None => return JSValue::undefined(),
1330 };
1331 let trimmed = s.trim_matches(is_js_trim_whitespace);
1332 JSValue::new_string(ctx.intern(trimmed))
1333}
1334
1335#[inline]
1336fn is_js_trim_whitespace(c: char) -> bool {
1337 matches!(
1338 c,
1339 '\u{0009}' | '\u{000A}' | '\u{000B}' | '\u{000C}' | '\u{000D}' | '\u{0020}'
1340 | '\u{00A0}' | '\u{FEFF}' | '\u{1680}' | '\u{2028}' | '\u{2029}' | '\u{202F}'
1341 | '\u{205F}' | '\u{3000}' | '\u{2000}'..='\u{200A}'
1342 )
1343}
1344
1345fn string_trim_start(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1346 if args.is_empty() {
1347 return JSValue::new_string(ctx.intern(""));
1348 }
1349 match this_to_string(ctx, &args[0]) {
1350 Some(s) => JSValue::new_string(ctx.intern(s.trim_start_matches(is_js_trim_whitespace))),
1351 None => JSValue::undefined(),
1352 }
1353}
1354
1355fn string_trim_end(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1356 if args.is_empty() {
1357 return JSValue::new_string(ctx.intern(""));
1358 }
1359 match this_to_string(ctx, &args[0]) {
1360 Some(s) => JSValue::new_string(ctx.intern(s.trim_end_matches(is_js_trim_whitespace))),
1361 None => JSValue::undefined(),
1362 }
1363}
1364
1365fn string_starts_with(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1366 let s = match this_to_string(ctx, &args[0]) {
1367 Some(s) => s,
1368 None => return JSValue::undefined(),
1369 };
1370 let search = if args.len() > 1 {
1371 ctx.get_atom_str(args[1].get_atom()).to_string()
1372 } else {
1373 "undefined".to_string()
1374 };
1375 if search.is_empty() {
1376 return JSValue::bool(true);
1377 }
1378 let pos = if args.len() > 2 {
1379 let idx = to_integer_index(&args[2], ctx);
1380 if idx < 0 { 0usize } else { idx as usize }
1381 } else {
1382 0usize
1383 };
1384 if pos > s.len() {
1385 return JSValue::bool(false);
1386 }
1387 JSValue::bool(s[pos..].starts_with(&search))
1388}
1389
1390fn string_ends_with(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1391 let s = match this_to_string(ctx, &args[0]) {
1392 Some(s) => s,
1393 None => return JSValue::undefined(),
1394 };
1395 let search = if args.len() > 1 {
1396 ctx.get_atom_str(args[1].get_atom()).to_string()
1397 } else {
1398 "undefined".to_string()
1399 };
1400 if search.is_empty() {
1401 return JSValue::bool(true);
1402 }
1403 let len = s.len();
1404 let end_pos = if args.len() > 2 {
1405 let idx = to_integer_index(&args[2], ctx);
1406 if idx < 0 {
1407 0usize
1408 } else {
1409 (idx as usize).min(len)
1410 }
1411 } else {
1412 len
1413 };
1414 JSValue::bool(s[..end_pos].ends_with(&search))
1415}
1416
1417fn string_repeat(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1418 if args.is_empty() {
1419 return JSValue::new_string(ctx.intern(""));
1420 }
1421 let s = match this_to_string(ctx, &args[0]) {
1422 Some(s) => s,
1423 None => return JSValue::undefined(),
1424 };
1425 if args.len() < 2 {
1426 return JSValue::new_string(ctx.intern(""));
1427 }
1428 let count = to_integer_index(&args[1], ctx);
1429 if count < 0 {
1430 return JSValue::new_string(ctx.intern(""));
1431 }
1432 let count = count as usize;
1433 let repeated = s.repeat(count);
1434 JSValue::new_string(ctx.intern(&repeated))
1435}
1436
1437fn string_includes(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1438 let s = match this_to_string(ctx, &args[0]) {
1439 Some(s) => s,
1440 None => return JSValue::undefined(),
1441 };
1442 if args.len() < 2 {
1443 return JSValue::bool(s.contains("undefined"));
1444 }
1445 let search = ctx.get_atom_str(args[1].get_atom()).to_string();
1446 let pos = if args.len() > 2 {
1447 let idx = to_integer_index(&args[2], ctx);
1448 if idx < 0 { 0usize } else { idx as usize }
1449 } else {
1450 0usize
1451 };
1452 if pos > s.len() {
1453 return JSValue::bool(false);
1454 }
1455 JSValue::bool(s[pos..].contains(&search))
1456}
1457
1458fn string_replace(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1459 if args.is_empty() {
1460 return JSValue::new_string(ctx.intern(""));
1461 }
1462 let s = match require_string_coercible(ctx, &args[0]) {
1463 Some(s) => s,
1464 None => return JSValue::undefined(),
1465 };
1466 if args.len() < 3 {
1467 return JSValue::new_string(ctx.intern(&s));
1468 }
1469
1470 if args[1].is_object() {
1471 let regexp_obj = args[1].as_object();
1472
1473 let pattern_atom = ctx.common_atoms.__pattern__;
1474 let flags_atom = ctx.common_atoms.__flags__;
1475
1476 let pattern = if let Some(p) = regexp_obj.get(pattern_atom) {
1477 if p.is_string() {
1478 ctx.get_atom_str(p.get_atom()).to_string()
1479 } else {
1480 return JSValue::new_string(ctx.intern(&s));
1481 }
1482 } else {
1483 return JSValue::new_string(ctx.intern(&s));
1484 };
1485
1486 let flags = if let Some(f) = regexp_obj.get(flags_atom) {
1487 if f.is_string() {
1488 ctx.get_atom_str(f.get_atom()).to_string()
1489 } else {
1490 String::new()
1491 }
1492 } else {
1493 String::new()
1494 };
1495
1496 let replacement = if args[2].is_string() {
1497 ctx.get_atom_str(args[2].get_atom()).to_string()
1498 } else {
1499 String::new()
1500 };
1501
1502 let ignore_case = flags.contains('i');
1503 let is_global = flags.contains('g');
1504
1505 if is_global {
1506 let mut result = String::new();
1507 let mut last_end = 0;
1508 let mut search_from = 0;
1509 let pat_lower = if ignore_case {
1510 pattern.to_lowercase()
1511 } else {
1512 String::new()
1513 };
1514 let s_lower = if ignore_case {
1515 s.to_lowercase()
1516 } else {
1517 String::new()
1518 };
1519
1520 loop {
1521 let found = if ignore_case {
1522 s_lower[search_from..]
1523 .find(&pat_lower)
1524 .map(|i| i + search_from)
1525 } else {
1526 s[search_from..].find(&pattern).map(|i| i + search_from)
1527 };
1528
1529 if let Some(pos) = found {
1530 result.push_str(&s[last_end..pos]);
1531 result.push_str(&replacement);
1532 last_end = pos + pattern.len();
1533 search_from = last_end;
1534 if pattern.is_empty() {
1535 if search_from < s.len() {
1536 result.push_str(&s[search_from..search_from + 1]);
1537 search_from += 1;
1538 last_end = search_from;
1539 } else {
1540 break;
1541 }
1542 }
1543 } else {
1544 break;
1545 }
1546 }
1547 result.push_str(&s[last_end..]);
1548 JSValue::new_string(ctx.intern(&result))
1549 } else {
1550 let found = if ignore_case {
1551 s.to_lowercase().find(&pattern.to_lowercase())
1552 } else {
1553 s.find(&pattern)
1554 };
1555
1556 if let Some(pos) = found {
1557 let mut result = String::new();
1558 result.push_str(&s[..pos]);
1559 result.push_str(&replacement);
1560 result.push_str(&s[pos + pattern.len()..]);
1561 JSValue::new_string(ctx.intern(&result))
1562 } else {
1563 JSValue::new_string(ctx.intern(&s))
1564 }
1565 }
1566 } else {
1567 let search = if args[1].is_string() {
1568 ctx.get_atom_str(args[1].get_atom()).to_string()
1569 } else {
1570 return JSValue::new_string(ctx.intern(&s));
1571 };
1572 match s.find(&search) {
1573 None => JSValue::new_string(ctx.intern(&s)),
1574 Some(pos) => {
1575 let end = pos + search.len();
1576 let matched = search.clone();
1577 let repl_str = if args[2].is_function() {
1578 call_replace_function(ctx, args[2], &matched, pos, &s, &[])
1579 } else if args[2].is_string() {
1580 let r = ctx.get_atom_str(args[2].get_atom()).to_string();
1581 apply_replace_pattern(&r, &matched, pos, end, &s)
1582 } else {
1583 String::new()
1584 };
1585 let mut result = String::with_capacity(s.len() + repl_str.len());
1586 result.push_str(&s[..pos]);
1587 result.push_str(&repl_str);
1588 result.push_str(&s[end..]);
1589 JSValue::new_string(ctx.intern(&result))
1590 }
1591 }
1592 }
1593}
1594
1595fn call_replace_function(
1596 ctx: &mut JSContext,
1597 func: JSValue,
1598 matched: &str,
1599 position: usize,
1600 full: &str,
1601 captures: &[&str],
1602) -> String {
1603 let mut args: Vec<JSValue> = Vec::with_capacity(3 + captures.len());
1604 args.push(JSValue::new_string(ctx.intern(matched)));
1605 for c in captures {
1606 args.push(JSValue::new_string(ctx.intern(c)));
1607 }
1608 args.push(JSValue::new_int(position as i64));
1609 args.push(JSValue::new_string(ctx.intern(full)));
1610 let val = if let Some(vm_ptr) = ctx.get_register_vm_ptr() {
1611 let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
1612 match vm.call_function_with_this(ctx, func, JSValue::undefined(), &args) {
1613 Ok(v) => v,
1614 Err(_) => JSValue::undefined(),
1615 }
1616 } else {
1617 JSValue::undefined()
1618 };
1619 js_to_string_arg(&val, ctx)
1620}
1621
1622fn apply_replace_pattern(replacement: &str, matched: &str, start: usize, end: usize, full: &str) -> String {
1623 let bytes = replacement.as_bytes();
1624 let mut out = String::with_capacity(replacement.len());
1625 let mut i = 0;
1626 while i < bytes.len() {
1627 if bytes[i] == b'$' && i + 1 < bytes.len() {
1628 let d = bytes[i + 1];
1629 match d {
1630 b'$' => {
1631 out.push('$');
1632 i += 2;
1633 continue;
1634 }
1635 b'&' => {
1636 out.push_str(matched);
1637 i += 2;
1638 continue;
1639 }
1640 b'`' => {
1641 out.push_str(&full[..start]);
1642 i += 2;
1643 continue;
1644 }
1645 b'\'' => {
1646 out.push_str(&full[end..]);
1647 i += 2;
1648 continue;
1649 }
1650 n @ (b'1'..=b'9') => {
1651 let _ = n;
1653 i += 2;
1654 continue;
1655 }
1656 _ => {}
1657 }
1658 }
1659 let ch_len = utf8_len(bytes[i]);
1661 out.push_str(std::str::from_utf8(&bytes[i..i + ch_len]).unwrap_or(""));
1662 i += ch_len;
1663 }
1664 out
1665}
1666
1667#[inline]
1668fn utf8_len(b: u8) -> usize {
1669 if b < 0x80 {
1670 1
1671 } else if b >> 5 == 0b110 {
1672 2
1673 } else if b >> 4 == 0b1110 {
1674 3
1675 } else {
1676 4
1677 }
1678}
1679
1680pub fn js_to_length(val: &JSValue) -> u64 {
1681 let n = val.to_number();
1682 if n.is_nan() || n <= 0.0 {
1683 0
1684 } else if n.is_infinite() {
1685 u64::MAX
1686 } else {
1687 n as u64
1688 }
1689}
1690
1691fn js_to_string_arg(val: &JSValue, ctx: &mut JSContext) -> String {
1692 if val.is_string() {
1693 ctx.get_atom_str(val.get_atom()).to_string()
1694 } else if val.is_int() {
1695 format!("{}", val.get_int())
1696 } else if val.is_float() {
1697 js_float_to_string(val.get_float())
1698 } else if val.is_bool() {
1699 if val.get_bool() {
1700 "true".to_string()
1701 } else {
1702 "false".to_string()
1703 }
1704 } else if val.is_undefined() {
1705 "undefined".to_string()
1706 } else if val.is_null() {
1707 "null".to_string()
1708 } else {
1709 "[object Object]".to_string()
1710 }
1711}
1712
1713fn string_pad_start(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1714 if args.is_empty() {
1715 return JSValue::new_string(ctx.intern(""));
1716 }
1717 let s = match require_string_coercible(ctx, &args[0]) {
1718 Some(s) => s,
1719 None => return JSValue::undefined(),
1720 };
1721 let target_len = if args.len() > 1 {
1722 js_to_length(&args[1]).min(1 << 30) as usize
1723 } else {
1724 0
1725 };
1726 let pad_str = if args.len() > 2 {
1727 js_to_string_arg(&args[2], ctx)
1728 } else {
1729 " ".to_string()
1730 };
1731
1732 let s_len = s.chars().count();
1733 if target_len <= s_len {
1734 return JSValue::new_string(ctx.intern(&s));
1735 }
1736
1737 let mut pad_count = target_len - s_len;
1738 let mut prepend = String::new();
1739 let pad_chars: Vec<char> = pad_str.chars().collect();
1740 let pad_char_len = pad_chars.len();
1741 if pad_char_len == 0 {
1742 return JSValue::new_string(ctx.intern(&s));
1743 }
1744 let mut i = 0;
1745 while pad_count > 0 {
1746 prepend.push(pad_chars[i % pad_char_len]);
1747 pad_count -= 1;
1748 i += 1;
1749 }
1750
1751 JSValue::new_string(ctx.intern(&(prepend + &s)))
1752}
1753
1754fn string_pad_end(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1755 if args.is_empty() {
1756 return JSValue::new_string(ctx.intern(""));
1757 }
1758 let s = match require_string_coercible(ctx, &args[0]) {
1759 Some(s) => s,
1760 None => return JSValue::undefined(),
1761 };
1762 let target_len = if args.len() > 1 {
1763 js_to_length(&args[1]).min(1 << 30) as usize
1764 } else {
1765 0
1766 };
1767 let pad_str = if args.len() > 2 {
1768 js_to_string_arg(&args[2], ctx)
1769 } else {
1770 " ".to_string()
1771 };
1772
1773 let s_len = s.chars().count();
1774 if target_len <= s_len {
1775 return JSValue::new_string(ctx.intern(&s));
1776 }
1777
1778 let mut pad_count = target_len - s_len;
1779 let mut append = String::new();
1780 let pad_chars: Vec<char> = pad_str.chars().collect();
1781 let pad_char_len = pad_chars.len();
1782 if pad_char_len == 0 {
1783 return JSValue::new_string(ctx.intern(&s));
1784 }
1785 let mut i = 0;
1786 while pad_count > 0 {
1787 append.push(pad_chars[i % pad_char_len]);
1788 pad_count -= 1;
1789 i += 1;
1790 }
1791
1792 JSValue::new_string(ctx.intern(&(s + &append)))
1793}
1794
1795fn string_replace_all(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1796 if args.is_empty() {
1797 return JSValue::new_string(ctx.intern(""));
1798 }
1799 let s = match require_string_coercible(ctx, &args[0]) {
1800 Some(s) => s,
1801 None => return JSValue::undefined(),
1802 };
1803 if args.len() < 3 {
1804 return JSValue::new_string(ctx.intern(&s));
1805 }
1806 let search = if args[1].is_string() {
1807 ctx.get_atom_str(args[1].get_atom()).to_string()
1808 } else {
1809 return args[0];
1810 };
1811 let replacement = if args[2].is_string() {
1812 ctx.get_atom_str(args[2].get_atom()).to_string()
1813 } else {
1814 String::new()
1815 };
1816
1817 if search.is_empty() {
1818 let mut result = String::new();
1819 for c in s.chars() {
1820 result.push_str(&replacement);
1821 result.push(c);
1822 }
1823 result.push_str(&replacement);
1824 return JSValue::new_string(ctx.intern(&result));
1825 }
1826
1827 let result = s.replace(&search as &str, &replacement as &str);
1828 JSValue::new_string(ctx.intern(&result))
1829}
1830
1831fn string_at(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1832 if args.is_empty() {
1833 return JSValue::undefined();
1834 }
1835 let s = match require_string_coercible(ctx, &args[0]) {
1836 Some(s) => s,
1837 None => return JSValue::undefined(),
1838 };
1839 let chars: Vec<char> = s.chars().collect();
1840 let len = chars.len();
1841 let idx = if args.len() > 1 {
1842 let pos = &args[1];
1843 if pos.is_int() {
1844 pos.get_int()
1845 } else if pos.is_float() {
1846 pos.get_float().trunc() as i64
1847 } else if pos.is_undefined() || pos.is_null() {
1848 0
1849 } else if pos.is_bool() {
1850 if pos.get_bool() { 1 } else { 0 }
1851 } else if pos.is_string() {
1852 let s = ctx.get_atom_str(pos.get_atom());
1853 match s.trim().parse::<f64>() {
1854 Ok(v) if !v.is_nan() => v.trunc() as i64,
1855 _ => 0,
1856 }
1857 } else {
1858 0
1859 }
1860 } else {
1861 0
1862 };
1863 let actual_idx = if idx < 0 { len as i64 + idx } else { idx };
1864 if actual_idx < 0 || actual_idx as usize >= len {
1865 return JSValue::undefined();
1866 }
1867 let c = chars[actual_idx as usize];
1868 JSValue::new_string(ctx.intern(&c.to_string()))
1869}
1870
1871fn string_is_well_formed(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1872 if args.is_empty() {
1873 return JSValue::bool(false);
1874 }
1875 let s = match require_string_coercible(_ctx, &args[0]) {
1876 Some(s) => s,
1877 None => return JSValue::undefined(),
1878 };
1879 let units: Vec<u16> = s.encode_utf16().collect();
1880 let mut i = 0;
1881 while i < units.len() {
1882 let unit = units[i];
1883 if (0xD800..=0xDBFF).contains(&unit) {
1884 if i + 1 < units.len() && (0xDC00..=0xDFFF).contains(&units[i + 1]) {
1885 i += 2;
1886 continue;
1887 }
1888 return JSValue::bool(false);
1889 }
1890 if (0xDC00..=0xDFFF).contains(&unit) {
1891 return JSValue::bool(false);
1892 }
1893 i += 1;
1894 }
1895 JSValue::bool(true)
1896}
1897
1898fn string_to_well_formed(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1899 if args.is_empty() {
1900 return JSValue::undefined();
1901 }
1902 let s = match require_string_coercible(ctx, &args[0]) {
1903 Some(s) => s,
1904 None => return JSValue::undefined(),
1905 };
1906 let units: Vec<u16> = s.encode_utf16().collect();
1907 let mut result: Vec<u16> = Vec::with_capacity(units.len());
1908 let mut i = 0;
1909 while i < units.len() {
1910 let unit = units[i];
1911 if (0xD800..=0xDBFF).contains(&unit) {
1912 if i + 1 < units.len() && (0xDC00..=0xDFFF).contains(&units[i + 1]) {
1913 result.push(unit);
1914 result.push(units[i + 1]);
1915 i += 2;
1916 continue;
1917 }
1918 result.push(0xFFFD);
1919 i += 1;
1920 continue;
1921 }
1922 if (0xDC00..=0xDFFF).contains(&unit) {
1923 result.push(0xFFFD);
1924 i += 1;
1925 continue;
1926 }
1927 result.push(unit);
1928 i += 1;
1929 }
1930 let formatted: String = String::from_utf16_lossy(&result);
1931 JSValue::new_string(ctx.intern(&formatted))
1932}
1933
1934fn string_code_point_at(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1935 if args.is_empty() {
1936 return JSValue::undefined();
1937 }
1938 let s = match require_string_coercible(ctx, &args[0]) {
1939 Some(s) => s,
1940 None => return JSValue::undefined(),
1941 };
1942 let chars: Vec<char> = s.chars().collect();
1943 let len = chars.len();
1944 let index = if args.len() > 1 {
1945 to_integer_index(&args[1], ctx)
1946 } else {
1947 0
1948 };
1949 let actual_index = if index < 0 { len as i64 + index } else { index };
1950 if actual_index < 0 || actual_index as usize >= len {
1951 return JSValue::undefined();
1952 }
1953 let c = chars[actual_index as usize];
1954 if (c as u32) >= 0xD800 && (c as u32) <= 0xDBFF && actual_index as usize + 1 < len {
1955 let next = chars[actual_index as usize + 1];
1956 if (next as u32) >= 0xDC00 && (next as u32) <= 0xDFFF {
1957 let cp = 0x10000 + (((c as u32) - 0xD800) << 10) + (next as u32) - 0xDC00;
1958 return JSValue::new_int(cp as i64);
1959 }
1960 }
1961 JSValue::new_int(c as u32 as i64)
1962}
1963
1964fn string_match_all(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1965 if args.is_empty() {
1966 let mut result = JSObject::new_array();
1967 if let Some(p) = ctx.get_array_prototype() {
1968 result.set_prototype_raw(p);
1969 }
1970 result.set(ctx.common_atoms.length, JSValue::new_int(0));
1971 let ptr = Box::into_raw(Box::new(result)) as usize;
1972 return JSValue::new_object(ptr);
1973 }
1974 let s = match require_string_coercible(ctx, &args[0]) {
1975 Some(s) => s,
1976 None => return JSValue::undefined(),
1977 };
1978
1979 let pattern = if args.len() > 1 {
1980 if args[1].is_object() {
1981 let regexp_obj = args[1].as_object();
1982
1983 let flags_atom = ctx.common_atoms.__flags__;
1984 let flags = if let Some(f) = regexp_obj.get(flags_atom) {
1985 if f.is_string() {
1986 ctx.get_atom_str(f.get_atom()).to_string()
1987 } else {
1988 String::new()
1989 }
1990 } else {
1991 String::new()
1992 };
1993
1994 if !flags.contains('g') {
1995 let mut err_obj = JSObject::new();
1996 err_obj.set(
1997 ctx.common_atoms.name,
1998 JSValue::new_string(ctx.intern("TypeError")),
1999 );
2000 err_obj.set(
2001 ctx.common_atoms.message,
2002 JSValue::new_string(ctx.intern("matchAll requires a global RegExp")),
2003 );
2004 let ptr = Box::into_raw(Box::new(err_obj)) as usize;
2005 return JSValue::new_object(ptr);
2006 }
2007
2008 let pattern_atom = ctx.common_atoms.__pattern__;
2009 if let Some(p) = regexp_obj.get(pattern_atom) {
2010 if p.is_string() {
2011 ctx.get_atom_str(p.get_atom()).to_string()
2012 } else {
2013 String::new()
2014 }
2015 } else {
2016 String::new()
2017 }
2018 } else if args[1].is_string() {
2019 ctx.get_atom_str(args[1].get_atom()).to_string()
2020 } else {
2021 String::new()
2022 }
2023 } else {
2024 String::new()
2025 };
2026
2027 let mut result_array = JSObject::new_array();
2028 if let Some(p) = ctx.get_array_prototype() {
2029 result_array.set_prototype_raw(p);
2030 }
2031 let length_atom = ctx.common_atoms.length;
2032 let mut match_count = 0;
2033
2034 if pattern.is_empty() {
2035 result_array.set(length_atom, JSValue::new_int(0));
2036 let ptr = Box::into_raw(Box::new(result_array)) as usize;
2037 return JSValue::new_object(ptr);
2038 }
2039
2040 let mut search_pos = 0;
2041 while search_pos <= s.len() {
2042 let max_start = if pattern.len() <= s.len() {
2043 s.len() - pattern.len()
2044 } else {
2045 break;
2046 };
2047
2048 let mut found = false;
2049 for i in search_pos..=max_start {
2050 if i + pattern.len() <= s.len() {
2051 let slice = &s[i..i + pattern.len()];
2052 if slice == pattern {
2053 found = true;
2054 let mut match_obj = JSObject::new();
2055 match_obj.set(ctx.intern("0"), JSValue::new_string(ctx.intern(slice)));
2056 match_obj.set(ctx.common_atoms.index, JSValue::new_int(i as i64));
2057 match_obj.set(ctx.common_atoms.input, JSValue::new_string(ctx.intern(&s)));
2058 match_obj.set(ctx.common_atoms.length, JSValue::new_int(1));
2059
2060 let key = ctx.int_atom_mut(match_count);
2061 result_array.set(
2062 key,
2063 JSValue::new_object(Box::into_raw(Box::new(match_obj)) as usize),
2064 );
2065
2066 match_count += 1;
2067 search_pos = i + pattern.len();
2068 if pattern.len() == 0 {
2069 search_pos += 1;
2070 }
2071 break;
2072 }
2073 }
2074 }
2075
2076 if !found {
2077 break;
2078 }
2079 }
2080
2081 result_array.set(length_atom, JSValue::new_int(match_count as i64));
2082 let ptr = Box::into_raw(Box::new(result_array)) as usize;
2083 JSValue::new_object(ptr)
2084}
2085
2086fn string_search(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
2087 if args.len() < 2 {
2088 return JSValue::new_int(-1);
2089 }
2090 let s = match require_string_coercible(ctx, &args[0]) {
2091 Some(s) => s,
2092 None => return JSValue::undefined(),
2093 };
2094
2095 if args[1].is_object() {
2096 let regexp_obj = args[1].as_object();
2097 let pattern_atom = ctx.common_atoms.__pattern__;
2098 let flags_atom = ctx.common_atoms.__flags__;
2099
2100 let pattern = if let Some(p) = regexp_obj.get(pattern_atom) {
2101 if p.is_string() {
2102 ctx.get_atom_str(p.get_atom()).to_string()
2103 } else {
2104 return JSValue::new_int(-1);
2105 }
2106 } else {
2107 return JSValue::new_int(-1);
2108 };
2109
2110 let flags = if let Some(f) = regexp_obj.get(flags_atom) {
2111 if f.is_string() {
2112 ctx.get_atom_str(f.get_atom()).to_string()
2113 } else {
2114 String::new()
2115 }
2116 } else {
2117 String::new()
2118 };
2119
2120 let ignore_case = flags.contains('i');
2121 let s_lower = if ignore_case {
2122 s.to_lowercase()
2123 } else {
2124 String::new()
2125 };
2126 let p_lower = if ignore_case {
2127 pattern.to_lowercase()
2128 } else {
2129 String::new()
2130 };
2131
2132 let found = if ignore_case {
2133 s_lower.find(&p_lower)
2134 } else {
2135 s.find(&pattern)
2136 };
2137 JSValue::new_int(found.map(|i| i as i64).unwrap_or(-1))
2138 } else if args[1].is_string() {
2139 let search = ctx.get_atom_str(args[1].get_atom()).to_string();
2140 JSValue::new_int(s.find(&search).map(|i| i as i64).unwrap_or(-1))
2141 } else {
2142 JSValue::new_int(-1)
2143 }
2144}
2145
2146fn string_match(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
2147 if args.len() < 2 {
2148 return JSValue::null();
2149 }
2150 let s = match require_string_coercible(ctx, &args[0]) {
2151 Some(s) => s,
2152 None => return JSValue::undefined(),
2153 };
2154
2155 if args[1].is_object() {
2156 let regexp_obj = args[1].as_object();
2157 let pattern_atom = ctx.common_atoms.__pattern__;
2158 let flags_atom = ctx.common_atoms.__flags__;
2159
2160 let pattern = if let Some(p) = regexp_obj.get(pattern_atom) {
2161 if p.is_string() {
2162 ctx.get_atom_str(p.get_atom()).to_string()
2163 } else {
2164 return JSValue::null();
2165 }
2166 } else {
2167 return JSValue::null();
2168 };
2169
2170 let flags = if let Some(f) = regexp_obj.get(flags_atom) {
2171 if f.is_string() {
2172 ctx.get_atom_str(f.get_atom()).to_string()
2173 } else {
2174 String::new()
2175 }
2176 } else {
2177 String::new()
2178 };
2179
2180 let is_global = flags.contains('g');
2181 let ignore_case = flags.contains('i');
2182 let s_lower = if ignore_case {
2183 s.to_lowercase()
2184 } else {
2185 String::new()
2186 };
2187 let p_lower = if ignore_case {
2188 pattern.to_lowercase()
2189 } else {
2190 String::new()
2191 };
2192
2193 if is_global {
2194 let mut results: Vec<String> = Vec::new();
2195 let mut search_from = 0;
2196 loop {
2197 let found = if ignore_case {
2198 s_lower[search_from..]
2199 .find(&p_lower)
2200 .map(|i| i + search_from)
2201 } else {
2202 s[search_from..].find(&pattern).map(|i| i + search_from)
2203 };
2204 if let Some(pos) = found {
2205 let end = (pos + pattern.len()).min(s.len());
2206 results.push(s[pos..end].to_string());
2207 search_from = end;
2208 if pattern.is_empty() {
2209 if search_from < s.len() {
2210 search_from += 1;
2211 } else {
2212 break;
2213 }
2214 }
2215 } else {
2216 break;
2217 }
2218 }
2219 if results.is_empty() {
2220 return JSValue::null();
2221 }
2222 let mut arr = JSObject::new_array();
2223 if let Some(p) = ctx.get_array_prototype() {
2224 arr.set_prototype_raw(p);
2225 }
2226 let length_atom = ctx.common_atoms.length;
2227 for (i, m) in results.iter().enumerate() {
2228 let key = ctx.int_atom_mut(i);
2229 arr.set(key, JSValue::new_string(ctx.intern(m)));
2230 }
2231 arr.set(length_atom, JSValue::new_int(results.len() as i64));
2232 let ptr = Box::into_raw(Box::new(arr)) as usize;
2233 JSValue::new_object(ptr)
2234 } else {
2235 let found = if ignore_case {
2236 s_lower.find(&p_lower)
2237 } else {
2238 s.find(&pattern)
2239 };
2240 if let Some(pos) = found {
2241 let end = (pos + pattern.len()).min(s.len());
2242 let match_str = &s[pos..end];
2243 let mut result = JSObject::new_array();
2244 if let Some(p) = ctx.get_array_prototype() {
2245 result.set_prototype_raw(p);
2246 }
2247 result.set(ctx.intern("0"), JSValue::new_string(ctx.intern(match_str)));
2248 result.set(ctx.common_atoms.index, JSValue::new_int(pos as i64));
2249 result.set(ctx.common_atoms.input, JSValue::new_string(ctx.intern(&s)));
2250 result.set(ctx.common_atoms.length, JSValue::new_int(1));
2251 let ptr = Box::into_raw(Box::new(result)) as usize;
2252 JSValue::new_object(ptr)
2253 } else {
2254 JSValue::null()
2255 }
2256 }
2257 } else {
2258 JSValue::null()
2259 }
2260}
2261
2262fn string_substr(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
2263 let this_str = match this_to_string(ctx, &args[0]) {
2264 Some(s) => s,
2265 None => return JSValue::new_string(ctx.intern("")),
2266 };
2267 let s = &this_str;
2268 let len = s.chars().count();
2269 let start = if args.len() > 1 {
2270 let v = args[1].get_int();
2271 if v < 0 {
2272 ((len as i64) + v).max(0) as usize
2273 } else {
2274 (v as usize).min(len)
2275 }
2276 } else {
2277 0
2278 };
2279
2280 let end = if args.len() > 2 {
2281 let v = args[2].get_int();
2282 if v <= 0 {
2283 return JSValue::new_string(ctx.intern(""));
2284 }
2285 (start + v as usize).min(len)
2286 } else {
2287 len
2288 };
2289 let start_b = byte_index_for_char_pos(&s, start);
2290 let end_b = byte_index_for_char_pos(&s, end);
2291 JSValue::new_string(ctx.intern(&s[start_b..end_b]))
2292}
2293
2294fn string_raw(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
2295 if args.is_empty() {
2296 let mut err =
2297 crate::object::object::JSObject::new_typed(crate::object::object::ObjectType::Error);
2298 err.set(
2299 ctx.common_atoms.name,
2300 JSValue::new_string(ctx.intern("TypeError")),
2301 );
2302 err.set(
2303 ctx.common_atoms.message,
2304 JSValue::new_string(ctx.intern("String.raw requires a template")),
2305 );
2306 if let Some(proto) = ctx.get_type_error_prototype() {
2307 err.prototype = Some(proto);
2308 }
2309 let ptr = Box::into_raw(Box::new(err)) as usize;
2310 ctx.runtime_mut().gc_heap_mut().track(ptr);
2311 ctx.pending_exception = Some(JSValue::new_object(ptr));
2312 return JSValue::undefined();
2313 }
2314 let template = &args[0];
2315 if !template.is_object() {
2316 let mut err =
2317 crate::object::object::JSObject::new_typed(crate::object::object::ObjectType::Error);
2318 err.set(
2319 ctx.common_atoms.name,
2320 JSValue::new_string(ctx.intern("TypeError")),
2321 );
2322 err.set(
2323 ctx.common_atoms.message,
2324 JSValue::new_string(ctx.intern("String.raw requires template to be an object")),
2325 );
2326 if let Some(proto) = ctx.get_type_error_prototype() {
2327 err.prototype = Some(proto);
2328 }
2329 let ptr = Box::into_raw(Box::new(err)) as usize;
2330 ctx.runtime_mut().gc_heap_mut().track(ptr);
2331 ctx.pending_exception = Some(JSValue::new_object(ptr));
2332 return JSValue::undefined();
2333 }
2334 let obj = template.as_object();
2335 let raw_atom = ctx.intern("raw");
2336 let raw_val = match obj.get(raw_atom) {
2337 Some(v) => v,
2338 None => {
2339 let mut err = crate::object::object::JSObject::new_typed(
2340 crate::object::object::ObjectType::Error,
2341 );
2342 err.set(
2343 ctx.common_atoms.name,
2344 JSValue::new_string(ctx.intern("TypeError")),
2345 );
2346 err.set(
2347 ctx.common_atoms.message,
2348 JSValue::new_string(ctx.intern("String.raw template has no raw property")),
2349 );
2350 if let Some(proto) = ctx.get_type_error_prototype() {
2351 err.prototype = Some(proto);
2352 }
2353 let ptr = Box::into_raw(Box::new(err)) as usize;
2354 ctx.runtime_mut().gc_heap_mut().track(ptr);
2355 ctx.pending_exception = Some(JSValue::new_object(ptr));
2356 return JSValue::undefined();
2357 }
2358 };
2359 if !raw_val.is_object() {
2360 let mut err =
2361 crate::object::object::JSObject::new_typed(crate::object::object::ObjectType::Error);
2362 err.set(
2363 ctx.common_atoms.name,
2364 JSValue::new_string(ctx.intern("TypeError")),
2365 );
2366 err.set(
2367 ctx.common_atoms.message,
2368 JSValue::new_string(ctx.intern("String.raw template.raw is not an object")),
2369 );
2370 if let Some(proto) = ctx.get_type_error_prototype() {
2371 err.prototype = Some(proto);
2372 }
2373 let ptr = Box::into_raw(Box::new(err)) as usize;
2374 ctx.runtime_mut().gc_heap_mut().track(ptr);
2375 ctx.pending_exception = Some(JSValue::new_object(ptr));
2376 return JSValue::undefined();
2377 }
2378 let raw_obj = raw_val.as_object();
2379 let length_atom = ctx.common_atoms.length;
2380 let len_val = raw_obj.get(length_atom).unwrap_or(JSValue::new_int(0));
2381 let len = if len_val.is_int() {
2382 len_val.get_int() as usize
2383 } else if len_val.is_float() {
2384 len_val.get_float() as usize
2385 } else {
2386 0
2387 };
2388 if len == 0 {
2389 return JSValue::new_string(ctx.intern(""));
2390 }
2391 let substitutions = if args.len() > 1 { &args[1..] } else { &[] };
2392 let mut result = String::new();
2393 let mut i = 0usize;
2394 while i < len {
2395 let key = ctx.int_atom_mut(i);
2396 let raw_str_val = if raw_obj.is_dense_array() {
2397 let arr_ptr = raw_obj as *const _ as usize;
2398 let arr = unsafe { &*(arr_ptr as *const crate::object::array_obj::JSArrayObject) };
2399 arr.get(i).unwrap_or(JSValue::new_string(ctx.intern("")))
2400 } else {
2401 raw_obj
2402 .get(key)
2403 .unwrap_or(JSValue::new_string(ctx.intern("")))
2404 };
2405 let raw_str = if raw_str_val.is_string() {
2406 ctx.get_atom_str(raw_str_val.get_atom()).to_string()
2407 } else {
2408 js_to_string_arg(&raw_str_val, ctx)
2409 };
2410 result.push_str(&raw_str);
2411 if i + 1 < len {
2412 if i < substitutions.len() {
2413 let sub = &substitutions[i];
2414 let sub_str = if sub.is_string() {
2415 ctx.get_atom_str(sub.get_atom()).to_string()
2416 } else {
2417 js_to_string_arg(sub, ctx)
2418 };
2419 result.push_str(&sub_str);
2420 }
2421 }
2422 i += 1;
2423 }
2424 JSValue::new_string(ctx.intern(&result))
2425}