summaryrefslogtreecommitdiff
path: root/core/string
diff options
context:
space:
mode:
Diffstat (limited to 'core/string')
-rw-r--r--core/string/char_utils.h92
-rw-r--r--core/string/locales.h1
-rw-r--r--core/string/optimized_translation.cpp10
-rw-r--r--core/string/print_string.cpp2
-rw-r--r--core/string/print_string.h12
-rw-r--r--core/string/string_name.cpp32
-rw-r--r--core/string/string_name.h27
-rw-r--r--core/string/translation.cpp85
-rw-r--r--core/string/translation.h20
-rw-r--r--core/string/translation_po.cpp43
-rw-r--r--core/string/ustring.cpp305
-rw-r--r--core/string/ustring.h12
12 files changed, 412 insertions, 229 deletions
diff --git a/core/string/char_utils.h b/core/string/char_utils.h
new file mode 100644
index 0000000000..0afd058f01
--- /dev/null
+++ b/core/string/char_utils.h
@@ -0,0 +1,92 @@
+/*************************************************************************/
+/* char_utils.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef CHAR_UTILS_H
+#define CHAR_UTILS_H
+
+#include "core/typedefs.h"
+
+static _FORCE_INLINE_ bool is_ascii_upper_case(char32_t c) {
+ return (c >= 'A' && c <= 'Z');
+}
+
+static _FORCE_INLINE_ bool is_ascii_lower_case(char32_t c) {
+ return (c >= 'a' && c <= 'z');
+}
+
+static _FORCE_INLINE_ bool is_digit(char32_t c) {
+ return (c >= '0' && c <= '9');
+}
+
+static _FORCE_INLINE_ bool is_hex_digit(char32_t c) {
+ return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
+}
+
+static _FORCE_INLINE_ bool is_binary_digit(char32_t c) {
+ return (c == '0' || c == '1');
+}
+
+static _FORCE_INLINE_ bool is_ascii_char(char32_t c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+static _FORCE_INLINE_ bool is_ascii_alphanumeric_char(char32_t c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
+}
+
+static _FORCE_INLINE_ bool is_ascii_identifier_char(char32_t c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
+}
+
+static _FORCE_INLINE_ bool is_symbol(char32_t c) {
+ return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' ');
+}
+
+static _FORCE_INLINE_ bool is_control(char32_t p_char) {
+ return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009f);
+}
+
+static _FORCE_INLINE_ bool is_whitespace(char32_t p_char) {
+ return (p_char == ' ') || (p_char == 0x00a0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085);
+}
+
+static _FORCE_INLINE_ bool is_linebreak(char32_t p_char) {
+ return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029);
+}
+
+static _FORCE_INLINE_ bool is_punct(char32_t p_char) {
+ return (p_char >= ' ' && p_char <= '/') || (p_char >= ':' && p_char <= '@') || (p_char >= '[' && p_char <= '^') || (p_char == '`') || (p_char >= '{' && p_char <= '~') || (p_char >= 0x2000 && p_char <= 0x206f) || (p_char >= 0x3000 && p_char <= 0x303f);
+}
+
+static _FORCE_INLINE_ bool is_underscore(char32_t p_char) {
+ return (p_char == '_');
+}
+
+#endif // CHAR_UTILS_H
diff --git a/core/string/locales.h b/core/string/locales.h
index 9ef6dee5ac..32d6608ec2 100644
--- a/core/string/locales.h
+++ b/core/string/locales.h
@@ -42,6 +42,7 @@ static const char *locale_renames[][2] = {
{ "in", "id" }, // Indonesian
{ "iw", "he" }, // Hebrew
{ "no", "nb" }, // Norwegian Bokmål
+ { "C", "en" }, // Locale is not set, fallback to English.
{ nullptr, nullptr }
};
diff --git a/core/string/optimized_translation.cpp b/core/string/optimized_translation.cpp
index 07b58f2418..07302cc8c3 100644
--- a/core/string/optimized_translation.cpp
+++ b/core/string/optimized_translation.cpp
@@ -37,9 +37,9 @@ extern "C" {
}
struct CompressedString {
- int orig_len;
+ int orig_len = 0;
CharString compressed;
- int offset;
+ int offset = 0;
};
void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
@@ -53,7 +53,7 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
int size = Math::larger_prime(keys.size());
Vector<Vector<Pair<int, CharString>>> buckets;
- Vector<Map<uint32_t, int>> table;
+ Vector<HashMap<uint32_t, int>> table;
Vector<uint32_t> hfunc_table;
Vector<CompressedString> compressed;
@@ -108,7 +108,7 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
for (int i = 0; i < size; i++) {
const Vector<Pair<int, CharString>> &b = buckets[i];
- Map<uint32_t, int> &t = table.write[i];
+ HashMap<uint32_t, int> &t = table.write[i];
if (b.size() == 0) {
continue;
@@ -147,7 +147,7 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
int btindex = 0;
for (int i = 0; i < size; i++) {
- const Map<uint32_t, int> &t = table[i];
+ const HashMap<uint32_t, int> &t = table[i];
if (t.size() == 0) {
htw[i] = 0xFFFFFFFF; //nothing
continue;
diff --git a/core/string/print_string.cpp b/core/string/print_string.cpp
index 97e119bcf6..919c9e08e3 100644
--- a/core/string/print_string.cpp
+++ b/core/string/print_string.cpp
@@ -45,7 +45,7 @@ void add_print_handler(PrintHandlerList *p_handler) {
_global_unlock();
}
-void remove_print_handler(PrintHandlerList *p_handler) {
+void remove_print_handler(const PrintHandlerList *p_handler) {
_global_lock();
PrintHandlerList *prev = nullptr;
diff --git a/core/string/print_string.h b/core/string/print_string.h
index 669d2ea316..f7d0f25030 100644
--- a/core/string/print_string.h
+++ b/core/string/print_string.h
@@ -54,13 +54,21 @@ String stringify_variants(Variant p_var, Args... p_args) {
}
void add_print_handler(PrintHandlerList *p_handler);
-void remove_print_handler(PrintHandlerList *p_handler);
+void remove_print_handler(const PrintHandlerList *p_handler);
extern bool _print_line_enabled;
extern bool _print_error_enabled;
extern void __print_line(String p_string);
extern void print_error(String p_string);
extern void print_verbose(String p_string);
-#define print_line(...) __print_line(stringify_variants(__VA_ARGS__))
+
+inline void print_line(Variant v) {
+ __print_line(stringify_variants(v));
+}
+
+template <typename... Args>
+void print_line(Variant p_var, Args... p_args) {
+ __print_line(stringify_variants(p_var, p_args...));
+}
#endif // PRINT_STRING_H
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index 61742ac582..9c4fc4e1b7 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -73,23 +73,38 @@ void StringName::cleanup() {
d = d->next;
}
}
- print_line("\nStringName Reference Ranking:\n");
+
+ print_line("\nStringName reference ranking (from most to least referenced):\n");
+
data.sort_custom<DebugSortReferences>();
- for (int i = 0; i < MIN(100, data.size()); i++) {
+ int unreferenced_stringnames = 0;
+ int rarely_referenced_stringnames = 0;
+ for (int i = 0; i < data.size(); i++) {
print_line(itos(i + 1) + ": " + data[i]->get_name() + " - " + itos(data[i]->debug_references));
+ if (data[i]->debug_references == 0) {
+ unreferenced_stringnames += 1;
+ } else if (data[i]->debug_references < 5) {
+ rarely_referenced_stringnames += 1;
+ }
}
+
+ print_line(vformat("\nOut of %d StringNames, %d StringNames were never referenced during this run (0 times) (%.2f%%).", data.size(), unreferenced_stringnames, unreferenced_stringnames / float(data.size()) * 100));
+ print_line(vformat("Out of %d StringNames, %d StringNames were rarely referenced during this run (1-4 times) (%.2f%%).", data.size(), rarely_referenced_stringnames, rarely_referenced_stringnames / float(data.size()) * 100));
}
#endif
int lost_strings = 0;
for (int i = 0; i < STRING_TABLE_LEN; i++) {
while (_table[i]) {
_Data *d = _table[i];
- lost_strings++;
- if (d->static_count.get() != d->refcount.get() && OS::get_singleton()->is_stdout_verbose()) {
- if (d->cname) {
- print_line("Orphan StringName: " + String(d->cname));
- } else {
- print_line("Orphan StringName: " + String(d->name));
+ if (d->static_count.get() != d->refcount.get()) {
+ lost_strings++;
+
+ if (OS::get_singleton()->is_stdout_verbose()) {
+ if (d->cname) {
+ print_line("Orphan StringName: " + String(d->cname));
+ } else {
+ print_line("Orphan StringName: " + String(d->name));
+ }
}
}
@@ -232,6 +247,7 @@ StringName::StringName(const char *p_name, bool p_static) {
_data->cname = nullptr;
_data->next = _table[idx];
_data->prev = nullptr;
+
#ifdef DEBUG_ENABLED
if (unlikely(debug_stringname)) {
// Keep in memory, force static.
diff --git a/core/string/string_name.h b/core/string/string_name.h
index 9653d2b4cf..ff4c41af94 100644
--- a/core/string/string_name.h
+++ b/core/string/string_name.h
@@ -35,6 +35,8 @@
#include "core/string/ustring.h"
#include "core/templates/safe_refcount.h"
+#define UNIQUE_NODE_PREFIX "%"
+
class Main;
struct StaticCString {
@@ -70,7 +72,7 @@ class StringName {
_Data *_data = nullptr;
union _HashUnion {
- _Data *ptr;
+ _Data *ptr = nullptr;
uint32_t hash;
};
@@ -100,6 +102,17 @@ public:
bool operator==(const String &p_name) const;
bool operator==(const char *p_name) const;
bool operator!=(const String &p_name) const;
+
+ _FORCE_INLINE_ bool is_node_unique_name() const {
+ if (!_data) {
+ return false;
+ }
+ if (_data->cname != nullptr) {
+ return (char32_t)_data->cname[0] == (char32_t)UNIQUE_NODE_PREFIX[0];
+ } else {
+ return (char32_t)_data->name[0] == (char32_t)UNIQUE_NODE_PREFIX[0];
+ }
+ }
_FORCE_INLINE_ bool operator<(const StringName &p_name) const {
return _data < p_name._data;
}
@@ -181,6 +194,18 @@ bool operator!=(const char *p_name, const StringName &p_string_name);
StringName _scs_create(const char *p_chr, bool p_static = false);
+/*
+ * The SNAME macro is used to speed up StringName creation, as it allows caching it after the first usage in a very efficient way.
+ * It should NOT be used everywhere, but instead in places where high performance is required and the creation of a StringName
+ * can be costly. Places where it should be used are:
+ * - Control::get_theme_*(<name> and Window::get_theme_*(<name> functions.
+ * - emit_signal(<name>,..) function
+ * - call_deferred(<name>,..) function
+ * - Comparisons to a StringName in overridden _set and _get methods.
+ *
+ * Use in places that can be called hundreds of times per frame (or more) is recommended, but this situation is very rare. If in doubt, do not use.
+ */
+
#define SNAME(m_arg) ([]() -> const StringName & { static StringName sname = _scs_create(m_arg, true); return sname; })()
#endif // STRING_NAME_H
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index 674098b06c..c64f815563 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -95,12 +95,12 @@ StringName Translation::get_message(const StringName &p_src_text, const StringNa
WARN_PRINT("Translation class doesn't handle context. Using context in get_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class");
}
- const Map<StringName, StringName>::Element *E = translation_map.find(p_src_text);
+ HashMap<StringName, StringName>::ConstIterator E = translation_map.find(p_src_text);
if (!E) {
return StringName();
}
- return E->get();
+ return E->value;
}
StringName Translation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
@@ -213,22 +213,14 @@ static _character_accent_pair _character_to_accented[] = {
{ 'z', U"ź" },
};
-static _FORCE_INLINE_ bool is_upper_case(char32_t c) {
- return (c >= 'A' && c <= 'Z');
-}
-
-static _FORCE_INLINE_ bool is_lower_case(char32_t c) {
- return (c >= 'a' && c <= 'z');
-}
-
Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
-Map<String, String> TranslationServer::language_map;
-Map<String, String> TranslationServer::script_map;
-Map<String, String> TranslationServer::locale_rename_map;
-Map<String, String> TranslationServer::country_name_map;
-Map<String, String> TranslationServer::variant_map;
-Map<String, String> TranslationServer::country_rename_map;
+HashMap<String, String> TranslationServer::language_map;
+HashMap<String, String> TranslationServer::script_map;
+HashMap<String, String> TranslationServer::locale_rename_map;
+HashMap<String, String> TranslationServer::country_name_map;
+HashMap<String, String> TranslationServer::variant_map;
+HashMap<String, String> TranslationServer::country_rename_map;
void TranslationServer::init_locale_info() {
// Init locale info.
@@ -309,15 +301,15 @@ String TranslationServer::standardize_locale(const String &p_locale) const {
Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_");
lang = locale_elements[0];
if (locale_elements.size() >= 2) {
- if (locale_elements[1].length() == 4 && is_upper_case(locale_elements[1][0]) && is_lower_case(locale_elements[1][1]) && is_lower_case(locale_elements[1][2]) && is_lower_case(locale_elements[1][3])) {
+ if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
script = locale_elements[1];
}
- if (locale_elements[1].length() == 2 && is_upper_case(locale_elements[1][0]) && is_upper_case(locale_elements[1][1])) {
+ if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
country = locale_elements[1];
}
}
if (locale_elements.size() >= 3) {
- if (locale_elements[2].length() == 2 && is_upper_case(locale_elements[2][0]) && is_upper_case(locale_elements[2][1])) {
+ if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
country = locale_elements[2];
} else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang) {
variant = locale_elements[2].to_lower();
@@ -434,15 +426,15 @@ String TranslationServer::get_locale_name(const String &p_locale) const {
Vector<String> locale_elements = locale.split("_");
lang = locale_elements[0];
if (locale_elements.size() >= 2) {
- if (locale_elements[1].length() == 4 && is_upper_case(locale_elements[1][0]) && is_lower_case(locale_elements[1][1]) && is_lower_case(locale_elements[1][2]) && is_lower_case(locale_elements[1][3])) {
+ if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
script = locale_elements[1];
}
- if (locale_elements[1].length() == 2 && is_upper_case(locale_elements[1][0]) && is_upper_case(locale_elements[1][1])) {
+ if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
country = locale_elements[1];
}
}
if (locale_elements.size() >= 3) {
- if (locale_elements[2].length() == 2 && is_upper_case(locale_elements[2][0]) && is_upper_case(locale_elements[2][1])) {
+ if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
country = locale_elements[2];
}
}
@@ -460,8 +452,8 @@ String TranslationServer::get_locale_name(const String &p_locale) const {
Vector<String> TranslationServer::get_all_languages() const {
Vector<String> languages;
- for (const Map<String, String>::Element *E = language_map.front(); E; E = E->next()) {
- languages.push_back(E->key());
+ for (const KeyValue<String, String> &E : language_map) {
+ languages.push_back(E.key);
}
return languages;
@@ -474,8 +466,8 @@ String TranslationServer::get_language_name(const String &p_language) const {
Vector<String> TranslationServer::get_all_scripts() const {
Vector<String> scripts;
- for (const Map<String, String>::Element *E = script_map.front(); E; E = E->next()) {
- scripts.push_back(E->key());
+ for (const KeyValue<String, String> &E : script_map) {
+ scripts.push_back(E.key);
}
return scripts;
@@ -488,8 +480,8 @@ String TranslationServer::get_script_name(const String &p_script) const {
Vector<String> TranslationServer::get_all_countries() const {
Vector<String> countries;
- for (const Map<String, String>::Element *E = country_name_map.front(); E; E = E->next()) {
- countries.push_back(E->key());
+ for (const KeyValue<String, String> &E : country_name_map) {
+ countries.push_back(E.key);
}
return countries;
@@ -515,7 +507,7 @@ String TranslationServer::get_locale() const {
Array TranslationServer::get_loaded_locales() const {
Array locales;
- for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
+ for (const RBSet<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
const Ref<Translation> &t = E->get();
ERR_FAIL_COND_V(t.is_null(), Array());
String l = t->get_locale();
@@ -538,13 +530,13 @@ Ref<Translation> TranslationServer::get_translation_object(const String &p_local
Ref<Translation> res;
int best_score = 0;
- for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
+ for (const RBSet<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
const Ref<Translation> &t = E->get();
ERR_FAIL_COND_V(t.is_null(), nullptr);
String l = t->get_locale();
int score = compare_locales(p_locale, l);
- if (score > best_score) {
+ if (score > 0 && score >= best_score) {
res = t;
best_score = score;
if (score == 10) {
@@ -566,8 +558,6 @@ StringName TranslationServer::translate(const StringName &p_message, const Strin
return p_message;
}
- ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid.");
-
StringName res = _get_message_from_translations(p_message, p_context, locale, false);
if (!res && fallback.length() >= 2) {
@@ -589,8 +579,6 @@ StringName TranslationServer::translate_plural(const StringName &p_message, cons
return p_message_plural;
}
- ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid.");
-
StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n);
if (!res && fallback.length() >= 2) {
@@ -611,19 +599,23 @@ StringName TranslationServer::_get_message_from_translations(const StringName &p
StringName res;
int best_score = 0;
- for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
+ for (const RBSet<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
const Ref<Translation> &t = E->get();
ERR_FAIL_COND_V(t.is_null(), p_message);
String l = t->get_locale();
int score = compare_locales(p_locale, l);
- if (score > best_score) {
+ if (score > 0 && score >= best_score) {
StringName r;
if (!plural) {
- res = t->get_message(p_message, p_context);
+ r = t->get_message(p_message, p_context);
} else {
- res = t->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ r = t->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ }
+ if (!r) {
+ continue;
}
+ res = r;
best_score = score;
if (score == 10) {
break; // Exact match, skip the rest.
@@ -693,8 +685,12 @@ Ref<Translation> TranslationServer::get_tool_translation() const {
String TranslationServer::get_tool_locale() {
#ifdef TOOLS_ENABLED
- if (TranslationServer::get_singleton()->get_tool_translation().is_valid() && (Engine::get_singleton()->is_editor_hint() || Main::is_project_manager())) {
- return tool_translation->get_locale();
+ if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
+ if (TranslationServer::get_singleton()->get_tool_translation().is_valid()) {
+ return tool_translation->get_locale();
+ } else {
+ return "en";
+ }
} else {
#else
{
@@ -895,7 +891,7 @@ String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const
return res;
}
-String TranslationServer::add_padding(String &p_message, int p_length) const {
+String TranslationServer::add_padding(const String &p_message, int p_length) const {
String res;
String prefix = pseudolocalization_prefix;
String suffix;
@@ -911,7 +907,7 @@ String TranslationServer::add_padding(String &p_message, int p_length) const {
}
const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
- if (!((p_character >= 'a' && p_character <= 'z') || (p_character >= 'A' && p_character <= 'Z'))) {
+ if (!is_ascii_char(p_character)) {
return nullptr;
}
@@ -925,7 +921,7 @@ const char32_t *TranslationServer::get_accented_version(char32_t p_character) co
}
bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
- return p_message[p_index] == '%' && p_index < p_message.size() - 1 &&
+ return p_index < p_message.size() - 1 && p_message[p_index] == '%' &&
(p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
}
@@ -933,6 +929,7 @@ bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
void TranslationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
+ ClassDB::bind_method(D_METHOD("get_tool_locale"), &TranslationServer::get_tool_locale);
ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales);
ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale);
diff --git a/core/string/translation.h b/core/string/translation.h
index 947ca4c6d8..f58f6f91a2 100644
--- a/core/string/translation.h
+++ b/core/string/translation.h
@@ -41,7 +41,7 @@ class Translation : public Resource {
RES_BASE_EXTENSION("translation");
String locale = "en";
- Map<StringName, StringName> translation_map;
+ HashMap<StringName, StringName> translation_map;
virtual Vector<String> _get_message_list() const;
virtual Dictionary _get_messages() const;
@@ -74,7 +74,7 @@ class TranslationServer : public Object {
String locale = "en";
String fallback;
- Set<Ref<Translation>> translations;
+ RBSet<Ref<Translation>> translations;
Ref<Translation> tool_translation;
Ref<Translation> doc_translation;
@@ -96,7 +96,7 @@ class TranslationServer : public Object {
String double_vowels(String &p_message) const;
String replace_with_accented_string(String &p_message) const;
String wrap_with_fakebidi_characters(String &p_message) const;
- String add_padding(String &p_message, int p_length) const;
+ String add_padding(const String &p_message, int p_length) const;
const char32_t *get_accented_version(char32_t p_character) const;
bool is_placeholder(String &p_message, int p_index) const;
@@ -111,16 +111,16 @@ class TranslationServer : public Object {
String name;
String script;
String default_country;
- Set<String> supported_countries;
+ RBSet<String> supported_countries;
};
static Vector<LocaleScriptInfo> locale_script_info;
- static Map<String, String> language_map;
- static Map<String, String> script_map;
- static Map<String, String> locale_rename_map;
- static Map<String, String> country_name_map;
- static Map<String, String> country_rename_map;
- static Map<String, String> variant_map;
+ static HashMap<String, String> language_map;
+ static HashMap<String, String> script_map;
+ static HashMap<String, String> locale_rename_map;
+ static HashMap<String, String> country_name_map;
+ static HashMap<String, String> country_rename_map;
+ static HashMap<String, String> variant_map;
void init_locale_info();
diff --git a/core/string/translation_po.cpp b/core/string/translation_po.cpp
index 1c991ee12d..fa656b634d 100644
--- a/core/string/translation_po.cpp
+++ b/core/string/translation_po.cpp
@@ -35,7 +35,7 @@
#ifdef DEBUG_TRANSLATION_PO
void TranslationPO::print_translation_map() {
Error err;
- FileAccess *file = FileAccess::open("translation_map_print_test.txt", FileAccess::WRITE, &err);
+ Ref<FileAccess> file = FileAccess::open("translation_map_print_test.txt", FileAccess::WRITE, &err);
if (err != OK) {
ERR_PRINT("Failed to open translation_map_print_test.txt");
return;
@@ -62,7 +62,6 @@ void TranslationPO::print_translation_map() {
file->store_line("");
}
}
- file->close();
}
#endif
@@ -71,21 +70,14 @@ Dictionary TranslationPO::_get_messages() const {
Dictionary d;
- List<StringName> context_l;
- translation_map.get_key_list(&context_l);
- for (const StringName &ctx : context_l) {
- const HashMap<StringName, Vector<StringName>> &id_str_map = translation_map[ctx];
-
+ for (const KeyValue<StringName, HashMap<StringName, Vector<StringName>>> &E : translation_map) {
Dictionary d2;
- List<StringName> id_l;
- id_str_map.get_key_list(&id_l);
- // Save list of id and strs associated with a context in a temporary dictionary.
- for (List<StringName>::Element *E2 = id_l.front(); E2; E2 = E2->next()) {
- StringName id = E2->get();
- d2[id] = id_str_map[id];
+
+ for (const KeyValue<StringName, Vector<StringName>> &E2 : E.value) {
+ d2[E2.key] = E2.value;
}
- d[ctx] = d2;
+ d[E.key] = d2;
}
return d;
@@ -275,31 +267,24 @@ void TranslationPO::get_message_list(List<StringName> *r_messages) const {
// OptimizedTranslation uses this function to get the list of msgid.
// Return all the keys of translation_map under "" context.
- List<StringName> context_l;
- translation_map.get_key_list(&context_l);
-
- for (const StringName &E : context_l) {
- if (String(E) != "") {
+ for (const KeyValue<StringName, HashMap<StringName, Vector<StringName>>> &E : translation_map) {
+ if (E.key != StringName()) {
continue;
}
- List<StringName> msgid_l;
- translation_map[E].get_key_list(&msgid_l);
-
- for (List<StringName>::Element *E2 = msgid_l.front(); E2; E2 = E2->next()) {
- r_messages->push_back(E2->get());
+ for (const KeyValue<StringName, Vector<StringName>> &E2 : E.value) {
+ r_messages->push_back(E2.key);
}
}
}
int TranslationPO::get_message_count() const {
- List<StringName> context_l;
- translation_map.get_key_list(&context_l);
-
int count = 0;
- for (const StringName &E : context_l) {
- count += translation_map[E].size();
+
+ for (const KeyValue<StringName, HashMap<StringName, Vector<StringName>>> &E : translation_map) {
+ count += E.value.size();
}
+
return count;
}
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 658dbc98cf..015dfbc651 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -35,6 +35,7 @@
#include "core/math/math_funcs.h"
#include "core/os/memory.h"
#include "core/string/print_string.h"
+#include "core/string/string_name.h"
#include "core/string/translation.h"
#include "core/string/ucaps.h"
#include "core/variant/variant.h"
@@ -54,34 +55,14 @@
static const int MAX_DECIMALS = 32;
-static _FORCE_INLINE_ bool is_digit(char32_t c) {
- return (c >= '0' && c <= '9');
-}
-
-static _FORCE_INLINE_ bool is_hex_digit(char32_t c) {
- return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
-}
-
-static _FORCE_INLINE_ bool is_upper_case(char32_t c) {
- return (c >= 'A' && c <= 'Z');
-}
-
-static _FORCE_INLINE_ bool is_lower_case(char32_t c) {
- return (c >= 'a' && c <= 'z');
-}
-
static _FORCE_INLINE_ char32_t lower_case(char32_t c) {
- return (is_upper_case(c) ? (c + ('a' - 'A')) : c);
+ return (is_ascii_upper_case(c) ? (c + ('a' - 'A')) : c);
}
const char CharString::_null = 0;
const char16_t Char16String::_null = 0;
const char32_t String::_null = 0;
-bool is_symbol(char32_t c) {
- return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' ');
-}
-
bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end) {
const String &s = p_s;
int beg = CLAMP(p_col, 0, s.length());
@@ -123,9 +104,12 @@ bool Char16String::operator<(const Char16String &p_right) const {
}
Char16String &Char16String::operator+=(char16_t p_char) {
- resize(size() ? size() + 1 : 2);
- set(length(), 0);
- set(length() - 1, p_char);
+ const int lhs_len = length();
+ resize(lhs_len + 2);
+
+ char16_t *dst = ptrw();
+ dst[lhs_len] = p_char;
+ dst[lhs_len + 1] = 0;
return *this;
}
@@ -178,9 +162,12 @@ bool CharString::operator<(const CharString &p_right) const {
}
CharString &CharString::operator+=(char p_char) {
- resize(size() ? size() + 1 : 2);
- set(length(), 0);
- set(length() - 1, p_char);
+ const int lhs_len = length();
+ resize(lhs_len + 2);
+
+ char *dst = ptrw();
+ dst[lhs_len] = p_char;
+ dst[lhs_len + 1] = 0;
return *this;
}
@@ -324,11 +311,7 @@ void String::copy_from(const char *p_cstr) {
return;
}
- int len = 0;
- const char *ptr = p_cstr;
- while (*(ptr++) != 0) {
- len++;
- }
+ const size_t len = strlen(p_cstr);
if (len == 0) {
resize(0);
@@ -339,7 +322,7 @@ void String::copy_from(const char *p_cstr) {
char32_t *dst = this->ptrw();
- for (int i = 0; i < len + 1; i++) {
+ for (size_t i = 0; i <= len; i++) {
dst[i] = p_cstr[i];
}
}
@@ -394,13 +377,14 @@ void String::copy_from(const wchar_t *p_cstr, const int p_clip_to) {
void String::copy_from(const char32_t &p_char) {
resize(2);
+ char32_t *dst = ptrw();
if ((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff)) {
print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(p_char, 16) + ".");
- set(0, 0xfffd);
+ dst[0] = 0xfffd;
} else {
- set(0, p_char);
+ dst[0] = p_char;
}
- set(1, 0);
+ dst[1] = 0;
}
void String::copy_from(const char32_t *p_cstr) {
@@ -449,9 +433,8 @@ void String::copy_from(const char32_t *p_cstr, const int p_clip_to) {
// p_length <= p_char strlen
void String::copy_from_unchecked(const char32_t *p_char, const int p_length) {
resize(p_length + 1);
- set(p_length, 0);
-
char32_t *dst = ptrw();
+ dst[p_length] = 0;
for (int i = 0; i < p_length; i++) {
if ((p_char[i] >= 0xd800 && p_char[i] <= 0xdfff) || (p_char[i] > 0x10ffff)) {
@@ -481,6 +464,12 @@ String String::operator+(const String &p_str) const {
return res;
}
+String String::operator+(char32_t p_char) const {
+ String res = *this;
+ res += p_char;
+ return res;
+}
+
String operator+(const char *p_chr, const String &p_str) {
String tmp = p_chr;
tmp += p_str;
@@ -504,27 +493,23 @@ String operator+(char32_t p_chr, const String &p_str) {
}
String &String::operator+=(const String &p_str) {
- if (is_empty()) {
+ const int lhs_len = length();
+ if (lhs_len == 0) {
*this = p_str;
return *this;
}
- if (p_str.is_empty()) {
+ const int rhs_len = p_str.length();
+ if (rhs_len == 0) {
return *this;
}
- int from = length();
-
- resize(length() + p_str.size());
+ resize(lhs_len + rhs_len + 1);
const char32_t *src = p_str.get_data();
- char32_t *dst = ptrw();
-
- set(length(), 0);
+ char32_t *dst = ptrw() + lhs_len;
- for (int i = 0; i < p_str.length(); i++) {
- dst[from + i] = src[i];
- }
+ memcpy(dst, src, (rhs_len + 1) * sizeof(char32_t));
return *this;
}
@@ -534,22 +519,15 @@ String &String::operator+=(const char *p_str) {
return *this;
}
- int src_len = 0;
- const char *ptr = p_str;
- while (*(ptr++) != 0) {
- src_len++;
- }
-
- int from = length();
+ const int lhs_len = length();
+ const size_t rhs_len = strlen(p_str);
- resize(from + src_len + 1);
-
- char32_t *dst = ptrw();
+ resize(lhs_len + rhs_len + 1);
- set(length(), 0);
+ char32_t *dst = ptrw() + lhs_len;
- for (int i = 0; i < src_len; i++) {
- dst[from + i] = p_str[i];
+ for (size_t i = 0; i <= rhs_len; i++) {
+ dst[i] = p_str[i];
}
return *this;
@@ -572,14 +550,16 @@ String &String::operator+=(const char32_t *p_str) {
}
String &String::operator+=(char32_t p_char) {
- resize(size() ? size() + 1 : 2);
- set(length(), 0);
+ const int lhs_len = length();
+ resize(lhs_len + 2);
+ char32_t *dst = ptrw();
if ((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff)) {
print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(p_char, 16) + ".");
- set(length() - 1, 0xfffd);
+ dst[lhs_len] = 0xfffd;
} else {
- set(length() - 1, p_char);
+ dst[lhs_len] = p_char;
}
+ dst[lhs_len + 1] = 0;
return *this;
}
@@ -974,21 +954,21 @@ String String::camelcase_to_underscore(bool lowercase) const {
int start_index = 0;
for (int i = 1; i < this->size(); i++) {
- bool is_upper = is_upper_case(cstr[i]);
+ bool is_upper = is_ascii_upper_case(cstr[i]);
bool is_number = is_digit(cstr[i]);
bool are_next_2_lower = false;
bool is_next_lower = false;
bool is_next_number = false;
- bool was_precedent_upper = is_upper_case(cstr[i - 1]);
+ 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_lower_case(cstr[i + 1]) && is_lower_case(cstr[i + 2]);
+ are_next_2_lower = is_ascii_lower_case(cstr[i + 1]) && is_ascii_lower_case(cstr[i + 2]);
}
if (i + 1 < this->size()) {
- is_next_lower = is_lower_case(cstr[i + 1]);
+ is_next_lower = is_ascii_lower_case(cstr[i + 1]);
is_next_number = is_digit(cstr[i + 1]);
}
@@ -1632,7 +1612,7 @@ String String::utf8(const char *p_utf8, int p_len) {
}
bool String::parse_utf8(const char *p_utf8, int p_len) {
-#define _UNICERROR(m_err) print_error("Unicode parsing error: " + String(m_err) + ". Is the string valid UTF-8?");
+#define UNICERROR(m_err) print_error("Unicode parsing error: " + String(m_err) + ". Is the string valid UTF-8?");
if (!p_utf8) {
return true;
@@ -1673,12 +1653,12 @@ bool String::parse_utf8(const char *p_utf8, int p_len) {
} else if ((c & 0xf8) == 0xf0) {
skip = 3;
} else {
- _UNICERROR("invalid skip at " + num_int64(cstr_size));
+ UNICERROR("invalid skip at " + num_int64(cstr_size));
return true; //invalid utf8
}
if (skip == 1 && (c & 0x1e) == 0) {
- _UNICERROR("overlong rejected at " + num_int64(cstr_size));
+ UNICERROR("overlong rejected at " + num_int64(cstr_size));
return true; //reject overlong
}
@@ -1693,7 +1673,7 @@ bool String::parse_utf8(const char *p_utf8, int p_len) {
}
if (skip) {
- _UNICERROR("no space left");
+ UNICERROR("no space left");
return true; //not enough space
}
}
@@ -1720,17 +1700,17 @@ bool String::parse_utf8(const char *p_utf8, int p_len) {
} else if ((*p_utf8 & 0xf8) == 0xf0) {
len = 4;
} else {
- _UNICERROR("invalid len");
+ UNICERROR("invalid len");
return true; //invalid UTF8
}
if (len > cstr_size) {
- _UNICERROR("no space left");
+ UNICERROR("no space left");
return true; //not enough space
}
if (len == 2 && (*p_utf8 & 0x1E) == 0) {
- _UNICERROR("no space left");
+ UNICERROR("no space left");
return true; //reject overlong
}
@@ -1745,18 +1725,18 @@ bool String::parse_utf8(const char *p_utf8, int p_len) {
for (int i = 1; i < len; i++) {
if ((p_utf8[i] & 0xc0) != 0x80) {
- _UNICERROR("invalid utf8");
+ UNICERROR("invalid utf8");
return true; //invalid utf8
}
if (unichar == 0 && i == 2 && ((p_utf8[i] & 0x7f) >> (7 - len)) == 0) {
- _UNICERROR("invalid utf8 overlong");
+ UNICERROR("invalid utf8 overlong");
return true; //no overlong
}
unichar = (unichar << 6) | (p_utf8[i] & 0x3f);
}
}
if (unichar >= 0xd800 && unichar <= 0xdfff) {
- _UNICERROR("invalid code point");
+ UNICERROR("invalid code point");
return CharString();
}
@@ -1766,7 +1746,7 @@ bool String::parse_utf8(const char *p_utf8, int p_len) {
}
return false;
-#undef _UNICERROR
+#undef UNICERROR
}
CharString String::utf8() const {
@@ -1840,7 +1820,7 @@ String String::utf16(const char16_t *p_utf16, int p_len) {
}
bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
-#define _UNICERROR(m_err) print_error("Unicode parsing error: " + String(m_err) + ". Is the string valid UTF-16?");
+#define UNICERROR(m_err) print_error("Unicode parsing error: " + String(m_err) + ". Is the string valid UTF-16?");
if (!p_utf16) {
return true;
@@ -1880,7 +1860,7 @@ bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
if ((c & 0xfffffc00) == 0xd800) {
skip = 1; // lead surrogate
} else if ((c & 0xfffffc00) == 0xdc00) {
- _UNICERROR("invalid utf16 surrogate at " + num_int64(cstr_size));
+ UNICERROR("invalid utf16 surrogate at " + num_int64(cstr_size));
return true; // invalid UTF16
} else {
skip = 0;
@@ -1890,7 +1870,7 @@ bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
if ((c & 0xfffffc00) == 0xdc00) { // trail surrogate
--skip;
} else {
- _UNICERROR("invalid utf16 surrogate at " + num_int64(cstr_size));
+ UNICERROR("invalid utf16 surrogate at " + num_int64(cstr_size));
return true; // invalid UTF16
}
}
@@ -1900,7 +1880,7 @@ bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
}
if (skip) {
- _UNICERROR("no space left");
+ UNICERROR("no space left");
return true; // not enough space
}
}
@@ -1925,7 +1905,7 @@ bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
}
if (len > cstr_size) {
- _UNICERROR("no space left");
+ UNICERROR("no space left");
return true; //not enough space
}
@@ -1943,7 +1923,7 @@ bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
}
return false;
-#undef _UNICERROR
+#undef UNICERROR
}
Char16String String::utf16() const {
@@ -2212,7 +2192,7 @@ bool String::is_numeric() const {
return false;
}
dot = true;
- } else if (c < '0' || c > '9') {
+ } else if (!is_digit(c)) {
return false;
}
}
@@ -3080,7 +3060,7 @@ bool String::is_subsequence_of(const String &p_string) const {
return _base_is_subsequence_of(p_string, false);
}
-bool String::is_subsequence_ofi(const String &p_string) const {
+bool String::is_subsequence_ofn(const String &p_string) const {
return _base_is_subsequence_of(p_string, true);
}
@@ -3558,6 +3538,10 @@ String String::rstrip(const String &p_chars) const {
return substr(0, end + 1);
}
+bool String::is_network_share_path() const {
+ return begins_with("//") || begins_with("\\\\");
+}
+
String String::simplify_path() const {
String s = *this;
String drive;
@@ -3570,6 +3554,9 @@ String String::simplify_path() const {
} else if (s.begins_with("user://")) {
drive = "user://";
s = s.substr(7, s.length());
+ } else if (is_network_share_path()) {
+ drive = s.substr(0, 2);
+ s = s.substr(2, s.length() - 2);
} else if (s.begins_with("/") || s.begins_with("\\")) {
drive = s.substr(0, 1);
s = s.substr(1, s.length() - 1);
@@ -3684,7 +3671,7 @@ bool String::is_valid_identifier() const {
}
}
- bool valid_char = is_digit(str[i]) || is_lower_case(str[i]) || is_upper_case(str[i]) || str[i] == '_';
+ bool valid_char = is_ascii_identifier_char(str[i]);
if (!valid_char) {
return false;
@@ -3709,7 +3696,7 @@ String String::uri_encode() const {
String res;
for (int i = 0; i < temp.length(); ++i) {
char ord = temp[i];
- if (ord == '.' || ord == '-' || ord == '_' || ord == '~' || is_lower_case(ord) || is_upper_case(ord) || is_digit(ord)) {
+ if (ord == '.' || ord == '-' || ord == '~' || is_ascii_identifier_char(ord)) {
res += ord;
} else {
char h_Val[3];
@@ -3731,9 +3718,9 @@ String String::uri_decode() const {
for (int i = 0; i < src.length(); ++i) {
if (src[i] == '%' && i + 2 < src.length()) {
char ord1 = src[i + 1];
- if (is_digit(ord1) || is_upper_case(ord1)) {
+ if (is_digit(ord1) || is_ascii_upper_case(ord1)) {
char ord2 = src[i + 2];
- if (is_digit(ord2) || is_upper_case(ord2)) {
+ if (is_digit(ord2) || is_ascii_upper_case(ord2)) {
char bytes[3] = { (char)ord1, (char)ord2, 0 };
res += (char)strtol(bytes, nullptr, 16);
i += 2;
@@ -3860,7 +3847,7 @@ static _FORCE_INLINE_ int _xml_unescape(const char32_t *p_src, int p_src_len, ch
for (int i = 2; i < p_src_len; i++) {
eat = i + 1;
char32_t ct = p_src[i];
- if (ct == ';' || ct < '0' || ct > '9') {
+ if (ct == ';' || !is_digit(ct)) {
break;
}
}
@@ -3990,7 +3977,7 @@ String String::pad_zeros(int p_digits) const {
int begin = 0;
- while (begin < end && (s[begin] < '0' || s[begin] > '9')) {
+ while (begin < end && !is_digit(s[begin])) {
begin++;
}
@@ -4035,7 +4022,7 @@ bool String::is_valid_int() const {
}
for (int i = from; i < len; i++) {
- if (operator[](i) < '0' || operator[](i) > '9') {
+ if (!is_digit(operator[](i))) {
return false; // no start with number plz
}
}
@@ -4137,15 +4124,11 @@ String String::path_to(const String &p_path) const {
dst += "/";
}
- String base;
-
if (src.begins_with("res://") && dst.begins_with("res://")) {
- base = "res:/";
src = src.replace("res://", "/");
dst = dst.replace("res://", "/");
} else if (src.begins_with("user://") && dst.begins_with("user://")) {
- base = "user:/";
src = src.replace("user://", "/");
dst = dst.replace("user://", "/");
@@ -4160,7 +4143,6 @@ String String::path_to(const String &p_path) const {
return p_path; //impossible to do this
}
- base = src_begin;
src = src.substr(src_begin.length(), src.length());
dst = dst.substr(dst_begin.length(), dst.length());
}
@@ -4271,13 +4253,13 @@ bool String::is_relative_path() const {
String String::get_base_dir() const {
int end = 0;
- // url scheme style base
+ // URL scheme style base.
int basepos = find("://");
if (basepos != -1) {
end = basepos + 3;
}
- // windows top level directory base
+ // Windows top level directory base.
if (end == 0) {
basepos = find(":/");
if (basepos == -1) {
@@ -4288,7 +4270,24 @@ String String::get_base_dir() const {
}
}
- // unix root directory base
+ // Windows UNC network share path.
+ if (end == 0) {
+ if (is_network_share_path()) {
+ basepos = find("/", 2);
+ if (basepos == -1) {
+ basepos = find("\\", 2);
+ }
+ int servpos = find("/", basepos + 1);
+ if (servpos == -1) {
+ servpos = find("\\", basepos + 1);
+ }
+ if (servpos != -1) {
+ end = servpos + 1;
+ }
+ }
+ }
+
+ // Unix root directory base.
if (end == 0) {
if (begins_with("/")) {
end = 1;
@@ -4354,7 +4353,7 @@ String String::property_name_encode() const {
}
// Changes made to the set of invalid characters must also be reflected in the String documentation.
-const String String::invalid_node_name_characters = ". : @ / \"";
+const String String::invalid_node_name_characters = ". : @ / \" " UNIQUE_NODE_PREFIX;
String String::validate_node_name() const {
Vector<String> chars = String::invalid_node_name_characters.split(" ");
@@ -4428,7 +4427,7 @@ String String::sprintf(const Array &values, bool *error) const {
int min_chars = 0;
int min_decimals = 0;
bool in_decimals = false;
- bool pad_with_zeroes = false;
+ bool pad_with_zeros = false;
bool left_justified = false;
bool show_sign = false;
@@ -4481,7 +4480,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Padding.
int pad_chars_count = (value < 0 || show_sign) ? min_chars - 1 : min_chars;
- String pad_char = pad_with_zeroes ? String("0") : String(" ");
+ String pad_char = pad_with_zeros ? String("0") : String(" ");
if (left_justified) {
str = str.rpad(pad_chars_count, pad_char);
} else {
@@ -4489,10 +4488,13 @@ String String::sprintf(const Array &values, bool *error) const {
}
// Sign.
- if (show_sign && value >= 0) {
- str = str.insert(pad_with_zeroes ? 0 : str.length() - number_len, "+");
- } else if (value < 0) {
- str = str.insert(pad_with_zeroes ? 0 : str.length() - number_len, "-");
+ if (show_sign || value < 0) {
+ String sign_char = value < 0 ? "-" : "+";
+ if (left_justified) {
+ str = str.insert(0, sign_char);
+ } else {
+ str = str.insert(pad_with_zeros ? 0 : str.length() - number_len, sign_char);
+ }
}
formatted += str;
@@ -4521,13 +4523,9 @@ String String::sprintf(const Array &values, bool *error) const {
// 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_zeroes ? String("0") : String(" ");
+ String pad_char = pad_with_zeros ? String("0") : String(" ");
if (left_justified) {
- if (pad_with_zeroes) {
- return "left justification cannot be used with zeros as the padding";
- } else {
- str = str.rpad(pad_chars_count, pad_char);
- }
+ str = str.rpad(pad_chars_count, pad_char);
} else {
str = str.lpad(pad_chars_count, pad_char);
}
@@ -4538,7 +4536,7 @@ String String::sprintf(const Array &values, bool *error) const {
if (left_justified) {
str = str.insert(0, sign_char);
} else {
- str = str.insert(pad_with_zeroes ? 0 : str.length() - initial_len, sign_char);
+ str = str.insert(pad_with_zeros ? 0 : str.length() - initial_len, sign_char);
}
}
@@ -4627,7 +4625,11 @@ String String::sprintf(const Array &values, bool *error) const {
min_decimals += n;
} else {
if (c == '0' && min_chars == 0) {
- pad_with_zeroes = true;
+ if (left_justified) {
+ WARN_PRINT("'0' flag ignored with '-' flag in string format");
+ } else {
+ pad_with_zeros = true;
+ }
} else {
min_chars *= 10;
min_chars += n;
@@ -4676,7 +4678,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Back to defaults:
min_chars = 0;
min_decimals = 6;
- pad_with_zeroes = false;
+ pad_with_zeros = false;
left_justified = false;
show_sign = false;
in_decimals = false;
@@ -4777,6 +4779,17 @@ Vector<uint8_t> String::to_utf32_buffer() const {
}
#ifdef TOOLS_ENABLED
+/**
+ * "Tools TRanslate". Performs string replacement for internationalization
+ * within the editor. A translation context can optionally be specified to
+ * disambiguate between identical source strings in translations. When
+ * placeholders are desired, use `vformat(TTR("Example: %s"), some_string)`.
+ * If a string mentions a quantity (and may therefore need a dynamic plural form),
+ * use `TTRN()` instead of `TTR()`.
+ *
+ * NOTE: Only use `TTR()` in editor-only code (typically within the `editor/` folder).
+ * For translations that can be supplied by exported projects, use `RTR()` instead.
+ */
String TTR(const String &p_text, const String &p_context) {
if (TranslationServer::get_singleton()) {
return TranslationServer::get_singleton()->tool_translate(p_text, p_context);
@@ -4785,6 +4798,18 @@ String TTR(const String &p_text, const String &p_context) {
return p_text;
}
+/**
+ * "Tools TRanslate for N items". Performs string replacement for
+ * internationalization within the editor. A translation context can optionally
+ * be specified to disambiguate between identical source strings in
+ * translations. Use `TTR()` if the string doesn't need dynamic plural form.
+ * When placeholders are desired, use
+ * `vformat(TTRN("%d item", "%d items", some_integer), some_integer)`.
+ * The placeholder must be present in both strings to avoid run-time warnings in `vformat()`.
+ *
+ * NOTE: Only use `TTRN()` in editor-only code (typically within the `editor/` folder).
+ * For translations that can be supplied by exported projects, use `RTRN()` instead.
+ */
String TTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
if (TranslationServer::get_singleton()) {
return TranslationServer::get_singleton()->tool_translate_plural(p_text, p_text_plural, p_n, p_context);
@@ -4797,9 +4822,10 @@ String TTRN(const String &p_text, const String &p_text_plural, int p_n, const St
return p_text_plural;
}
-/* DTR and DTRN are used for the documentation, handling descriptions extracted
- * from the XML.
- * They also replace `$DOCS_URL` with the actual URL to the documentation's branch,
+/**
+ * "Docs TRanslate". Used for the editor class reference documentation,
+ * handling descriptions extracted from the XML.
+ * It also replaces `$DOCS_URL` with the actual URL to the documentation's branch,
* to allow dehardcoding it in the XML and doing proper substitutions everywhere.
*/
String DTR(const String &p_text, const String &p_context) {
@@ -4813,6 +4839,12 @@ String DTR(const String &p_text, const String &p_context) {
return text.replace("$DOCS_URL", VERSION_DOCS_URL);
}
+/**
+ * "Docs TRanslate for N items". Used for the editor class reference documentation
+ * (with support for plurals), handling descriptions extracted from the XML.
+ * It also replaces `$DOCS_URL` with the actual URL to the documentation's branch,
+ * to allow dehardcoding it in the XML and doing proper substitutions everywhere.
+ */
String DTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
const String text = p_text.dedent().strip_edges();
const String text_plural = p_text_plural.dedent().strip_edges();
@@ -4829,6 +4861,19 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St
}
#endif
+/**
+ * "Run-time TRanslate". Performs string replacement for internationalization
+ * within a running project. The translation string must be supplied by the
+ * project, as Godot does not provide built-in translations for `RTR()` strings
+ * to keep binary size low. A translation context can optionally be specified to
+ * disambiguate between identical source strings in translations. When
+ * placeholders are desired, use `vformat(RTR("Example: %s"), some_string)`.
+ * If a string mentions a quantity (and may therefore need a dynamic plural form),
+ * use `RTRN()` instead of `RTR()`.
+ *
+ * NOTE: Do not use `RTR()` in editor-only code (typically within the `editor/`
+ * folder). For editor translations, use `TTR()` instead.
+ */
String RTR(const String &p_text, const String &p_context) {
if (TranslationServer::get_singleton()) {
String rtr = TranslationServer::get_singleton()->tool_translate(p_text, p_context);
@@ -4842,6 +4887,20 @@ String RTR(const String &p_text, const String &p_context) {
return p_text;
}
+/**
+ * "Run-time TRanslate for N items". Performs string replacement for
+ * internationalization within a running project. The translation string must be
+ * supplied by the project, as Godot does not provide built-in translations for
+ * `RTRN()` strings to keep binary size low. A translation context can
+ * optionally be specified to disambiguate between identical source strings in
+ * translations. Use `RTR()` if the string doesn't need dynamic plural form.
+ * When placeholders are desired, use
+ * `vformat(RTRN("%d item", "%d items", some_integer), some_integer)`.
+ * The placeholder must be present in both strings to avoid run-time warnings in `vformat()`.
+ *
+ * NOTE: Do not use `RTRN()` in editor-only code (typically within the `editor/`
+ * folder). For editor translations, use `TTRN()` instead.
+ */
String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
if (TranslationServer::get_singleton()) {
String rtr = TranslationServer::get_singleton()->tool_translate_plural(p_text, p_text_plural, p_n, p_context);
diff --git a/core/string/ustring.h b/core/string/ustring.h
index 4840c236c0..48f2e45105 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -32,6 +32,7 @@
#define USTRING_GODOT_H
// Note: Renamed to avoid conflict with ICU header with the same name.
+#include "core/string/char_utils.h"
#include "core/templates/cowdata.h"
#include "core/templates/vector.h"
#include "core/typedefs.h"
@@ -224,6 +225,7 @@ public:
bool operator==(const String &p_str) const;
bool operator!=(const String &p_str) const;
String operator+(const String &p_str) const;
+ String operator+(char32_t p_char) const;
String &operator+=(const String &);
String &operator+=(char32_t p_char);
@@ -285,7 +287,7 @@ public:
bool ends_with(const String &p_string) const;
bool is_enclosed_in(const String &p_string) const;
bool is_subsequence_of(const String &p_string) const;
- bool is_subsequence_ofi(const String &p_string) const;
+ bool is_subsequence_ofn(const String &p_string) const;
bool is_quoted() const;
Vector<String> bigrams() const;
float similarity(const String &p_string) const;
@@ -394,6 +396,8 @@ public:
Vector<uint8_t> sha256_buffer() const;
_FORCE_INLINE_ bool is_empty() const { return length() == 0; }
+ _FORCE_INLINE_ bool contains(const char *p_str) const { return find(p_str) != -1; }
+ _FORCE_INLINE_ bool contains(const String &p_str) const { return find(p_str) != -1; }
// path functions
bool is_absolute_path() const;
@@ -405,6 +409,7 @@ public:
String get_file() const;
static String humanize_size(uint64_t p_size);
String simplify_path() const;
+ bool is_network_share_path() const;
String xml_escape(bool p_escape_quotes = false) const;
String xml_unescape() const;
@@ -518,10 +523,6 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St
#define TTRGET(m_value) TTR(m_value)
#else
-#define TTR(m_value) String()
-#define TTRN(m_value) String()
-#define DTR(m_value) String()
-#define DTRN(m_value) String()
#define TTRC(m_value) (m_value)
#define TTRGET(m_value) (m_value)
#endif
@@ -530,7 +531,6 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St
String RTR(const String &p_text, const String &p_context = "");
String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
-bool is_symbol(char32_t c);
bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end);
_FORCE_INLINE_ void sarray_add_str(Vector<String> &arr) {