1use crate::host::HostFunction;
2use crate::object::object::JSObject;
3use crate::runtime::context::JSContext;
4use crate::value::JSValue;
5
6fn create_builtin_function(ctx: &mut JSContext, name: &str) -> JSValue {
7 let mut func = crate::object::function::JSFunction::new_builtin(ctx.intern(name), 1);
8 func.set_builtin_marker(ctx, name);
9 let ptr = Box::into_raw(Box::new(func)) as usize;
10 ctx.runtime_mut().gc_heap_mut().track_function(ptr);
11 JSValue::new_function(ptr)
12}
13
14pub fn init_string(ctx: &mut JSContext) {
15 let string_atom = ctx.common_atoms.string;
16
17 let mut string_func = crate::object::function::JSFunction::new_builtin(string_atom, 1);
18 string_func.set_builtin_marker(ctx, "string_constructor");
19
20 string_func.base.set(
21 ctx.intern("fromCharCode"),
22 create_builtin_function(ctx, "string_fromCharCode"),
23 );
24 string_func.base.set(
25 ctx.intern("fromCodePoint"),
26 create_builtin_function(ctx, "string_fromCodePoint"),
27 );
28
29 let string_ptr = Box::into_raw(Box::new(string_func)) as usize;
30 ctx.runtime_mut().gc_heap_mut().track_function(string_ptr);
31 let string_value = JSValue::new_function(string_ptr);
32 let global = ctx.global();
33 if global.is_object() {
34 let global_obj = global.as_object_mut();
35 global_obj.set(string_atom, string_value);
36 }
37
38 let proto_atom = ctx.intern("StringPrototype");
39 let mut proto_obj = JSObject::new();
40 proto_obj.set(
41 ctx.intern("charAt"),
42 create_builtin_function(ctx, "string_charAt"),
43 );
44 proto_obj.set(
45 ctx.intern("charCodeAt"),
46 create_builtin_function(ctx, "string_charCodeAt"),
47 );
48 proto_obj.set(
49 ctx.intern("concat"),
50 create_builtin_function(ctx, "string_concat"),
51 );
52 proto_obj.set(
53 ctx.intern("indexOf"),
54 create_builtin_function(ctx, "string_indexOf"),
55 );
56 proto_obj.set(
57 ctx.intern("lastIndexOf"),
58 create_builtin_function(ctx, "string_lastIndexOf"),
59 );
60 proto_obj.set(
61 ctx.intern("slice"),
62 create_builtin_function(ctx, "string_slice"),
63 );
64 proto_obj.set(
65 ctx.intern("substring"),
66 create_builtin_function(ctx, "string_substring"),
67 );
68 proto_obj.set(
69 ctx.intern("toString"),
70 create_builtin_function(ctx, "string_toString"),
71 );
72 proto_obj.set(
73 ctx.intern("valueOf"),
74 create_builtin_function(ctx, "string_toString"),
75 );
76 proto_obj.set(
77 ctx.intern("toLowerCase"),
78 create_builtin_function(ctx, "string_toLowerCase"),
79 );
80 proto_obj.set(
81 ctx.intern("toUpperCase"),
82 create_builtin_function(ctx, "string_toUpperCase"),
83 );
84 proto_obj.set(
85 ctx.intern("split"),
86 create_builtin_function(ctx, "string_split"),
87 );
88 proto_obj.set(
89 ctx.common_atoms.length,
90 create_builtin_function(ctx, "string_length"),
91 );
92 proto_obj.set(
93 ctx.intern("trim"),
94 create_builtin_function(ctx, "string_trim"),
95 );
96 proto_obj.set(
97 ctx.intern("trimStart"),
98 create_builtin_function(ctx, "string_trimStart"),
99 );
100 proto_obj.set(
101 ctx.intern("trimLeft"),
102 create_builtin_function(ctx, "string_trimStart"),
103 );
104 proto_obj.set(
105 ctx.intern("trimEnd"),
106 create_builtin_function(ctx, "string_trimEnd"),
107 );
108 proto_obj.set(
109 ctx.intern("trimRight"),
110 create_builtin_function(ctx, "string_trimEnd"),
111 );
112 proto_obj.set(
113 ctx.intern("startsWith"),
114 create_builtin_function(ctx, "string_startsWith"),
115 );
116 proto_obj.set(
117 ctx.intern("endsWith"),
118 create_builtin_function(ctx, "string_endsWith"),
119 );
120 proto_obj.set(
121 ctx.intern("repeat"),
122 create_builtin_function(ctx, "string_repeat"),
123 );
124 proto_obj.set(
125 ctx.intern("includes"),
126 create_builtin_function(ctx, "string_includes"),
127 );
128 proto_obj.set(
129 ctx.intern("replace"),
130 create_builtin_function(ctx, "string_replace"),
131 );
132 proto_obj.set(
133 ctx.intern("padStart"),
134 create_builtin_function(ctx, "string_padStart"),
135 );
136 proto_obj.set(
137 ctx.intern("padEnd"),
138 create_builtin_function(ctx, "string_padEnd"),
139 );
140 proto_obj.set(
141 ctx.intern("replaceAll"),
142 create_builtin_function(ctx, "string_replaceAll"),
143 );
144 proto_obj.set(
145 ctx.intern("search"),
146 create_builtin_function(ctx, "string_search"),
147 );
148 proto_obj.set(
149 ctx.intern("match"),
150 create_builtin_function(ctx, "string_match"),
151 );
152 proto_obj.set(ctx.intern("at"), create_builtin_function(ctx, "string_at"));
153 proto_obj.set(
154 ctx.intern("isWellFormed"),
155 create_builtin_function(ctx, "string_isWellFormed"),
156 );
157 proto_obj.set(
158 ctx.intern("toWellFormed"),
159 create_builtin_function(ctx, "string_toWellFormed"),
160 );
161 proto_obj.set(
162 ctx.intern("codePointAt"),
163 create_builtin_function(ctx, "string_codePointAt"),
164 );
165 proto_obj.set(
166 ctx.intern("matchAll"),
167 create_builtin_function(ctx, "string_matchAll"),
168 );
169 proto_obj.set(
170 ctx.intern("substr"),
171 create_builtin_function(ctx, "string_substr"),
172 );
173
174 if let Some(obj_proto_ptr) = ctx.get_object_prototype() {
175 proto_obj.prototype = Some(obj_proto_ptr);
176 }
177
178 let proto_ptr = Box::into_raw(Box::new(proto_obj)) as usize;
179 ctx.runtime_mut().gc_heap_mut().track(proto_ptr);
180
181 ctx.set_string_prototype(proto_ptr);
182 let proto_value = JSValue::new_object(proto_ptr);
183
184 let string_func_ptr = string_ptr as *mut crate::object::function::JSFunction;
185 unsafe {
186 (*string_func_ptr)
187 .base
188 .set(ctx.common_atoms.prototype, proto_value);
189 }
190
191 if global.is_object() {
192 let global_obj = global.as_object_mut();
193 global_obj.set(proto_atom, proto_value);
194 }
195}
196
197pub fn register_builtins(ctx: &mut JSContext) {
198 ctx.register_builtin(
199 "string_constructor",
200 HostFunction::new("String", 1, string_constructor),
201 );
202 ctx.register_builtin(
203 "string_charAt",
204 HostFunction::new("charAt", 1, string_char_at),
205 );
206 ctx.register_builtin(
207 "string_charCodeAt",
208 HostFunction::new("charCodeAt", 1, string_char_code_at),
209 );
210 ctx.register_builtin(
211 "string_concat",
212 HostFunction::new("concat", 1, string_concat),
213 );
214 ctx.register_builtin(
215 "string_indexOf",
216 HostFunction::new("indexOf", 1, string_index_of),
217 );
218 ctx.register_builtin(
219 "string_lastIndexOf",
220 HostFunction::new("lastIndexOf", 1, string_last_index_of),
221 );
222 ctx.register_builtin("string_slice", HostFunction::new("slice", 2, string_slice));
223 ctx.register_builtin(
224 "string_substring",
225 HostFunction::new("substring", 2, string_substring),
226 );
227 ctx.register_builtin(
228 "string_toString",
229 HostFunction::new("toString", 0, string_to_string),
230 );
231 ctx.register_builtin(
232 "string_toLowerCase",
233 HostFunction::new("toLowerCase", 0, string_to_lower_case),
234 );
235 ctx.register_builtin(
236 "string_toUpperCase",
237 HostFunction::new("toUpperCase", 0, string_to_upper_case),
238 );
239 ctx.register_builtin("string_split", HostFunction::new("split", 1, string_split));
240 ctx.register_builtin(
241 "string_length",
242 HostFunction::new("length", 0, string_length),
243 );
244 ctx.register_builtin(
245 "string_fromCharCode",
246 HostFunction::new("fromCharCode", 1, string_fromcharcode),
247 );
248 ctx.register_builtin(
249 "string_fromCodePoint",
250 HostFunction::new("fromCodePoint", 1, string_fromcodepoint),
251 );
252 ctx.register_builtin("string_trim", HostFunction::new("trim", 0, string_trim));
253 ctx.register_builtin(
254 "string_trimStart",
255 HostFunction::new("trimStart", 0, string_trim_start),
256 );
257 ctx.register_builtin(
258 "string_trimEnd",
259 HostFunction::new("trimEnd", 0, string_trim_end),
260 );
261 ctx.register_builtin(
262 "string_startsWith",
263 HostFunction::new("startsWith", 1, string_starts_with),
264 );
265 ctx.register_builtin(
266 "string_endsWith",
267 HostFunction::new("endsWith", 1, string_ends_with),
268 );
269 ctx.register_builtin(
270 "string_repeat",
271 HostFunction::new("repeat", 1, string_repeat),
272 );
273 ctx.register_builtin(
274 "string_includes",
275 HostFunction::new("includes", 1, string_includes),
276 );
277 ctx.register_builtin(
278 "string_replace",
279 HostFunction::new("replace", 2, string_replace),
280 );
281 ctx.register_builtin(
282 "string_padStart",
283 HostFunction::new("padStart", 1, string_pad_start),
284 );
285 ctx.register_builtin(
286 "string_padEnd",
287 HostFunction::new("padEnd", 1, string_pad_end),
288 );
289 ctx.register_builtin(
290 "string_replaceAll",
291 HostFunction::new("replaceAll", 2, string_replace_all),
292 );
293 ctx.register_builtin(
294 "string_search",
295 HostFunction::new("search", 1, string_search),
296 );
297 ctx.register_builtin("string_match", HostFunction::new("match", 1, string_match));
298 ctx.register_builtin("string_at", HostFunction::new("at", 1, string_at));
299 ctx.register_builtin(
300 "string_isWellFormed",
301 HostFunction::new("isWellFormed", 0, string_is_well_formed),
302 );
303 ctx.register_builtin(
304 "string_toWellFormed",
305 HostFunction::new("toWellFormed", 0, string_to_well_formed),
306 );
307 ctx.register_builtin(
308 "string_codePointAt",
309 HostFunction::new("codePointAt", 1, string_code_point_at),
310 );
311 ctx.register_builtin(
312 "string_matchAll",
313 HostFunction::new("matchAll", 1, string_match_all),
314 );
315 ctx.register_builtin(
316 "string_substr",
317 HostFunction::new("substr", 2, string_substr),
318 );
319}
320
321fn string_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
322 let s = if args.is_empty() {
323 JSValue::new_string(ctx.intern(""))
324 } else {
325 let val = &args[0];
326 if val.is_string() {
327 val.clone()
328 } else if val.is_int() {
329 JSValue::new_string(ctx.intern(&val.get_int().to_string()))
330 } else if val.is_float() {
331 JSValue::new_string(ctx.intern(&val.get_float().to_string()))
332 } else if val.is_bigint() {
333 let obj = unsafe { crate::value::JSValue::object_from_ptr(val.get_ptr()) };
334 let n = obj.get_bigint_value();
335 JSValue::new_string(ctx.intern(&n.to_string()))
336 } else if val.is_bool() {
337 JSValue::new_string(ctx.intern(&val.get_bool().to_string()))
338 } else if val.is_null() {
339 JSValue::new_string(ctx.common_atoms.null)
340 } else if val.is_undefined() {
341 JSValue::new_string(ctx.common_atoms.undefined)
342 } else if val.is_object() {
343 JSValue::new_string(ctx.intern("[object Object]"))
344 } else {
345 JSValue::new_string(ctx.intern(""))
346 }
347 };
348 s
349}
350
351fn char_count(s: &str) -> usize {
352 s.chars().count()
353}
354
355fn byte_index_for_char_pos(s: &str, pos: usize) -> usize {
356 if pos == 0 {
357 return 0;
358 }
359 match s.char_indices().nth(pos) {
360 Some((idx, _)) => idx,
361 None => s.len(),
362 }
363}
364
365fn string_char_at(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
366 if args.is_empty() {
367 return JSValue::new_string(ctx.intern(""));
368 }
369
370 let atom = if args[0].is_string() {
371 args[0].get_atom()
372 } else {
373 return JSValue::new_string(ctx.intern(""));
374 };
375
376 let index = if args.len() > 1 {
377 args[1].get_int() as usize
378 } else {
379 0
380 };
381
382 if index >= ctx.string_char_count(atom) {
383 return JSValue::new_string(ctx.intern(""));
384 }
385
386 match ctx.string_char_code_at(atom, index) {
387 Some(code) => {
388 let c = char::from_u32(code).unwrap_or('\0');
389 let mut buf = [0u8; 4];
390 let s = c.encode_utf8(&mut buf);
391 JSValue::new_string(ctx.intern(s))
392 }
393 None => JSValue::new_string(ctx.intern("")),
394 }
395}
396
397fn string_char_code_at(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
398 if args.is_empty() {
399 return JSValue::new_float(f64::NAN);
400 }
401
402 let atom = if args[0].is_string() {
403 args[0].get_atom()
404 } else {
405 return JSValue::new_float(f64::NAN);
406 };
407
408 let index = if args.len() > 1 {
409 let v = args[1];
410 if v.is_int() {
411 let i = v.get_int();
412 if i < 0 {
413 return JSValue::new_float(f64::NAN);
414 }
415 i as usize
416 } else if v.is_float() {
417 let f = v.get_float();
418 if f < 0.0 || f.is_nan() || f.is_infinite() {
419 return JSValue::new_float(f64::NAN);
420 }
421 f as usize
422 } else {
423 0
424 }
425 } else {
426 0
427 };
428
429 match ctx.string_char_code_at(atom, index) {
430 Some(code) => JSValue::new_int(code as i64),
431 None => JSValue::new_float(f64::NAN),
432 }
433}
434
435fn string_concat(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
436 if args.is_empty() {
437 return JSValue::new_string(ctx.intern(""));
438 }
439
440 let mut result = if args[0].is_string() {
441 ctx.get_atom_str(args[0].get_atom()).to_string()
442 } else {
443 String::new()
444 };
445
446 for arg in args.iter().skip(1) {
447 if arg.is_string() {
448 result.push_str(ctx.get_atom_str(arg.get_atom()));
449 } else if arg.is_int() {
450 result.push_str(&arg.get_int().to_string());
451 } else if arg.is_float() {
452 result.push_str(&arg.get_float().to_string());
453 } else if arg.is_bool() {
454 result.push_str(&arg.get_bool().to_string());
455 }
456 }
457
458 JSValue::new_string(ctx.intern(&result))
459}
460
461fn string_index_of(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
462 if args.len() < 2 {
463 return JSValue::new_int(-1);
464 }
465
466 let s = if args[0].is_string() {
467 ctx.get_atom_str(args[0].get_atom()).to_string()
468 } else {
469 return JSValue::new_int(-1);
470 };
471
472 let search = if args[1].is_string() {
473 ctx.get_atom_str(args[1].get_atom()).to_string()
474 } else {
475 return JSValue::new_int(-1);
476 };
477
478 let from_index = if args.len() > 2 {
479 args[2].get_int().max(0) as usize
480 } else {
481 0
482 };
483
484 let s_char_len = char_count(&s);
485 if from_index > s_char_len {
486 return JSValue::new_int(-1);
487 }
488
489 let from_byte = byte_index_for_char_pos(&s, from_index);
490
491 match s[from_byte..].find(&search) {
492 Some(byte_pos) => {
493 let byte_idx = from_byte + byte_pos;
494 let char_idx = s[..byte_idx].chars().count();
495 JSValue::new_int(char_idx as i64)
496 }
497 None => JSValue::new_int(-1),
498 }
499}
500
501fn string_last_index_of(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
502 if args.len() < 2 {
503 return JSValue::new_int(-1);
504 }
505
506 let s = if args[0].is_string() {
507 ctx.get_atom_str(args[0].get_atom()).to_string()
508 } else {
509 return JSValue::new_int(-1);
510 };
511
512 let search = if args[1].is_string() {
513 ctx.get_atom_str(args[1].get_atom()).to_string()
514 } else {
515 return JSValue::new_int(-1);
516 };
517
518 match s.rfind(&search) {
519 Some(byte_pos) => {
520 let char_idx = s[..byte_pos].chars().count();
521 JSValue::new_int(char_idx as i64)
522 }
523 None => JSValue::new_int(-1),
524 }
525}
526
527fn string_substring(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
528 if args.is_empty() {
529 return JSValue::new_string(ctx.intern(""));
530 }
531
532 let s = if args[0].is_string() {
533 ctx.get_atom_str(args[0].get_atom()).to_string()
534 } else {
535 return JSValue::new_string(ctx.intern(""));
536 };
537
538 let len = char_count(&s);
539 let start = if args.len() > 1 {
540 let v = args[1].get_int().max(0) as usize;
541 v.min(len)
542 } else {
543 0
544 };
545
546 let end = if args.len() > 2 {
547 let v = args[2].get_int().max(0) as usize;
548 v.min(len)
549 } else {
550 len
551 };
552
553 let (start, end) = if start > end {
554 (end, start)
555 } else {
556 (start, end)
557 };
558
559 let start_b = byte_index_for_char_pos(&s, start);
560 let end_b = byte_index_for_char_pos(&s, end);
561 JSValue::new_string(ctx.intern(&s[start_b..end_b]))
562}
563
564fn string_slice(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
565 if args.is_empty() {
566 return JSValue::new_string(ctx.intern(""));
567 }
568
569 let s = if args[0].is_string() {
570 ctx.get_atom_str(args[0].get_atom()).to_string()
571 } else {
572 return JSValue::new_string(ctx.intern(""));
573 };
574
575 let len = char_count(&s);
576 let start = if args.len() > 1 {
577 let v = args[1].get_int();
578 if v < 0 {
579 (len as i64 + v).max(0) as usize
580 } else {
581 (v as usize).min(len)
582 }
583 } else {
584 0
585 };
586
587 let end = if args.len() > 2 {
588 let v = args[2].get_int();
589 if v < 0 {
590 (len as i64 + v).max(0) as usize
591 } else {
592 (v as usize).min(len)
593 }
594 } else {
595 len
596 };
597
598 if start >= end {
599 return JSValue::new_string(ctx.intern(""));
600 }
601
602 let start_b = byte_index_for_char_pos(&s, start);
603 let end_b = byte_index_for_char_pos(&s, end);
604 JSValue::new_string(ctx.intern(&s[start_b..end_b]))
605}
606
607fn string_to_string(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
608 if args.is_empty() {
609 return JSValue::new_string(ctx.intern(""));
610 }
611
612 if args[0].is_string() {
613 return args[0].clone();
614 }
615
616 if args[0].is_object() {
617 let obj = args[0].as_object();
618 let prim = obj.get(ctx.common_atoms.__value__);
619 if let Some(v) = prim {
620 if v.is_string() {
621 return v;
622 }
623 }
624 }
625 args[0].clone()
626}
627
628fn string_to_lower_case(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
629 if args.is_empty() {
630 return JSValue::new_string(ctx.intern(""));
631 }
632
633 let s = if args[0].is_string() {
634 ctx.get_atom_str(args[0].get_atom()).to_lowercase()
635 } else {
636 return JSValue::new_string(ctx.intern(""));
637 };
638
639 JSValue::new_string(ctx.intern(&s))
640}
641
642fn string_to_upper_case(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
643 if args.is_empty() {
644 return JSValue::new_string(ctx.intern(""));
645 }
646
647 let s = if args[0].is_string() {
648 ctx.get_atom_str(args[0].get_atom()).to_uppercase()
649 } else {
650 return JSValue::new_string(ctx.intern(""));
651 };
652
653 JSValue::new_string(ctx.intern(&s))
654}
655
656fn string_split(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
657 if args.is_empty() {
658 let mut result = JSObject::new_array();
659 result.set(ctx.common_atoms.length, JSValue::new_int(0));
660 let ptr = Box::into_raw(Box::new(result)) as usize;
661 return JSValue::new_object(ptr);
662 }
663
664 let s_atom = if args[0].is_string() {
665 args[0].get_atom()
666 } else {
667 let mut result = JSObject::new_array();
668 result.set(ctx.common_atoms.length, JSValue::new_int(0));
669 let ptr = Box::into_raw(Box::new(result)) as usize;
670 return JSValue::new_object(ptr);
671 };
672
673 let length_atom = ctx.common_atoms.length;
674
675 if args.len() > 1 && args[1].is_object() {
676 let sep_obj = args[1].as_object();
677 let pattern_atom = ctx.common_atoms.__pattern__;
678 if sep_obj.get(pattern_atom).is_some() {
679 let s = ctx.get_atom_str(s_atom).to_string();
680 let limit = if args.len() > 2 && args[2].is_int() {
681 args[2].get_int() as usize
682 } else {
683 usize::MAX
684 };
685
686 let mut parts: Vec<String> = Vec::new();
687 let compiled_re = args[1]
688 .as_object()
689 .get_compiled_regex()
690 .map(|re| re as *const crate::regexp::Regex);
691 if let Some(re_ptr) = compiled_re {
692 let re = unsafe { &*re_ptr };
693 let mut last = 0usize;
694 let mut search_start = 0usize;
695 loop {
696 if parts.len() >= limit {
697 break;
698 }
699 if search_start > s.len() {
700 break;
701 }
702 let sub = &s[search_start..];
703 if let Some(m) = re.find(sub) {
704 let match_start = search_start + m.start();
705 let match_end = search_start + m.end();
706 if match_end == search_start && match_start == last {
707 search_start += s[search_start..]
708 .chars()
709 .next()
710 .map(|c| c.len_utf8())
711 .unwrap_or(1);
712 continue;
713 }
714 parts.push(s[last..match_start].to_string());
715
716 for cap in m.iter().skip(1) {
717 parts.push(cap.unwrap_or("").to_string());
718 }
719 last = match_end;
720 search_start = match_end;
721 if match_end == match_start {
722 search_start += s[search_start..]
723 .chars()
724 .next()
725 .map(|c| c.len_utf8())
726 .unwrap_or(1);
727 }
728 } else {
729 break;
730 }
731 }
732 if parts.len() < limit {
733 parts.push(s[last..].to_string());
734 }
735 } else {
736 parts.push(s);
737 }
738
739 let mut result = JSObject::new_array();
740 for (i, part) in parts.iter().enumerate() {
741 let key = ctx.int_atom_mut(i);
742 let part_atom = ctx.intern(part);
743 result.set(key, JSValue::new_string(part_atom));
744 }
745 result.set(length_atom, JSValue::new_int(parts.len() as i64));
746 let ptr = Box::into_raw(Box::new(result)) as usize;
747 return JSValue::new_object(ptr);
748 }
749 }
750
751 let sep_atom = if args.len() > 1 && args[1].is_string() {
752 Some(args[1].get_atom())
753 } else {
754 None
755 };
756
757 let s = ctx.get_atom_str(s_atom);
758 let separator = sep_atom.map(|a| ctx.get_atom_str(a)).unwrap_or("");
759
760 let mut result = JSObject::new_array();
761
762 if separator.is_empty() {
763 let chars: Vec<char> = s.chars().collect();
764 for (i, c) in chars.iter().enumerate() {
765 let key = ctx.int_atom_mut(i);
766 let c_str = c.to_string();
767 let c_atom = ctx.intern(&c_str);
768 result.set(key, JSValue::new_string(c_atom));
769 }
770 result.set(length_atom, JSValue::new_int(chars.len() as i64));
771 } else {
772 let parts: Vec<String> = s.split(separator).map(|s| s.to_string()).collect();
773 for (i, part) in parts.iter().enumerate() {
774 let key = ctx.int_atom_mut(i);
775 let part_atom = ctx.intern(part);
776 result.set(key, JSValue::new_string(part_atom));
777 }
778 result.set(length_atom, JSValue::new_int(parts.len() as i64));
779 }
780
781 let ptr = Box::into_raw(Box::new(result)) as usize;
782 JSValue::new_object(ptr)
783}
784
785fn string_length(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
786 if args.is_empty() {
787 return JSValue::new_int(0);
788 }
789
790 if args[0].is_string() {
791 JSValue::new_int(ctx.string_char_count(args[0].get_atom()) as i64)
792 } else {
793 JSValue::new_int(0)
794 }
795}
796
797fn from_str_or_hex(s: &str) -> u32 {
798 let s = s.trim();
799 if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
800 u32::from_str_radix(hex, 16).unwrap_or(0)
801 } else {
802 s.parse::<f64>().unwrap_or(0.0) as u32
803 }
804}
805
806fn string_fromcharcode(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
807 let mut result = String::new();
808 for arg in args {
809 let code = if arg.is_string() {
810 from_str_or_hex(ctx.get_atom_str(arg.get_atom()))
811 } else if arg.is_int() {
812 arg.get_int() as u32
813 } else {
814 0
815 };
816 if let Some(c) = char::from_u32(code & 0xFFFF) {
817 result.push(c);
818 }
819 }
820 JSValue::new_string(ctx.intern(&result))
821}
822
823fn string_fromcodepoint(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
824 let mut result = String::new();
825 for arg in args {
826 let code = arg.get_int() as u32;
827 if let Some(c) = char::from_u32(code) {
828 result.push(c);
829 }
830 }
831 JSValue::new_string(ctx.intern(&result))
832}
833
834fn string_trim(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
835 if args.is_empty() {
836 return JSValue::new_string(ctx.intern(""));
837 }
838 let s = ctx.get_atom_str(args[0].get_atom()).to_string();
839 let trimmed = s.trim();
840 JSValue::new_string(ctx.intern(trimmed))
841}
842
843fn string_trim_start(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
844 if args.is_empty() {
845 return JSValue::new_string(ctx.intern(""));
846 }
847 let s = ctx.get_atom_str(args[0].get_atom()).to_string();
848 let trimmed = s.trim_start();
849 JSValue::new_string(ctx.intern(trimmed))
850}
851
852fn string_trim_end(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
853 if args.is_empty() {
854 return JSValue::new_string(ctx.intern(""));
855 }
856 let s = ctx.get_atom_str(args[0].get_atom()).to_string();
857 let trimmed = s.trim_end();
858 JSValue::new_string(ctx.intern(trimmed))
859}
860
861fn string_starts_with(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
862 if args.len() < 2 {
863 return JSValue::bool(false);
864 }
865 let s = ctx.get_atom_str(args[0].get_atom()).to_string();
866 let prefix = ctx.get_atom_str(args[1].get_atom()).to_string();
867 JSValue::bool(s.starts_with(&prefix))
868}
869
870fn string_ends_with(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
871 if args.len() < 2 {
872 return JSValue::bool(false);
873 }
874 let s = ctx.get_atom_str(args[0].get_atom()).to_string();
875 let suffix = ctx.get_atom_str(args[1].get_atom()).to_string();
876 JSValue::bool(s.ends_with(&suffix))
877}
878
879fn string_repeat(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
880 if args.len() < 2 {
881 return JSValue::new_string(ctx.intern(""));
882 }
883 let s = ctx.get_atom_str(args[0].get_atom()).to_string();
884 let count = args[1].get_int();
885 if count < 0 {
886 return JSValue::new_string(ctx.intern(""));
887 }
888 let count = count as usize;
889 let repeated = s.repeat(count);
890 JSValue::new_string(ctx.intern(&repeated))
891}
892
893fn string_includes(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
894 if args.len() < 2 {
895 return JSValue::bool(false);
896 }
897 let s = ctx.get_atom_str(args[0].get_atom()).to_string();
898 let search = ctx.get_atom_str(args[1].get_atom()).to_string();
899 JSValue::bool(s.contains(&search))
900}
901
902fn string_replace(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
903 if args.len() < 3 {
904 if args.is_empty() {
905 return JSValue::new_string(ctx.intern(""));
906 }
907 return args[0].clone();
908 }
909 let s = if args[0].is_string() {
910 ctx.get_atom_str(args[0].get_atom()).to_string()
911 } else {
912 return args[0].clone();
913 };
914
915 if args[1].is_object() {
916 let regexp_obj = args[1].as_object();
917
918 let pattern_atom = ctx.common_atoms.__pattern__;
919 let flags_atom = ctx.common_atoms.__flags__;
920
921 let pattern = if let Some(p) = regexp_obj.get(pattern_atom) {
922 if p.is_string() {
923 ctx.get_atom_str(p.get_atom()).to_string()
924 } else {
925 return JSValue::new_string(ctx.intern(&s));
926 }
927 } else {
928 return JSValue::new_string(ctx.intern(&s));
929 };
930
931 let flags = if let Some(f) = regexp_obj.get(flags_atom) {
932 if f.is_string() {
933 ctx.get_atom_str(f.get_atom()).to_string()
934 } else {
935 String::new()
936 }
937 } else {
938 String::new()
939 };
940
941 let replacement = if args[2].is_string() {
942 ctx.get_atom_str(args[2].get_atom()).to_string()
943 } else {
944 String::new()
945 };
946
947 let ignore_case = flags.contains('i');
948 let is_global = flags.contains('g');
949
950 if is_global {
951 let mut result = String::new();
952 let mut last_end = 0;
953 let mut search_from = 0;
954 let pat_lower = if ignore_case {
955 pattern.to_lowercase()
956 } else {
957 String::new()
958 };
959 let s_lower = if ignore_case {
960 s.to_lowercase()
961 } else {
962 String::new()
963 };
964
965 loop {
966 let found = if ignore_case {
967 s_lower[search_from..]
968 .find(&pat_lower)
969 .map(|i| i + search_from)
970 } else {
971 s[search_from..].find(&pattern).map(|i| i + search_from)
972 };
973
974 if let Some(pos) = found {
975 result.push_str(&s[last_end..pos]);
976 result.push_str(&replacement);
977 last_end = pos + pattern.len();
978 search_from = last_end;
979 if pattern.is_empty() {
980 if search_from < s.len() {
981 result.push_str(&s[search_from..search_from + 1]);
982 search_from += 1;
983 last_end = search_from;
984 } else {
985 break;
986 }
987 }
988 } else {
989 break;
990 }
991 }
992 result.push_str(&s[last_end..]);
993 JSValue::new_string(ctx.intern(&result))
994 } else {
995 let found = if ignore_case {
996 s.to_lowercase().find(&pattern.to_lowercase())
997 } else {
998 s.find(&pattern)
999 };
1000
1001 if let Some(pos) = found {
1002 let mut result = String::new();
1003 result.push_str(&s[..pos]);
1004 result.push_str(&replacement);
1005 result.push_str(&s[pos + pattern.len()..]);
1006 JSValue::new_string(ctx.intern(&result))
1007 } else {
1008 JSValue::new_string(ctx.intern(&s))
1009 }
1010 }
1011 } else {
1012 let search = if args[1].is_string() {
1013 ctx.get_atom_str(args[1].get_atom()).to_string()
1014 } else {
1015 return JSValue::new_string(ctx.intern(&s));
1016 };
1017 let replacement = if args[2].is_string() {
1018 ctx.get_atom_str(args[2].get_atom()).to_string()
1019 } else if args[2].is_function() {
1020 if let Some(vm_ptr) = ctx.get_register_vm_ptr() {
1021 let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
1022 let result = vm.call_function_with_this(
1023 ctx,
1024 args[2],
1025 JSValue::undefined(),
1026 &[args[1].clone(), JSValue::new_int(0), args[0].clone()],
1027 );
1028 match result {
1029 Ok(v) => {
1030 if v.is_string() {
1031 ctx.get_atom_str(v.get_atom()).to_string()
1032 } else if v.is_undefined() {
1033 "undefined".to_string()
1034 } else if v.is_null() {
1035 "null".to_string()
1036 } else {
1037 format!("{}", v.get_int())
1038 }
1039 }
1040 Err(_) => String::new(),
1041 }
1042 } else {
1043 String::new()
1044 }
1045 } else {
1046 String::new()
1047 };
1048 let result = s.replace(&search, &replacement);
1049 JSValue::new_string(ctx.intern(&result))
1050 }
1051}
1052
1053fn string_pad_start(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1054 if args.is_empty() {
1055 return JSValue::new_string(ctx.intern(""));
1056 }
1057 let s = ctx.get_atom_str(args[0].get_atom()).to_string();
1058 let target_len = if args.len() > 1 {
1059 args[1].get_int() as usize
1060 } else {
1061 0
1062 };
1063 let pad_str = if args.len() > 2 && args[2].is_string() {
1064 ctx.get_atom_str(args[2].get_atom()).to_string()
1065 } else {
1066 " ".to_string()
1067 };
1068
1069 if target_len <= s.len() {
1070 return args[0];
1071 }
1072
1073 let mut pad_count = target_len - s.len();
1074 let mut prepend = String::new();
1075 while pad_count > 0 {
1076 if pad_count >= pad_str.len() {
1077 prepend.push_str(&pad_str);
1078 pad_count -= pad_str.len();
1079 } else {
1080 prepend.push_str(&pad_str[..pad_count]);
1081 pad_count = 0;
1082 }
1083 }
1084
1085 JSValue::new_string(ctx.intern(&(prepend + &s)))
1086}
1087
1088fn string_pad_end(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1089 if args.is_empty() {
1090 return JSValue::new_string(ctx.intern(""));
1091 }
1092 let s = ctx.get_atom_str(args[0].get_atom()).to_string();
1093 let target_len = if args.len() > 1 {
1094 args[1].get_int() as usize
1095 } else {
1096 0
1097 };
1098 let pad_str = if args.len() > 2 && args[2].is_string() {
1099 ctx.get_atom_str(args[2].get_atom()).to_string()
1100 } else {
1101 " ".to_string()
1102 };
1103
1104 if target_len <= s.len() {
1105 return args[0];
1106 }
1107
1108 let mut pad_count = target_len - s.len();
1109 let mut append = String::new();
1110 while pad_count > 0 {
1111 if pad_count >= pad_str.len() {
1112 append.push_str(&pad_str);
1113 pad_count -= pad_str.len();
1114 } else {
1115 append.push_str(&pad_str[..pad_count]);
1116 pad_count = 0;
1117 }
1118 }
1119
1120 JSValue::new_string(ctx.intern(&(s + &append)))
1121}
1122
1123fn string_replace_all(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1124 if args.len() < 3 {
1125 if args.is_empty() {
1126 return JSValue::new_string(ctx.intern(""));
1127 }
1128 return args[0];
1129 }
1130 let s = if args[0].is_string() {
1131 ctx.get_atom_str(args[0].get_atom()).to_string()
1132 } else {
1133 return args[0];
1134 };
1135 let search = if args[1].is_string() {
1136 ctx.get_atom_str(args[1].get_atom()).to_string()
1137 } else {
1138 return args[0];
1139 };
1140 let replacement = if args[2].is_string() {
1141 ctx.get_atom_str(args[2].get_atom()).to_string()
1142 } else {
1143 String::new()
1144 };
1145
1146 let result = s.replace(&search as &str, &replacement as &str);
1147 JSValue::new_string(ctx.intern(&result))
1148}
1149
1150fn string_at(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1151 if args.is_empty() {
1152 return JSValue::undefined();
1153 }
1154 let s = if args[0].is_string() {
1155 ctx.get_atom_str(args[0].get_atom()).to_string()
1156 } else {
1157 return JSValue::undefined();
1158 };
1159 let chars: Vec<char> = s.chars().collect();
1160 let len = chars.len();
1161 let idx = if args.len() > 1 { args[1].get_int() } else { 0 };
1162 let actual_idx = if idx < 0 { len as i64 + idx } else { idx };
1163 if actual_idx < 0 || actual_idx as usize >= len {
1164 return JSValue::undefined();
1165 }
1166 let c = chars[actual_idx as usize];
1167 JSValue::new_string(ctx.intern(&c.to_string()))
1168}
1169
1170fn string_is_well_formed(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1171 if args.is_empty() {
1172 return JSValue::bool(false);
1173 }
1174 let this = args[0];
1175 if !this.is_string() {
1176 return JSValue::bool(false);
1177 }
1178
1179 JSValue::bool(true)
1180}
1181
1182fn string_to_well_formed(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1183 if args.is_empty() {
1184 return JSValue::undefined();
1185 }
1186 let this = args[0];
1187 if !this.is_string() {
1188 return this;
1189 }
1190 let s = ctx.get_atom_str(this.get_atom()).to_string();
1191
1192 JSValue::new_string(ctx.intern(&s))
1193}
1194
1195fn string_code_point_at(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1196 if args.is_empty() {
1197 return JSValue::undefined();
1198 }
1199 let this = args[0];
1200 if !this.is_string() {
1201 return JSValue::undefined();
1202 }
1203 let s = ctx.get_atom_str(this.get_atom()).to_string();
1204 let chars: Vec<char> = s.chars().collect();
1205 let len = chars.len();
1206 let index = if args.len() > 1 {
1207 args[1].get_int() as i64
1208 } else {
1209 0
1210 };
1211 let actual_index = if index < 0 { len as i64 + index } else { index };
1212 if actual_index < 0 || actual_index as usize >= len {
1213 return JSValue::undefined();
1214 }
1215 let c = chars[actual_index as usize];
1216 JSValue::new_int(c as u32 as i64)
1217}
1218
1219fn string_match_all(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1220 if args.is_empty() {
1221 let mut result = JSObject::new_array();
1222 result.set(ctx.common_atoms.length, JSValue::new_int(0));
1223 let ptr = Box::into_raw(Box::new(result)) as usize;
1224 return JSValue::new_object(ptr);
1225 }
1226
1227 let this = args[0];
1228 let s = if this.is_string() {
1229 ctx.get_atom_str(this.get_atom()).to_string()
1230 } else {
1231 let mut result = JSObject::new_array();
1232 result.set(ctx.common_atoms.length, JSValue::new_int(0));
1233 let ptr = Box::into_raw(Box::new(result)) as usize;
1234 return JSValue::new_object(ptr);
1235 };
1236
1237 let pattern = if args.len() > 1 {
1238 if args[1].is_object() {
1239 let regexp_obj = args[1].as_object();
1240
1241 let flags_atom = ctx.common_atoms.__flags__;
1242 let flags = if let Some(f) = regexp_obj.get(flags_atom) {
1243 if f.is_string() {
1244 ctx.get_atom_str(f.get_atom()).to_string()
1245 } else {
1246 String::new()
1247 }
1248 } else {
1249 String::new()
1250 };
1251
1252 if !flags.contains('g') {
1253 let mut err_obj = JSObject::new();
1254 err_obj.set(
1255 ctx.common_atoms.name,
1256 JSValue::new_string(ctx.intern("TypeError")),
1257 );
1258 err_obj.set(
1259 ctx.common_atoms.message,
1260 JSValue::new_string(ctx.intern("matchAll requires a global RegExp")),
1261 );
1262 let ptr = Box::into_raw(Box::new(err_obj)) as usize;
1263 return JSValue::new_object(ptr);
1264 }
1265
1266 let pattern_atom = ctx.common_atoms.__pattern__;
1267 if let Some(p) = regexp_obj.get(pattern_atom) {
1268 if p.is_string() {
1269 ctx.get_atom_str(p.get_atom()).to_string()
1270 } else {
1271 String::new()
1272 }
1273 } else {
1274 String::new()
1275 }
1276 } else if args[1].is_string() {
1277 ctx.get_atom_str(args[1].get_atom()).to_string()
1278 } else {
1279 String::new()
1280 }
1281 } else {
1282 String::new()
1283 };
1284
1285 let mut result_array = JSObject::new_array();
1286 let length_atom = ctx.common_atoms.length;
1287 let mut match_count = 0;
1288
1289 if pattern.is_empty() {
1290 result_array.set(length_atom, JSValue::new_int(0));
1291 let ptr = Box::into_raw(Box::new(result_array)) as usize;
1292 return JSValue::new_object(ptr);
1293 }
1294
1295 let mut search_pos = 0;
1296 while search_pos <= s.len() {
1297 let max_start = if pattern.len() <= s.len() {
1298 s.len() - pattern.len()
1299 } else {
1300 break;
1301 };
1302
1303 let mut found = false;
1304 for i in search_pos..=max_start {
1305 if i + pattern.len() <= s.len() {
1306 let slice = &s[i..i + pattern.len()];
1307 if slice == pattern {
1308 found = true;
1309 let mut match_obj = JSObject::new();
1310 match_obj.set(ctx.intern("0"), JSValue::new_string(ctx.intern(slice)));
1311 match_obj.set(ctx.common_atoms.index, JSValue::new_int(i as i64));
1312 match_obj.set(ctx.common_atoms.input, JSValue::new_string(ctx.intern(&s)));
1313 match_obj.set(ctx.common_atoms.length, JSValue::new_int(1));
1314
1315 let key = ctx.int_atom_mut(match_count);
1316 result_array.set(
1317 key,
1318 JSValue::new_object(Box::into_raw(Box::new(match_obj)) as usize),
1319 );
1320
1321 match_count += 1;
1322 search_pos = i + pattern.len();
1323 if pattern.len() == 0 {
1324 search_pos += 1;
1325 }
1326 break;
1327 }
1328 }
1329 }
1330
1331 if !found {
1332 break;
1333 }
1334 }
1335
1336 result_array.set(length_atom, JSValue::new_int(match_count as i64));
1337 let ptr = Box::into_raw(Box::new(result_array)) as usize;
1338 JSValue::new_object(ptr)
1339}
1340
1341fn string_search(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1342 if args.len() < 2 {
1343 return JSValue::new_int(-1);
1344 }
1345 let s = if args[0].is_string() {
1346 ctx.get_atom_str(args[0].get_atom()).to_string()
1347 } else {
1348 return JSValue::new_int(-1);
1349 };
1350
1351 if args[1].is_object() {
1352 let regexp_obj = args[1].as_object();
1353 let pattern_atom = ctx.common_atoms.__pattern__;
1354 let flags_atom = ctx.common_atoms.__flags__;
1355
1356 let pattern = if let Some(p) = regexp_obj.get(pattern_atom) {
1357 if p.is_string() {
1358 ctx.get_atom_str(p.get_atom()).to_string()
1359 } else {
1360 return JSValue::new_int(-1);
1361 }
1362 } else {
1363 return JSValue::new_int(-1);
1364 };
1365
1366 let flags = if let Some(f) = regexp_obj.get(flags_atom) {
1367 if f.is_string() {
1368 ctx.get_atom_str(f.get_atom()).to_string()
1369 } else {
1370 String::new()
1371 }
1372 } else {
1373 String::new()
1374 };
1375
1376 let ignore_case = flags.contains('i');
1377 let s_lower = if ignore_case {
1378 s.to_lowercase()
1379 } else {
1380 String::new()
1381 };
1382 let p_lower = if ignore_case {
1383 pattern.to_lowercase()
1384 } else {
1385 String::new()
1386 };
1387
1388 let found = if ignore_case {
1389 s_lower.find(&p_lower)
1390 } else {
1391 s.find(&pattern)
1392 };
1393 JSValue::new_int(found.map(|i| i as i64).unwrap_or(-1))
1394 } else if args[1].is_string() {
1395 let search = ctx.get_atom_str(args[1].get_atom()).to_string();
1396 JSValue::new_int(s.find(&search).map(|i| i as i64).unwrap_or(-1))
1397 } else {
1398 JSValue::new_int(-1)
1399 }
1400}
1401
1402fn string_match(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1403 if args.len() < 2 {
1404 return JSValue::null();
1405 }
1406 let s = if args[0].is_string() {
1407 ctx.get_atom_str(args[0].get_atom()).to_string()
1408 } else {
1409 return JSValue::null();
1410 };
1411
1412 if args[1].is_object() {
1413 let regexp_obj = args[1].as_object();
1414 let pattern_atom = ctx.common_atoms.__pattern__;
1415 let flags_atom = ctx.common_atoms.__flags__;
1416
1417 let pattern = if let Some(p) = regexp_obj.get(pattern_atom) {
1418 if p.is_string() {
1419 ctx.get_atom_str(p.get_atom()).to_string()
1420 } else {
1421 return JSValue::null();
1422 }
1423 } else {
1424 return JSValue::null();
1425 };
1426
1427 let flags = if let Some(f) = regexp_obj.get(flags_atom) {
1428 if f.is_string() {
1429 ctx.get_atom_str(f.get_atom()).to_string()
1430 } else {
1431 String::new()
1432 }
1433 } else {
1434 String::new()
1435 };
1436
1437 let is_global = flags.contains('g');
1438 let ignore_case = flags.contains('i');
1439 let s_lower = if ignore_case {
1440 s.to_lowercase()
1441 } else {
1442 String::new()
1443 };
1444 let p_lower = if ignore_case {
1445 pattern.to_lowercase()
1446 } else {
1447 String::new()
1448 };
1449
1450 if is_global {
1451 let mut results: Vec<String> = Vec::new();
1452 let mut search_from = 0;
1453 loop {
1454 let found = if ignore_case {
1455 s_lower[search_from..]
1456 .find(&p_lower)
1457 .map(|i| i + search_from)
1458 } else {
1459 s[search_from..].find(&pattern).map(|i| i + search_from)
1460 };
1461 if let Some(pos) = found {
1462 let end = (pos + pattern.len()).min(s.len());
1463 results.push(s[pos..end].to_string());
1464 search_from = end;
1465 if pattern.is_empty() {
1466 if search_from < s.len() {
1467 search_from += 1;
1468 } else {
1469 break;
1470 }
1471 }
1472 } else {
1473 break;
1474 }
1475 }
1476 if results.is_empty() {
1477 return JSValue::null();
1478 }
1479 let mut arr = JSObject::new_array();
1480 let length_atom = ctx.common_atoms.length;
1481 for (i, m) in results.iter().enumerate() {
1482 let key = ctx.int_atom_mut(i);
1483 arr.set(key, JSValue::new_string(ctx.intern(m)));
1484 }
1485 arr.set(length_atom, JSValue::new_int(results.len() as i64));
1486 let ptr = Box::into_raw(Box::new(arr)) as usize;
1487 JSValue::new_object(ptr)
1488 } else {
1489 let found = if ignore_case {
1490 s_lower.find(&p_lower)
1491 } else {
1492 s.find(&pattern)
1493 };
1494 if let Some(pos) = found {
1495 let end = (pos + pattern.len()).min(s.len());
1496 let match_str = &s[pos..end];
1497 let mut result = JSObject::new_array();
1498 result.set(ctx.intern("0"), JSValue::new_string(ctx.intern(match_str)));
1499 result.set(ctx.common_atoms.index, JSValue::new_int(pos as i64));
1500 result.set(ctx.common_atoms.input, JSValue::new_string(ctx.intern(&s)));
1501 result.set(ctx.common_atoms.length, JSValue::new_int(1));
1502 let ptr = Box::into_raw(Box::new(result)) as usize;
1503 JSValue::new_object(ptr)
1504 } else {
1505 JSValue::null()
1506 }
1507 }
1508 } else {
1509 JSValue::null()
1510 }
1511}
1512
1513fn string_substr(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
1514 if args.is_empty() {
1515 return JSValue::new_string(ctx.intern(""));
1516 }
1517 let s = if args[0].is_string() {
1518 ctx.get_atom_str(args[0].get_atom()).to_string()
1519 } else {
1520 return JSValue::new_string(ctx.intern(""));
1521 };
1522 let len = char_count(&s);
1523
1524 let start = if args.len() > 1 {
1525 let v = args[1].get_int();
1526 if v < 0 {
1527 (len as i64 + v).max(0) as usize
1528 } else {
1529 (v as usize).min(len)
1530 }
1531 } else {
1532 0
1533 };
1534
1535 let end = if args.len() > 2 {
1536 let v = args[2].get_int();
1537 if v <= 0 {
1538 return JSValue::new_string(ctx.intern(""));
1539 }
1540 (start + v as usize).min(len)
1541 } else {
1542 len
1543 };
1544 let start_b = byte_index_for_char_pos(&s, start);
1545 let end_b = byte_index_for_char_pos(&s, end);
1546 JSValue::new_string(ctx.intern(&s[start_b..end_b]))
1547}