summaryrefslogtreecommitdiff
path: root/core/string
diff options
context:
space:
mode:
Diffstat (limited to 'core/string')
-rw-r--r--core/string/node_path.cpp23
-rw-r--r--core/string/optimized_translation.cpp21
-rw-r--r--core/string/print_string.cpp6
-rw-r--r--core/string/print_string.h12
-rw-r--r--core/string/string_name.cpp108
-rw-r--r--core/string/string_name.h36
-rw-r--r--core/string/translation.cpp307
-rw-r--r--core/string/translation.h32
-rw-r--r--core/string/translation_po.cpp27
-rw-r--r--core/string/ustring.cpp498
-rw-r--r--core/string/ustring.h34
11 files changed, 751 insertions, 353 deletions
diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp
index d3afa7b4dd..7ab85ac9d0 100644
--- a/core/string/node_path.cpp
+++ b/core/string/node_path.cpp
@@ -240,19 +240,26 @@ NodePath NodePath::rel_path_to(const NodePath &p_np) const {
common_parent--;
Vector<StringName> relpath;
+ relpath.resize(src_dirs.size() + dst_dirs.size() + 1);
- for (int i = src_dirs.size() - 1; i > common_parent; i--) {
- relpath.push_back("..");
+ StringName *relpath_ptr = relpath.ptrw();
+
+ int path_size = 0;
+ StringName back_str("..");
+ for (int i = common_parent + 1; i < src_dirs.size(); i++) {
+ relpath_ptr[path_size++] = back_str;
}
for (int i = common_parent + 1; i < dst_dirs.size(); i++) {
- relpath.push_back(dst_dirs[i]);
+ relpath_ptr[path_size++] = dst_dirs[i];
}
- if (relpath.size() == 0) {
- relpath.push_back(".");
+ if (path_size == 0) {
+ relpath_ptr[path_size++] = ".";
}
+ relpath.resize(path_size);
+
return NodePath(relpath, p_np.get_subnames(), false);
}
@@ -286,12 +293,12 @@ void NodePath::simplify() {
break;
}
if (data->path[i].operator String() == ".") {
- data->path.remove(i);
+ data->path.remove_at(i);
i--;
} else if (i > 0 && data->path[i].operator String() == ".." && data->path[i - 1].operator String() != "." && data->path[i - 1].operator String() != "..") {
//remove both
- data->path.remove(i - 1);
- data->path.remove(i - 1);
+ data->path.remove_at(i - 1);
+ data->path.remove_at(i - 1);
i -= 2;
if (data->path.size() == 0) {
data->path.push_back(".");
diff --git a/core/string/optimized_translation.cpp b/core/string/optimized_translation.cpp
index 268562d971..f8be564740 100644
--- a/core/string/optimized_translation.cpp
+++ b/core/string/optimized_translation.cpp
@@ -64,11 +64,10 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
int idx = 0;
int total_compression_size = 0;
- int total_string_size = 0;
- for (List<StringName>::Element *E = keys.front(); E; E = E->next()) {
+ for (const StringName &E : keys) {
//hash string
- CharString cs = E->get().operator String().utf8();
+ CharString cs = E.operator String().utf8();
uint32_t h = hash(0, cs.get_data());
Pair<int, CharString> p;
p.first = idx;
@@ -76,7 +75,7 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
buckets.write[h % size].push_back(p);
//compress string
- CharString src_s = p_from->get_message(E->get()).operator String().utf8();
+ CharString src_s = p_from->get_message(E).operator String().utf8();
CompressedString ps;
ps.orig_len = src_s.size();
ps.offset = total_compression_size;
@@ -102,7 +101,6 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
compressed.write[idx] = ps;
total_compression_size += ps.compressed.size();
- total_string_size += src_s.size();
idx++;
}
@@ -147,26 +145,23 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
uint32_t *btw = (uint32_t *)&btwb[0];
int btindex = 0;
- int collisions = 0;
for (int i = 0; i < size; i++) {
const Map<uint32_t, int> &t = table[i];
if (t.size() == 0) {
htw[i] = 0xFFFFFFFF; //nothing
continue;
- } else if (t.size() > 1) {
- collisions += t.size() - 1;
}
htw[i] = btindex;
btw[btindex++] = t.size();
btw[btindex++] = hfunc_table[i];
- for (Map<uint32_t, int>::Element *E = t.front(); E; E = E->next()) {
- btw[btindex++] = E->key();
- btw[btindex++] = compressed[E->get()].offset;
- btw[btindex++] = compressed[E->get()].compressed.size();
- btw[btindex++] = compressed[E->get()].orig_len;
+ for (const KeyValue<uint32_t, int> &E : t) {
+ btw[btindex++] = E.key;
+ btw[btindex++] = compressed[E.value].offset;
+ btw[btindex++] = compressed[E.value].compressed.size();
+ btw[btindex++] = compressed[E.value].orig_len;
}
}
diff --git a/core/string/print_string.cpp b/core/string/print_string.cpp
index 345371d733..adc218f597 100644
--- a/core/string/print_string.cpp
+++ b/core/string/print_string.cpp
@@ -69,7 +69,7 @@ void remove_print_handler(PrintHandlerList *p_handler) {
ERR_FAIL_COND(l == nullptr);
}
-void print_line(String p_string) {
+void __print_line(String p_string) {
if (!_print_line_enabled) {
return;
}
@@ -108,3 +108,7 @@ void print_verbose(String p_string) {
print_line(p_string);
}
}
+
+String stringify_variants(Variant p_var) {
+ return p_var.operator String();
+}
diff --git a/core/string/print_string.h b/core/string/print_string.h
index 1a9ff1efd6..3cd170b68e 100644
--- a/core/string/print_string.h
+++ b/core/string/print_string.h
@@ -31,7 +31,7 @@
#ifndef PRINT_STRING_H
#define PRINT_STRING_H
-#include "core/string/ustring.h"
+#include "core/variant/variant.h"
extern void (*_print_func)(String);
@@ -46,13 +46,21 @@ struct PrintHandlerList {
PrintHandlerList() {}
};
+String stringify_variants(Variant p_var);
+
+template <typename... Args>
+String stringify_variants(Variant p_var, Args... p_args) {
+ return p_var.operator String() + " " + stringify_variants(p_args...);
+}
+
void add_print_handler(PrintHandlerList *p_handler);
void remove_print_handler(PrintHandlerList *p_handler);
extern bool _print_line_enabled;
extern bool _print_error_enabled;
-extern void print_line(String p_string);
+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__))
#endif // PRINT_STRING_H
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index 14b87072bb..9024f60dae 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -41,13 +41,17 @@ StaticCString StaticCString::create(const char *p_ptr) {
StringName::_Data *StringName::_table[STRING_TABLE_LEN];
-StringName _scs_create(const char *p_chr) {
- return (p_chr[0] ? StringName(StaticCString::create(p_chr)) : StringName());
+StringName _scs_create(const char *p_chr, bool p_static) {
+ return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName());
}
bool StringName::configured = false;
Mutex StringName::mutex;
+#ifdef DEBUG_ENABLED
+bool StringName::debug_stringname = false;
+#endif
+
void StringName::setup() {
ERR_FAIL_COND(configured);
for (int i = 0; i < STRING_TABLE_LEN; i++) {
@@ -59,12 +63,29 @@ void StringName::setup() {
void StringName::cleanup() {
MutexLock lock(mutex);
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ Vector<_Data *> data;
+ for (int i = 0; i < STRING_TABLE_LEN; i++) {
+ _Data *d = _table[i];
+ while (d) {
+ data.push_back(d);
+ d = d->next;
+ }
+ }
+ print_line("\nStringName Reference Ranking:\n");
+ data.sort_custom<DebugSortReferences>();
+ for (int i = 0; i < MIN(100, data.size()); i++) {
+ print_line(itos(i + 1) + ": " + data[i]->get_name() + " - " + itos(data[i]->debug_references));
+ }
+ }
+#endif
int lost_strings = 0;
for (int i = 0; i < STRING_TABLE_LEN; i++) {
while (_table[i]) {
_Data *d = _table[i];
lost_strings++;
- if (OS::get_singleton()->is_stdout_verbose()) {
+ 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 {
@@ -79,6 +100,7 @@ void StringName::cleanup() {
if (lost_strings) {
print_verbose("StringName: " + itos(lost_strings) + " unclaimed string names at exit.");
}
+ configured = false;
}
void StringName::unref() {
@@ -87,6 +109,13 @@ void StringName::unref() {
if (_data && _data->refcount.unref()) {
MutexLock lock(mutex);
+ if (_data->static_count.get() > 0) {
+ if (_data->cname) {
+ ERR_PRINT("BUG: Unreferenced static string to 0: " + String(_data->cname));
+ } else {
+ ERR_PRINT("BUG: Unreferenced static string to 0: " + String(_data->name));
+ }
+ }
if (_data->prev) {
_data->prev->next = _data->next;
} else {
@@ -153,7 +182,7 @@ StringName::StringName(const StringName &p_name) {
}
}
-StringName::StringName(const char *p_name) {
+StringName::StringName(const char *p_name, bool p_static) {
_data = nullptr;
ERR_FAIL_COND(!configured);
@@ -181,25 +210,42 @@ StringName::StringName(const char *p_name) {
if (_data) {
if (_data->refcount.ref()) {
// exists
- return;
+ if (p_static) {
+ _data->static_count.increment();
+ }
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
+ }
+#endif
}
+
+ return;
}
_data = memnew(_Data);
_data->name = p_name;
_data->refcount.init();
+ _data->static_count.set(p_static ? 1 : 0);
_data->hash = hash;
_data->idx = idx;
_data->cname = nullptr;
_data->next = _table[idx];
_data->prev = nullptr;
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ // Keep in memory, force static.
+ _data->refcount.ref();
+ _data->static_count.increment();
+ }
+#endif
if (_table[idx]) {
_table[idx]->prev = _data;
}
_table[idx] = _data;
}
-StringName::StringName(const StaticCString &p_static_string) {
+StringName::StringName(const StaticCString &p_static_string, bool p_static) {
_data = nullptr;
ERR_FAIL_COND(!configured);
@@ -225,6 +271,14 @@ StringName::StringName(const StaticCString &p_static_string) {
if (_data) {
if (_data->refcount.ref()) {
// exists
+ if (p_static) {
+ _data->static_count.increment();
+ }
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
+ }
+#endif
return;
}
}
@@ -232,18 +286,26 @@ StringName::StringName(const StaticCString &p_static_string) {
_data = memnew(_Data);
_data->refcount.init();
+ _data->static_count.set(p_static ? 1 : 0);
_data->hash = hash;
_data->idx = idx;
_data->cname = p_static_string.ptr;
_data->next = _table[idx];
_data->prev = nullptr;
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ // Keep in memory, force static.
+ _data->refcount.ref();
+ _data->static_count.increment();
+ }
+#endif
if (_table[idx]) {
_table[idx]->prev = _data;
}
_table[idx] = _data;
}
-StringName::StringName(const String &p_name) {
+StringName::StringName(const String &p_name, bool p_static) {
_data = nullptr;
ERR_FAIL_COND(!configured);
@@ -269,6 +331,14 @@ StringName::StringName(const String &p_name) {
if (_data) {
if (_data->refcount.ref()) {
// exists
+ if (p_static) {
+ _data->static_count.increment();
+ }
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
+ }
+#endif
return;
}
}
@@ -276,11 +346,20 @@ StringName::StringName(const String &p_name) {
_data = memnew(_Data);
_data->name = p_name;
_data->refcount.init();
+ _data->static_count.set(p_static ? 1 : 0);
_data->hash = hash;
_data->idx = idx;
_data->cname = nullptr;
_data->next = _table[idx];
_data->prev = nullptr;
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ // Keep in memory, force static.
+ _data->refcount.ref();
+ _data->static_count.increment();
+ }
+#endif
+
if (_table[idx]) {
_table[idx]->prev = _data;
}
@@ -311,6 +390,12 @@ StringName StringName::search(const char *p_name) {
}
if (_data && _data->refcount.ref()) {
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
+ }
+#endif
+
return StringName(_data);
}
@@ -368,16 +453,17 @@ StringName StringName::search(const String &p_name) {
}
if (_data && _data->refcount.ref()) {
+#ifdef DEBUG_ENABLED
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
+ }
+#endif
return StringName(_data);
}
return StringName(); //does not exist
}
-StringName::~StringName() {
- unref();
-}
-
bool operator==(const String &p_name, const StringName &p_string_name) {
return p_name == p_string_name.operator String();
}
diff --git a/core/string/string_name.h b/core/string/string_name.h
index 44d0ea14fa..ce7988744b 100644
--- a/core/string/string_name.h
+++ b/core/string/string_name.h
@@ -44,16 +44,19 @@ struct StaticCString {
class StringName {
enum {
- STRING_TABLE_BITS = 12,
+ STRING_TABLE_BITS = 16,
STRING_TABLE_LEN = 1 << STRING_TABLE_BITS,
STRING_TABLE_MASK = STRING_TABLE_LEN - 1
};
struct _Data {
SafeRefCount refcount;
+ SafeNumeric<uint32_t> static_count;
const char *cname = nullptr;
String name;
-
+#ifdef DEBUG_ENABLED
+ uint32_t debug_references = 0;
+#endif
String get_name() const { return cname ? String(cname) : name; }
int idx = 0;
uint32_t hash = 0;
@@ -79,6 +82,15 @@ class StringName {
static void setup();
static void cleanup();
static bool configured;
+#ifdef DEBUG_ENABLED
+ struct DebugSortReferences {
+ bool operator()(const _Data *p_left, const _Data *p_right) const {
+ return p_left->debug_references > p_right->debug_references;
+ }
+ };
+
+ static bool debug_stringname;
+#endif
StringName(_Data *p_data) { _data = p_data; }
@@ -146,12 +158,20 @@ public:
};
void operator=(const StringName &p_name);
- StringName(const char *p_name);
+ StringName(const char *p_name, bool p_static = false);
StringName(const StringName &p_name);
- StringName(const String &p_name);
- StringName(const StaticCString &p_static_string);
+ StringName(const String &p_name, bool p_static = false);
+ StringName(const StaticCString &p_static_string, bool p_static = false);
StringName() {}
- ~StringName();
+ _FORCE_INLINE_ ~StringName() {
+ if (likely(configured) && _data) { //only free if configured
+ unref();
+ }
+ }
+
+#ifdef DEBUG_ENABLED
+ static void set_debug_stringnames(bool p_enable) { debug_stringname = p_enable; }
+#endif
};
bool operator==(const String &p_name, const StringName &p_string_name);
@@ -159,6 +179,8 @@ bool operator!=(const String &p_name, const StringName &p_string_name);
bool operator==(const char *p_name, const StringName &p_string_name);
bool operator!=(const char *p_name, const StringName &p_string_name);
-StringName _scs_create(const char *p_chr);
+StringName _scs_create(const char *p_chr, bool p_static = false);
+
+#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 153f0190fd..b98aad9b58 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -35,7 +35,6 @@
#include "core/os/os.h"
#ifdef TOOLS_ENABLED
-#include "editor/editor_settings.h"
#include "main/main.h"
#endif
@@ -84,6 +83,7 @@ static const char *locale_list[] = {
"ast_ES", // Asturian (Spain)
"ayc_PE", // Southern Aymara (Peru)
"ay_PE", // Aymara (Peru)
+ "az", // Azerbaijani
"az_AZ", // Azerbaijani (Azerbaijan)
"be", // Belarusian
"be_BY", // Belarusian (Belarus)
@@ -240,6 +240,7 @@ static const char *locale_list[] = {
"ka_GE", // Georgian (Georgia)
"kk_KZ", // Kazakh (Kazakhstan)
"kl_GL", // Kalaallisut (Greenland)
+ "km", // Central Khmer
"km_KH", // Central Khmer (Cambodia)
"kn_IN", // Kannada (India)
"kok_IN", // Konkani (India)
@@ -390,6 +391,7 @@ static const char *locale_list[] = {
"tr_CY", // Turkish (Cyprus)
"tr_TR", // Turkish (Turkey)
"ts_ZA", // Tsonga (South Africa)
+ "tt", // Tatar
"tt_RU", // Tatar (Russia)
"tzm", // Central Atlas Tamazight
"tzm_MA", // Central Atlas Tamazight (Marrocos)
@@ -458,6 +460,7 @@ static const char *locale_names[] = {
"Asturian (Spain)",
"Southern Aymara (Peru)",
"Aymara (Peru)",
+ "Azerbaijani",
"Azerbaijani (Azerbaijan)",
"Belarusian",
"Belarusian (Belarus)",
@@ -614,6 +617,7 @@ static const char *locale_names[] = {
"Georgian (Georgia)",
"Kazakh (Kazakhstan)",
"Kalaallisut (Greenland)",
+ "Central Khmer",
"Central Khmer (Cambodia)",
"Kannada (India)",
"Konkani (India)",
@@ -764,6 +768,7 @@ static const char *locale_names[] = {
"Turkish (Cyprus)",
"Turkish (Turkey)",
"Tsonga (South Africa)",
+ "Tatar",
"Tatar (Russia)",
"Central Atlas Tamazight",
"Central Atlas Tamazight (Marrocos)",
@@ -804,9 +809,12 @@ static const char *locale_names[] = {
// - https://msdn.microsoft.com/en-us/library/windows/desktop/ms693062(v=vs.85).aspx
static const char *locale_renames[][2] = {
- { "in", "id" }, // Indonesian
- { "iw", "he" }, // Hebrew
- { "no", "nb" }, // Norwegian Bokmål
+ { "in", "id" }, // Indonesian
+ { "iw", "he" }, // Hebrew
+ { "no", "nb" }, // Norwegian Bokmål
+ { "C", "en" }, // "C" is the simple/default/untranslated Computer locale.
+ // ASCII-only, English, no currency symbols. Godot treats this as "en".
+ // See https://unix.stackexchange.com/a/87763/164141 "The C locale is"...
{ nullptr, nullptr }
};
@@ -814,8 +822,8 @@ static const char *locale_renames[][2] = {
Dictionary Translation::_get_messages() const {
Dictionary d;
- for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) {
- d[E->key()] = E->value();
+ for (const KeyValue<StringName, StringName> &E : translation_map) {
+ d[E.key] = E.value;
}
return d;
}
@@ -824,8 +832,8 @@ Vector<String> Translation::_get_message_list() const {
Vector<String> msgs;
msgs.resize(translation_map.size());
int idx = 0;
- for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) {
- msgs.set(idx, E->key());
+ for (const KeyValue<StringName, StringName> &E : translation_map) {
+ msgs.set(idx, E.key);
idx += 1;
}
@@ -835,8 +843,8 @@ Vector<String> Translation::_get_message_list() const {
void Translation::_set_messages(const Dictionary &p_messages) {
List<Variant> keys;
p_messages.get_key_list(&keys);
- for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
- translation_map[E->get()] = p_messages[E->get()];
+ for (const Variant &E : keys) {
+ translation_map[E] = p_messages[E];
}
}
@@ -869,6 +877,11 @@ void Translation::add_plural_message(const StringName &p_src_text, const Vector<
}
StringName Translation::get_message(const StringName &p_src_text, const StringName &p_context) const {
+ StringName ret;
+ if (GDVIRTUAL_CALL(_get_message, p_src_text, p_context, ret)) {
+ return ret;
+ }
+
if (p_context != StringName()) {
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");
}
@@ -882,6 +895,11 @@ StringName Translation::get_message(const StringName &p_src_text, const StringNa
}
StringName Translation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
+ StringName ret;
+ if (GDVIRTUAL_CALL(_get_plural_message, p_src_text, p_plural_text, p_n, p_context, ret)) {
+ return ret;
+ }
+
WARN_PRINT("Translation class doesn't handle plural messages. Calling get_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class");
return get_message(p_src_text);
}
@@ -895,8 +913,8 @@ void Translation::erase_message(const StringName &p_src_text, const StringName &
}
void Translation::get_message_list(List<StringName> *r_messages) const {
- for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) {
- r_messages->push_back(E->key());
+ for (const KeyValue<StringName, StringName> &E : translation_map) {
+ r_messages->push_back(E.key);
}
}
@@ -917,12 +935,75 @@ void Translation::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_messages"), &Translation::_set_messages);
ClassDB::bind_method(D_METHOD("_get_messages"), &Translation::_get_messages);
- ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages");
+ GDVIRTUAL_BIND(_get_plural_message, "src_message", "src_plural_message", "n", "context");
+ GDVIRTUAL_BIND(_get_message, "src_message", "context");
+
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "locale"), "set_locale", "get_locale");
}
///////////////////////////////////////////////
+struct _character_accent_pair {
+ const char32_t character;
+ const char32_t *accented_character;
+};
+
+static _character_accent_pair _character_to_accented[] = {
+ { 'A', U"Å" },
+ { 'B', U"ß" },
+ { 'C', U"Ç" },
+ { 'D', U"Ð" },
+ { 'E', U"É" },
+ { 'F', U"F́" },
+ { 'G', U"Ĝ" },
+ { 'H', U"Ĥ" },
+ { 'I', U"Ĩ" },
+ { 'J', U"Ĵ" },
+ { 'K', U"ĸ" },
+ { 'L', U"Ł" },
+ { 'M', U"Ḿ" },
+ { 'N', U"й" },
+ { 'O', U"Ö" },
+ { 'P', U"Ṕ" },
+ { 'Q', U"Q́" },
+ { 'R', U"Ř" },
+ { 'S', U"Ŝ" },
+ { 'T', U"Ŧ" },
+ { 'U', U"Ũ" },
+ { 'V', U"Ṽ" },
+ { 'W', U"Ŵ" },
+ { 'X', U"X́" },
+ { 'Y', U"Ÿ" },
+ { 'Z', U"Ž" },
+ { 'a', U"á" },
+ { 'b', U"ḅ" },
+ { 'c', U"ć" },
+ { 'd', U"d́" },
+ { 'e', U"é" },
+ { 'f', U"f́" },
+ { 'g', U"ǵ" },
+ { 'h', U"h̀" },
+ { 'i', U"í" },
+ { 'j', U"ǰ" },
+ { 'k', U"ḱ" },
+ { 'l', U"ł" },
+ { 'm', U"m̀" },
+ { 'n', U"ή" },
+ { 'o', U"ô" },
+ { 'p', U"ṕ" },
+ { 'q', U"q́" },
+ { 'r', U"ŕ" },
+ { 's', U"š" },
+ { 't', U"ŧ" },
+ { 'u', U"ü" },
+ { 'v', U"ṽ" },
+ { 'w', U"ŵ" },
+ { 'x', U"x́" },
+ { 'y', U"ý" },
+ { 'z', U"ź" },
+};
+
bool TranslationServer::is_locale_valid(const String &p_locale) {
const char **ptr = locale_list;
@@ -1095,10 +1176,10 @@ StringName TranslationServer::translate(const StringName &p_message, const Strin
}
if (!res) {
- return p_message;
+ return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message;
}
- return res;
+ return pseudolocalization_enabled ? pseudolocalize(res) : res;
}
StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
@@ -1211,7 +1292,18 @@ void TranslationServer::setup() {
} else {
set_locale(OS::get_singleton()->get_locale());
}
+
fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
+ pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false);
+ pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true);
+ pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false);
+ pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false);
+ pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false);
+ expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0);
+ pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "[");
+ pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]");
+ pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true);
+
#ifdef TOOLS_ENABLED
{
String options = "";
@@ -1252,10 +1344,10 @@ StringName TranslationServer::tool_translate(const StringName &p_message, const
if (tool_translation.is_valid()) {
StringName r = tool_translation->get_message(p_message, p_context);
if (r) {
- return r;
+ return editor_pseudolocalization ? tool_pseudolocalize(r) : r;
}
}
- return p_message;
+ return editor_pseudolocalization ? tool_pseudolocalize(p_message) : p_message;
}
StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
@@ -1300,6 +1392,181 @@ StringName TranslationServer::doc_translate_plural(const StringName &p_message,
return p_message_plural;
}
+bool TranslationServer::is_pseudolocalization_enabled() const {
+ return pseudolocalization_enabled;
+}
+
+void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
+ pseudolocalization_enabled = p_enabled;
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+ ResourceLoader::reload_translation_remaps();
+}
+
+void TranslationServer::set_editor_pseudolocalization(bool p_enabled) {
+ editor_pseudolocalization = p_enabled;
+}
+
+void TranslationServer::reload_pseudolocalization() {
+ pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents");
+ pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels");
+ pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi");
+ pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override");
+ expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio");
+ pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix");
+ pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix");
+ pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders");
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+ ResourceLoader::reload_translation_remaps();
+}
+
+StringName TranslationServer::pseudolocalize(const StringName &p_message) const {
+ String message = p_message;
+ int length = message.length();
+ if (pseudolocalization_override_enabled) {
+ message = get_override_string(message);
+ }
+
+ if (pseudolocalization_double_vowels_enabled) {
+ message = double_vowels(message);
+ }
+
+ if (pseudolocalization_accents_enabled) {
+ message = replace_with_accented_string(message);
+ }
+
+ if (pseudolocalization_fake_bidi_enabled) {
+ message = wrap_with_fakebidi_characters(message);
+ }
+
+ StringName res = add_padding(message, length);
+ return res;
+}
+
+StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const {
+ String message = p_message;
+ message = double_vowels(message);
+ message = replace_with_accented_string(message);
+ StringName res = "[!!! " + message + " !!!]";
+ return res;
+}
+
+String TranslationServer::get_override_string(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.size(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += '*';
+ }
+ return res;
+}
+
+String TranslationServer::double_vowels(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.size(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += p_message[i];
+ if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
+ p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
+ res += p_message[i];
+ }
+ }
+ return res;
+};
+
+String TranslationServer::replace_with_accented_string(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.size(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ const char32_t *accented = get_accented_version(p_message[i]);
+ if (accented) {
+ res += accented;
+ } else {
+ res += p_message[i];
+ }
+ }
+ return res;
+}
+
+String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const {
+ String res;
+ char32_t fakebidiprefix = U'\u202e';
+ char32_t fakebidisuffix = U'\u202c';
+ res += fakebidiprefix;
+ // The fake bidi unicode gets popped at every newline so pushing it back at every newline.
+ for (int i = 0; i < p_message.size(); i++) {
+ if (p_message[i] == '\n') {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += fakebidiprefix;
+ } else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += p_message[i + 1];
+ res += fakebidiprefix;
+ i++;
+ } else {
+ res += p_message[i];
+ }
+ }
+ res += fakebidisuffix;
+ return res;
+}
+
+String TranslationServer::add_padding(String &p_message, int p_length) const {
+ String res;
+ String prefix = pseudolocalization_prefix;
+ String suffix;
+ for (int i = 0; i < p_length * expansion_ratio / 2; i++) {
+ prefix += "_";
+ suffix += "_";
+ }
+ suffix += pseudolocalization_suffix;
+ res += prefix;
+ res += p_message;
+ res += suffix;
+ return res;
+}
+
+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'))) {
+ return nullptr;
+ }
+
+ for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
+ if (_character_to_accented[i].character == p_character) {
+ return _character_to_accented[i].accented_character;
+ }
+ }
+
+ return nullptr;
+}
+
+bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
+ return p_message[p_index] == '%' && p_index < p_message.size() - 1 &&
+ (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');
+}
+
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);
@@ -1316,6 +1583,12 @@ void TranslationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
+
+ ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization);
+ ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize);
+ ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
}
void TranslationServer::load_translations() {
diff --git a/core/string/translation.h b/core/string/translation.h
index 72a828227e..6aec0bb8ea 100644
--- a/core/string/translation.h
+++ b/core/string/translation.h
@@ -32,6 +32,8 @@
#define TRANSLATION_H
#include "core/io/resource.h"
+#include "core/object/gdvirtual.gen.inc"
+#include "core/object/script_language.h"
class Translation : public Resource {
GDCLASS(Translation, Resource);
@@ -48,6 +50,9 @@ class Translation : public Resource {
protected:
static void _bind_methods();
+ GDVIRTUAL2RC(StringName, _get_message, StringName, StringName);
+ GDVIRTUAL4RC(StringName, _get_plural_message, StringName, StringName, int, StringName);
+
public:
void set_locale(const String &p_locale);
_FORCE_INLINE_ String get_locale() const { return locale; }
@@ -77,6 +82,26 @@ class TranslationServer : public Object {
bool enabled = true;
+ bool pseudolocalization_enabled = false;
+ bool pseudolocalization_accents_enabled = false;
+ bool pseudolocalization_double_vowels_enabled = false;
+ bool pseudolocalization_fake_bidi_enabled = false;
+ bool pseudolocalization_override_enabled = false;
+ bool pseudolocalization_skip_placeholders_enabled = false;
+ bool editor_pseudolocalization = false;
+ float expansion_ratio = 0.0;
+ String pseudolocalization_prefix;
+ String pseudolocalization_suffix;
+
+ StringName tool_pseudolocalize(const StringName &p_message) const;
+ String get_override_string(String &p_message) const;
+ 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;
+ const char32_t *get_accented_version(char32_t p_character) const;
+ bool is_placeholder(String &p_message, int p_index) const;
+
static TranslationServer *singleton;
bool _load_translations(const String &p_from);
@@ -104,6 +129,13 @@ public:
StringName translate(const StringName &p_message, const StringName &p_context = "") const;
StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+ StringName pseudolocalize(const StringName &p_message) const;
+
+ bool is_pseudolocalization_enabled() const;
+ void set_pseudolocalization_enabled(bool p_enabled);
+ void set_editor_pseudolocalization(bool p_enabled);
+ void reload_pseudolocalization();
+
static Vector<String> get_all_locales();
static Vector<String> get_all_locale_names();
static bool is_locale_valid(const String &p_locale);
diff --git a/core/string/translation_po.cpp b/core/string/translation_po.cpp
index ad768f7140..1da00aa54b 100644
--- a/core/string/translation_po.cpp
+++ b/core/string/translation_po.cpp
@@ -30,7 +30,7 @@
#include "translation_po.h"
-#include "core/os/file_access.h"
+#include "core/io/file_access.h"
#ifdef DEBUG_TRANSLATION_PO
void TranslationPO::print_translation_map() {
@@ -47,8 +47,7 @@ void TranslationPO::print_translation_map() {
List<StringName> context_l;
translation_map.get_key_list(&context_l);
- for (List<StringName>::Element *E = context_l.front(); E; E = E->next()) {
- StringName ctx = E->get();
+ for (const StringName &ctx : context_l) {
file->store_line(" ===== Context: " + String::utf8(String(ctx).utf8()) + " ===== ");
const HashMap<StringName, Vector<StringName>> &inner_map = translation_map[ctx];
@@ -74,8 +73,7 @@ Dictionary TranslationPO::_get_messages() const {
List<StringName> context_l;
translation_map.get_key_list(&context_l);
- for (List<StringName>::Element *E = context_l.front(); E; E = E->next()) {
- StringName ctx = E->get();
+ for (const StringName &ctx : context_l) {
const HashMap<StringName, Vector<StringName>> &id_str_map = translation_map[ctx];
Dictionary d2;
@@ -98,8 +96,7 @@ void TranslationPO::_set_messages(const Dictionary &p_messages) {
List<Variant> context_l;
p_messages.get_key_list(&context_l);
- for (List<Variant>::Element *E = context_l.front(); E; E = E->next()) {
- StringName ctx = E->get();
+ for (const Variant &ctx : context_l) {
const Dictionary &id_str_map = p_messages[ctx];
HashMap<StringName, Vector<StringName>> temp_map;
@@ -121,8 +118,8 @@ Vector<String> TranslationPO::_get_message_list() const {
get_message_list(&msgs);
Vector<String> v;
- for (List<StringName>::Element *E = msgs.front(); E; E = E->next()) {
- v.push_back(E->get());
+ for (const StringName &E : msgs) {
+ v.push_back(E);
}
return v;
@@ -188,7 +185,7 @@ void TranslationPO::set_plural_rule(const String &p_plural_rule) {
plural_rule = plural_rule.replacen("(", "");
plural_rule = plural_rule.replacen(")", "");
_cache_plural_tests(plural_rule);
- expr.instance();
+ expr.instantiate();
input_name.push_back("n");
}
@@ -281,13 +278,13 @@ void TranslationPO::get_message_list(List<StringName> *r_messages) const {
List<StringName> context_l;
translation_map.get_key_list(&context_l);
- for (List<StringName>::Element *E = context_l.front(); E; E = E->next()) {
- if (String(E->get()) != "") {
+ for (const StringName &E : context_l) {
+ if (String(E) != "") {
continue;
}
List<StringName> msgid_l;
- translation_map[E->get()].get_key_list(&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());
@@ -300,8 +297,8 @@ int TranslationPO::get_message_count() const {
translation_map.get_key_list(&context_l);
int count = 0;
- for (List<StringName>::Element *E = context_l.front(); E; E = E->next()) {
- count += translation_map[E->get()].size();
+ for (const StringName &E : context_l) {
+ count += translation_map[E].size();
}
return count;
}
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index bdb66526a4..320aae2ba4 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -38,13 +38,11 @@
#include "core/string/translation.h"
#include "core/string/ucaps.h"
#include "core/variant/variant.h"
+#include "core/version_generated.gen.h"
-#include <cstdint>
-
-#ifndef NO_USE_STDLIB
#include <stdio.h>
#include <stdlib.h>
-#endif
+#include <cstdint>
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS // to disable build-time warning which suggested to use strcpy_s instead strcpy
@@ -54,11 +52,27 @@
#define snprintf _snprintf_s
#endif
-#define MAX_DIGITS 6
-#define UPPERCASE(m_c) (((m_c) >= 'a' && (m_c) <= 'z') ? ((m_c) - ('a' - 'A')) : (m_c))
-#define LOWERCASE(m_c) (((m_c) >= 'A' && (m_c) <= 'Z') ? ((m_c) + ('a' - 'A')) : (m_c))
-#define IS_DIGIT(m_d) ((m_d) >= '0' && (m_d) <= '9')
-#define IS_HEX_DIGIT(m_d) (((m_d) >= '0' && (m_d) <= '9') || ((m_d) >= 'a' && (m_d) <= 'f') || ((m_d) >= 'A' && (m_d) <= 'F'))
+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);
+}
const char CharString::_null = 0;
const char16_t Char16String::_null = 0;
@@ -275,7 +289,7 @@ Error String::parse_url(String &r_scheme, String &r_host, int &r_port, String &r
base = base.substr(pos + 1, base.length() - pos - 1);
} else {
// Anything else
- if (base.get_slice_count(":") > 1) {
+ if (base.get_slice_count(":") > 2) {
return ERR_INVALID_PARAMETER;
}
pos = base.rfind(":");
@@ -294,7 +308,7 @@ Error String::parse_url(String &r_scheme, String &r_host, int &r_port, String &r
// Port
if (base.begins_with(":")) {
base = base.substr(1, base.length() - 1);
- if (!base.is_valid_integer()) {
+ if (!base.is_valid_int()) {
return ERR_INVALID_PARAMETER;
}
r_port = base.to_int();
@@ -738,6 +752,7 @@ bool String::operator<=(const String &p_str) const {
bool String::operator>(const String &p_str) const {
return p_str < *this;
}
+
bool String::operator>=(const String &p_str) const {
return !(*this < p_str);
}
@@ -871,8 +886,8 @@ signed char String::naturalnocasecmp_to(const String &p_str) const {
while (*this_str) {
if (!*that_str) {
return 1;
- } else if (IS_DIGIT(*this_str)) {
- if (!IS_DIGIT(*that_str)) {
+ } else if (is_digit(*this_str)) {
+ if (!is_digit(*that_str)) {
return -1;
}
@@ -881,10 +896,10 @@ signed char String::naturalnocasecmp_to(const String &p_str) const {
const char32_t *that_substr = that_str;
// Compare lengths of both numerical sequences, ignoring leading zeros
- while (IS_DIGIT(*this_str)) {
+ while (is_digit(*this_str)) {
this_str++;
}
- while (IS_DIGIT(*that_str)) {
+ while (is_digit(*that_str)) {
that_str++;
}
while (*this_substr == '0') {
@@ -912,7 +927,7 @@ signed char String::naturalnocasecmp_to(const String &p_str) const {
this_substr++;
that_substr++;
}
- } else if (IS_DIGIT(*that_str)) {
+ } else if (is_digit(*that_str)) {
return 1;
} else {
if (_find_upper(*this_str) < _find_upper(*that_str)) { //more than
@@ -938,10 +953,6 @@ const char32_t *String::get_data() const {
return size() ? &operator[](0) : &zero;
}
-void String::erase(int p_pos, int p_chars) {
- *this = left(p_pos) + substr(p_pos + p_chars, length() - ((p_pos + p_chars)));
-}
-
String String::capitalize() const {
String aux = this->camelcase_to_underscore(true).replace("_", " ").strip_edges();
String cap;
@@ -962,26 +973,25 @@ String String::capitalize() const {
String String::camelcase_to_underscore(bool lowercase) const {
const char32_t *cstr = get_data();
String new_string;
- const char A = 'A', Z = 'Z';
- const char a = 'a', z = 'z';
int start_index = 0;
for (int i = 1; i < this->size(); i++) {
- bool is_upper = cstr[i] >= A && cstr[i] <= Z;
- bool is_number = cstr[i] >= '0' && cstr[i] <= '9';
+ bool is_upper = is_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 = cstr[i - 1] >= A && cstr[i - 1] <= Z;
- bool was_precedent_number = cstr[i - 1] >= '0' && cstr[i - 1] <= '9';
+ bool was_precedent_upper = is_upper_case(cstr[i - 1]);
+ bool was_precedent_number = is_digit(cstr[i - 1]);
if (i + 2 < this->size()) {
- are_next_2_lower = cstr[i + 1] >= a && cstr[i + 1] <= z && cstr[i + 2] >= a && cstr[i + 2] <= z;
+ are_next_2_lower = is_lower_case(cstr[i + 1]) && is_lower_case(cstr[i + 2]);
}
if (i + 1 < this->size()) {
- is_next_lower = cstr[i + 1] >= a && cstr[i + 1] <= z;
- is_next_number = cstr[i + 1] >= '0' && cstr[i + 1] <= '9';
+ is_next_lower = is_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;
@@ -1377,10 +1387,18 @@ String String::num(double p_num, int p_decimals) {
return "inf";
}
}
-#ifndef NO_USE_STDLIB
- if (p_decimals > 16) {
- p_decimals = 16;
+ if (p_decimals < 0) {
+ p_decimals = 14;
+ const double abs_num = ABS(p_num);
+ if (abs_num > 10) {
+ // We want to align the digits to the above sane default, so we only
+ // need to subtract log10 for numbers with a positive power of ten.
+ p_decimals -= (int)floor(log10(abs_num));
+ }
+ }
+ if (p_decimals > MAX_DECIMALS) {
+ p_decimals = MAX_DECIMALS;
}
char fmt[7];
@@ -1391,7 +1409,6 @@ String String::num(double p_num, int p_decimals) {
fmt[1] = 'l';
fmt[2] = 'f';
fmt[3] = 0;
-
} else if (p_decimals < 10) {
fmt[2] = '0' + p_decimals;
fmt[3] = 'l';
@@ -1442,84 +1459,6 @@ String String::num(double p_num, int p_decimals) {
}
return buf;
-#else
-
- String s;
- String sd;
- /* integer part */
-
- bool neg = p_num < 0;
- p_num = ABS(p_num);
- int intn = (int)p_num;
-
- /* decimal part */
-
- if (p_decimals > 0 || (p_decimals == -1 && (int)p_num != p_num)) {
- double dec = p_num - (double)((int)p_num);
-
- int digit = 0;
- if (p_decimals > MAX_DIGITS)
- p_decimals = MAX_DIGITS;
-
- int dec_int = 0;
- int dec_max = 0;
-
- while (true) {
- dec *= 10.0;
- dec_int = dec_int * 10 + (int)dec % 10;
- dec_max = dec_max * 10 + 9;
- digit++;
-
- if (p_decimals == -1) {
- if (digit == MAX_DIGITS) //no point in going to infinite
- break;
-
- if (dec - (double)((int)dec) < 1e-6) {
- break;
- }
- }
-
- if (digit == p_decimals)
- break;
- }
- dec *= 10;
- int last = (int)dec % 10;
-
- if (last > 5) {
- if (dec_int == dec_max) {
- dec_int = 0;
- intn++;
- } else {
- dec_int++;
- }
- }
-
- String decimal;
- for (int i = 0; i < digit; i++) {
- char num[2] = { 0, 0 };
- num[0] = '0' + dec_int % 10;
- decimal = num + decimal;
- dec_int /= 10;
- }
- sd = '.' + decimal;
- }
-
- if (intn == 0)
-
- s = "0";
- else {
- while (intn) {
- char32_t num = '0' + (intn % 10);
- intn /= 10;
- s = num + s;
- }
- }
-
- s = s + sd;
- if (neg)
- s = "-" + s;
- return s;
-#endif
}
String String::num_int64(int64_t p_num, int base, bool capitalize_hex) {
@@ -1589,7 +1528,7 @@ String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) {
return s;
}
-String String::num_real(double p_num) {
+String String::num_real(double p_num, bool p_trailing) {
if (Math::is_nan(p_num)) {
return "nan";
}
@@ -1604,30 +1543,52 @@ String String::num_real(double p_num) {
String s;
String sd;
- /* integer part */
+
+ // Integer part.
bool neg = p_num < 0;
p_num = ABS(p_num);
- int intn = (int)p_num;
+ int64_t intn = (int64_t)p_num;
- /* decimal part */
+ // Decimal part.
- if ((int)p_num != p_num) {
- double dec = p_num - (double)((int)p_num);
+ if (intn != p_num) {
+ double dec = p_num - (double)intn;
int digit = 0;
- int decimals = MAX_DIGITS;
- int dec_int = 0;
- int dec_max = 0;
+#ifdef REAL_T_IS_DOUBLE
+ int decimals = 14;
+ double tolerance = 1e-14;
+#else
+ int decimals = 6;
+ double tolerance = 1e-6;
+#endif
+ // We want to align the digits to the above sane default, so we only
+ // need to subtract log10 for numbers with a positive power of ten.
+ if (p_num > 10) {
+ decimals -= (int)floor(log10(p_num));
+ }
+
+ if (decimals > MAX_DECIMALS) {
+ decimals = MAX_DECIMALS;
+ }
+
+ // In case the value ends up ending in "99999", we want to add a
+ // tiny bit to the value we're checking when deciding when to stop,
+ // so we multiply by slightly above 1 (1 + 1e-7 or 1e-15).
+ double check_multiplier = 1 + tolerance / 10;
+
+ int64_t dec_int = 0;
+ int64_t dec_max = 0;
while (true) {
dec *= 10.0;
- dec_int = dec_int * 10 + (int)dec % 10;
+ dec_int = dec_int * 10 + (int64_t)dec % 10;
dec_max = dec_max * 10 + 9;
digit++;
- if ((dec - (double)((int)dec)) < 1e-6) {
+ if ((dec - (double)(int64_t)(dec * check_multiplier)) < tolerance) {
break;
}
@@ -1637,7 +1598,7 @@ String String::num_real(double p_num) {
}
dec *= 10;
- int last = (int)dec % 10;
+ int last = (int64_t)dec % 10;
if (last > 5) {
if (dec_int == dec_max) {
@@ -1656,8 +1617,10 @@ String String::num_real(double p_num) {
dec_int /= 10;
}
sd = '.' + decimal;
- } else {
+ } else if (p_trailing) {
sd = ".0";
+ } else {
+ sd = "";
}
if (intn == 0) {
@@ -1689,19 +1652,18 @@ String String::num_scientific(double p_num) {
return "inf";
}
}
-#ifndef NO_USE_STDLIB
char buf[256];
#if defined(__GNUC__) || defined(_MSC_VER)
-#if (defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER < 1900)) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT)
- // MinGW and old MSC require _set_output_format() to conform to C99 output for printf
+#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT)
+ // MinGW requires _set_output_format() to conform to C99 output for printf
unsigned int old_exponent_format = _set_output_format(_TWO_DIGIT_EXPONENT);
#endif
snprintf(buf, 256, "%lg", p_num);
-#if (defined(__MINGW32__) || (defined(_MSC_VER) && _MSC_VER < 1900)) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT)
+#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT)
_set_output_format(old_exponent_format);
#endif
@@ -1712,10 +1674,6 @@ String String::num_scientific(double p_num) {
buf[255] = 0;
return buf;
-#else
-
- return String::num(p_num);
-#endif
}
String String::md5(const uint8_t *p_md5) {
@@ -2176,21 +2134,21 @@ int64_t String::hex_to_int() const {
s++;
}
- if (len > 2 && s[0] == '0' && s[1] == 'x') {
+ if (len > 2 && s[0] == '0' && lower_case(s[1]) == 'x') {
s += 2;
}
int64_t hex = 0;
while (*s) {
- char32_t c = LOWERCASE(*s);
+ char32_t c = lower_case(*s);
int64_t n;
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
n = c - '0';
} else if (c >= 'a' && c <= 'f') {
n = (c - 'a') + 10;
} else {
- return 0;
+ ERR_FAIL_COND_V_MSG(true, 0, "Invalid hexadecimal notation character \"" + chr(*s) + "\" in string \"" + *this + "\".");
}
// Check for overflow/underflow, with special case to ensure INT64_MIN does not result in error
bool overflow = ((hex > INT64_MAX / 16) && (sign == 1 || (sign == -1 && hex != (INT64_MAX >> 4) + 1))) || (sign == -1 && hex == (INT64_MAX >> 4) + 1 && c > '0');
@@ -2217,14 +2175,14 @@ int64_t String::bin_to_int() const {
s++;
}
- if (len > 2 && s[0] == '0' && s[1] == 'b') {
+ if (len > 2 && s[0] == '0' && lower_case(s[1]) == 'b') {
s += 2;
}
int64_t binary = 0;
while (*s) {
- char32_t c = LOWERCASE(*s);
+ char32_t c = lower_case(*s);
int64_t n;
if (c == '0' || c == '1') {
n = c - '0';
@@ -2254,7 +2212,7 @@ int64_t String::to_int() const {
for (int i = 0; i < to; i++) {
char32_t c = operator[](i);
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8')));
ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as 64-bit integer, provided value is " + (sign == 1 ? "too big." : "too small."));
integer *= 10;
@@ -2283,7 +2241,7 @@ int64_t String::to_int(const char *p_str, int p_len) {
for (int i = 0; i < to; i++) {
char c = p_str[i];
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8')));
ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as integer, provided value is " + (sign == 1 ? "too big." : "too small."));
integer *= 10;
@@ -2314,7 +2272,7 @@ int64_t String::to_int(const wchar_t *p_str, int p_len) {
for (int i = 0; i < to; i++) {
wchar_t c = p_str[i];
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8')));
ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as integer, provided value is " + (sign == 1 ? "too big." : "too small."));
integer *= 10;
@@ -2356,28 +2314,33 @@ bool String::is_numeric() const {
}
template <class C>
-static double built_in_strtod(const C *string, /* A decimal ASCII floating-point number,
- * optionally preceded by white space. Must
- * have form "-I.FE-X", where I is the integer
- * part of the mantissa, F is the fractional
- * part of the mantissa, and X is the
- * exponent. Either of the signs may be "+",
- * "-", or omitted. Either I or F may be
- * omitted, or both. The decimal point isn't
- * necessary unless F is present. The "E" may
- * actually be an "e". E and X may both be
- * omitted (but not just one). */
- C **endPtr = nullptr) /* If non-nullptr, store terminating Cacter's
- * address here. */
-{
- static const int maxExponent = 511; /* Largest possible base 10 exponent. Any
- * exponent larger than this will already
- * produce underflow or overflow, so there's
- * no need to worry about additional digits.
- */
- static const double powersOf10[] = { /* Table giving binary powers of 10. Entry */
- 10., /* is 10^2^i. Used to convert decimal */
- 100., /* exponents into floating-point numbers. */
+static double built_in_strtod(
+ /* A decimal ASCII floating-point number,
+ * optionally preceded by white space. Must
+ * have form "-I.FE-X", where I is the integer
+ * part of the mantissa, F is the fractional
+ * part of the mantissa, and X is the
+ * exponent. Either of the signs may be "+",
+ * "-", or omitted. Either I or F may be
+ * omitted, or both. The decimal point isn't
+ * necessary unless F is present. The "E" may
+ * actually be an "e". E and X may both be
+ * omitted (but not just one). */
+ const C *string,
+ /* If non-nullptr, store terminating Cacter's
+ * address here. */
+ C **endPtr = nullptr) {
+ /* Largest possible base 10 exponent. Any
+ * exponent larger than this will already
+ * produce underflow or overflow, so there's
+ * no need to worry about additional digits. */
+ static const int maxExponent = 511;
+ /* Table giving binary powers of 10. Entry
+ * is 10^2^i. Used to convert decimal
+ * exponents into floating-point numbers. */
+ static const double powersOf10[] = {
+ 10.,
+ 100.,
1.0e4,
1.0e8,
1.0e16,
@@ -2392,25 +2355,28 @@ static double built_in_strtod(const C *string, /* A decimal ASCII floating-point
const double *d;
const C *p;
int c;
- int exp = 0; /* Exponent read from "EX" field. */
- int fracExp = 0; /* Exponent that derives from the fractional
- * part. Under normal circumstances, it is
- * the negative of the number of digits in F.
- * However, if I is very long, the last digits
- * of I get dropped (otherwise a long I with a
- * large negative exponent could cause an
- * unnecessary overflow on I alone). In this
- * case, fracExp is incremented one for each
- * dropped digit. */
- int mantSize; /* Number of digits in mantissa. */
- int decPt; /* Number of mantissa digits BEFORE decimal
- * point. */
- const C *pExp; /* Temporarily holds location of exponent in
- * string. */
+ /* Exponent read from "EX" field. */
+ int exp = 0;
+ /* Exponent that derives from the fractional
+ * part. Under normal circumstances, it is
+ * the negative of the number of digits in F.
+ * However, if I is very long, the last digits
+ * of I get dropped (otherwise a long I with a
+ * large negative exponent could cause an
+ * unnecessary overflow on I alone). In this
+ * case, fracExp is incremented one for each
+ * dropped digit. */
+ int fracExp = 0;
+ /* Number of digits in mantissa. */
+ int mantSize;
+ /* Number of mantissa digits BEFORE decimal point. */
+ int decPt;
+ /* Temporarily holds location of exponent in string. */
+ const C *pExp;
/*
- * Strip off leading blanks and check for a sign.
- */
+ * Strip off leading blanks and check for a sign.
+ */
p = string;
while (*p == ' ' || *p == '\t' || *p == '\n') {
@@ -2427,14 +2393,14 @@ static double built_in_strtod(const C *string, /* A decimal ASCII floating-point
}
/*
- * Count the number of digits in the mantissa (including the decimal
- * point), and also locate the decimal point.
- */
+ * Count the number of digits in the mantissa (including the decimal
+ * point), and also locate the decimal point.
+ */
decPt = -1;
for (mantSize = 0;; mantSize += 1) {
c = *p;
- if (!IS_DIGIT(c)) {
+ if (!is_digit(c)) {
if ((c != '.') || (decPt >= 0)) {
break;
}
@@ -2444,11 +2410,11 @@ static double built_in_strtod(const C *string, /* A decimal ASCII floating-point
}
/*
- * Now suck up the digits in the mantissa. Use two integers to collect 9
- * digits each (this is faster than using floating-point). If the mantissa
- * has more than 18 digits, ignore the extras, since they can't affect the
- * value anyway.
- */
+ * Now suck up the digits in the mantissa. Use two integers to collect 9
+ * digits each (this is faster than using floating-point). If the mantissa
+ * has more than 18 digits, ignore the extras, since they can't affect the
+ * value anyway.
+ */
pExp = p;
p -= mantSize;
@@ -2494,8 +2460,8 @@ static double built_in_strtod(const C *string, /* A decimal ASCII floating-point
}
/*
- * Skim off the exponent.
- */
+ * Skim off the exponent.
+ */
p = pExp;
if ((*p == 'E') || (*p == 'e')) {
@@ -2509,11 +2475,11 @@ static double built_in_strtod(const C *string, /* A decimal ASCII floating-point
}
expSign = false;
}
- if (!IS_DIGIT(char32_t(*p))) {
+ if (!is_digit(char32_t(*p))) {
p = pExp;
goto done;
}
- while (IS_DIGIT(char32_t(*p))) {
+ while (is_digit(char32_t(*p))) {
exp = exp * 10 + (*p - '0');
p += 1;
}
@@ -2525,10 +2491,10 @@ static double built_in_strtod(const C *string, /* A decimal ASCII floating-point
}
/*
- * Generate a floating-point number that represents the exponent. Do this
- * by processing the exponent one bit at a time to combine many powers of
- * 2 of 10. Then combine the exponent with the fraction.
- */
+ * Generate a floating-point number that represents the exponent. Do this
+ * by processing the exponent one bit at a time to combine many powers of
+ * 2 of 10. Then combine the exponent with the fraction.
+ */
if (exp < 0) {
expSign = true;
@@ -2599,7 +2565,7 @@ int64_t String::to_int(const char32_t *p_str, int p_len, bool p_clamp) {
char32_t c = *(str++);
switch (reading) {
case READING_SIGN: {
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
reading = READING_INT;
// let it fallthrough
} else if (c == '-') {
@@ -2616,7 +2582,7 @@ int64_t String::to_int(const char32_t *p_str, int p_len, bool p_clamp) {
[[fallthrough]];
}
case READING_INT: {
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
if (integer > INT64_MAX / 10) {
String number("");
str = p_str;
@@ -3382,17 +3348,10 @@ String String::format(const Variant &values, String placeholder) const {
if (value_arr.size() == 2) {
Variant v_key = value_arr[0];
String key = v_key;
- if (key.left(1) == "\"" && key.right(key.length() - 1) == "\"") {
- key = key.substr(1, key.length() - 2);
- }
Variant v_val = value_arr[1];
String val = v_val;
- if (val.left(1) == "\"" && val.right(val.length() - 1) == "\"") {
- val = val.substr(1, val.length() - 2);
- }
-
new_string = new_string.replace(placeholder.replace("_", key), val);
} else {
ERR_PRINT(String("STRING.format Inner Array size != 2 ").ascii().get_data());
@@ -3401,10 +3360,6 @@ String String::format(const Variant &values, String placeholder) const {
Variant v_val = values_arr[i];
String val = v_val;
- if (val.left(1) == "\"" && val.right(val.length() - 1) == "\"") {
- val = val.substr(1, val.length() - 2);
- }
-
if (placeholder.find("_") > -1) {
new_string = new_string.replace(placeholder.replace("_", i_as_str), val);
} else {
@@ -3417,19 +3372,8 @@ String String::format(const Variant &values, String placeholder) const {
List<Variant> keys;
d.get_key_list(&keys);
- for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
- String key = E->get();
- String val = d[E->get()];
-
- if (key.left(1) == "\"" && key.right(key.length() - 1) == "\"") {
- key = key.substr(1, key.length() - 2);
- }
-
- if (val.left(1) == "\"" && val.right(val.length() - 1) == "\"") {
- val = val.substr(1, val.length() - 2);
- }
-
- new_string = new_string.replace(placeholder.replace("_", key), val);
+ for (const Variant &key : keys) {
+ new_string = new_string.replace(placeholder.replace("_", key), d[key]);
}
} else {
ERR_PRINT(String("Invalid type: use Array or Dictionary.").ascii().get_data());
@@ -3529,6 +3473,10 @@ String String::repeat(int p_count) const {
}
String String::left(int p_pos) const {
+ if (p_pos < 0) {
+ p_pos = length() + p_pos;
+ }
+
if (p_pos <= 0) {
return "";
}
@@ -3541,15 +3489,19 @@ String String::left(int p_pos) const {
}
String String::right(int p_pos) const {
- if (p_pos >= length()) {
- return "";
+ if (p_pos < 0) {
+ p_pos = length() + p_pos;
}
if (p_pos <= 0) {
+ return "";
+ }
+
+ if (p_pos >= length()) {
return *this;
}
- return substr(p_pos, (length() - p_pos));
+ return substr(length() - p_pos);
}
char32_t String::unicode_at(int p_idx) const {
@@ -3615,7 +3567,7 @@ String String::strip_edges(bool left, bool right) const {
}
if (right) {
- for (int i = (int)(len - 1); i >= 0; i--) {
+ for (int i = len - 1; i >= 0; i--) {
if (operator[](i) <= 32) {
end--;
} else {
@@ -3718,15 +3670,15 @@ String String::simplify_path() const {
for (int i = 0; i < dirs.size(); i++) {
String d = dirs[i];
if (d == ".") {
- dirs.remove(i);
+ dirs.remove_at(i);
i--;
} else if (d == "..") {
if (i == 0) {
- dirs.remove(i);
+ dirs.remove_at(i);
i--;
} else {
- dirs.remove(i);
- dirs.remove(i - 1);
+ dirs.remove_at(i);
+ dirs.remove_at(i - 1);
i -= 2;
}
}
@@ -3778,7 +3730,7 @@ String String::humanize_size(uint64_t p_size) {
return String::num(p_size / divisor).pad_decimals(digits) + " " + prefixes[prefix_idx];
}
-bool String::is_abs_path() const {
+bool String::is_absolute_path() const {
if (length() > 1) {
return (operator[](0) == '/' || operator[](0) == '\\' || find(":/") != -1 || find(":\\") != -1);
} else if ((length()) == 1) {
@@ -3799,12 +3751,12 @@ bool String::is_valid_identifier() const {
for (int i = 0; i < len; i++) {
if (i == 0) {
- if (str[0] >= '0' && str[0] <= '9') {
+ if (is_digit(str[0])) {
return false; // no start with number plz
}
}
- bool valid_char = (str[i] >= '0' && str[i] <= '9') || (str[i] >= 'a' && str[i] <= 'z') || (str[i] >= 'A' && str[i] <= 'Z') || str[i] == '_';
+ bool valid_char = is_digit(str[i]) || is_lower_case(str[i]) || is_upper_case(str[i]) || str[i] == '_';
if (!valid_char) {
return false;
@@ -3829,10 +3781,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 == '~' ||
- (ord >= 'a' && ord <= 'z') ||
- (ord >= 'A' && ord <= 'Z') ||
- (ord >= '0' && ord <= '9')) {
+ if (ord == '.' || ord == '-' || ord == '_' || ord == '~' || is_lower_case(ord) || is_upper_case(ord) || is_digit(ord)) {
res += ord;
} else {
char h_Val[3];
@@ -3854,9 +3803,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 ((ord1 >= '0' && ord1 <= '9') || (ord1 >= 'A' && ord1 <= 'Z')) {
+ if (is_digit(ord1) || is_upper_case(ord1)) {
char ord2 = src[i + 2];
- if ((ord2 >= '0' && ord2 <= '9') || (ord2 >= 'A' && ord2 <= 'Z')) {
+ if (is_digit(ord2) || is_upper_case(ord2)) {
char bytes[3] = { (char)ord1, (char)ord2, 0 };
res += (char)strtol(bytes, nullptr, 16);
i += 2;
@@ -3962,7 +3911,7 @@ static _FORCE_INLINE_ int _xml_unescape(const char32_t *p_src, int p_src_len, ch
char32_t ct = p_src[i];
if (ct == ';') {
break;
- } else if (ct >= '0' && ct <= '9') {
+ } else if (is_digit(ct)) {
ct = ct - '0';
} else if (ct >= 'a' && ct <= 'f') {
ct = (ct - 'a') + 10;
@@ -4145,7 +4094,7 @@ String String::trim_suffix(const String &p_suffix) const {
return s;
}
-bool String::is_valid_integer() const {
+bool String::is_valid_int() const {
int len = length();
if (len == 0) {
@@ -4190,7 +4139,7 @@ bool String::is_valid_hex_number(bool p_with_prefix) const {
for (int i = from; i < len; i++) {
char32_t c = operator[](i);
- if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
+ if (is_hex_digit(c)) {
continue;
}
return false;
@@ -4218,7 +4167,7 @@ bool String::is_valid_float() const {
bool numbers_found = false;
for (int i = from; i < len; i++) {
- if (operator[](i) >= '0' && operator[](i) <= '9') {
+ if (is_digit(operator[](i))) {
if (exponent_found) {
exponent_values_found = true;
} else {
@@ -4370,7 +4319,7 @@ bool String::is_valid_ip_address() const {
}
for (int i = 0; i < ip.size(); i++) {
String n = ip[i];
- if (!n.is_valid_integer()) {
+ if (!n.is_valid_int()) {
return false;
}
int val = n.to_int();
@@ -4387,28 +4336,44 @@ bool String::is_resource_file() const {
return begins_with("res://") && find("::") == -1;
}
-bool String::is_rel_path() const {
- return !is_abs_path();
+bool String::is_relative_path() const {
+ return !is_absolute_path();
}
String String::get_base_dir() const {
- int basepos = find(":/");
- if (basepos == -1) {
- basepos = find(":\\");
+ int end = 0;
+
+ // url scheme style base
+ int basepos = find("://");
+ if (basepos != -1) {
+ end = basepos + 3;
+ }
+
+ // windows top level directory base
+ if (end == 0) {
+ basepos = find(":/");
+ if (basepos == -1) {
+ basepos = find(":\\");
+ }
+ if (basepos != -1) {
+ end = basepos + 2;
+ }
+ }
+
+ // unix root directory base
+ if (end == 0) {
+ if (begins_with("/")) {
+ end = 1;
+ }
}
+
String rs;
String base;
- if (basepos != -1) {
- int end = basepos + 3;
+ if (end != 0) {
rs = substr(end, length());
base = substr(0, end);
} else {
- if (begins_with("/")) {
- rs = substr(1, length());
- base = "/";
- } else {
- rs = *this;
- }
+ rs = *this;
}
int sep = MAX(rs.rfind("/"), rs.rfind("\\"));
@@ -4452,7 +4417,7 @@ String String::property_name_encode() const {
// as well as '"', '=' or ' ' (32)
const char32_t *cstr = get_data();
for (int i = 0; cstr[i]; i++) {
- if (cstr[i] == '=' || cstr[i] == '"' || cstr[i] < 33 || cstr[i] > 126) {
+ if (cstr[i] == '=' || cstr[i] == '"' || cstr[i] == ';' || cstr[i] == '[' || cstr[i] == ']' || cstr[i] < 33 || cstr[i] > 126) {
return "\"" + c_escape_multiline() + "\"";
}
}
@@ -4904,15 +4869,20 @@ 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,
+ * to allow dehardcoding it in the XML and doing proper substitutions everywhere.
+ */
String DTR(const String &p_text, const String &p_context) {
// Comes straight from the XML, so remove indentation and any trailing whitespace.
const String text = p_text.dedent().strip_edges();
if (TranslationServer::get_singleton()) {
- return TranslationServer::get_singleton()->doc_translate(text, p_context);
+ return String(TranslationServer::get_singleton()->doc_translate(text, p_context)).replace("$DOCS_URL", VERSION_DOCS_URL);
}
- return text;
+ return text.replace("$DOCS_URL", VERSION_DOCS_URL);
}
String DTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
@@ -4920,14 +4890,14 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St
const String text_plural = p_text_plural.dedent().strip_edges();
if (TranslationServer::get_singleton()) {
- return TranslationServer::get_singleton()->doc_translate_plural(text, text_plural, p_n, p_context);
+ return String(TranslationServer::get_singleton()->doc_translate_plural(text, text_plural, p_n, p_context)).replace("$DOCS_URL", VERSION_DOCS_URL);
}
// Return message based on English plural rule if translation is not possible.
if (p_n == 1) {
- return text;
+ return text.replace("$DOCS_URL", VERSION_DOCS_URL);
}
- return text_plural;
+ return text_plural.replace("$DOCS_URL", VERSION_DOCS_URL);
}
#endif
diff --git a/core/string/ustring.h b/core/string/ustring.h
index a56845deff..f25c36f66c 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -51,11 +51,15 @@ class CharProxy {
CowData<T> &_cowdata;
static const T _null = 0;
- _FORCE_INLINE_ CharProxy(const int &p_index, CowData<T> &cowdata) :
+ _FORCE_INLINE_ CharProxy(const int &p_index, CowData<T> &p_cowdata) :
_index(p_index),
- _cowdata(cowdata) {}
+ _cowdata(p_cowdata) {}
public:
+ _FORCE_INLINE_ CharProxy(const CharProxy<T> &p_other) :
+ _index(p_other._index),
+ _cowdata(p_other._cowdata) {}
+
_FORCE_INLINE_ operator T() const {
if (unlikely(_index == _cowdata.size())) {
return _null;
@@ -68,12 +72,12 @@ public:
return _cowdata.ptr() + _index;
}
- _FORCE_INLINE_ void operator=(const T &other) const {
- _cowdata.set(_index, other);
+ _FORCE_INLINE_ void operator=(const T &p_other) const {
+ _cowdata.set(_index, p_other);
}
- _FORCE_INLINE_ void operator=(const CharProxy<T> &other) const {
- _cowdata.set(_index, other.operator T());
+ _FORCE_INLINE_ void operator=(const CharProxy<T> &p_other) const {
+ _cowdata.set(_index, p_other.operator T());
}
};
@@ -205,7 +209,7 @@ public:
_FORCE_INLINE_ char32_t *ptrw() { return _cowdata.ptrw(); }
_FORCE_INLINE_ const char32_t *ptr() const { return _cowdata.ptr(); }
- void remove(int p_index) { _cowdata.remove(p_index); }
+ void remove_at(int p_index) { _cowdata.remove_at(p_index); }
_FORCE_INLINE_ void clear() { resize(0); }
@@ -309,7 +313,7 @@ public:
String unquote() const;
static String num(double p_num, int p_decimals = -1);
static String num_scientific(double p_num);
- static String num_real(double p_num);
+ static String num_real(double p_num, bool p_trailing = true);
static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false);
static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false);
static String chr(char32_t p_char);
@@ -397,8 +401,8 @@ public:
_FORCE_INLINE_ bool is_empty() const { return length() == 0; }
// path functions
- bool is_abs_path() const;
- bool is_rel_path() const;
+ bool is_absolute_path() const;
+ bool is_relative_path() const;
bool is_resource_file() const;
String path_to(const String &p_path) const;
String path_to_file(const String &p_path) const;
@@ -425,7 +429,7 @@ public:
String validate_node_name() const;
bool is_valid_identifier() const;
- bool is_valid_integer() const;
+ bool is_valid_int() const;
bool is_valid_float() const;
bool is_valid_hex_number(bool p_with_prefix) const;
bool is_valid_html_color() const;
@@ -523,10 +527,10 @@ 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 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