rhai/packages/string_more.rs
1use crate::plugin::*;
2use crate::{
3 def_package, Dynamic, ExclusiveRange, ImmutableString, InclusiveRange, RhaiResultOf,
4 SmartString, INT,
5};
6#[cfg(feature = "no_std")]
7use std::prelude::v1::*;
8use std::{any::TypeId, convert::TryFrom, mem};
9
10use super::string_basic::{print_with_func, FUNC_TO_STRING};
11
12def_package! {
13 /// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage]
14 pub MoreStringPackage(lib) {
15 lib.set_standard_lib(true);
16
17 combine_with_exported_module!(lib, "string", string_functions);
18 }
19}
20
21#[export_module]
22mod string_functions {
23 #[rhai_fn(name = "+", pure)]
24 pub fn add_append(
25 ctx: NativeCallContext,
26 string: &mut ImmutableString,
27 mut item: Dynamic,
28 ) -> ImmutableString {
29 let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item);
30
31 if s.is_empty() {
32 return string.clone();
33 }
34
35 let mut buf = <ImmutableString as AsRef<SmartString>>::as_ref(string).clone();
36 buf.push_str(&s);
37 buf.into()
38 }
39 #[rhai_fn(name = "+=", name = "append")]
40 pub fn add(ctx: NativeCallContext, string: &mut ImmutableString, mut item: Dynamic) {
41 let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item);
42
43 if s.is_empty() {
44 return;
45 }
46
47 let mut buf = <ImmutableString as AsRef<SmartString>>::as_ref(string).clone();
48 buf.push_str(&s);
49 *string = buf.into();
50 }
51 #[rhai_fn(name = "+", pure)]
52 pub fn add_prepend(
53 ctx: NativeCallContext,
54 item: &mut Dynamic,
55 string: &str,
56 ) -> ImmutableString {
57 let mut s = print_with_func(FUNC_TO_STRING, &ctx, item);
58
59 if !string.is_empty() {
60 s.make_mut().push_str(string);
61 }
62
63 s
64 }
65
66 // The following are needed in order to override the generic versions with `Dynamic` parameters.
67
68 #[rhai_fn(name = "+")]
69 pub fn add_append_str(string1: &str, string2: &str) -> ImmutableString {
70 let mut buf = SmartString::new_const();
71 buf.push_str(string1);
72 buf.push_str(string2);
73 buf.into()
74 }
75 #[rhai_fn(name = "+")]
76 pub fn add_append_char(string: &str, character: char) -> ImmutableString {
77 let mut buf = SmartString::new_const();
78 buf.push_str(string);
79 buf.push(character);
80 buf.into()
81 }
82 #[rhai_fn(name = "+")]
83 pub fn add_prepend_char(character: char, string: &str) -> ImmutableString {
84 let mut buf = SmartString::new_const();
85 buf.push(character);
86 buf.push_str(string);
87 buf.into()
88 }
89
90 #[allow(unused_variables)]
91 #[rhai_fn(name = "+")]
92 pub const fn add_append_unit(string: ImmutableString, item: ()) -> ImmutableString {
93 string
94 }
95 #[allow(unused_variables)]
96 #[rhai_fn(name = "+")]
97 pub const fn add_prepend_unit(item: (), string: ImmutableString) -> ImmutableString {
98 string
99 }
100
101 #[rhai_fn(name = "+=")]
102 pub fn add_assign_append_str(string1: &mut ImmutableString, string2: ImmutableString) {
103 *string1 += string2;
104 }
105 #[rhai_fn(name = "+=", pure)]
106 pub fn add_assign_append_char(string: &mut ImmutableString, character: char) {
107 *string += character;
108 }
109 #[allow(unused_variables)]
110 #[rhai_fn(name = "+=")]
111 pub fn add_assign_append_unit(string: &mut ImmutableString, item: ()) {}
112
113 #[cfg(not(feature = "no_index"))]
114 pub mod blob_functions {
115 use crate::Blob;
116
117 #[rhai_fn(name = "+")]
118 pub fn add_append(string: ImmutableString, utf8: Blob) -> ImmutableString {
119 if utf8.is_empty() {
120 return string;
121 }
122
123 let s = String::from_utf8_lossy(&utf8);
124
125 if string.is_empty() {
126 match s {
127 std::borrow::Cow::Borrowed(_) => String::from_utf8(utf8).unwrap().into(),
128 std::borrow::Cow::Owned(_) => s.into_owned().into(),
129 }
130 } else {
131 let mut x = string.into_owned();
132 x += s.as_ref();
133 x.into()
134 }
135 }
136 #[rhai_fn(name = "+=", name = "append")]
137 pub fn add(string: &mut ImmutableString, utf8: Blob) {
138 if utf8.is_empty() {
139 return;
140 }
141
142 let mut s = <ImmutableString as AsRef<SmartString>>::as_ref(string).clone();
143 s.push_str(&String::from_utf8_lossy(&utf8));
144 *string = s.into();
145 }
146 #[rhai_fn(name = "+")]
147 pub fn add_prepend(utf8: Blob, string: &str) -> ImmutableString {
148 let s = String::from_utf8_lossy(&utf8);
149 let mut s = match s {
150 std::borrow::Cow::Borrowed(_) => String::from_utf8(utf8).unwrap(),
151 std::borrow::Cow::Owned(_) => s.into_owned(),
152 };
153
154 if !string.is_empty() {
155 s += string;
156 }
157
158 s.into()
159 }
160
161 /// Convert the string into an UTF-8 encoded byte-stream as a BLOB.
162 ///
163 /// # Example
164 ///
165 /// ```rhai
166 /// let text = "朝には紅顔ありて夕べには白骨となる";
167 ///
168 /// let bytes = text.to_blob();
169 ///
170 /// print(bytes.len()); // prints 51
171 /// ```
172 pub fn to_blob(string: &str) -> Blob {
173 if string.is_empty() {
174 return Blob::new();
175 }
176
177 string.as_bytes().into()
178 }
179 }
180
181 /// Return the length of the string, in number of characters.
182 ///
183 /// # Example
184 ///
185 /// ```rhai
186 /// let text = "朝には紅顔ありて夕べには白骨となる";
187 ///
188 /// print(text.len); // prints 17
189 /// ```
190 #[rhai_fn(name = "len", get = "len")]
191 pub fn len(string: &str) -> INT {
192 if string.is_empty() {
193 return 0;
194 }
195
196 INT::try_from(string.chars().count()).unwrap_or(INT::MAX)
197 }
198 /// Return true if the string is empty.
199 #[rhai_fn(name = "is_empty", get = "is_empty")]
200 pub const fn is_empty(string: &str) -> bool {
201 string.len() == 0
202 }
203 /// Return the length of the string, in number of bytes used to store it in UTF-8 encoding.
204 ///
205 /// # Example
206 ///
207 /// ```rhai
208 /// let text = "朝には紅顔ありて夕べには白骨となる";
209 ///
210 /// print(text.bytes); // prints 51
211 /// ```
212 #[rhai_fn(name = "bytes", get = "bytes")]
213 pub fn bytes(string: &str) -> INT {
214 if string.is_empty() {
215 return 0;
216 }
217
218 INT::try_from(string.len()).unwrap_or(INT::MAX)
219 }
220 /// Remove all occurrences of a sub-string from the string.
221 ///
222 /// # Example
223 ///
224 /// ```rhai
225 /// let text = "hello, world! hello, foobar!";
226 ///
227 /// text.remove("hello");
228 ///
229 /// print(text); // prints ", world! , foobar!"
230 /// ```
231 pub fn remove(string: &mut ImmutableString, sub_string: &str) {
232 *string -= sub_string;
233 }
234 /// Remove all occurrences of a character from the string.
235 ///
236 /// # Example
237 ///
238 /// ```rhai
239 /// let text = "hello, world! hello, foobar!";
240 ///
241 /// text.remove("o");
242 ///
243 /// print(text); // prints "hell, wrld! hell, fbar!"
244 /// ```
245 #[rhai_fn(name = "remove")]
246 pub fn remove_char(string: &mut ImmutableString, character: char) {
247 *string -= character;
248 }
249 /// Clear the string, making it empty.
250 pub fn clear(string: &mut ImmutableString) {
251 if string.is_empty() {
252 return;
253 }
254
255 match string.get_mut() {
256 Some(s) => s.clear(),
257 _ => *string = ImmutableString::new(),
258 }
259 }
260 /// Cut off the string at the specified number of characters.
261 ///
262 /// * If `len` ≤ 0, the string is cleared.
263 /// * If `len` ≥ length of string, the string is not truncated.
264 ///
265 /// # Example
266 ///
267 /// ```rhai
268 /// let text = "hello, world! hello, foobar!";
269 ///
270 /// text.truncate(13);
271 ///
272 /// print(text); // prints "hello, world!"
273 ///
274 /// text.truncate(10);
275 ///
276 /// print(text); // prints "hello, world!"
277 /// ```
278 pub fn truncate(string: &mut ImmutableString, len: INT) {
279 if len <= 0 {
280 clear(string);
281 return;
282 }
283 let Ok(len) = usize::try_from(len) else {
284 return;
285 };
286
287 if let Some((index, _)) = string.char_indices().nth(len) {
288 let copy = string.make_mut();
289 copy.truncate(index);
290 }
291 }
292 /// Remove whitespace characters from both ends of the string.
293 ///
294 /// # Example
295 ///
296 /// ```rhai
297 /// let text = " hello ";
298 ///
299 /// text.trim();
300 ///
301 /// print(text); // prints "hello"
302 /// ```
303 pub fn trim(string: &mut ImmutableString) {
304 if let Some(s) = string.get_mut() {
305 let trimmed = s.trim();
306
307 if trimmed != s {
308 *s = trimmed.into();
309 }
310 } else {
311 let trimmed = string.trim();
312
313 if trimmed != string {
314 *string = trimmed.into();
315 }
316 }
317 }
318 /// Remove the last character from the string and return it.
319 ///
320 /// If the string is empty, `()` is returned.
321 ///
322 /// # Example
323 ///
324 /// ```rhai
325 /// let text = "hello, world!";
326 ///
327 /// print(text.pop()); // prints '!'
328 ///
329 /// print(text); // prints "hello, world"
330 /// ```
331 pub fn pop(string: &mut ImmutableString) -> Dynamic {
332 if string.is_empty() {
333 return Dynamic::UNIT;
334 }
335
336 string.make_mut().pop().map_or(Dynamic::UNIT, Into::into)
337 }
338 /// Remove a specified number of characters from the end of the string and return it as a
339 /// new string.
340 ///
341 /// * If `len` ≤ 0, the string is not modified and an empty string is returned.
342 /// * If `len` ≥ length of string, the string is cleared and the entire string returned.
343 ///
344 /// # Example
345 ///
346 /// ```rhai
347 /// let text = "hello, world!";
348 ///
349 /// print(text.pop(4)); // prints "rld!"
350 ///
351 /// print(text); // prints "hello, wo"
352 /// ```
353 #[rhai_fn(name = "pop")]
354 pub fn pop_string(
355 ctx: NativeCallContext,
356 string: &mut ImmutableString,
357 len: INT,
358 ) -> ImmutableString {
359 if string.is_empty() || len <= 0 {
360 return ctx.engine().const_empty_string();
361 }
362 if let Ok(len) = usize::try_from(len) {
363 if len < string.chars().count() {
364 let s = string.make_mut();
365
366 let chars = (0..len.min(s.len()))
367 .filter_map(|_| s.pop())
368 .collect::<Vec<_>>();
369
370 return chars.into_iter().rev().collect::<SmartString>().into();
371 }
372 }
373
374 let mut r = ctx.engine().const_empty_string();
375 mem::swap(string, &mut r);
376 r
377 }
378
379 /// Convert the string to all upper-case and return it as a new string.
380 ///
381 /// # Example
382 ///
383 /// ```rhai
384 /// let text = "hello, world!"
385 ///
386 /// print(text.to_upper()); // prints "HELLO, WORLD!"
387 ///
388 /// print(text); // prints "hello, world!"
389 /// ```
390 pub fn to_upper(string: ImmutableString) -> ImmutableString {
391 if string.chars().all(char::is_uppercase) {
392 return string;
393 }
394
395 string.to_uppercase().into()
396 }
397 /// Convert the string to all upper-case.
398 ///
399 /// # Example
400 ///
401 /// ```rhai
402 /// let text = "hello, world!"
403 ///
404 /// text.make_upper();
405 ///
406 /// print(text); // prints "HELLO, WORLD!";
407 /// ```
408 pub fn make_upper(string: &mut ImmutableString) {
409 if string.is_empty() || string.chars().all(char::is_uppercase) {
410 return;
411 }
412
413 *string = string.to_uppercase().into();
414 }
415 /// Convert the string to all lower-case and return it as a new string.
416 ///
417 /// # Example
418 ///
419 /// ```rhai
420 /// let text = "HELLO, WORLD!"
421 ///
422 /// print(text.to_lower()); // prints "hello, world!"
423 ///
424 /// print(text); // prints "HELLO, WORLD!"
425 /// ```
426 pub fn to_lower(string: ImmutableString) -> ImmutableString {
427 if string.is_empty() || string.chars().all(char::is_lowercase) {
428 return string;
429 }
430
431 string.to_lowercase().into()
432 }
433 /// Convert the string to all lower-case.
434 ///
435 /// # Example
436 ///
437 /// ```rhai
438 /// let text = "HELLO, WORLD!"
439 ///
440 /// text.make_lower();
441 ///
442 /// print(text); // prints "hello, world!";
443 /// ```
444 pub fn make_lower(string: &mut ImmutableString) {
445 if string.is_empty() || string.chars().all(char::is_lowercase) {
446 return;
447 }
448
449 *string = string.to_lowercase().into();
450 }
451
452 /// Convert the character to upper-case and return it as a new character.
453 ///
454 /// # Example
455 ///
456 /// ```rhai
457 /// let ch = 'a';
458 ///
459 /// print(ch.to_upper()); // prints 'A'
460 ///
461 /// print(ch); // prints 'a'
462 /// ```
463 #[rhai_fn(name = "to_upper")]
464 pub fn to_upper_char(character: char) -> char {
465 let mut stream = character.to_uppercase();
466 let ch = stream.next().unwrap();
467 if stream.next().is_some() {
468 character
469 } else {
470 ch
471 }
472 }
473 /// Convert the character to upper-case.
474 ///
475 /// # Example
476 ///
477 /// ```rhai
478 /// let ch = 'a';
479 ///
480 /// ch.make_upper();
481 ///
482 /// print(ch); // prints 'A'
483 /// ```
484 #[rhai_fn(name = "make_upper")]
485 pub fn make_upper_char(character: &mut char) {
486 *character = to_upper_char(*character);
487 }
488 /// Convert the character to lower-case and return it as a new character.
489 ///
490 /// # Example
491 ///
492 /// ```rhai
493 /// let ch = 'A';
494 ///
495 /// print(ch.to_lower()); // prints 'a'
496 ///
497 /// print(ch); // prints 'A'
498 /// ```
499 #[rhai_fn(name = "to_lower")]
500 pub fn to_lower_char(character: char) -> char {
501 let mut stream = character.to_lowercase();
502 let ch = stream.next().unwrap();
503 if stream.next().is_some() {
504 character
505 } else {
506 ch
507 }
508 }
509 /// Convert the character to lower-case.
510 ///
511 /// # Example
512 ///
513 /// ```rhai
514 /// let ch = 'A';
515 ///
516 /// ch.make_lower();
517 ///
518 /// print(ch); // prints 'a'
519 /// ```
520 #[rhai_fn(name = "make_lower")]
521 pub fn make_lower_char(character: &mut char) {
522 *character = to_lower_char(*character);
523 }
524
525 /// Return `true` if the string contains a specified string.
526 ///
527 /// # Example
528 ///
529 /// ```rhai
530 /// let text = "hello, world!";
531 ///
532 /// print(text.contains("hello")); // prints true
533 ///
534 /// print(text.contains("hey")); // prints false
535 /// ```
536 pub fn contains(string: &str, match_string: &str) -> bool {
537 string.contains(match_string)
538 }
539
540 /// Return `true` if the string contains a specified character.
541 ///
542 /// # Example
543 ///
544 /// ```rhai
545 /// let text = "hello, world!";
546 ///
547 /// print(text.contains('h')); // prints true
548 ///
549 /// print(text.contains('x')); // prints false
550 /// ```
551 #[rhai_fn(name = "contains")]
552 pub fn contains_char(string: &str, character: char) -> bool {
553 string.contains(character)
554 }
555
556 /// Return `true` if the string starts with a specified string.
557 ///
558 /// # Example
559 ///
560 /// ```rhai
561 /// let text = "hello, world!";
562 ///
563 /// print(text.starts_with("hello")); // prints true
564 ///
565 /// print(text.starts_with("world")); // prints false
566 /// ```
567 pub fn starts_with(string: &str, match_string: &str) -> bool {
568 string.starts_with(match_string)
569 }
570 /// Return `true` if the string starts with a specified character.
571 ///
572 /// # Example
573 ///
574 /// ```rhai
575 /// let text = "hello, world!";
576 ///
577 /// print(text.starts_with('h')); // prints true
578 ///
579 /// print(text.starts_with('w')); // prints false
580 /// ```
581 #[rhai_fn(name = "starts_with")]
582 pub fn starts_with_char(string: &str, character: char) -> bool {
583 string.starts_with(character)
584 }
585 /// Return `true` if the string ends with a specified string.
586 ///
587 /// # Example
588 ///
589 /// ```rhai
590 /// let text = "hello, world!";
591 ///
592 /// print(text.ends_with("world!")); // prints true
593 ///
594 /// print(text.ends_with("hello")); // prints false
595 /// ```
596 pub fn ends_with(string: &str, match_string: &str) -> bool {
597 string.ends_with(match_string)
598 }
599 /// Return `true` if the string ends with a specified character.
600 ///
601 /// # Example
602 ///
603 /// ```rhai
604 /// let text = "hello, world!";
605 ///
606 /// print(text.ends_with('w')); // prints true
607 ///
608 /// print(text.ends_with('h')); // prints false
609 /// ```
610 #[rhai_fn(name = "ends_with")]
611 pub fn ends_with_char(string: &str, character: char) -> bool {
612 string.ends_with(character)
613 }
614
615 /// Find the specified `character` in the string, starting from the specified `start` position,
616 /// and return the first index where it is found.
617 /// If the `character` is not found, `-1` is returned.
618 ///
619 /// * If `start` < 0, position counts from the end of the string (`-1` is the last character).
620 /// * If `start` < -length of string, position counts from the beginning of the string.
621 /// * If `start` ≥ length of string, `-1` is returned.
622 ///
623 /// # Example
624 ///
625 /// ```rhai
626 /// let text = "hello, world!";
627 ///
628 /// print(text.index_of('l', 5)); // prints 10 (first index after 5)
629 ///
630 /// print(text.index_of('o', -7)); // prints 8
631 ///
632 /// print(text.index_of('x', 0)); // prints -1
633 /// ```
634 #[rhai_fn(name = "index_of")]
635 pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT {
636 if string.is_empty() {
637 return -1;
638 }
639
640 let start = if start < 0 {
641 let Ok(abs_start) = usize::try_from(start.unsigned_abs()) else {
642 return -1 as INT;
643 };
644
645 let chars: Vec<_> = string.chars().collect();
646 let num_chars = chars.len();
647
648 if abs_start > num_chars {
649 0
650 } else {
651 chars
652 .into_iter()
653 .take(num_chars - abs_start)
654 .collect::<String>()
655 .len()
656 }
657 } else if start == 0 {
658 0
659 } else if let Ok(start) = usize::try_from(start) {
660 if start >= string.chars().count() {
661 return -1;
662 }
663 string.chars().take(start).collect::<String>().len()
664 } else {
665 return -1;
666 };
667
668 string[start..]
669 .find(character)
670 .and_then(|index| INT::try_from(string[0..start + index].chars().count()).ok())
671 .unwrap_or(-1)
672 }
673 /// Find the specified `character` in the string and return the first index where it is found.
674 /// If the `character` is not found, `-1` is returned.
675 ///
676 /// # Example
677 ///
678 /// ```rhai
679 /// let text = "hello, world!";
680 ///
681 /// print(text.index_of('l')); // prints 2 (first index)
682 ///
683 /// print(text.index_of('x')); // prints -1
684 /// ```
685 #[rhai_fn(name = "index_of")]
686 pub fn index_of_char(string: &str, character: char) -> INT {
687 if string.is_empty() {
688 return -1;
689 }
690
691 string.find(character).map_or(-1 as INT, |index| {
692 INT::try_from(string[0..index].chars().count()).unwrap_or(-1)
693 })
694 }
695 /// Find the specified sub-string in the string, starting from the specified `start` position,
696 /// and return the first index where it is found.
697 /// If the sub-string is not found, `-1` is returned.
698 ///
699 /// * If `start` < 0, position counts from the end of the string (`-1` is the last character).
700 /// * If `start` < -length of string, position counts from the beginning of the string.
701 /// * If `start` ≥ length of string, `-1` is returned.
702 ///
703 /// # Example
704 ///
705 /// ```rhai
706 /// let text = "hello, world! hello, foobar!";
707 ///
708 /// print(text.index_of("ll", 5)); // prints 16 (first index after 5)
709 ///
710 /// print(text.index_of("ll", -15)); // prints 16
711 ///
712 /// print(text.index_of("xx", 0)); // prints -1
713 /// ```
714 #[rhai_fn(name = "index_of")]
715 pub fn index_of_string_starting_from(string: &str, find_string: &str, start: INT) -> INT {
716 if string.is_empty() {
717 return -1;
718 }
719
720 let start = if start < 0 {
721 let Ok(abs_start) = usize::try_from(start.unsigned_abs()) else {
722 return -1 as INT;
723 };
724
725 let chars = string.chars().collect::<Vec<_>>();
726 let num_chars = chars.len();
727
728 if abs_start > num_chars {
729 0
730 } else {
731 chars
732 .into_iter()
733 .take(num_chars - abs_start)
734 .collect::<String>()
735 .len()
736 }
737 } else if start == 0 {
738 0
739 } else if let Ok(start) = usize::try_from(start) {
740 if start >= string.chars().count() {
741 return -1;
742 }
743 string.chars().take(start).collect::<String>().len()
744 } else {
745 return -1;
746 };
747
748 string[start..]
749 .find(find_string)
750 .and_then(|index| INT::try_from(string[0..start + index].chars().count()).ok())
751 .unwrap_or(-1)
752 }
753 /// Find the specified `character` in the string and return the first index where it is found.
754 /// If the `character` is not found, `-1` is returned.
755 ///
756 /// # Example
757 ///
758 /// ```rhai
759 /// let text = "hello, world! hello, foobar!";
760 ///
761 /// print(text.index_of("ll")); // prints 2 (first index)
762 ///
763 /// print(text.index_of("xx:)); // prints -1
764 /// ```
765 #[rhai_fn(name = "index_of")]
766 pub fn index_of(string: &str, find_string: &str) -> INT {
767 if string.is_empty() {
768 return -1;
769 }
770
771 string.find(find_string).map_or(-1 as INT, |index| {
772 INT::try_from(string[0..index].chars().count()).unwrap_or(-1)
773 })
774 }
775
776 /// Get the character at the `index` position in the string.
777 ///
778 /// * If `index` < 0, position counts from the end of the string (`-1` is the last character).
779 /// * If `index` < -length of string, zero is returned.
780 /// * If `index` ≥ length of string, zero is returned.
781 ///
782 /// # Example
783 ///
784 /// ```rhai
785 /// let text = "hello, world!";
786 ///
787 /// print(text.get(0)); // prints 'h'
788 ///
789 /// print(text.get(-1)); // prints '!'
790 ///
791 /// print(text.get(99)); // prints empty (for '()')'
792 /// ```
793 pub fn get(string: &str, index: INT) -> Dynamic {
794 if index >= 0 {
795 let Ok(index) = usize::try_from(index) else {
796 return Dynamic::UNIT;
797 };
798
799 string
800 .chars()
801 .nth(index)
802 .map_or_else(|| Dynamic::UNIT, Into::into)
803 } else {
804 // Count from end if negative
805 let Ok(abs_index) = usize::try_from(index.unsigned_abs()) else {
806 return Dynamic::UNIT;
807 };
808
809 string
810 .chars()
811 .rev()
812 .nth(abs_index - 1)
813 .map_or_else(|| Dynamic::UNIT, Into::into)
814 }
815 }
816 /// Set the `index` position in the string to a new `character`.
817 ///
818 /// * If `index` < 0, position counts from the end of the string (`-1` is the last character).
819 /// * If `index` < -length of string, the string is not modified.
820 /// * If `index` ≥ length of string, the string is not modified.
821 ///
822 /// # Example
823 ///
824 /// ```rhai
825 /// let text = "hello, world!";
826 ///
827 /// text.set(3, 'x');
828 ///
829 /// print(text); // prints "helxo, world!"
830 ///
831 /// text.set(-3, 'x');
832 ///
833 /// print(text); // prints "hello, worxd!"
834 ///
835 /// text.set(99, 'x');
836 ///
837 /// print(text); // prints "hello, worxd!"
838 /// ```
839 pub fn set(string: &mut ImmutableString, index: INT, character: char) {
840 if index >= 0 {
841 let Ok(index) = usize::try_from(index) else {
842 return;
843 };
844
845 *string = string
846 .chars()
847 .enumerate()
848 .map(|(i, ch)| if i == index { character } else { ch })
849 .collect();
850 } else {
851 let Ok(abs_index) = usize::try_from(index.unsigned_abs()) else {
852 return;
853 };
854 let string_len = string.chars().count();
855
856 if abs_index <= string_len {
857 let index = string_len - abs_index;
858 *string = string
859 .chars()
860 .enumerate()
861 .map(|(i, ch)| if i == index { character } else { ch })
862 .collect();
863 }
864 }
865 }
866
867 /// Copy an exclusive range of characters from the string and return it as a new string.
868 ///
869 /// # Example
870 ///
871 /// ```rhai
872 /// let text = "hello, world!";
873 ///
874 /// print(text.sub_string(3..7)); // prints "lo, "
875 /// ```
876 #[rhai_fn(name = "sub_string")]
877 pub fn sub_string_range(
878 ctx: NativeCallContext,
879 string: &str,
880 range: ExclusiveRange,
881 ) -> ImmutableString {
882 let start = INT::max(range.start, 0);
883 let end = INT::max(range.end, start);
884 sub_string(ctx, string, start, end - start)
885 }
886 /// Copy an inclusive range of characters from the string and return it as a new string.
887 ///
888 /// # Example
889 ///
890 /// ```rhai
891 /// let text = "hello, world!";
892 ///
893 /// print(text.sub_string(3..=7)); // prints "lo, w"
894 /// ```
895 #[rhai_fn(name = "sub_string")]
896 pub fn sub_string_inclusive_range(
897 ctx: NativeCallContext,
898 string: &str,
899 range: InclusiveRange,
900 ) -> ImmutableString {
901 let start = INT::max(*range.start(), 0);
902 let end = INT::min(INT::max(*range.end(), start), INT::MAX - 1);
903 sub_string(ctx, string, start, end - start + 1)
904 }
905 /// Copy a portion of the string and return it as a new string.
906 ///
907 /// * If `start` < 0, position counts from the end of the string (`-1` is the last character).
908 /// * If `start` < -length of string, position counts from the beginning of the string.
909 /// * If `start` ≥ length of string, an empty string is returned.
910 /// * If `len` ≤ 0, an empty string is returned.
911 /// * If `start` position + `len` ≥ length of string, entire portion of the string after the `start` position is copied and returned.
912 ///
913 /// # Example
914 ///
915 /// ```rhai
916 /// let text = "hello, world!";
917 ///
918 /// print(text.sub_string(3, 4)); // prints "lo, "
919 ///
920 /// print(text.sub_string(-8, 3)); // prints ", w"
921 /// ```
922 pub fn sub_string(
923 ctx: NativeCallContext,
924 string: &str,
925 start: INT,
926 len: INT,
927 ) -> ImmutableString {
928 if string.is_empty() || len <= 0 {
929 return ctx.engine().const_empty_string();
930 }
931
932 let mut chars = Vec::with_capacity(string.len());
933
934 if string.is_empty() || len <= 0 {
935 return ctx.engine().const_empty_string();
936 }
937
938 let offset = if start < 0 {
939 let Ok(abs_start) = usize::try_from(start.unsigned_abs()) else {
940 return ctx.engine().const_empty_string();
941 };
942
943 chars.extend(string.chars());
944
945 if abs_start > chars.len() {
946 0
947 } else {
948 chars.len() - abs_start
949 }
950 } else if let Ok(start) = usize::try_from(start) {
951 if start >= string.chars().count() {
952 return ctx.engine().const_empty_string();
953 }
954 start
955 } else {
956 return ctx.engine().const_empty_string();
957 };
958
959 if chars.is_empty() {
960 chars.extend(string.chars());
961 }
962
963 let len = usize::try_from(len).map_or_else(
964 |_| chars.len() - offset,
965 |len| {
966 if len.checked_add(offset).map_or(true, |x| x > chars.len()) {
967 chars.len() - offset
968 } else {
969 len
970 }
971 },
972 );
973
974 chars
975 .iter()
976 .skip(offset)
977 .take(len)
978 .copied()
979 .collect::<String>()
980 .into()
981 }
982 /// Copy a portion of the string beginning at the `start` position till the end and return it as
983 /// a new string.
984 ///
985 /// * If `start` < 0, position counts from the end of the string (`-1` is the last character).
986 /// * If `start` < -length of string, the entire string is copied and returned.
987 /// * If `start` ≥ length of string, an empty string is returned.
988 ///
989 /// # Example
990 ///
991 /// ```rhai
992 /// let text = "hello, world!";
993 ///
994 /// print(text.sub_string(5)); // prints ", world!"
995 ///
996 /// print(text.sub_string(-5)); // prints "orld!"
997 /// ```
998 #[rhai_fn(name = "sub_string")]
999 pub fn sub_string_starting_from(
1000 ctx: NativeCallContext,
1001 string: &str,
1002 start: INT,
1003 ) -> ImmutableString {
1004 if string.is_empty() {
1005 return ctx.engine().const_empty_string();
1006 }
1007
1008 let len = INT::try_from(string.len()).unwrap_or(INT::MAX);
1009 sub_string(ctx, string, start, len)
1010 }
1011
1012 /// Remove all characters from the string except those within an exclusive `range`.
1013 ///
1014 /// # Example
1015 ///
1016 /// ```rhai
1017 /// let text = "hello, world!";
1018 ///
1019 /// text.crop(2..8);
1020 ///
1021 /// print(text); // prints "llo, w"
1022 /// ```
1023 #[rhai_fn(name = "crop")]
1024 pub fn crop_range(ctx: NativeCallContext, string: &mut ImmutableString, range: ExclusiveRange) {
1025 let start = INT::max(range.start, 0);
1026 let end = INT::max(range.end, start);
1027 crop(ctx, string, start, end - start);
1028 }
1029 /// Remove all characters from the string except those within an inclusive `range`.
1030 ///
1031 /// # Example
1032 ///
1033 /// ```rhai
1034 /// let text = "hello, world!";
1035 ///
1036 /// text.crop(2..=8);
1037 ///
1038 /// print(text); // prints "llo, wo"
1039 /// ```
1040 #[rhai_fn(name = "crop")]
1041 pub fn crop_inclusive_range(
1042 ctx: NativeCallContext,
1043 string: &mut ImmutableString,
1044 range: InclusiveRange,
1045 ) {
1046 let start = INT::max(*range.start(), 0);
1047 let end = INT::min(INT::max(*range.end(), start), INT::MAX - 1);
1048 crop(ctx, string, start, end - start + 1);
1049 }
1050
1051 /// Remove all characters from the string except those within a range.
1052 ///
1053 /// * If `start` < 0, position counts from the end of the string (`-1` is the last character).
1054 /// * If `start` < -length of string, position counts from the beginning of the string.
1055 /// * If `start` ≥ length of string, the entire string is cleared.
1056 /// * If `len` ≤ 0, the entire string is cleared.
1057 /// * If `start` position + `len` ≥ length of string, only the portion of the string after the `start` position is retained.
1058 ///
1059 /// # Example
1060 ///
1061 /// ```rhai
1062 /// let text = "hello, world!";
1063 ///
1064 /// text.crop(2, 8);
1065 ///
1066 /// print(text); // prints "llo, wor"
1067 ///
1068 /// text.crop(-5, 3);
1069 ///
1070 /// print(text); // prints ", w"
1071 /// ```
1072 #[rhai_fn(name = "crop")]
1073 pub fn crop(ctx: NativeCallContext, string: &mut ImmutableString, start: INT, len: INT) {
1074 if string.is_empty() {
1075 return;
1076 }
1077 if len <= 0 {
1078 *string = ctx.engine().const_empty_string();
1079 return;
1080 }
1081
1082 let mut chars = Vec::with_capacity(string.len());
1083
1084 if string.is_empty() || len <= 0 {
1085 string.make_mut().clear();
1086 return;
1087 }
1088
1089 let offset = if start < 0 {
1090 let Ok(abs_start) = usize::try_from(start.unsigned_abs()) else {
1091 return;
1092 };
1093
1094 chars.extend(string.chars());
1095
1096 if abs_start > chars.len() {
1097 0
1098 } else {
1099 chars.len() - abs_start
1100 }
1101 } else if let Ok(start) = usize::try_from(start) {
1102 if start >= string.chars().count() {
1103 string.make_mut().clear();
1104 return;
1105 }
1106 start
1107 } else {
1108 string.make_mut().clear();
1109 return;
1110 };
1111
1112 if chars.is_empty() {
1113 chars.extend(string.chars());
1114 }
1115
1116 let len = usize::try_from(len).map_or_else(
1117 |_| chars.len() - offset,
1118 |len| {
1119 if len.checked_add(offset).map_or(true, |x| x > chars.len()) {
1120 chars.len() - offset
1121 } else {
1122 len
1123 }
1124 },
1125 );
1126
1127 let copy = string.make_mut();
1128 copy.clear();
1129 copy.extend(chars.iter().skip(offset).take(len));
1130 }
1131 /// Remove all characters from the string up to the `start` position.
1132 ///
1133 /// * If `start` < 0, position counts from the end of the string (`-1` is the last character).
1134 /// * If `start` < -length of string, the string is not modified.
1135 /// * If `start` ≥ length of string, the entire string is cleared.
1136 ///
1137 /// # Example
1138 ///
1139 /// ```rhai
1140 /// let text = "hello, world!";
1141 ///
1142 /// text.crop(5);
1143 ///
1144 /// print(text); // prints ", world!"
1145 ///
1146 /// text.crop(-3);
1147 ///
1148 /// print(text); // prints "ld!"
1149 /// ```
1150 #[rhai_fn(name = "crop")]
1151 pub fn crop_string_starting_from(
1152 ctx: NativeCallContext,
1153 string: &mut ImmutableString,
1154 start: INT,
1155 ) {
1156 let len = INT::try_from(string.len()).unwrap_or(INT::MAX);
1157 crop(ctx, string, start, len);
1158 }
1159
1160 /// Replace all occurrences of the specified sub-string in the string with another string.
1161 ///
1162 /// # Example
1163 ///
1164 /// ```rhai
1165 /// let text = "hello, world! hello, foobar!";
1166 ///
1167 /// text.replace("hello", "hey");
1168 ///
1169 /// print(text); // prints "hey, world! hey, foobar!"
1170 /// ```
1171 #[rhai_fn(name = "replace")]
1172 pub fn replace(string: &mut ImmutableString, find_string: &str, substitute_string: &str) {
1173 if string.is_empty() {
1174 return;
1175 }
1176
1177 *string = string.replace(find_string, substitute_string).into();
1178 }
1179 /// Replace all occurrences of the specified sub-string in the string with the specified character.
1180 ///
1181 /// # Example
1182 ///
1183 /// ```rhai
1184 /// let text = "hello, world! hello, foobar!";
1185 ///
1186 /// text.replace("hello", '*');
1187 ///
1188 /// print(text); // prints "*, world! *, foobar!"
1189 /// ```
1190 #[rhai_fn(name = "replace")]
1191 pub fn replace_string_with_char(
1192 string: &mut ImmutableString,
1193 find_string: &str,
1194 substitute_character: char,
1195 ) {
1196 if string.is_empty() {
1197 return;
1198 }
1199
1200 *string = string
1201 .replace(find_string, &substitute_character.to_string())
1202 .into();
1203 }
1204 /// Replace all occurrences of the specified character in the string with another string.
1205 ///
1206 /// # Example
1207 ///
1208 /// ```rhai
1209 /// let text = "hello, world! hello, foobar!";
1210 ///
1211 /// text.replace('l', "(^)");
1212 ///
1213 /// print(text); // prints "he(^)(^)o, wor(^)d! he(^)(^)o, foobar!"
1214 /// ```
1215 #[rhai_fn(name = "replace")]
1216 pub fn replace_char_with_string(
1217 string: &mut ImmutableString,
1218 find_character: char,
1219 substitute_string: &str,
1220 ) {
1221 if string.is_empty() {
1222 return;
1223 }
1224
1225 *string = string
1226 .replace(&find_character.to_string(), substitute_string)
1227 .into();
1228 }
1229 /// Replace all occurrences of the specified character in the string with another character.
1230 ///
1231 /// # Example
1232 ///
1233 /// ```rhai
1234 /// let text = "hello, world! hello, foobar!";
1235 ///
1236 /// text.replace("l", '*');
1237 ///
1238 /// print(text); // prints "he**o, wor*d! he**o, foobar!"
1239 /// ```
1240 #[rhai_fn(name = "replace")]
1241 pub fn replace_char(
1242 string: &mut ImmutableString,
1243 find_character: char,
1244 substitute_character: char,
1245 ) {
1246 if string.is_empty() {
1247 return;
1248 }
1249
1250 *string = string
1251 .replace(
1252 &find_character.to_string(),
1253 &substitute_character.to_string(),
1254 )
1255 .into();
1256 }
1257
1258 /// Pad the string to at least the specified number of characters with the specified `character`.
1259 ///
1260 /// If `len` ≤ length of string, no padding is done.
1261 ///
1262 /// # Example
1263 ///
1264 /// ```rhai
1265 /// let text = "hello";
1266 ///
1267 /// text.pad(8, '!');
1268 ///
1269 /// print(text); // prints "hello!!!"
1270 ///
1271 /// text.pad(5, '*');
1272 ///
1273 /// print(text); // prints "hello!!!"
1274 /// ```
1275 #[rhai_fn(return_raw)]
1276 pub fn pad(
1277 ctx: NativeCallContext,
1278 string: &mut ImmutableString,
1279 len: INT,
1280 character: char,
1281 ) -> RhaiResultOf<()> {
1282 if len <= 0 {
1283 return Ok(());
1284 }
1285
1286 let Ok(len) = usize::try_from(len) else {
1287 return Err(crate::ERR::ErrorDataTooLarge(
1288 "Length of string".to_string(),
1289 crate::Position::NONE,
1290 )
1291 .into());
1292 };
1293
1294 let _ctx = ctx;
1295
1296 // Check if string will be over max size limit
1297 #[cfg(not(feature = "unchecked"))]
1298 if _ctx.engine().max_string_size() > 0 && len > _ctx.engine().max_string_size() {
1299 return Err(crate::ERR::ErrorDataTooLarge(
1300 "Length of string".to_string(),
1301 crate::Position::NONE,
1302 )
1303 .into());
1304 }
1305
1306 let orig_len = string.chars().count();
1307
1308 if len <= orig_len {
1309 return Ok(());
1310 }
1311
1312 let p = string.make_mut();
1313
1314 for _ in 0..(len - orig_len) {
1315 p.push(character);
1316 }
1317
1318 #[cfg(not(feature = "unchecked"))]
1319 if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size() {
1320 return Err(crate::ERR::ErrorDataTooLarge(
1321 "Length of string".to_string(),
1322 crate::Position::NONE,
1323 )
1324 .into());
1325 }
1326
1327 Ok(())
1328 }
1329 /// Pad the string to at least the specified number of characters with the specified string.
1330 ///
1331 /// If `len` ≤ length of string, no padding is done.
1332 ///
1333 /// # Example
1334 ///
1335 /// ```rhai
1336 /// let text = "hello";
1337 ///
1338 /// text.pad(10, "(!)");
1339 ///
1340 /// print(text); // prints "hello(!)(!)"
1341 ///
1342 /// text.pad(8, '***');
1343 ///
1344 /// print(text); // prints "hello(!)(!)"
1345 /// ```
1346 #[rhai_fn(name = "pad", return_raw)]
1347 pub fn pad_with_string(
1348 ctx: NativeCallContext,
1349 string: &mut ImmutableString,
1350 len: INT,
1351 padding: &str,
1352 ) -> RhaiResultOf<()> {
1353 if len <= 0 {
1354 return Ok(());
1355 }
1356 let Ok(len) = usize::try_from(len) else {
1357 return Err(crate::ERR::ErrorDataTooLarge(
1358 "Length of string".to_string(),
1359 crate::Position::NONE,
1360 )
1361 .into());
1362 };
1363
1364 let _ctx = ctx;
1365
1366 // Check if string will be over max size limit
1367 #[cfg(not(feature = "unchecked"))]
1368 if _ctx.engine().max_string_size() > 0 && len > _ctx.engine().max_string_size() {
1369 return Err(crate::ERR::ErrorDataTooLarge(
1370 "Length of string".to_string(),
1371 crate::Position::NONE,
1372 )
1373 .into());
1374 }
1375
1376 let mut str_len = string.chars().count();
1377 let padding_len = padding.chars().count();
1378
1379 if len <= str_len {
1380 return Ok(());
1381 }
1382
1383 let p = string.make_mut();
1384
1385 while str_len < len {
1386 if str_len + padding_len <= len {
1387 p.push_str(padding);
1388 str_len += padding_len;
1389 } else {
1390 p.extend(padding.chars().take(len - str_len));
1391 str_len = len;
1392 }
1393 }
1394
1395 #[cfg(not(feature = "unchecked"))]
1396 if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size() {
1397 return Err(crate::ERR::ErrorDataTooLarge(
1398 "Length of string".to_string(),
1399 crate::Position::NONE,
1400 )
1401 .into());
1402 }
1403
1404 Ok(())
1405 }
1406 /// Return the string that is lexically greater than the other string.
1407 ///
1408 /// # Example
1409 ///
1410 /// ```rhai
1411 /// max("hello", "world"); // returns "world"
1412 /// ```
1413 #[rhai_fn(name = "max")]
1414 pub fn max_string(string1: ImmutableString, string2: ImmutableString) -> ImmutableString {
1415 if string1 >= string2 {
1416 string1
1417 } else {
1418 string2
1419 }
1420 }
1421 /// Return the string that is lexically smaller than the other string.
1422 ///
1423 /// # Example
1424 ///
1425 /// ```rhai
1426 /// min("hello", "world"); // returns "hello"
1427 /// ```
1428 #[rhai_fn(name = "min")]
1429 pub fn min_string(string1: ImmutableString, string2: ImmutableString) -> ImmutableString {
1430 if string1 <= string2 {
1431 string1
1432 } else {
1433 string2
1434 }
1435 }
1436 /// Return the character that is lexically greater than the other character.
1437 ///
1438 /// # Example
1439 ///
1440 /// ```rhai
1441 /// max('h', 'w'); // returns 'w'
1442 /// ```
1443 #[rhai_fn(name = "max")]
1444 pub const fn max_char(char1: char, char2: char) -> char {
1445 if char1 >= char2 {
1446 char1
1447 } else {
1448 char2
1449 }
1450 }
1451 /// Return the character that is lexically smaller than the other character.
1452 ///
1453 /// # Example
1454 ///
1455 /// ```rhai
1456 /// max('h', 'w'); // returns 'h'
1457 /// ```
1458 #[rhai_fn(name = "min")]
1459 pub const fn min_char(char1: char, char2: char) -> char {
1460 if char1 <= char2 {
1461 char1
1462 } else {
1463 char2
1464 }
1465 }
1466
1467 #[cfg(not(feature = "no_index"))]
1468 pub mod arrays {
1469 use crate::Array;
1470
1471 /// Split the string into two at the specified `index` position and return it both strings
1472 /// as an array.
1473 ///
1474 /// The character at the `index` position (if any) is returned in the _second_ string.
1475 ///
1476 /// * If `index` < 0, position counts from the end of the string (`-1` is the last character).
1477 /// * If `index` < -length of string, it is equivalent to cutting at position 0.
1478 /// * If `index` ≥ length of string, it is equivalent to cutting at the end of the string.
1479 ///
1480 /// # Example
1481 ///
1482 /// ```rhai
1483 /// let text = "hello, world!";
1484 ///
1485 /// print(text.split(6)); // prints ["hello,", " world!"]
1486 ///
1487 /// print(text.split(13)); // prints ["hello, world!", ""]
1488 ///
1489 /// print(text.split(-6)); // prints ["hello, ", "world!"]
1490 ///
1491 /// print(text.split(-99)); // prints ["", "hello, world!"]
1492 /// ```
1493 #[rhai_fn(name = "split")]
1494 pub fn split_at(ctx: NativeCallContext, string: &mut ImmutableString, index: INT) -> Array {
1495 if index <= 0 {
1496 let Ok(abs_index) = usize::try_from(index.unsigned_abs()) else {
1497 return vec![
1498 ctx.engine().const_empty_string().into(),
1499 string.clone().into(),
1500 ];
1501 };
1502
1503 let num_chars = string.chars().count();
1504
1505 if abs_index > num_chars {
1506 vec![
1507 ctx.engine().const_empty_string().into(),
1508 string.clone().into(),
1509 ]
1510 } else {
1511 let prefix: String = string.chars().take(num_chars - abs_index).collect();
1512 let prefix_len = prefix.len();
1513 vec![prefix.into(), string[prefix_len..].into()]
1514 }
1515 } else if let Ok(index) = usize::try_from(index) {
1516 let prefix: String = string.chars().take(index).collect();
1517 let prefix_len = prefix.len();
1518 vec![prefix.into(), string[prefix_len..].into()]
1519 } else {
1520 vec![
1521 string.clone().into(),
1522 ctx.engine().const_empty_string().into(),
1523 ]
1524 }
1525 }
1526 /// Return an array containing all the characters of the string.
1527 ///
1528 /// # Example
1529 ///
1530 /// ```rhai
1531 /// let text = "hello";
1532 ///
1533 /// print(text.to_chars()); // prints "['h', 'e', 'l', 'l', 'o']"
1534 /// ```
1535 #[rhai_fn(name = "to_chars")]
1536 pub fn to_chars(string: &str) -> Array {
1537 if string.is_empty() {
1538 Array::new()
1539 } else {
1540 string.chars().map(Into::into).collect()
1541 }
1542 }
1543 /// Split the string into segments based on whitespaces, returning an array of the segments.
1544 ///
1545 /// # Example
1546 ///
1547 /// ```rhai
1548 /// let text = "hello, world! hello, foo!";
1549 ///
1550 /// print(text.split()); // prints ["hello,", "world!", "hello,", "foo!"]
1551 /// ```
1552 #[rhai_fn(name = "split")]
1553 pub fn split_whitespace(string: &mut ImmutableString) -> Array {
1554 if string.is_empty() {
1555 vec![string.clone().into()]
1556 } else {
1557 string.split_whitespace().map(Into::into).collect()
1558 }
1559 }
1560 /// Split the string into segments based on a `delimiter` string, returning an array of the segments.
1561 ///
1562 /// # Example
1563 ///
1564 /// ```rhai
1565 /// let text = "hello, world! hello, foo!";
1566 ///
1567 /// print(text.split("ll")); // prints ["he", "o, world! he", "o, foo!"]
1568 /// ```
1569 pub fn split(string: &mut ImmutableString, delimiter: &str) -> Array {
1570 if string.is_empty() || (!delimiter.is_empty() && !string.contains(delimiter)) {
1571 vec![string.clone().into()]
1572 } else {
1573 string.split(delimiter).map(Into::into).collect()
1574 }
1575 }
1576 /// Split the string into at most the specified number of `segments` based on a `delimiter` string,
1577 /// returning an array of the segments.
1578 ///
1579 /// If `segments` < 1, only one segment is returned.
1580 ///
1581 /// # Example
1582 ///
1583 /// ```rhai
1584 /// let text = "hello, world! hello, foo!";
1585 ///
1586 /// print(text.split("ll", 2)); // prints ["he", "o, world! hello, foo!"]
1587 /// ```
1588 #[rhai_fn(name = "split")]
1589 pub fn splitn(string: &mut ImmutableString, delimiter: &str, segments: INT) -> Array {
1590 if segments <= 1
1591 || string.is_empty()
1592 || (!delimiter.is_empty() && !string.contains(delimiter))
1593 {
1594 vec![string.clone().into()]
1595 } else {
1596 let segments = usize::try_from(segments).unwrap_or(usize::MAX);
1597 string.splitn(segments, delimiter).map(Into::into).collect()
1598 }
1599 }
1600 /// Split the string into segments based on a `delimiter` character, returning an array of the segments.
1601 ///
1602 /// # Example
1603 ///
1604 /// ```rhai
1605 /// let text = "hello, world! hello, foo!";
1606 ///
1607 /// print(text.split('l')); // prints ["he", "", "o, wor", "d! he", "", "o, foo!"]
1608 /// ```
1609 #[rhai_fn(name = "split")]
1610 pub fn split_char(string: &mut ImmutableString, delimiter: char) -> Array {
1611 if string.is_empty() || !string.contains(delimiter) {
1612 vec![string.clone().into()]
1613 } else {
1614 string.split(delimiter).map(Into::into).collect()
1615 }
1616 }
1617 /// Split the string into at most the specified number of `segments` based on a `delimiter` character,
1618 /// returning an array of the segments.
1619 ///
1620 /// If `segments` < 1, only one segment is returned.
1621 ///
1622 /// # Example
1623 ///
1624 /// ```rhai
1625 /// let text = "hello, world! hello, foo!";
1626 ///
1627 /// print(text.split('l', 3)); // prints ["he", "", "o, world! hello, foo!"]
1628 /// ```
1629 #[rhai_fn(name = "split")]
1630 pub fn splitn_char(string: &mut ImmutableString, delimiter: char, segments: INT) -> Array {
1631 if segments <= 1 || string.is_empty() || !string.contains(delimiter) {
1632 [string.clone().into()].into()
1633 } else {
1634 let segments = usize::try_from(segments).unwrap_or(usize::MAX);
1635 string.splitn(segments, delimiter).map(Into::into).collect()
1636 }
1637 }
1638 /// Split the string into segments based on a `delimiter` string, returning an array of the
1639 /// segments in _reverse_ order.
1640 ///
1641 /// # Example
1642 ///
1643 /// ```rhai
1644 /// let text = "hello, world! hello, foo!";
1645 ///
1646 /// print(text.split_rev("ll")); // prints ["o, foo!", "o, world! he", "he"]
1647 /// ```
1648 #[rhai_fn(name = "split_rev")]
1649 pub fn rsplit(string: &mut ImmutableString, delimiter: &str) -> Array {
1650 if string.is_empty() || (!delimiter.is_empty() && !string.contains(delimiter)) {
1651 vec![string.clone().into()]
1652 } else {
1653 string.rsplit(delimiter).map(Into::into).collect()
1654 }
1655 }
1656 /// Split the string into at most a specified number of `segments` based on a `delimiter` string,
1657 /// returning an array of the segments in _reverse_ order.
1658 ///
1659 /// If `segments` < 1, only one segment is returned.
1660 ///
1661 /// # Example
1662 ///
1663 /// ```rhai
1664 /// let text = "hello, world! hello, foo!";
1665 ///
1666 /// print(text.split_rev("ll", 2)); // prints ["o, foo!", "hello, world! he"]
1667 /// ```
1668 #[rhai_fn(name = "split_rev")]
1669 pub fn rsplitn(string: &mut ImmutableString, delimiter: &str, segments: INT) -> Array {
1670 if segments <= 1
1671 || string.is_empty()
1672 || (!delimiter.is_empty() && !string.contains(delimiter))
1673 {
1674 vec![string.clone().into()]
1675 } else {
1676 let segments = usize::try_from(segments).unwrap_or(usize::MAX);
1677 string
1678 .rsplitn(segments, delimiter)
1679 .map(Into::into)
1680 .collect()
1681 }
1682 }
1683 /// Split the string into segments based on a `delimiter` character, returning an array of
1684 /// the segments in _reverse_ order.
1685 ///
1686 /// # Example
1687 ///
1688 /// ```rhai
1689 /// let text = "hello, world! hello, foo!";
1690 ///
1691 /// print(text.split_rev('l')); // prints ["o, foo!", "", "d! he", "o, wor", "", "he"]
1692 /// ```
1693 #[rhai_fn(name = "split_rev")]
1694 pub fn rsplit_char(string: &mut ImmutableString, delimiter: char) -> Array {
1695 if string.is_empty() || !string.contains(delimiter) {
1696 vec![string.clone().into()]
1697 } else {
1698 string.rsplit(delimiter).map(Into::into).collect()
1699 }
1700 }
1701 /// Split the string into at most the specified number of `segments` based on a `delimiter` character,
1702 /// returning an array of the segments.
1703 ///
1704 /// If `segments` < 1, only one segment is returned.
1705 ///
1706 /// # Example
1707 ///
1708 /// ```rhai
1709 /// let text = "hello, world! hello, foo!";
1710 ///
1711 /// print(text.split('l', 3)); // prints ["o, foo!", "", "hello, world! he"
1712 /// ```
1713 #[rhai_fn(name = "split_rev")]
1714 pub fn rsplitn_char(string: &mut ImmutableString, delimiter: char, segments: INT) -> Array {
1715 if segments <= 1 || string.is_empty() || !string.contains(delimiter) {
1716 [string.clone().into()].into()
1717 } else {
1718 let segments = usize::try_from(segments).unwrap_or(usize::MAX);
1719 string
1720 .rsplitn(segments, delimiter)
1721 .map(Into::into)
1722 .collect()
1723 }
1724 }
1725 }
1726}