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}