Add const char * overloads to String class

Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com>
This commit is contained in:
Wilson E. Alvarez 2023-11-02 15:15:48 -04:00
parent 55b8724bd5
commit d4154dbc55
No known key found for this signature in database
GPG key ID: A32174A3D2ED3F9E
5 changed files with 670 additions and 158 deletions

View file

@ -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> String::split(const String &p_splitter, bool p_allow_empty, int p
return ret;
}
Vector<String> String::split(const char *p_splitter, bool p_allow_empty, int p_maxsplit) const {
Vector<String> 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> String::rsplit(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const {
Vector<String> ret;
const int len = length();
@ -1380,6 +1489,49 @@ Vector<String> String::rsplit(const String &p_splitter, bool p_allow_empty, int
return ret;
}
Vector<String> String::rsplit(const char *p_splitter, bool p_allow_empty, int p_maxsplit) const {
Vector<String> 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<double> String::split_floats(const String &p_splitter, bool p_allow_empty) const {
Vector<double> 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<char32_t>(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();

View file

@ -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<String> &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<String> split(const String &p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
Vector<String> split(const char *p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
Vector<String> rsplit(const String &p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
Vector<String> rsplit(const char *p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
Vector<String> split_spaces() const;
Vector<double> split_floats(const String &p_splitter, bool p_allow_empty = true) const;
Vector<float> split_floats_mk(const Vector<String> &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;

View file

@ -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 (String::*)(const String &, int) const>(&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<int (String::*)(const String &) const>(&String::get_slice_count), sarray("delimiter"), varray());
bind_string_methodv(find, static_cast<int (String::*)(const String &, int) const>(&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<int (String::*)(const String &, int) const>(&String::findn), sarray("what", "from"), varray(0));
bind_string_methodv(count, static_cast<int (String::*)(const String &, int, int) const>(&String::count), sarray("what", "from", "to"), varray(0, 0));
bind_string_methodv(countn, static_cast<int (String::*)(const String &, int, int) const>(&String::countn), sarray("what", "from", "to"), varray(0, 0));
bind_string_methodv(rfind, static_cast<int (String::*)(const String &, int) const>(&String::rfind), sarray("what", "from"), varray(-1));
bind_string_methodv(rfindn, static_cast<int (String::*)(const String &, int) const>(&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<bool (String::*)(const String &) const>(&String::begins_with), sarray("text"), varray());
bind_string_method(ends_with, sarray("text"), varray());
bind_string_methodv(ends_with, static_cast<bool (String::*)(const String &) const>(&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 (String::*)(const String &, const String &) const>(&String::replace), sarray("what", "forwhat"), varray());
bind_string_method(replacen, sarray("what", "forwhat"), varray());
bind_string_methodv(replacen, static_cast<String (String::*)(const String &, const String &) const>(&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<Vector<String> (String::*)(const String &, bool, int) const>(&String::split), sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0));
bind_string_methodv(rsplit, static_cast<Vector<String> (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 (String::*)(const String &) const>(&String::trim_prefix), sarray("prefix"), varray());
bind_string_methodv(trim_suffix, static_cast<String (String::*)(const String &) const>(&String::trim_suffix), sarray("suffix"), varray());
bind_string_method(to_ascii_buffer, sarray(), varray());
bind_string_method(to_utf8_buffer, sarray(), varray());

View file

@ -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<String> 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<String> 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") {

View file

@ -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> 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