godot_core/builtin/string/
string_macros.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
/*
 * Copyright (c) godot-rust; Bromeon and contributors.
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#[macro_export]
macro_rules! impl_shared_string_api {
    (
        builtin: $Builtin:ty,
        find_builder: $FindBuilder:ident,
        split_builder: $SplitBuilder:ident,
    ) => {
        // --------------------------------------------------------------------------------------------------------------------------------------
        // Extending the builtin itself

        /// Manually-declared, shared methods between `GString` and `StringName`.
        impl $Builtin {
            /// Returns the Unicode code point ("character") at position `index`.
            ///
            /// # Panics
            /// In debug builds, if `index` is out of bounds. In Release builds, `0` is returned instead.
            // Unicode conversion panic is not documented because we rely on Godot strings having valid Unicode.
            // TODO implement Index/IndexMut (for GString; StringName may have varying reprs).
            pub fn unicode_at(&self, index: usize) -> char {
                debug_assert!(index < self.len(), "unicode_at: index {} out of bounds (len {})", index, self.len());

                let char_i64 = self.as_inner().unicode_at(index as i64);

                u32::try_from(char_i64).ok()
                    .and_then(|char_u32| char::from_u32(char_u32))
                    .unwrap_or_else(|| {
                        panic!("cannot map Unicode code point (value {char_i64}) to char (at position {index})")
                    })
            }

            /// Find first occurrence of `what` and return index, or `None` if not found.
            ///
            /// Check [`find_ex()`](Self::find_ex) for all custom options.
            pub fn find(&self, what: impl AsArg<GString>) -> Option<usize> {
                self.find_ex(what).done()
            }

            /// Returns a builder for finding substrings, with various configuration options.
            ///
            /// The builder struct offers methods to configure 3 dimensions, which map to different Godot functions in the back:
            ///
            /// | Method        | Default behavior                          | Behavior after method call         |
            /// |---------------|-------------------------------------------|------------------------------------|
            /// | `r()`         | forward search (`find*`)                  | backward search (`rfind*`)         |
            /// | `n()`         | case-sensitive search (`*find`)           | case-insensitive search (`*findn`) |
            /// | `from(index)` | search from beginning (or end if reverse) | search from specified index        |
            ///
            /// Returns `Some(index)` of the first occurrence, or `None` if not found.
            ///
            /// # Example
            /// To find the substring `"O"` in `"Hello World"`, not considering case and starting from position 5, you can write:
            /// ```no_run
            /// # use godot::prelude::*;
            /// # fn do_sth_with_index(_i: usize) {}
            /// let s = GString::from("Hello World");
            /// if let Some(found) = s.find_ex("O").n().from(5).done() {
            ///    do_sth_with_index(found)
            /// }
            /// ```
            /// This is equivalent to the following GDScript code:
            /// ```gdscript
            /// var s: GString = "Hello World"
            /// var found = s.findn("O", 5)
            /// if found != -1:
            ///     do_sth_with_index(found)
            /// ```
            #[doc(alias = "findn", alias = "rfind", alias = "rfindn")]
            pub fn find_ex<'s, 'w>(
                &'s self,
                what: impl AsArg<GString> + 'w,
            ) -> $FindBuilder<'s, 'w> {
                $FindBuilder::new(self, what.into_arg())
            }

            /// Count how many times `what` appears within `range`. Use `..` for full string search.
            pub fn count(&self, what: impl AsArg<GString>, range: impl std::ops::RangeBounds<usize>) -> usize {
                let (from, to) = super::to_godot_fromto(range);
                self.as_inner().count(what, from, to) as usize
            }

            /// Count how many times `what` appears within `range`, case-insensitively. Use `..` for full string search.
            pub fn countn(&self, what: impl AsArg<GString>, range: impl std::ops::RangeBounds<usize>) -> usize {
                let (from, to) = super::to_godot_fromto(range);
                self.as_inner().countn(what, from, to) as usize
            }

            /// Splits the string according to `delimiter`.
            ///
            /// See [`split_ex()`][Self::split_ex] if you need further configuration.
            pub fn split(&self, delimiter: impl AsArg<GString>) -> $crate::builtin::PackedStringArray {
                self.split_ex(delimiter).done()
            }

            /// Returns a builder that splits this string into substrings using `delimiter`.
            ///
            /// If `delimiter` is an empty string, each substring will be a single character.
            ///
            /// The builder struct offers methods to configure multiple dimensions. Note that `rsplit` in Godot is not useful without the
            /// `maxsplit` argument, so the two are combined in Rust as `maxsplit_r`.
            ///
            /// | Method             | Default behavior       | Behavior after method call                        |
            /// |--------------------|------------------------|---------------------------------------------------|
            /// | `disallow_empty()` | allows empty parts     | empty parts are removed from result               |
            /// | `maxsplit(n)`      | entire string is split | splits `n` times -> `n+1` parts                   |
            /// | `maxsplit_r(n)`    | entire string is split | splits `n` times -> `n+1` parts (start from back) |
            #[doc(alias = "rsplit")]
            pub fn split_ex<'s, 'w>(
                &'s self,
                delimiter: impl AsArg<GString> + 'w,
            ) -> $SplitBuilder<'s, 'w> {
                $SplitBuilder::new(self, delimiter.into_arg())
            }

            /// Returns a substring of this, as another `GString`.
            // TODO is there no efficient way to implement this for StringName by interning?
            pub fn substr(&self, range: impl std::ops::RangeBounds<usize>) -> GString {
                let (from, len) = super::to_godot_fromlen_neg1(range);

                self.as_inner().substr(from, len)
            }

            /// Splits the string using a string delimiter and returns the substring at index `slice`.
            ///
            /// Returns the original string if delimiter does not occur in the string. Returns `None` if `slice` is out of bounds.
            ///
            /// This is faster than [`split()`][Self::split], if you only need one substring.
            pub fn get_slice(
                &self,
                delimiter: impl AsArg<GString>,
                slice: usize,
            ) -> Option<GString> {
                let sliced = self.as_inner().get_slice(delimiter, slice as i64);

                // Note: self="" always returns None.
                super::populated_or_none(sliced)
            }

            /// Splits the string using a Unicode char `delimiter` and returns the substring at index `slice`.
            ///
            /// Returns the original string if delimiter does not occur in the string. Returns `None` if `slice` is out of bounds.
            ///
            /// This is faster than [`split()`][Self::split], if you only need one substring.
            pub fn get_slicec(&self, delimiter: char, slice: usize) -> Option<GString> {
                let sliced = self.as_inner().get_slicec(delimiter as i64, slice as i64);

                // Note: self="" always returns None.
                super::populated_or_none(sliced)
            }

            /// Returns the total number of slices, when the string is split with the given delimiter.
            ///
            /// See also [`split()`][Self::split] and [`get_slice()`][Self::get_slice].
            pub fn get_slice_count(&self, delimiter: impl AsArg<GString>) -> usize {
                self.as_inner().get_slice_count(delimiter) as usize
            }

            /// Returns a copy of the string without the specified index range.
            pub fn erase(&self, range: impl std::ops::RangeBounds<usize>) -> GString {
                let (from, len) = super::to_godot_fromlen_i32max(range);
                self.as_inner().erase(from, len)
            }

            /// Returns a copy of the string with an additional string inserted at the given position.
            ///
            /// If the position is out of bounds, the string will be inserted at the end.
            ///
            /// Consider using [`format()`](Self::format) for more flexibility.
            pub fn insert(&self, position: usize, what: impl AsArg<GString>) -> GString {
                self.as_inner().insert(position as i64, what)
            }

            /// Format a string using substitutions from an array or dictionary.
            ///
            /// See Godot's [`String.format()`](https://docs.godotengine.org/en/stable/classes/class_string.html#class-string-method-format).
            pub fn format(&self, array_or_dict: &Variant) -> GString {
                self.as_inner().format(array_or_dict, "{_}")
            }

            /// Format a string using substitutions from an array or dictionary + custom placeholder.
            ///
            /// See Godot's [`String.format()`](https://docs.godotengine.org/en/stable/classes/class_string.html#class-string-method-format).
            pub fn format_with_placeholder(
                &self,
                array_or_dict: &Variant,
                placeholder: impl AsArg<GString>,
            ) -> GString {
                self.as_inner().format(array_or_dict, placeholder)
            }

            // left() + right() are not redefined, as their i64 can be negative.

            /// Formats the string to be at least `min_length` long, by adding characters to the left of the string, if necessary.
            ///
            /// Godot itself allows padding with multiple characters, but that behavior is not very useful, because `min_length` isn't
            /// respected in that case. The parameter in Godot is even called `character`. In Rust, we directly expose `char` instead.
            ///
            /// See also [`rpad()`](Self::rpad).
            pub fn lpad(&self, min_length: usize, character: char) -> GString {
                let one_char_string = GString::from([character].as_slice());
                self.as_inner().lpad(min_length as i64, &one_char_string)
            }

            /// Formats the string to be at least `min_length` long, by adding characters to the right of the string, if necessary.
            ///
            /// Godot itself allows padding with multiple characters, but that behavior is not very useful, because `min_length` isn't
            /// respected in that case. The parameter in Godot is even called `character`. In Rust, we directly expose `char` instead.
            ///
            /// See also [`lpad()`](Self::lpad).
            pub fn rpad(&self, min_length: usize, character: char) -> GString {
                let one_char_string = GString::from([character].as_slice());
                self.as_inner().rpad(min_length as i64, &one_char_string)
            }

            /// Formats the string representing a number to have an exact number of `digits` _after_ the decimal point.
            pub fn pad_decimals(&self, digits: usize) -> GString {
                self.as_inner().pad_decimals(digits as i64)
            }

            /// Formats the string representing a number to have an exact number of `digits` _before_ the decimal point.
            pub fn pad_zeros(&self, digits: usize) -> GString {
                self.as_inner().pad_zeros(digits as i64)
            }

            /// Case-sensitive, lexicographic comparison to another string.
            ///
            /// Returns the `Ordering` relation of `self` towards `to`. Ordering is determined by the Unicode code points of each string, which
            /// roughly matches the alphabetical order.
            ///
            /// See also [`nocasecmp_to()`](Self::nocasecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
            pub fn casecmp_to(&self, to: impl AsArg<GString>) -> std::cmp::Ordering {
                sys::i64_to_ordering(self.as_inner().casecmp_to(to))
            }

            /// Case-**insensitive**, lexicographic comparison to another string.
            ///
            /// Returns the `Ordering` relation of `self` towards `to`. Ordering is determined by the Unicode code points of each string, which
            /// roughly matches the alphabetical order.
            ///
            /// See also [`casecmp_to()`](Self::casecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
            pub fn nocasecmp_to(&self, to: impl AsArg<GString>) -> std::cmp::Ordering {
                sys::i64_to_ordering(self.as_inner().nocasecmp_to(to))
            }

            /// Case-sensitive, **natural-order** comparison to another string.
            ///
            /// Returns the `Ordering` relation of `self` towards `to`. Ordering is determined by the Unicode code points of each string, which
            /// roughly matches the alphabetical order.
            ///
            /// When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often
            /// expected, instead of the single digit's value. A sorted sequence of numbered strings will be `["1", "2", "3", ...]`, not
            /// `["1", "10", "2", "3", ...]`.
            ///
            /// With different string lengths, returns `Ordering::Greater` if this string is longer than the `to` string, or `Ordering::Less`
            /// if shorter.
            ///
            /// See also [`casecmp_to()`](Self::casecmp_to), [`naturalnocasecmp_to()`](Self::naturalnocasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
            pub fn naturalcasecmp_to(&self, to: impl AsArg<GString>) -> std::cmp::Ordering {
                sys::i64_to_ordering(self.as_inner().naturalcasecmp_to(to))
            }

            /// Case-insensitive, **natural-order** comparison to another string.
            ///
            /// Returns the `Ordering` relation of `self` towards `to`. Ordering is determined by the Unicode code points of each string, which
            /// roughly matches the alphabetical order.
            ///
            /// When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often
            /// expected, instead of the single digit's value. A sorted sequence of numbered strings will be `["1", "2", "3", ...]`, not
            /// `["1", "10", "2", "3", ...]`.
            ///
            /// With different string lengths, returns `Ordering::Greater` if this string is longer than the `to` string, or `Ordering::Less`
            /// if shorter.
            ///
            /// See also [`casecmp_to()`](Self::casecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
            pub fn naturalnocasecmp_to(&self, to: impl AsArg<GString>) -> std::cmp::Ordering {
                sys::i64_to_ordering(self.as_inner().naturalnocasecmp_to(to))
            }

            /// Case-sensitive, filename-oriented comparison to another string.
            ///
            /// Like [`naturalcasecmp_to()`][Self::naturalcasecmp_to], but prioritizes strings that begin with periods (`.`) and underscores
            /// (`_`) before any other character. Useful when sorting folders or file names.
            ///
            /// See also [`casecmp_to()`](Self::casecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filenocasecmp_to()`](Self::filenocasecmp_to).
            #[cfg(since_api = "4.3")]
            pub fn filecasecmp_to(&self, to: impl AsArg<GString>) -> std::cmp::Ordering {
                sys::i64_to_ordering(self.as_inner().filecasecmp_to(to))
            }

            /// Case-insensitive, filename-oriented comparison to another string.
            ///
            /// Like [`naturalnocasecmp_to()`][Self::naturalnocasecmp_to], but prioritizes strings that begin with periods (`.`) and underscores
            /// (`_`) before any other character. Useful when sorting folders or file names.
            ///
            /// See also [`casecmp_to()`](Self::casecmp_to), [`naturalcasecmp_to()`](Self::naturalcasecmp_to), [`filecasecmp_to()`](Self::filecasecmp_to).
            #[cfg(since_api = "4.3")]
            pub fn filenocasecmp_to(&self, to: impl AsArg<GString>) -> std::cmp::Ordering {
                sys::i64_to_ordering(self.as_inner().filenocasecmp_to(to))
            }

            /// Simple expression match (also called "glob" or "globbing"), where `*` matches zero or more arbitrary characters and `?`
            /// matches any single character except a period (`.`).
            ///
            /// An empty string or empty expression always evaluates to `false`.
            ///
            /// Renamed from `match` because of collision with Rust keyword + possible confusion with `String::matches()` that can match regex.
            #[doc(alias = "match")]
            pub fn match_glob(&self, pattern: impl AsArg<GString>) -> bool {
                self.as_inner().match_(pattern)
            }

            /// Simple **case-insensitive** expression match (also called "glob" or "globbing"), where `*` matches zero or more arbitrary
            /// characters and `?` matches any single character except a period (`.`).
            ///
            /// An empty string or empty expression always evaluates to `false`.
            ///
            /// Renamed from `matchn` because of collision with Rust keyword + possible confusion with `String::matches()` that can match regex.
            #[doc(alias = "matchn")]
            pub fn matchn_glob(&self, pattern: impl AsArg<GString>) -> bool {
                self.as_inner().matchn(pattern)
            }
        }

        // --------------------------------------------------------------------------------------------------------------------------------------
        // find() support

        #[doc = concat!("Builder for [`", stringify!($Builtin), "::find_ex()`][", stringify!($Builtin), "::find_ex].")]
        #[must_use]
        pub struct $FindBuilder<'s, 'w> {
            owner: &'s $Builtin,
            what: meta::CowArg<'w, GString>,
            reverse: bool,
            case_insensitive: bool,
            from_index: Option<usize>,
        }

        impl<'s, 'w> $FindBuilder<'s, 'w> {
            pub(crate) fn new(owner: &'s $Builtin, what: meta::CowArg<'w, GString>) -> Self {
                Self {
                    owner,
                    what,
                    reverse: false,
                    case_insensitive: false,
                    from_index: None,
                }
            }

            /// Reverse search direction (start at back).
            pub fn r(self) -> Self {
                Self {
                    reverse: true,
                    ..self
                }
            }

            /// Case-**insensitive** search.
            pub fn n(self) -> Self {
                Self {
                    case_insensitive: true,
                    ..self
                }
            }

            /// Start index -- begin search here rather than at start/end of string.
            pub fn from(self, index: usize) -> Self {
                Self {
                    from_index: Some(index),
                    ..self
                }
            }

            /// Does the actual work. Must be called to finalize find operation.
            pub fn done(self) -> Option<usize> {
                let from_index = self.from_index.map(|i| i as i64);
                let inner = self.owner.as_inner();
                let what = self.what;

                let godot_found = if self.reverse {
                    let from_index = from_index.unwrap_or(-1);

                    if self.case_insensitive {
                        inner.rfindn(what, from_index)
                    } else {
                        inner.rfind(what, from_index)
                    }
                } else {
                    let from_index = from_index.unwrap_or(0);

                    if self.case_insensitive {
                        inner.findn(what, from_index)
                    } else {
                        inner.find(what, from_index)
                    }
                };

                super::found_to_option(godot_found)
            }
        }

        // --------------------------------------------------------------------------------------------------------------------------------------
        // split() support

        #[doc = concat!("Builder for [`", stringify!($Builtin), "::split_ex()`][", stringify!($Builtin), "::split_ex].")]
        #[must_use]
        pub struct $SplitBuilder<'s, 'w> {
            owner: &'s $Builtin,
            delimiter: meta::CowArg<'w, GString>,
            reverse: bool,
            allow_empty: bool,
            maxsplit: usize,
        }

        impl<'s, 'w> $SplitBuilder<'s, 'w> {
            pub(crate) fn new(owner: &'s $Builtin, delimiter: meta::CowArg<'w, GString>) -> Self {
                Self {
                    owner,
                    delimiter,
                    reverse: false,
                    allow_empty: true,
                    maxsplit: 0,
                }
            }

            /// After calling this method, empty strings between adjacent delimiters are excluded from the array.
            pub fn disallow_empty(self) -> Self {
                Self {
                    allow_empty: false,
                    ..self
                }
            }

            /// Limit number of splits (forward mode).
            ///
            /// If `maxsplit` is greater than 0, the number of splits may not exceed `maxsplit`. By default, the entire string is split.
            ///
            /// Note that `number_of_splits` refers to the number of times a split occurs, which is the resulting element count **minus one**.
            pub fn maxsplit(self, number_of_splits: usize) -> Self {
                Self {
                    maxsplit: number_of_splits,
                    ..self
                }
            }

            /// Limit number of splits (reverse mode).
            ///
            /// If `maxsplit` is greater than 0, the number of splits may not exceed `maxsplit`. By default, the entire string is split.
            ///
            /// Note that `number_of_splits` refers to the number of times a split occurs, which is the resulting element count **minus one**.
            pub fn maxsplit_r(self, number_of_splits: usize) -> Self {
                Self {
                    maxsplit: number_of_splits,
                    reverse: true,
                    ..self
                }
            }

            /// Does the actual work. Must be called to finalize split operation.
            pub fn done(self) -> $crate::builtin::PackedStringArray {
                let inner = self.owner.as_inner();
                let delimiter = self.delimiter;

                if self.reverse {
                    inner.rsplit(delimiter, self.allow_empty, self.maxsplit as i64)
                } else {
                    inner.split(delimiter, self.allow_empty, self.maxsplit as i64)
                }
            }
        }
    };
}