From d4154dbc55400c55d487f6bd4a5ff05d1db1f638 Mon Sep 17 00:00:00 2001 From: "Wilson E. Alvarez" Date: Thu, 2 Nov 2023 15:15:48 -0400 Subject: [PATCH] Add const char * overloads to String class Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> --- core/string/ustring.cpp | 434 +++++++++++++++++++++++++++++++- core/string/ustring.h | 15 ++ core/variant/variant_call.cpp | 26 +- tests/core/string/test_string.h | 286 +++++++++++---------- tests/test_macros.h | 67 +++++ 5 files changed, 670 insertions(+), 158 deletions(-) diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 2b62b72a51a..7cd1a39d38c 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -1184,6 +1184,26 @@ int String::get_slice_count(const String &p_splitter) const { return slices; } +int String::get_slice_count(const char *p_splitter) const { + if (is_empty()) { + return 0; + } + if (p_splitter == nullptr || *p_splitter == '\0') { + return 0; + } + + int pos = 0; + int slices = 1; + int splitter_length = strlen(p_splitter); + + while ((pos = find(p_splitter, pos)) >= 0) { + slices++; + pos += splitter_length; + } + + return slices; +} + String String::get_slice(const String &p_splitter, int p_slice) const { if (is_empty() || p_splitter.is_empty()) { return ""; @@ -1224,6 +1244,47 @@ String String::get_slice(const String &p_splitter, int p_slice) const { return ""; //no find! } +String String::get_slice(const char *p_splitter, int p_slice) const { + if (is_empty() || p_splitter == nullptr || *p_splitter == '\0') { + return ""; + } + + int pos = 0; + int prev_pos = 0; + //int slices=1; + if (p_slice < 0) { + return ""; + } + if (find(p_splitter) == -1) { + return *this; + } + + int i = 0; + int splitter_length = strlen(p_splitter); + while (true) { + pos = find(p_splitter, pos); + if (pos == -1) { + pos = length(); //reached end + } + + int from = prev_pos; + //int to=pos; + + if (p_slice == i) { + return substr(from, pos - from); + } + + if (pos == length()) { //reached end and no find + break; + } + pos += splitter_length; + prev_pos = pos; + i++; + } + + return ""; //no find! +} + String String::get_slicec(char32_t p_splitter, int p_slice) const { if (is_empty()) { return String(); @@ -1338,6 +1399,54 @@ Vector String::split(const String &p_splitter, bool p_allow_empty, int p return ret; } +Vector String::split(const char *p_splitter, bool p_allow_empty, int p_maxsplit) const { + Vector ret; + + if (is_empty()) { + if (p_allow_empty) { + ret.push_back(""); + } + return ret; + } + + int from = 0; + int len = length(); + + while (true) { + int end; + if (p_splitter == nullptr || *p_splitter == '\0') { + end = from + 1; + } else { + end = find(p_splitter, from); + if (end < 0) { + end = len; + } + } + if (p_allow_empty || (end > from)) { + if (p_maxsplit <= 0) { + ret.push_back(substr(from, end - from)); + } else { + // Put rest of the string and leave cycle. + if (p_maxsplit == ret.size()) { + ret.push_back(substr(from, len)); + break; + } + + // Otherwise, push items until positive limit is reached. + ret.push_back(substr(from, end - from)); + } + } + + if (end == len) { + break; + } + + from = end + strlen(p_splitter); + } + + return ret; +} + Vector String::rsplit(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const { Vector ret; const int len = length(); @@ -1380,6 +1489,49 @@ Vector String::rsplit(const String &p_splitter, bool p_allow_empty, int return ret; } +Vector String::rsplit(const char *p_splitter, bool p_allow_empty, int p_maxsplit) const { + Vector ret; + const int len = length(); + const int splitter_length = strlen(p_splitter); + int remaining_len = len; + + while (true) { + if (remaining_len < splitter_length || (p_maxsplit > 0 && p_maxsplit == ret.size())) { + // no room for another splitter or hit max splits, push what's left and we're done + if (p_allow_empty || remaining_len > 0) { + ret.push_back(substr(0, remaining_len)); + } + break; + } + + int left_edge; + if (p_splitter == nullptr || *p_splitter == '\0') { + left_edge = remaining_len - 1; + if (left_edge == 0) { + left_edge--; // Skip to the < 0 condition. + } + } else { + left_edge = rfind(p_splitter, remaining_len - splitter_length); + } + + if (left_edge < 0) { + // no more splitters, we're done + ret.push_back(substr(0, remaining_len)); + break; + } + + int substr_start = left_edge + splitter_length; + if (p_allow_empty || substr_start < remaining_len) { + ret.push_back(substr(substr_start, remaining_len - substr_start)); + } + + remaining_len = left_edge; + } + + ret.reverse(); + return ret; +} + Vector String::split_floats(const String &p_splitter, bool p_allow_empty) const { Vector ret; int from = 0; @@ -3087,23 +3239,20 @@ int String::find(const String &p_str, int p_from) const { } int String::find(const char *p_str, int p_from) const { - if (p_from < 0) { + if (p_from < 0 || !p_str) { return -1; } + const int src_len = strlen(p_str); + const int len = length(); - if (len == 0) { + if (len == 0 || src_len == 0) { return -1; // won't find anything! } const char32_t *src = get_data(); - int src_len = 0; - while (p_str[src_len] != '\0') { - src_len++; - } - if (src_len == 1) { const char32_t needle = p_str[0]; @@ -3238,6 +3387,46 @@ int String::findn(const String &p_str, int p_from) const { return -1; } +int String::findn(const char *p_str, int p_from) const { + if (p_from < 0) { + return -1; + } + + int src_len = strlen(p_str); + + if (src_len == 0 || length() == 0) { + return -1; // won't find anything! + } + + const char32_t *srcd = get_data(); + + for (int i = p_from; i <= (length() - src_len); i++) { + bool found = true; + for (int j = 0; j < src_len; j++) { + int read_pos = i + j; + + if (read_pos >= length()) { + ERR_PRINT("read_pos>=length()"); + return -1; + } + + char32_t src = _find_lower(srcd[read_pos]); + char32_t dst = _find_lower(p_str[j]); + + if (src != dst) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + int String::rfind(const String &p_str, int p_from) const { // establish a limit int limit = length() - p_str.length(); @@ -3285,6 +3474,57 @@ int String::rfind(const String &p_str, int p_from) const { return -1; } +int String::rfind(const char *p_str, int p_from) const { + const int source_length = length(); + int substring_length = strlen(p_str); + + if (source_length == 0 || substring_length == 0) { + return -1; // won't find anything! + } + + // establish a limit + int limit = length() - substring_length; + if (limit < 0) { + return -1; + } + + // establish a starting point + int starting_point; + if (p_from < 0) { + starting_point = limit; + } else if (p_from > limit) { + starting_point = limit; + } else { + starting_point = p_from; + } + + const char32_t *source = get_data(); + + for (int i = starting_point; i >= 0; i--) { + bool found = true; + for (int j = 0; j < substring_length; j++) { + int read_pos = i + j; + + if (read_pos >= source_length) { + ERR_PRINT("read_pos>=source_length"); + return -1; + } + + const char32_t key_needle = p_str[j]; + if (source[read_pos] != key_needle) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + int String::rfindn(const String &p_str, int p_from) const { // establish a limit int limit = length() - p_str.length(); @@ -3335,6 +3575,60 @@ int String::rfindn(const String &p_str, int p_from) const { return -1; } +int String::rfindn(const char *p_str, int p_from) const { + const int source_length = length(); + int substring_length = strlen(p_str); + + if (source_length == 0 || substring_length == 0) { + return -1; // won't find anything! + } + + // establish a limit + int limit = length() - substring_length; + if (limit < 0) { + return -1; + } + + // establish a starting point + int starting_point; + if (p_from < 0) { + starting_point = limit; + } else if (p_from > limit) { + starting_point = limit; + } else { + starting_point = p_from; + } + + const char32_t *source = get_data(); + + for (int i = starting_point; i >= 0; i--) { + bool found = true; + for (int j = 0; j < substring_length; j++) { + int read_pos = i + j; + + if (read_pos >= source_length) { + ERR_PRINT("read_pos>=source_length"); + return -1; + } + + const char32_t key_needle = p_str[j]; + int srcc = _find_lower(source[read_pos]); + int keyc = _find_lower(key_needle); + + if (srcc != keyc) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + bool String::ends_with(const String &p_string) const { int l = p_string.length(); if (l > length()) { @@ -3357,6 +3651,31 @@ bool String::ends_with(const String &p_string) const { return true; } +bool String::ends_with(const char *p_string) const { + if (!p_string) { + return false; + } + + int l = strlen(p_string); + if (l > length()) { + return false; + } + + if (l == 0) { + return true; + } + + const char32_t *s = &operator[](length() - l); + + for (int i = 0; i < l; i++) { + if (static_cast(p_string[i]) != s[i]) { + return false; + } + } + + return true; +} + bool String::begins_with(const String &p_string) const { int l = p_string.length(); if (l > length()) { @@ -3380,11 +3699,11 @@ bool String::begins_with(const String &p_string) const { } bool String::begins_with(const char *p_string) const { - int l = length(); if (!p_string) { return false; } + int l = length(); if (l == 0) { return *p_string == 0; } @@ -3456,14 +3775,61 @@ int String::_count(const String &p_string, int p_from, int p_to, bool p_case_ins return c; } +int String::_count(const char *p_string, int p_from, int p_to, bool p_case_insensitive) const { + int substring_length = strlen(p_string); + if (substring_length == 0) { + return 0; + } + const int source_length = length(); + + if (source_length < substring_length) { + return 0; + } + String str; + int search_limit = p_to; + if (p_from >= 0 && p_to >= 0) { + if (p_to == 0) { + search_limit = source_length; + } else if (p_from >= p_to) { + return 0; + } + if (p_from == 0 && search_limit == source_length) { + str = String(); + str.copy_from_unchecked(&get_data()[0], source_length); + } else { + str = substr(p_from, search_limit - p_from); + } + } else { + return 0; + } + int c = 0; + int idx = -1; + do { + idx = p_case_insensitive ? str.findn(p_string) : str.find(p_string); + if (idx != -1) { + str = str.substr(idx + substring_length, str.length() - substring_length); + ++c; + } + } while (idx != -1); + return c; +} + int String::count(const String &p_string, int p_from, int p_to) const { return _count(p_string, p_from, p_to, false); } +int String::count(const char *p_string, int p_from, int p_to) const { + return _count(p_string, p_from, p_to, false); +} + int String::countn(const String &p_string, int p_from, int p_to) const { return _count(p_string, p_from, p_to, true); } +int String::countn(const char *p_string, int p_from, int p_to) const { + return _count(p_string, p_from, p_to, true); +} + bool String::_base_is_subsequence_of(const String &p_string, bool case_insensitive) const { int len = length(); if (len == 0) { @@ -3673,6 +4039,16 @@ String String::replace_first(const String &p_key, const String &p_with) const { return *this; } +String String::replace_first(const char *p_key, const char *p_with) const { + int pos = find(p_key); + if (pos >= 0) { + int substring_length = strlen(p_key); + return substr(0, pos) + p_with + substr(pos + substring_length, length()); + } + + return *this; +} + String String::replacen(const String &p_key, const String &p_with) const { String new_string; int search_from = 0; @@ -3692,6 +4068,31 @@ String String::replacen(const String &p_key, const String &p_with) const { return new_string; } +String String::replacen(const char *p_key, const char *p_with) const { + String new_string; + int search_from = 0; + int result = 0; + int substring_length = strlen(p_key); + + if (substring_length == 0) { + return *this; // there's nothing to match or substitute + } + + while ((result = findn(p_key, search_from)) >= 0) { + new_string += substr(search_from, result - search_from); + new_string += p_with; + search_from = result + substring_length; + } + + if (search_from == 0) { + return *this; + } + + new_string += substr(search_from, length() - search_from); + + return new_string; +} + String String::repeat(int p_count) const { ERR_FAIL_COND_V_MSG(p_count < 0, "", "Parameter count should be a positive number."); @@ -4420,6 +4821,15 @@ String String::trim_prefix(const String &p_prefix) const { return s; } +String String::trim_prefix(const char *p_prefix) const { + String s = *this; + if (s.begins_with(p_prefix)) { + int prefix_length = strlen(p_prefix); + return s.substr(prefix_length, s.length() - prefix_length); + } + return s; +} + String String::trim_suffix(const String &p_suffix) const { String s = *this; if (s.ends_with(p_suffix)) { @@ -4428,6 +4838,14 @@ String String::trim_suffix(const String &p_suffix) const { return s; } +String String::trim_suffix(const char *p_suffix) const { + String s = *this; + if (s.ends_with(p_suffix)) { + return s.substr(0, s.length() - strlen(p_suffix)); + } + return s; +} + bool String::is_valid_int() const { int len = length(); diff --git a/core/string/ustring.h b/core/string/ustring.h index 693df6dcbab..a020c7d372e 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -198,6 +198,7 @@ class String { bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const; int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const; + int _count(const char *p_string, int p_from, int p_to, bool p_case_insensitive) const; String _camelcase_to_underscore() const; public: @@ -288,14 +289,18 @@ public: int find(const char *p_str, int p_from = 0) const; ///< return <0 if failed int find_char(const char32_t &p_char, int p_from = 0) const; ///< return <0 if failed int findn(const String &p_str, int p_from = 0) const; ///< return <0 if failed, case insensitive + int findn(const char *p_str, int p_from = 0) const; ///< return <0 if failed int rfind(const String &p_str, int p_from = -1) const; ///< return <0 if failed + int rfind(const char *p_str, int p_from = -1) const; ///< return <0 if failed int rfindn(const String &p_str, int p_from = -1) const; ///< return <0 if failed, case insensitive + int rfindn(const char *p_str, int p_from = -1) const; ///< return <0 if failed int findmk(const Vector &p_keys, int p_from = 0, int *r_key = nullptr) const; ///< return <0 if failed bool match(const String &p_wildcard) const; bool matchn(const String &p_wildcard) const; bool begins_with(const String &p_string) const; bool begins_with(const char *p_string) const; bool ends_with(const String &p_string) const; + bool ends_with(const char *p_string) const; bool is_enclosed_in(const String &p_string) const; bool is_subsequence_of(const String &p_string) const; bool is_subsequence_ofn(const String &p_string) const; @@ -304,9 +309,11 @@ public: float similarity(const String &p_string) const; String format(const Variant &values, const String &placeholder = "{_}") const; String replace_first(const String &p_key, const String &p_with) const; + String replace_first(const char *p_key, const char *p_with) const; String replace(const String &p_key, const String &p_with) const; String replace(const char *p_key, const char *p_with) const; String replacen(const String &p_key, const String &p_with) const; + String replacen(const char *p_key, const char *p_with) const; String repeat(int p_count) const; String reverse() const; String insert(int p_at_pos, const String &p_string) const; @@ -314,7 +321,9 @@ public: String pad_decimals(int p_digits) const; String pad_zeros(int p_digits) const; String trim_prefix(const String &p_prefix) const; + String trim_prefix(const char *p_prefix) const; String trim_suffix(const String &p_suffix) const; + String trim_suffix(const char *p_suffix) const; String lpad(int min_length, const String &character = " ") const; String rpad(int min_length, const String &character = " ") const; String sprintf(const Array &values, bool *error) const; @@ -353,11 +362,15 @@ public: String get_with_code_lines() const; int get_slice_count(const String &p_splitter) const; + int get_slice_count(const char *p_splitter) const; String get_slice(const String &p_splitter, int p_slice) const; + String get_slice(const char *p_splitter, int p_slice) const; String get_slicec(char32_t p_splitter, int p_slice) const; Vector split(const String &p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const; + Vector split(const char *p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const; Vector rsplit(const String &p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const; + Vector rsplit(const char *p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const; Vector split_spaces() const; Vector split_floats(const String &p_splitter, bool p_allow_empty = true) const; Vector split_floats_mk(const Vector &p_splitters, bool p_allow_empty = true) const; @@ -372,7 +385,9 @@ public: String to_lower() const; int count(const String &p_string, int p_from = 0, int p_to = 0) const; + int count(const char *p_string, int p_from = 0, int p_to = 0) const; int countn(const String &p_string, int p_from = 0, int p_to = 0) const; + int countn(const char *p_string, int p_from = 0, int p_to = 0) const; String left(int p_len) const; String right(int p_len) const; diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 7012bf698d1..c5861f3fbb4 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1648,19 +1648,19 @@ static void _register_variant_builtin_methods() { bind_string_method(filenocasecmp_to, sarray("to"), varray()); bind_string_method(length, sarray(), varray()); bind_string_method(substr, sarray("from", "len"), varray(-1)); - bind_string_method(get_slice, sarray("delimiter", "slice"), varray()); + bind_string_methodv(get_slice, static_cast(&String::get_slice), sarray("delimiter", "slice"), varray()); bind_string_method(get_slicec, sarray("delimiter", "slice"), varray()); - bind_string_method(get_slice_count, sarray("delimiter"), varray()); + bind_string_methodv(get_slice_count, static_cast(&String::get_slice_count), sarray("delimiter"), varray()); bind_string_methodv(find, static_cast(&String::find), sarray("what", "from"), varray(0)); - bind_string_method(count, sarray("what", "from", "to"), varray(0, 0)); - bind_string_method(countn, sarray("what", "from", "to"), varray(0, 0)); - bind_string_method(findn, sarray("what", "from"), varray(0)); - bind_string_method(rfind, sarray("what", "from"), varray(-1)); - bind_string_method(rfindn, sarray("what", "from"), varray(-1)); + bind_string_methodv(findn, static_cast(&String::findn), sarray("what", "from"), varray(0)); + bind_string_methodv(count, static_cast(&String::count), sarray("what", "from", "to"), varray(0, 0)); + bind_string_methodv(countn, static_cast(&String::countn), sarray("what", "from", "to"), varray(0, 0)); + bind_string_methodv(rfind, static_cast(&String::rfind), sarray("what", "from"), varray(-1)); + bind_string_methodv(rfindn, static_cast(&String::rfindn), sarray("what", "from"), varray(-1)); bind_string_method(match, sarray("expr"), varray()); bind_string_method(matchn, sarray("expr"), varray()); bind_string_methodv(begins_with, static_cast(&String::begins_with), sarray("text"), varray()); - bind_string_method(ends_with, sarray("text"), varray()); + bind_string_methodv(ends_with, static_cast(&String::ends_with), sarray("text"), varray()); bind_string_method(is_subsequence_of, sarray("text"), varray()); bind_string_method(is_subsequence_ofn, sarray("text"), varray()); bind_string_method(bigrams, sarray(), varray()); @@ -1668,7 +1668,7 @@ static void _register_variant_builtin_methods() { bind_string_method(format, sarray("values", "placeholder"), varray("{_}")); bind_string_methodv(replace, static_cast(&String::replace), sarray("what", "forwhat"), varray()); - bind_string_method(replacen, sarray("what", "forwhat"), varray()); + bind_string_methodv(replacen, static_cast(&String::replacen), sarray("what", "forwhat"), varray()); bind_string_method(repeat, sarray("count"), varray()); bind_string_method(reverse, sarray(), varray()); bind_string_method(insert, sarray("position", "what"), varray()); @@ -1677,8 +1677,8 @@ static void _register_variant_builtin_methods() { bind_string_method(to_camel_case, sarray(), varray()); bind_string_method(to_pascal_case, sarray(), varray()); bind_string_method(to_snake_case, sarray(), varray()); - bind_string_method(split, sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0)); - bind_string_method(rsplit, sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0)); + bind_string_methodv(split, static_cast (String::*)(const String &, bool, int) const>(&String::split), sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0)); + bind_string_methodv(rsplit, static_cast (String::*)(const String &, bool, int) const>(&String::rsplit), sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0)); bind_string_method(split_floats, sarray("delimiter", "allow_empty"), varray(true)); bind_string_method(join, sarray("parts"), varray()); @@ -1741,8 +1741,8 @@ static void _register_variant_builtin_methods() { bind_string_method(rpad, sarray("min_length", "character"), varray(" ")); bind_string_method(pad_decimals, sarray("digits"), varray()); bind_string_method(pad_zeros, sarray("digits"), varray()); - bind_string_method(trim_prefix, sarray("prefix"), varray()); - bind_string_method(trim_suffix, sarray("suffix"), varray()); + bind_string_methodv(trim_prefix, static_cast(&String::trim_prefix), sarray("prefix"), varray()); + bind_string_methodv(trim_suffix, static_cast(&String::trim_suffix), sarray("suffix"), varray()); bind_string_method(to_ascii_buffer, sarray(), varray()); bind_string_method(to_utf8_buffer, sarray(), varray()); diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index 64f03e58799..18828c3b70b 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -360,18 +360,37 @@ TEST_CASE("[String] Substr") { TEST_CASE("[String] Find") { String s = "Pretty Woman Woman"; - CHECK(s.find("tty") == 3); - CHECK(s.find("Wo", 9) == 13); - CHECK(s.find("Revenge of the Monster Truck") == -1); - CHECK(s.rfind("man") == 15); + MULTICHECK_STRING_EQ(s, find, "tty", 3); + MULTICHECK_STRING_EQ(s, find, "Revenge of the Monster Truck", -1); + MULTICHECK_STRING_INT_EQ(s, find, "Wo", 9, 13); + MULTICHECK_STRING_EQ(s, find, "", -1); + MULTICHECK_STRING_EQ(s, find, "Pretty Woman Woman", 0); + MULTICHECK_STRING_EQ(s, find, "WOMAN", -1); + MULTICHECK_STRING_INT_EQ(s, find, "", 9, -1); + + MULTICHECK_STRING_EQ(s, rfind, "", -1); + MULTICHECK_STRING_EQ(s, rfind, "foo", -1); + MULTICHECK_STRING_EQ(s, rfind, "Pretty Woman Woman", 0); + MULTICHECK_STRING_EQ(s, rfind, "man", 15); + MULTICHECK_STRING_EQ(s, rfind, "WOMAN", -1); + MULTICHECK_STRING_INT_EQ(s, rfind, "", 15, -1); } TEST_CASE("[String] Find no case") { String s = "Pretty Whale Whale"; - CHECK(s.findn("WHA") == 7); - CHECK(s.findn("WHA", 9) == 13); - CHECK(s.findn("Revenge of the Monster SawFish") == -1); - CHECK(s.rfindn("WHA") == 13); + MULTICHECK_STRING_EQ(s, findn, "WHA", 7); + MULTICHECK_STRING_INT_EQ(s, findn, "WHA", 9, 13); + MULTICHECK_STRING_EQ(s, findn, "Revenge of the Monster SawFish", -1); + MULTICHECK_STRING_EQ(s, findn, "", -1); + MULTICHECK_STRING_EQ(s, findn, "wha", 7); + MULTICHECK_STRING_EQ(s, findn, "Wha", 7); + MULTICHECK_STRING_INT_EQ(s, findn, "", 3, -1); + + MULTICHECK_STRING_EQ(s, rfindn, "WHA", 13); + MULTICHECK_STRING_EQ(s, rfindn, "", -1); + MULTICHECK_STRING_EQ(s, rfindn, "wha", 13); + MULTICHECK_STRING_EQ(s, rfindn, "Wha", 13); + MULTICHECK_STRING_INT_EQ(s, rfindn, "", 13, -1); } TEST_CASE("[String] Find MK") { @@ -392,11 +411,9 @@ TEST_CASE("[String] Find MK") { TEST_CASE("[String] Find and replace") { String s = "Happy Birthday, Anna!"; - s = s.replace("Birthday", "Halloween"); - CHECK(s == "Happy Halloween, Anna!"); - - s = s.replace_first("H", "W"); - CHECK(s == "Wappy Halloween, Anna!"); + MULTICHECK_STRING_STRING_EQ(s, replace, "Birthday", "Halloween", "Happy Halloween, Anna!"); + MULTICHECK_STRING_STRING_EQ(s, replace_first, "y", "Y", "HappY Birthday, Anna!"); + MULTICHECK_STRING_STRING_EQ(s, replacen, "Y", "Y", "HappY BirthdaY, Anna!"); } TEST_CASE("[String] Insertion") { @@ -557,51 +574,76 @@ TEST_CASE("[String] String to float") { TEST_CASE("[String] Slicing") { String s = "Mars,Jupiter,Saturn,Uranus"; - const char *slices[4] = { "Mars", "Jupiter", "Saturn", "Uranus" }; - for (int i = 0; i < s.get_slice_count(","); i++) { - CHECK(s.get_slice(",", i) == slices[i]); - } + MULTICHECK_GET_SLICE(s, ",", slices); +} + +TEST_CASE("[String] Begins with") { + // Test cases for true: + MULTICHECK_STRING_EQ(String("res://foobar"), begins_with, "res://", true); + MULTICHECK_STRING_EQ(String("abc"), begins_with, "abc", true); + MULTICHECK_STRING_EQ(String("abc"), begins_with, "", true); + MULTICHECK_STRING_EQ(String(""), begins_with, "", true); + + // Test cases for false: + MULTICHECK_STRING_EQ(String("res"), begins_with, "res://", false); + MULTICHECK_STRING_EQ(String("abcdef"), begins_with, "foo", false); + MULTICHECK_STRING_EQ(String("abc"), begins_with, "ax", false); + MULTICHECK_STRING_EQ(String(""), begins_with, "abc", false); + + // Test "const char *" version also with nullptr. + String s("foo"); + bool state = s.begins_with(nullptr) == false; + CHECK_MESSAGE(state, "nullptr check failed"); + + String empty(""); + state = empty.begins_with(nullptr) == false; + CHECK_MESSAGE(state, "nullptr check with empty string failed"); +} + +TEST_CASE("[String] Ends with") { + // Test cases for true: + MULTICHECK_STRING_EQ(String("res://foobar"), ends_with, "foobar", true); + MULTICHECK_STRING_EQ(String("abc"), ends_with, "abc", true); + MULTICHECK_STRING_EQ(String("abc"), ends_with, "", true); + MULTICHECK_STRING_EQ(String(""), ends_with, "", true); + + // Test cases for false: + MULTICHECK_STRING_EQ(String("res"), ends_with, "res://", false); + MULTICHECK_STRING_EQ(String("abcdef"), ends_with, "foo", false); + MULTICHECK_STRING_EQ(String("abc"), ends_with, "ax", false); + MULTICHECK_STRING_EQ(String(""), ends_with, "abc", false); + + // Test "const char *" version also with nullptr. + String s("foo"); + bool state = s.ends_with(nullptr) == false; + CHECK_MESSAGE(state, "nullptr check failed"); + + String empty(""); + state = empty.ends_with(nullptr) == false; + CHECK_MESSAGE(state, "nullptr check with empty string failed"); } TEST_CASE("[String] Splitting") { String s = "Mars,Jupiter,Saturn,Uranus"; - Vector l; - const char *slices_l[3] = { "Mars", "Jupiter", "Saturn,Uranus" }; + MULTICHECK_SPLIT(s, split, ",", true, 2, slices_l, 3); + const char *slices_r[3] = { "Mars,Jupiter", "Saturn", "Uranus" }; - const char *slices_3[4] = { "t", "e", "s", "t" }; - - l = s.split(",", true, 2); - CHECK(l.size() == 3); - for (int i = 0; i < l.size(); i++) { - CHECK(l[i] == slices_l[i]); - } - - l = s.rsplit(",", true, 2); - CHECK(l.size() == 3); - for (int i = 0; i < l.size(); i++) { - CHECK(l[i] == slices_r[i]); - } + MULTICHECK_SPLIT(s, rsplit, ",", true, 2, slices_r, 3); s = "test"; - l = s.split(); - CHECK(l.size() == 4); - for (int i = 0; i < l.size(); i++) { - CHECK(l[i] == slices_3[i]); - } + const char *slices_3[4] = { "t", "e", "s", "t" }; + MULTICHECK_SPLIT(s, split, "", true, 0, slices_3, 4); s = ""; - l = s.split(); - CHECK(l.size() == 1); - CHECK(l[0] == ""); - - l = s.split("", false); - CHECK(l.size() == 0); + const char *slices_4[1] = { "" }; + MULTICHECK_SPLIT(s, split, "", true, 0, slices_4, 1); + MULTICHECK_SPLIT(s, split, "", false, 0, slices_4, 0); s = "Mars Jupiter Saturn Uranus"; const char *slices_s[4] = { "Mars", "Jupiter", "Saturn", "Uranus" }; - l = s.split_spaces(); + Vector l = s.split_spaces(); for (int i = 0; i < l.size(); i++) { CHECK(l[i] == slices_s[i]); } @@ -644,69 +686,6 @@ TEST_CASE("[String] Splitting") { } } -struct test_27_data { - char const *data; - char const *part; - bool expected; -}; - -TEST_CASE("[String] Begins with") { - test_27_data tc[] = { - // Test cases for true: - { "res://foobar", "res://", true }, - { "abc", "abc", true }, - { "abc", "", true }, - { "", "", true }, - // Test cases for false: - { "res", "res://", false }, - { "abcdef", "foo", false }, - { "abc", "ax", false }, - { "", "abc", false } - }; - size_t count = sizeof(tc) / sizeof(tc[0]); - bool state = true; - for (size_t i = 0; i < count; ++i) { - String s = tc[i].data; - state = s.begins_with(tc[i].part) == tc[i].expected; - CHECK_MESSAGE(state, "first check failed at: ", i); - - String sb = tc[i].part; - state = s.begins_with(sb) == tc[i].expected; - CHECK_MESSAGE(state, "second check failed at: ", i); - } - - // Test "const char *" version also with nullptr. - String s("foo"); - state = s.begins_with(nullptr) == false; - CHECK_MESSAGE(state, "nullptr check failed"); - - String empty(""); - state = empty.begins_with(nullptr) == false; - CHECK_MESSAGE(state, "nullptr check with empty string failed"); -} - -TEST_CASE("[String] Ends with") { - test_27_data tc[] = { - // test cases for true: - { "res://foobar", "foobar", true }, - { "abc", "abc", true }, - { "abc", "", true }, - { "", "", true }, - // test cases for false: - { "res", "res://", false }, - { "", "abc", false }, - { "abcdef", "foo", false }, - { "abc", "xc", false } - }; - size_t count = sizeof(tc) / sizeof(tc[0]); - for (size_t i = 0; i < count; ++i) { - String s = tc[i].data; - String sb = tc[i].part; - bool state = s.ends_with(sb) == tc[i].expected; - CHECK_MESSAGE(state, "check failed at: ", i); - } -} - TEST_CASE("[String] format") { const String value_format = "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\""; @@ -1498,39 +1477,62 @@ TEST_CASE("[String] Cyrillic to_lower()") { } TEST_CASE("[String] Count and countn functionality") { -#define COUNT_TEST(x) \ - { \ - bool success = x; \ - state = state && success; \ - } + String s = String(""); + MULTICHECK_STRING_EQ(s, count, "Test", 0); - bool state = true; + s = "Test"; + MULTICHECK_STRING_EQ(s, count, "", 0); - COUNT_TEST(String("").count("Test") == 0); - COUNT_TEST(String("Test").count("") == 0); - COUNT_TEST(String("Test").count("test") == 0); - COUNT_TEST(String("Test").count("TEST") == 0); - COUNT_TEST(String("TEST").count("TEST") == 1); - COUNT_TEST(String("Test").count("Test") == 1); - COUNT_TEST(String("aTest").count("Test") == 1); - COUNT_TEST(String("Testa").count("Test") == 1); - COUNT_TEST(String("TestTestTest").count("Test") == 3); - COUNT_TEST(String("TestTestTest").count("TestTest") == 1); - COUNT_TEST(String("TestGodotTestGodotTestGodot").count("Test") == 3); + s = "Test"; + MULTICHECK_STRING_EQ(s, count, "test", 0); - COUNT_TEST(String("TestTestTestTest").count("Test", 4, 8) == 1); - COUNT_TEST(String("TestTestTestTest").count("Test", 4, 12) == 2); - COUNT_TEST(String("TestTestTestTest").count("Test", 4, 16) == 3); - COUNT_TEST(String("TestTestTestTest").count("Test", 4) == 3); + s = "Test"; + MULTICHECK_STRING_EQ(s, count, "TEST", 0); - COUNT_TEST(String("Test").countn("test") == 1); - COUNT_TEST(String("Test").countn("TEST") == 1); - COUNT_TEST(String("testTest-Testatest").countn("tEst") == 4); - COUNT_TEST(String("testTest-TeStatest").countn("tEsT", 4, 16) == 2); + s = "TEST"; + MULTICHECK_STRING_EQ(s, count, "TEST", 1); - CHECK(state); + s = "Test"; + MULTICHECK_STRING_EQ(s, count, "Test", 1); -#undef COUNT_TEST + s = "aTest"; + MULTICHECK_STRING_EQ(s, count, "Test", 1); + + s = "Testa"; + MULTICHECK_STRING_EQ(s, count, "Test", 1); + + s = "TestTestTest"; + MULTICHECK_STRING_EQ(s, count, "Test", 3); + + s = "TestTestTest"; + MULTICHECK_STRING_EQ(s, count, "TestTest", 1); + + s = "TestGodotTestGodotTestGodot"; + MULTICHECK_STRING_EQ(s, count, "Test", 3); + + s = "TestTestTestTest"; + MULTICHECK_STRING_INT_INT_EQ(s, count, "Test", 4, 8, 1); + + s = "TestTestTestTest"; + MULTICHECK_STRING_INT_INT_EQ(s, count, "Test", 4, 12, 2); + + s = "TestTestTestTest"; + MULTICHECK_STRING_INT_INT_EQ(s, count, "Test", 4, 16, 3); + + s = "TestTestTestTest"; + MULTICHECK_STRING_INT_EQ(s, count, "Test", 4, 3); + + s = "Test"; + MULTICHECK_STRING_EQ(s, countn, "test", 1); + + s = "Test"; + MULTICHECK_STRING_EQ(s, countn, "TEST", 1); + + s = "testTest-Testatest"; + MULTICHECK_STRING_EQ(s, countn, "tEst", 4); + + s = "testTest-TeStatest"; + MULTICHECK_STRING_INT_INT_EQ(s, countn, "tEsT", 4, 16, 2); } TEST_CASE("[String] Bigrams") { @@ -1703,9 +1705,19 @@ TEST_CASE("[String] Strip edges") { TEST_CASE("[String] Trim") { String s = "aaaTestbbb"; - CHECK(s.trim_prefix("aaa") == "Testbbb"); - CHECK(s.trim_suffix("bbb") == "aaaTest"); - CHECK(s.trim_suffix("Test") == s); + MULTICHECK_STRING_EQ(s, trim_prefix, "aaa", "Testbbb"); + MULTICHECK_STRING_EQ(s, trim_prefix, "Test", s); + MULTICHECK_STRING_EQ(s, trim_prefix, "", s); + MULTICHECK_STRING_EQ(s, trim_prefix, "aaaTestbbb", ""); + MULTICHECK_STRING_EQ(s, trim_prefix, "bbb", s); + MULTICHECK_STRING_EQ(s, trim_prefix, "AAA", s); + + MULTICHECK_STRING_EQ(s, trim_suffix, "bbb", "aaaTest"); + MULTICHECK_STRING_EQ(s, trim_suffix, "Test", s); + MULTICHECK_STRING_EQ(s, trim_suffix, "", s); + MULTICHECK_STRING_EQ(s, trim_suffix, "aaaTestbbb", ""); + MULTICHECK_STRING_EQ(s, trim_suffix, "aaa", s); + MULTICHECK_STRING_EQ(s, trim_suffix, "BBB", s); } TEST_CASE("[String] Right/Left") { diff --git a/tests/test_macros.h b/tests/test_macros.h index 25e48c1e059..10f4c59a908 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -406,4 +406,71 @@ public: #define SIGNAL_CHECK_FALSE(m_signal) CHECK(SignalWatcher::get_singleton()->check_false(m_signal)); #define SIGNAL_DISCARD(m_signal) SignalWatcher::get_singleton()->discard_signal(m_signal); +#define MULTICHECK_STRING_EQ(m_obj, m_func, m_param1, m_eq) \ + CHECK(m_obj.m_func(m_param1) == m_eq); \ + CHECK(m_obj.m_func(U##m_param1) == m_eq); \ + CHECK(m_obj.m_func(L##m_param1) == m_eq); \ + CHECK(m_obj.m_func(String(m_param1)) == m_eq); + +#define MULTICHECK_STRING_INT_EQ(m_obj, m_func, m_param1, m_param2, m_eq) \ + CHECK(m_obj.m_func(m_param1, m_param2) == m_eq); \ + CHECK(m_obj.m_func(U##m_param1, m_param2) == m_eq); \ + CHECK(m_obj.m_func(L##m_param1, m_param2) == m_eq); \ + CHECK(m_obj.m_func(String(m_param1), m_param2) == m_eq); + +#define MULTICHECK_STRING_INT_INT_EQ(m_obj, m_func, m_param1, m_param2, m_param3, m_eq) \ + CHECK(m_obj.m_func(m_param1, m_param2, m_param3) == m_eq); \ + CHECK(m_obj.m_func(U##m_param1, m_param2, m_param3) == m_eq); \ + CHECK(m_obj.m_func(L##m_param1, m_param2, m_param3) == m_eq); \ + CHECK(m_obj.m_func(String(m_param1), m_param2, m_param3) == m_eq); + +#define MULTICHECK_STRING_STRING_EQ(m_obj, m_func, m_param1, m_param2, m_eq) \ + CHECK(m_obj.m_func(m_param1, m_param2) == m_eq); \ + CHECK(m_obj.m_func(U##m_param1, U##m_param2) == m_eq); \ + CHECK(m_obj.m_func(L##m_param1, L##m_param2) == m_eq); \ + CHECK(m_obj.m_func(String(m_param1), String(m_param2)) == m_eq); + +#define MULTICHECK_GET_SLICE(m_obj, m_param1, m_slices) \ + for (int i = 0; i < m_obj.get_slice_count(m_param1); ++i) { \ + CHECK(m_obj.get_slice(m_param1, i) == m_slices[i]); \ + } \ + for (int i = 0; i < m_obj.get_slice_count(U##m_param1); ++i) { \ + CHECK(m_obj.get_slice(U##m_param1, i) == m_slices[i]); \ + } \ + for (int i = 0; i < m_obj.get_slice_count(L##m_param1); ++i) { \ + CHECK(m_obj.get_slice(L##m_param1, i) == m_slices[i]); \ + } \ + for (int i = 0; i < m_obj.get_slice_count(String(m_param1)); ++i) { \ + CHECK(m_obj.get_slice(String(m_param1), i) == m_slices[i]); \ + } + +#define MULTICHECK_SPLIT(m_obj, m_func, m_param1, m_param2, m_param3, m_slices, m_expected_size) \ + do { \ + Vector string_list; \ + \ + string_list = m_obj.m_func(m_param1, m_param2, m_param3); \ + CHECK(m_expected_size == string_list.size()); \ + for (int i = 0; i < string_list.size(); ++i) { \ + CHECK(string_list[i] == m_slices[i]); \ + } \ + \ + string_list = m_obj.m_func(U##m_param1, m_param2, m_param3); \ + CHECK(m_expected_size == string_list.size()); \ + for (int i = 0; i < string_list.size(); ++i) { \ + CHECK(string_list[i] == m_slices[i]); \ + } \ + \ + string_list = m_obj.m_func(L##m_param1, m_param2, m_param3); \ + CHECK(m_expected_size == string_list.size()); \ + for (int i = 0; i < string_list.size(); ++i) { \ + CHECK(string_list[i] == m_slices[i]); \ + } \ + \ + string_list = m_obj.m_func(String(m_param1), m_param2, m_param3); \ + CHECK(m_expected_size == string_list.size()); \ + for (int i = 0; i < string_list.size(); ++i) { \ + CHECK(string_list[i] == m_slices[i]); \ + } \ + } while (0) + #endif // TEST_MACROS_H