summaryrefslogtreecommitdiffstats
path: root/core/string/ustring.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'core/string/ustring.cpp')
-rw-r--r--core/string/ustring.cpp251
1 files changed, 178 insertions, 73 deletions
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index beefe54faf..872c8357ae 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -531,10 +531,12 @@ String &String::operator+=(const String &p_str) {
resize(lhs_len + rhs_len + 1);
- const char32_t *src = p_str.get_data();
+ const char32_t *src = p_str.ptr();
char32_t *dst = ptrw() + lhs_len;
- memcpy(dst, src, (rhs_len + 1) * sizeof(char32_t));
+ // Don't copy the terminating null with `memcpy` to avoid undefined behavior when string is being added to itself (it would overlap the destination).
+ memcpy(dst, src, rhs_len * sizeof(char32_t));
+ *(dst + rhs_len) = _null;
return *this;
}
@@ -968,62 +970,71 @@ const char32_t *String::get_data() const {
return size() ? &operator[](0) : &zero;
}
-String String::capitalize() const {
- String aux = this->camelcase_to_underscore(true).replace("_", " ").strip_edges();
- String cap;
- for (int i = 0; i < aux.get_slice_count(" "); i++) {
- String slice = aux.get_slicec(' ', i);
- if (slice.length() > 0) {
- slice[0] = _find_upper(slice[0]);
- if (i > 0) {
- cap += " ";
- }
- cap += slice;
- }
- }
-
- return cap;
-}
-
-String String::camelcase_to_underscore(bool lowercase) const {
+String String::_camelcase_to_underscore() const {
const char32_t *cstr = get_data();
String new_string;
int start_index = 0;
for (int i = 1; i < this->size(); i++) {
- bool is_upper = is_ascii_upper_case(cstr[i]);
- bool is_number = is_digit(cstr[i]);
+ bool is_prev_upper = is_ascii_upper_case(cstr[i - 1]);
+ bool is_prev_lower = is_ascii_lower_case(cstr[i - 1]);
+ bool is_prev_digit = is_digit(cstr[i - 1]);
- bool are_next_2_lower = false;
- bool is_next_lower = false;
- bool is_next_number = false;
- bool was_precedent_upper = is_ascii_upper_case(cstr[i - 1]);
- bool was_precedent_number = is_digit(cstr[i - 1]);
-
- if (i + 2 < this->size()) {
- are_next_2_lower = is_ascii_lower_case(cstr[i + 1]) && is_ascii_lower_case(cstr[i + 2]);
- }
+ bool is_curr_upper = is_ascii_upper_case(cstr[i]);
+ bool is_curr_lower = is_ascii_lower_case(cstr[i]);
+ bool is_curr_digit = is_digit(cstr[i]);
+ bool is_next_lower = false;
if (i + 1 < this->size()) {
is_next_lower = is_ascii_lower_case(cstr[i + 1]);
- is_next_number = is_digit(cstr[i + 1]);
}
- const bool cond_a = is_upper && !was_precedent_upper && !was_precedent_number;
- const bool cond_b = was_precedent_upper && is_upper && are_next_2_lower;
- const bool cond_c = is_number && !was_precedent_number;
- const bool can_break_number_letter = is_number && !was_precedent_number && is_next_lower;
- const bool can_break_letter_number = !is_number && was_precedent_number && (is_next_lower || is_next_number);
+ const bool cond_a = is_prev_lower && is_curr_upper; // aA
+ const bool cond_b = (is_prev_upper || is_prev_digit) && is_curr_upper && is_next_lower; // AAa, 2Aa
+ const bool cond_c = is_prev_digit && is_curr_lower && is_next_lower; // 2aa
+ const bool cond_d = (is_prev_upper || is_prev_lower) && is_curr_digit; // A2, a2
- bool should_split = cond_a || cond_b || cond_c || can_break_number_letter || can_break_letter_number;
- if (should_split) {
+ if (cond_a || cond_b || cond_c || cond_d) {
new_string += this->substr(start_index, i - start_index) + "_";
start_index = i;
}
}
new_string += this->substr(start_index, this->size() - start_index);
- return lowercase ? new_string.to_lower() : new_string;
+ return new_string.to_lower();
+}
+
+String String::capitalize() const {
+ String aux = this->_camelcase_to_underscore().replace("_", " ").strip_edges();
+ String cap;
+ for (int i = 0; i < aux.get_slice_count(" "); i++) {
+ String slice = aux.get_slicec(' ', i);
+ if (slice.length() > 0) {
+ slice[0] = _find_upper(slice[0]);
+ if (i > 0) {
+ cap += " ";
+ }
+ cap += slice;
+ }
+ }
+
+ return cap;
+}
+
+String String::to_camel_case() const {
+ String s = this->to_pascal_case();
+ if (!s.is_empty()) {
+ s[0] = _find_lower(s[0]);
+ }
+ return s;
+}
+
+String String::to_pascal_case() const {
+ return this->capitalize().replace(" ", "");
+}
+
+String String::to_snake_case() const {
+ return this->_camelcase_to_underscore().replace(" ", "_").strip_edges();
}
String String::get_with_code_lines() const {
@@ -1656,7 +1667,7 @@ String String::utf8(const char *p_utf8, int p_len) {
return ret;
}
-Error String::parse_utf8(const char *p_utf8, int p_len) {
+Error String::parse_utf8(const char *p_utf8, int p_len, bool p_skip_cr) {
if (!p_utf8) {
return ERR_INVALID_DATA;
}
@@ -1689,6 +1700,10 @@ Error String::parse_utf8(const char *p_utf8, int p_len) {
uint8_t c = *ptrtmp >= 0 ? *ptrtmp : uint8_t(256 + *ptrtmp);
if (skip == 0) {
+ if (p_skip_cr && c == '\r') {
+ ptrtmp++;
+ continue;
+ }
/* Determine the number of characters in sequence */
if ((c & 0x80) == 0) {
skip = 0;
@@ -1753,6 +1768,10 @@ Error String::parse_utf8(const char *p_utf8, int p_len) {
uint8_t c = *p_utf8 >= 0 ? *p_utf8 : uint8_t(256 + *p_utf8);
if (skip == 0) {
+ if (p_skip_cr && c == '\r') {
+ p_utf8++;
+ continue;
+ }
/* Determine the number of characters in sequence */
if ((c & 0x80) == 0) {
*(dst++) = c;
@@ -2605,10 +2624,11 @@ double String::to_float() const {
uint32_t String::hash(const char *p_cstr) {
uint32_t hashv = 5381;
- uint32_t c;
+ uint32_t c = *p_cstr++;
- while ((c = *p_cstr++)) {
+ while (c) {
hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */
+ c = *p_cstr++;
}
return hashv;
@@ -2634,10 +2654,11 @@ uint32_t String::hash(const wchar_t *p_cstr, int p_len) {
uint32_t String::hash(const wchar_t *p_cstr) {
uint32_t hashv = 5381;
- uint32_t c;
+ uint32_t c = *p_cstr++;
- while ((c = *p_cstr++)) {
+ while (c) {
hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */
+ c = *p_cstr++;
}
return hashv;
@@ -2654,10 +2675,11 @@ uint32_t String::hash(const char32_t *p_cstr, int p_len) {
uint32_t String::hash(const char32_t *p_cstr) {
uint32_t hashv = 5381;
- uint32_t c;
+ uint32_t c = *p_cstr++;
- while ((c = *p_cstr++)) {
+ while (c) {
hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */
+ c = *p_cstr++;
}
return hashv;
@@ -2668,10 +2690,11 @@ uint32_t String::hash() const {
const char32_t *chr = get_data();
uint32_t hashv = 5381;
- uint32_t c;
+ uint32_t c = *chr++;
- while ((c = *chr++)) {
+ while (c) {
hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */
+ c = *chr++;
}
return hashv;
@@ -2682,10 +2705,11 @@ uint64_t String::hash64() const {
const char32_t *chr = get_data();
uint64_t hashv = 5381;
- uint64_t c;
+ uint64_t c = *chr++;
- while ((c = *chr++)) {
+ while (c) {
hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */
+ c = *chr++;
}
return hashv;
@@ -3441,18 +3465,19 @@ String String::replacen(const String &p_key, const String &p_with) const {
String String::repeat(int p_count) const {
ERR_FAIL_COND_V_MSG(p_count < 0, "", "Parameter count should be a positive number.");
- String new_string;
- const char32_t *src = this->get_data();
-
- new_string.resize(length() * p_count + 1);
- new_string[length() * p_count] = 0;
-
- for (int i = 0; i < p_count; i++) {
- for (int j = 0; j < length(); j++) {
- new_string[i * length() + j] = src[j];
- }
- }
-
+ int len = length();
+ String new_string = *this;
+ new_string.resize(p_count * len + 1);
+
+ char32_t *dst = new_string.ptrw();
+ int offset = 1;
+ int stride = 1;
+ while (offset < p_count) {
+ memcpy(dst + offset * len, dst, stride * len * sizeof(char32_t));
+ offset += stride;
+ stride = MIN(stride * 2, p_count - offset);
+ }
+ dst[p_count * len] = _null;
return new_string;
}
@@ -3644,13 +3669,16 @@ String String::simplify_path() const {
String drive;
if (s.begins_with("local://")) {
drive = "local://";
- s = s.substr(8, s.length());
+ s = s.substr(8);
} else if (s.begins_with("res://")) {
drive = "res://";
- s = s.substr(6, s.length());
+ s = s.substr(6);
} else if (s.begins_with("user://")) {
drive = "user://";
- s = s.substr(7, s.length());
+ s = s.substr(7);
+ } else if (s.begins_with("uid://")) {
+ drive = "uid://";
+ s = s.substr(6);
} else if (is_network_share_path()) {
drive = s.substr(0, 2);
s = s.substr(2, s.length() - 2);
@@ -3664,7 +3692,7 @@ String String::simplify_path() const {
}
if (p != -1 && p < s.find("/")) {
drive = s.substr(0, p + 2);
- s = s.substr(p + 2, s.length());
+ s = s.substr(p + 2);
}
}
@@ -4440,7 +4468,7 @@ String String::get_extension() const {
return substr(pos + 1, length());
}
-String String::plus_file(const String &p_file) const {
+String String::path_join(const String &p_file) const {
if (is_empty()) {
return p_file;
}
@@ -4626,15 +4654,18 @@ String String::sprintf(const Array &values, bool *error) const {
double value = values[value_index];
bool is_negative = (value < 0);
String str = String::num(ABS(value), min_decimals);
+ const bool is_finite = Math::is_finite(value);
// Pad decimals out.
- str = str.pad_decimals(min_decimals);
+ if (is_finite) {
+ str = str.pad_decimals(min_decimals);
+ }
int initial_len = str.length();
// Padding. Leave room for sign later if required.
int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars;
- String pad_char = pad_with_zeros ? String("0") : String(" ");
+ String pad_char = (pad_with_zeros && is_finite) ? String("0") : String(" "); // Never pad NaN or inf with zeros
if (left_justified) {
str = str.rpad(pad_chars_count, pad_char);
} else {
@@ -4656,6 +4687,76 @@ String String::sprintf(const Array &values, bool *error) const {
in_format = false;
break;
}
+ case 'v': { // Vector2/3/4/2i/3i/4i
+ if (value_index >= values.size()) {
+ return "not enough arguments for format string";
+ }
+
+ int count;
+ switch (values[value_index].get_type()) {
+ case Variant::VECTOR2:
+ case Variant::VECTOR2I: {
+ count = 2;
+ } break;
+ case Variant::VECTOR3:
+ case Variant::VECTOR3I: {
+ count = 3;
+ } break;
+ case Variant::VECTOR4:
+ case Variant::VECTOR4I: {
+ count = 4;
+ } break;
+ default: {
+ return "%v requires a vector type (Vector2/3/4/2i/3i/4i)";
+ }
+ }
+
+ Vector4 vec = values[value_index];
+ String str = "(";
+ for (int i = 0; i < count; i++) {
+ double val = vec[i];
+ String number_str = String::num(ABS(val), min_decimals);
+ const bool is_finite = Math::is_finite(val);
+
+ // Pad decimals out.
+ if (is_finite) {
+ number_str = number_str.pad_decimals(min_decimals);
+ }
+
+ int initial_len = number_str.length();
+
+ // Padding. Leave room for sign later if required.
+ int pad_chars_count = val < 0 ? min_chars - 1 : min_chars;
+ String pad_char = (pad_with_zeros && is_finite) ? String("0") : String(" "); // Never pad NaN or inf with zeros
+ if (left_justified) {
+ number_str = number_str.rpad(pad_chars_count, pad_char);
+ } else {
+ number_str = number_str.lpad(pad_chars_count, pad_char);
+ }
+
+ // Add sign if needed.
+ if (val < 0) {
+ if (left_justified) {
+ number_str = number_str.insert(0, "-");
+ } else {
+ number_str = number_str.insert(pad_with_zeros ? 0 : number_str.length() - initial_len, "-");
+ }
+ }
+
+ // Add number to combined string
+ str += number_str;
+
+ if (i < count - 1) {
+ str += ", ";
+ }
+ }
+ str += ")";
+
+ formatted += str;
+ ++value_index;
+ in_format = false;
+ break;
+ }
case 's': { // String
if (value_index >= values.size()) {
return "not enough arguments for format string";
@@ -4748,7 +4849,7 @@ String String::sprintf(const Array &values, bool *error) const {
}
break;
}
- case '.': { // Float separator.
+ case '.': { // Float/Vector separator.
if (in_decimals) {
return "too many decimal points in format";
}
@@ -4762,8 +4863,12 @@ String String::sprintf(const Array &values, bool *error) const {
return "not enough arguments for format string";
}
- if (!values[value_index].is_num()) {
- return "* wants number";
+ Variant::Type value_type = values[value_index].get_type();
+ if (!values[value_index].is_num() &&
+ value_type != Variant::VECTOR2 && value_type != Variant::VECTOR2I &&
+ value_type != Variant::VECTOR3 && value_type != Variant::VECTOR3I &&
+ value_type != Variant::VECTOR4 && value_type != Variant::VECTOR4I) {
+ return "* wants number or vector";
}
int size = values[value_index];