feat: updated engine

This commit is contained in:
Sara Gerretsen 2026-07-10 17:04:34 +02:00
parent cbe99774ff
commit f4cf6b3999
6607 changed files with 910135 additions and 430025 deletions

View file

@ -3,6 +3,4 @@ from misc.utility.scons_hints import *
Import("env")
env_string = env.Clone()
env_string.add_source_files(env.core_sources, "*.cpp")
env.add_source_files(env.core_sources, "*.cpp")

View file

@ -1,5 +1,5 @@
/**************************************************************************/
/* char_range.inc */
/* char_range.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -30,19 +30,13 @@
// This file was generated using the `misc/scripts/char_range_fetch.py` script.
#pragma once
#include "core/typedefs.h"
#include "core/string/char_utils.h"
// Unicode Derived Core Properties
// Source: https://www.unicode.org/Public/16.0.0/ucd/DerivedCoreProperties.txt
// Source: https://www.unicode.org/Public/17.0.0/ucd/DerivedCoreProperties.txt
struct CharRange {
char32_t start;
char32_t end;
};
constexpr inline CharRange xid_start[] = {
const int xid_start_size = 692;
const CharRange xid_start[xid_start_size] = {
{ 0x41, 0x5a },
{ 0x5f, 0x5f },
{ 0x61, 0x7a },
@ -94,7 +88,7 @@ constexpr inline CharRange xid_start[] = {
{ 0x840, 0x858 },
{ 0x860, 0x86a },
{ 0x870, 0x887 },
{ 0x889, 0x88e },
{ 0x889, 0x88f },
{ 0x8a0, 0x8c9 },
{ 0x904, 0x939 },
{ 0x93d, 0x93d },
@ -160,7 +154,7 @@ constexpr inline CharRange xid_start[] = {
{ 0xc2a, 0xc39 },
{ 0xc3d, 0xc3d },
{ 0xc58, 0xc5a },
{ 0xc5d, 0xc5d },
{ 0xc5c, 0xc5d },
{ 0xc60, 0xc61 },
{ 0xc80, 0xc80 },
{ 0xc85, 0xc8c },
@ -169,7 +163,7 @@ constexpr inline CharRange xid_start[] = {
{ 0xcaa, 0xcb3 },
{ 0xcb5, 0xcb9 },
{ 0xcbd, 0xcbd },
{ 0xcdd, 0xcde },
{ 0xcdc, 0xcde },
{ 0xce0, 0xce1 },
{ 0xcf1, 0xcf2 },
{ 0xd04, 0xd0c },
@ -351,11 +345,8 @@ constexpr inline CharRange xid_start[] = {
{ 0xa6a0, 0xa6ef },
{ 0xa717, 0xa71f },
{ 0xa722, 0xa788 },
{ 0xa78b, 0xa7cd },
{ 0xa7d0, 0xa7d1 },
{ 0xa7d3, 0xa7d3 },
{ 0xa7d5, 0xa7dc },
{ 0xa7f2, 0xa801 },
{ 0xa78b, 0xa7dc },
{ 0xa7f1, 0xa801 },
{ 0xa803, 0xa805 },
{ 0xa807, 0xa80a },
{ 0xa80c, 0xa822 },
@ -478,6 +469,7 @@ constexpr inline CharRange xid_start[] = {
{ 0x108f4, 0x108f5 },
{ 0x10900, 0x10915 },
{ 0x10920, 0x10939 },
{ 0x10940, 0x10959 },
{ 0x10980, 0x109b7 },
{ 0x109be, 0x109bf },
{ 0x10a00, 0x10a00 },
@ -500,7 +492,7 @@ constexpr inline CharRange xid_start[] = {
{ 0x10d6f, 0x10d85 },
{ 0x10e80, 0x10ea9 },
{ 0x10eb0, 0x10eb1 },
{ 0x10ec2, 0x10ec4 },
{ 0x10ec2, 0x10ec7 },
{ 0x10f00, 0x10f1c },
{ 0x10f27, 0x10f27 },
{ 0x10f30, 0x10f45 },
@ -593,6 +585,7 @@ constexpr inline CharRange xid_start[] = {
{ 0x11d67, 0x11d68 },
{ 0x11d6a, 0x11d89 },
{ 0x11d98, 0x11d98 },
{ 0x11db0, 0x11ddb },
{ 0x11ee0, 0x11ef2 },
{ 0x11f02, 0x11f02 },
{ 0x11f04, 0x11f10 },
@ -617,14 +610,17 @@ constexpr inline CharRange xid_start[] = {
{ 0x16b7d, 0x16b8f },
{ 0x16d40, 0x16d6c },
{ 0x16e40, 0x16e7f },
{ 0x16ea0, 0x16eb8 },
{ 0x16ebb, 0x16ed3 },
{ 0x16f00, 0x16f4a },
{ 0x16f50, 0x16f50 },
{ 0x16f93, 0x16f9f },
{ 0x16fe0, 0x16fe1 },
{ 0x16fe3, 0x16fe3 },
{ 0x17000, 0x187f7 },
{ 0x18800, 0x18cd5 },
{ 0x18cff, 0x18d08 },
{ 0x16ff2, 0x16ff6 },
{ 0x17000, 0x18cd5 },
{ 0x18cff, 0x18d1e },
{ 0x18d80, 0x18df2 },
{ 0x1aff0, 0x1aff3 },
{ 0x1aff5, 0x1affb },
{ 0x1affd, 0x1affe },
@ -679,6 +675,12 @@ constexpr inline CharRange xid_start[] = {
{ 0x1e4d0, 0x1e4eb },
{ 0x1e5d0, 0x1e5ed },
{ 0x1e5f0, 0x1e5f0 },
{ 0x1e6c0, 0x1e6de },
{ 0x1e6e0, 0x1e6e2 },
{ 0x1e6e4, 0x1e6e5 },
{ 0x1e6e7, 0x1e6ed },
{ 0x1e6f0, 0x1e6f4 },
{ 0x1e6fe, 0x1e6ff },
{ 0x1e7e0, 0x1e7e6 },
{ 0x1e7e8, 0x1e7eb },
{ 0x1e7ed, 0x1e7ee },
@ -720,17 +722,17 @@ constexpr inline CharRange xid_start[] = {
{ 0x1eea5, 0x1eea9 },
{ 0x1eeab, 0x1eebb },
{ 0x20000, 0x2a6df },
{ 0x2a700, 0x2b739 },
{ 0x2b740, 0x2b81d },
{ 0x2b820, 0x2cea1 },
{ 0x2a700, 0x2b81d },
{ 0x2b820, 0x2cead },
{ 0x2ceb0, 0x2ebe0 },
{ 0x2ebf0, 0x2ee5d },
{ 0x2f800, 0x2fa1d },
{ 0x30000, 0x3134a },
{ 0x31350, 0x323af },
{ 0x31350, 0x33479 },
};
constexpr inline CharRange xid_continue[] = {
const int xid_continue_size = 806;
const CharRange xid_continue[xid_continue_size] = {
{ 0x30, 0x39 },
{ 0x41, 0x5a },
{ 0x5f, 0x5f },
@ -783,7 +785,7 @@ constexpr inline CharRange xid_continue[] = {
{ 0x840, 0x85b },
{ 0x860, 0x86a },
{ 0x870, 0x887 },
{ 0x889, 0x88e },
{ 0x889, 0x88f },
{ 0x897, 0x8e1 },
{ 0x8e3, 0x963 },
{ 0x966, 0x96f },
@ -873,7 +875,7 @@ constexpr inline CharRange xid_continue[] = {
{ 0xc4a, 0xc4d },
{ 0xc55, 0xc56 },
{ 0xc58, 0xc5a },
{ 0xc5d, 0xc5d },
{ 0xc5c, 0xc5d },
{ 0xc60, 0xc63 },
{ 0xc66, 0xc6f },
{ 0xc80, 0xc83 },
@ -886,7 +888,7 @@ constexpr inline CharRange xid_continue[] = {
{ 0xcc6, 0xcc8 },
{ 0xcca, 0xccd },
{ 0xcd5, 0xcd6 },
{ 0xcdd, 0xcde },
{ 0xcdc, 0xcde },
{ 0xce0, 0xce3 },
{ 0xce6, 0xcef },
{ 0xcf1, 0xcf3 },
@ -999,7 +1001,8 @@ constexpr inline CharRange xid_continue[] = {
{ 0x1a90, 0x1a99 },
{ 0x1aa7, 0x1aa7 },
{ 0x1ab0, 0x1abd },
{ 0x1abf, 0x1ace },
{ 0x1abf, 0x1add },
{ 0x1ae0, 0x1aeb },
{ 0x1b00, 0x1b4c },
{ 0x1b50, 0x1b59 },
{ 0x1b6b, 0x1b73 },
@ -1092,11 +1095,8 @@ constexpr inline CharRange xid_continue[] = {
{ 0xa67f, 0xa6f1 },
{ 0xa717, 0xa71f },
{ 0xa722, 0xa788 },
{ 0xa78b, 0xa7cd },
{ 0xa7d0, 0xa7d1 },
{ 0xa7d3, 0xa7d3 },
{ 0xa7d5, 0xa7dc },
{ 0xa7f2, 0xa827 },
{ 0xa78b, 0xa7dc },
{ 0xa7f1, 0xa827 },
{ 0xa82c, 0xa82c },
{ 0xa840, 0xa873 },
{ 0xa880, 0xa8c5 },
@ -1218,6 +1218,7 @@ constexpr inline CharRange xid_continue[] = {
{ 0x108f4, 0x108f5 },
{ 0x10900, 0x10915 },
{ 0x10920, 0x10939 },
{ 0x10940, 0x10959 },
{ 0x10980, 0x109b7 },
{ 0x109be, 0x109bf },
{ 0x10a00, 0x10a03 },
@ -1246,8 +1247,8 @@ constexpr inline CharRange xid_continue[] = {
{ 0x10e80, 0x10ea9 },
{ 0x10eab, 0x10eac },
{ 0x10eb0, 0x10eb1 },
{ 0x10ec2, 0x10ec4 },
{ 0x10efc, 0x10f1c },
{ 0x10ec2, 0x10ec7 },
{ 0x10efa, 0x10f1c },
{ 0x10f27, 0x10f27 },
{ 0x10f30, 0x10f50 },
{ 0x10f70, 0x10f85 },
@ -1341,6 +1342,7 @@ constexpr inline CharRange xid_continue[] = {
{ 0x11a50, 0x11a99 },
{ 0x11a9d, 0x11a9d },
{ 0x11ab0, 0x11af8 },
{ 0x11b60, 0x11b67 },
{ 0x11bc0, 0x11be0 },
{ 0x11bf0, 0x11bf9 },
{ 0x11c00, 0x11c08 },
@ -1363,6 +1365,8 @@ constexpr inline CharRange xid_continue[] = {
{ 0x11d90, 0x11d91 },
{ 0x11d93, 0x11d98 },
{ 0x11da0, 0x11da9 },
{ 0x11db0, 0x11ddb },
{ 0x11de0, 0x11de9 },
{ 0x11ee0, 0x11ef6 },
{ 0x11f00, 0x11f10 },
{ 0x11f12, 0x11f3a },
@ -1393,15 +1397,17 @@ constexpr inline CharRange xid_continue[] = {
{ 0x16d40, 0x16d6c },
{ 0x16d70, 0x16d79 },
{ 0x16e40, 0x16e7f },
{ 0x16ea0, 0x16eb8 },
{ 0x16ebb, 0x16ed3 },
{ 0x16f00, 0x16f4a },
{ 0x16f4f, 0x16f87 },
{ 0x16f8f, 0x16f9f },
{ 0x16fe0, 0x16fe1 },
{ 0x16fe3, 0x16fe4 },
{ 0x16ff0, 0x16ff1 },
{ 0x17000, 0x187f7 },
{ 0x18800, 0x18cd5 },
{ 0x18cff, 0x18d08 },
{ 0x16ff0, 0x16ff6 },
{ 0x17000, 0x18cd5 },
{ 0x18cff, 0x18d1e },
{ 0x18d80, 0x18df2 },
{ 0x1aff0, 0x1aff3 },
{ 0x1aff5, 0x1affb },
{ 0x1affd, 0x1affe },
@ -1479,6 +1485,9 @@ constexpr inline CharRange xid_continue[] = {
{ 0x1e2c0, 0x1e2f9 },
{ 0x1e4d0, 0x1e4f9 },
{ 0x1e5d0, 0x1e5fa },
{ 0x1e6c0, 0x1e6de },
{ 0x1e6e0, 0x1e6f5 },
{ 0x1e6fe, 0x1e6ff },
{ 0x1e7e0, 0x1e7e6 },
{ 0x1e7e8, 0x1e7eb },
{ 0x1e7ed, 0x1e7ee },
@ -1522,18 +1531,18 @@ constexpr inline CharRange xid_continue[] = {
{ 0x1eeab, 0x1eebb },
{ 0x1fbf0, 0x1fbf9 },
{ 0x20000, 0x2a6df },
{ 0x2a700, 0x2b739 },
{ 0x2b740, 0x2b81d },
{ 0x2b820, 0x2cea1 },
{ 0x2a700, 0x2b81d },
{ 0x2b820, 0x2cead },
{ 0x2ceb0, 0x2ebe0 },
{ 0x2ebf0, 0x2ee5d },
{ 0x2f800, 0x2fa1d },
{ 0x30000, 0x3134a },
{ 0x31350, 0x323af },
{ 0x31350, 0x33479 },
{ 0xe0100, 0xe01ef },
};
constexpr inline CharRange uppercase_letter[] = {
const int uppercase_letter_size = 660;
const CharRange uppercase_letter[uppercase_letter_size] = {
{ 0x41, 0x5a },
{ 0xc0, 0xd6 },
{ 0xd8, 0xde },
@ -2138,7 +2147,10 @@ constexpr inline CharRange uppercase_letter[] = {
{ 0xa7c4, 0xa7c7 },
{ 0xa7c9, 0xa7c9 },
{ 0xa7cb, 0xa7cc },
{ 0xa7ce, 0xa7ce },
{ 0xa7d0, 0xa7d0 },
{ 0xa7d2, 0xa7d2 },
{ 0xa7d4, 0xa7d4 },
{ 0xa7d6, 0xa7d6 },
{ 0xa7d8, 0xa7d8 },
{ 0xa7da, 0xa7da },
@ -2155,6 +2167,7 @@ constexpr inline CharRange uppercase_letter[] = {
{ 0x10d50, 0x10d65 },
{ 0x118a0, 0x118bf },
{ 0x16e40, 0x16e5f },
{ 0x16ea0, 0x16eb8 },
{ 0x1d400, 0x1d419 },
{ 0x1d434, 0x1d44d },
{ 0x1d468, 0x1d481 },
@ -2192,7 +2205,8 @@ constexpr inline CharRange uppercase_letter[] = {
{ 0x1f170, 0x1f189 },
};
constexpr inline CharRange lowercase_letter[] = {
const int lowercase_letter_size = 677;
const CharRange lowercase_letter[lowercase_letter_size] = {
{ 0x61, 0x7a },
{ 0xaa, 0xaa },
{ 0xb5, 0xb5 },
@ -2340,7 +2354,7 @@ constexpr inline CharRange lowercase_letter[] = {
{ 0x24b, 0x24b },
{ 0x24d, 0x24d },
{ 0x24f, 0x293 },
{ 0x295, 0x2b8 },
{ 0x296, 0x2b8 },
{ 0x2c0, 0x2c1 },
{ 0x2e0, 0x2e4 },
{ 0x345, 0x345 },
@ -2806,13 +2820,14 @@ constexpr inline CharRange lowercase_letter[] = {
{ 0xa7c8, 0xa7c8 },
{ 0xa7ca, 0xa7ca },
{ 0xa7cd, 0xa7cd },
{ 0xa7cf, 0xa7cf },
{ 0xa7d1, 0xa7d1 },
{ 0xa7d3, 0xa7d3 },
{ 0xa7d5, 0xa7d5 },
{ 0xa7d7, 0xa7d7 },
{ 0xa7d9, 0xa7d9 },
{ 0xa7db, 0xa7db },
{ 0xa7f2, 0xa7f4 },
{ 0xa7f1, 0xa7f4 },
{ 0xa7f6, 0xa7f6 },
{ 0xa7f8, 0xa7fa },
{ 0xab30, 0xab5a },
@ -2835,6 +2850,7 @@ constexpr inline CharRange lowercase_letter[] = {
{ 0x10d70, 0x10d85 },
{ 0x118c0, 0x118df },
{ 0x16e60, 0x16e7f },
{ 0x16ebb, 0x16ed3 },
{ 0x1d41a, 0x1d433 },
{ 0x1d44e, 0x1d454 },
{ 0x1d456, 0x1d467 },
@ -2870,7 +2886,8 @@ constexpr inline CharRange lowercase_letter[] = {
{ 0x1e922, 0x1e943 },
};
constexpr inline CharRange unicode_letter[] = {
const int unicode_letter_size = 761;
const CharRange unicode_letter[unicode_letter_size] = {
{ 0x41, 0x5a },
{ 0x61, 0x7a },
{ 0xaa, 0xaa },
@ -2924,7 +2941,7 @@ constexpr inline CharRange unicode_letter[] = {
{ 0x840, 0x858 },
{ 0x860, 0x86a },
{ 0x870, 0x887 },
{ 0x889, 0x88e },
{ 0x889, 0x88f },
{ 0x897, 0x897 },
{ 0x8a0, 0x8c9 },
{ 0x8d4, 0x8df },
@ -3015,7 +3032,7 @@ constexpr inline CharRange unicode_letter[] = {
{ 0xc4a, 0xc4c },
{ 0xc55, 0xc56 },
{ 0xc58, 0xc5a },
{ 0xc5d, 0xc5d },
{ 0xc5c, 0xc5d },
{ 0xc60, 0xc63 },
{ 0xc80, 0xc83 },
{ 0xc85, 0xc8c },
@ -3027,7 +3044,7 @@ constexpr inline CharRange unicode_letter[] = {
{ 0xcc6, 0xcc8 },
{ 0xcca, 0xccc },
{ 0xcd5, 0xcd6 },
{ 0xcdd, 0xcde },
{ 0xcdc, 0xcde },
{ 0xce0, 0xce3 },
{ 0xcf1, 0xcf3 },
{ 0xd00, 0xd0c },
@ -3227,11 +3244,8 @@ constexpr inline CharRange unicode_letter[] = {
{ 0xa67f, 0xa6ef },
{ 0xa717, 0xa71f },
{ 0xa722, 0xa788 },
{ 0xa78b, 0xa7cd },
{ 0xa7d0, 0xa7d1 },
{ 0xa7d3, 0xa7d3 },
{ 0xa7d5, 0xa7dc },
{ 0xa7f2, 0xa805 },
{ 0xa78b, 0xa7dc },
{ 0xa7f1, 0xa805 },
{ 0xa807, 0xa827 },
{ 0xa840, 0xa873 },
{ 0xa880, 0xa8c3 },
@ -3340,6 +3354,7 @@ constexpr inline CharRange unicode_letter[] = {
{ 0x108f4, 0x108f5 },
{ 0x10900, 0x10915 },
{ 0x10920, 0x10939 },
{ 0x10940, 0x10959 },
{ 0x10980, 0x109b7 },
{ 0x109be, 0x109bf },
{ 0x10a00, 0x10a03 },
@ -3365,8 +3380,8 @@ constexpr inline CharRange unicode_letter[] = {
{ 0x10e80, 0x10ea9 },
{ 0x10eab, 0x10eac },
{ 0x10eb0, 0x10eb1 },
{ 0x10ec2, 0x10ec4 },
{ 0x10efc, 0x10efc },
{ 0x10ec2, 0x10ec7 },
{ 0x10efa, 0x10efc },
{ 0x10f00, 0x10f1c },
{ 0x10f27, 0x10f27 },
{ 0x10f30, 0x10f45 },
@ -3459,6 +3474,7 @@ constexpr inline CharRange unicode_letter[] = {
{ 0x11a50, 0x11a97 },
{ 0x11a9d, 0x11a9d },
{ 0x11ab0, 0x11af8 },
{ 0x11b60, 0x11b67 },
{ 0x11bc0, 0x11be0 },
{ 0x11c00, 0x11c08 },
{ 0x11c0a, 0x11c36 },
@ -3481,6 +3497,7 @@ constexpr inline CharRange unicode_letter[] = {
{ 0x11d90, 0x11d91 },
{ 0x11d93, 0x11d96 },
{ 0x11d98, 0x11d98 },
{ 0x11db0, 0x11ddb },
{ 0x11ee0, 0x11ef6 },
{ 0x11f00, 0x11f10 },
{ 0x11f12, 0x11f3a },
@ -3505,15 +3522,17 @@ constexpr inline CharRange unicode_letter[] = {
{ 0x16b7d, 0x16b8f },
{ 0x16d40, 0x16d6c },
{ 0x16e40, 0x16e7f },
{ 0x16ea0, 0x16eb8 },
{ 0x16ebb, 0x16ed3 },
{ 0x16f00, 0x16f4a },
{ 0x16f4f, 0x16f87 },
{ 0x16f8f, 0x16f9f },
{ 0x16fe0, 0x16fe1 },
{ 0x16fe3, 0x16fe3 },
{ 0x16ff0, 0x16ff1 },
{ 0x17000, 0x187f7 },
{ 0x18800, 0x18cd5 },
{ 0x18cff, 0x18d08 },
{ 0x16ff0, 0x16ff6 },
{ 0x17000, 0x18cd5 },
{ 0x18cff, 0x18d1e },
{ 0x18d80, 0x18df2 },
{ 0x1aff0, 0x1aff3 },
{ 0x1aff5, 0x1affb },
{ 0x1affd, 0x1affe },
@ -3575,6 +3594,9 @@ constexpr inline CharRange unicode_letter[] = {
{ 0x1e4d0, 0x1e4eb },
{ 0x1e5d0, 0x1e5ed },
{ 0x1e5f0, 0x1e5f0 },
{ 0x1e6c0, 0x1e6de },
{ 0x1e6e0, 0x1e6f5 },
{ 0x1e6fe, 0x1e6ff },
{ 0x1e7e0, 0x1e7e6 },
{ 0x1e7e8, 0x1e7eb },
{ 0x1e7ed, 0x1e7ee },
@ -3620,12 +3642,11 @@ constexpr inline CharRange unicode_letter[] = {
{ 0x1f150, 0x1f169 },
{ 0x1f170, 0x1f189 },
{ 0x20000, 0x2a6df },
{ 0x2a700, 0x2b739 },
{ 0x2b740, 0x2b81d },
{ 0x2b820, 0x2cea1 },
{ 0x2a700, 0x2b81d },
{ 0x2b820, 0x2cead },
{ 0x2ceb0, 0x2ebe0 },
{ 0x2ebf0, 0x2ee5d },
{ 0x2f800, 0x2fa1d },
{ 0x30000, 0x3134a },
{ 0x31350, 0x323af },
{ 0x31350, 0x33479 },
};

View file

@ -32,48 +32,62 @@
#include "core/typedefs.h"
#include "char_range.inc"
static constexpr char hex_char_table_upper[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
static constexpr char hex_char_table_lower[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
#define BSEARCH_CHAR_RANGE(m_array) \
int low = 0; \
int high = std_size(m_array) - 1; \
int middle = (low + high) / 2; \
\
while (low <= high) { \
if (p_char < m_array[middle].start) { \
high = middle - 1; \
struct CharRange {
char32_t start;
char32_t end;
};
extern const CharRange xid_start[];
extern const int xid_start_size;
extern const CharRange xid_continue[];
extern const int xid_continue_size;
extern const CharRange uppercase_letter[];
extern const int uppercase_letter_size;
extern const CharRange lowercase_letter[];
extern const int lowercase_letter_size;
extern const CharRange unicode_letter[];
extern const int unicode_letter_size;
#define BSEARCH_CHAR_RANGE(m_array, m_size) \
int low = 0; \
int high = m_size - 1; \
int middle = (low + high) / 2; \
\
while (low <= high) { \
if (p_char < m_array[middle].start) { \
high = middle - 1; \
} else if (p_char > m_array[middle].end) { \
low = middle + 1; \
} else { \
return true; \
} \
\
middle = (low + high) / 2; \
} \
\
low = middle + 1; \
} else { \
return true; \
} \
\
middle = (low + high) / 2; \
} \
\
return false
constexpr bool is_unicode_identifier_start(char32_t p_char) {
BSEARCH_CHAR_RANGE(xid_start);
inline bool is_unicode_identifier_start(char32_t p_char) {
BSEARCH_CHAR_RANGE(xid_start, xid_start_size);
}
constexpr bool is_unicode_identifier_continue(char32_t p_char) {
BSEARCH_CHAR_RANGE(xid_continue);
inline bool is_unicode_identifier_continue(char32_t p_char) {
BSEARCH_CHAR_RANGE(xid_continue, xid_continue_size);
}
constexpr bool is_unicode_upper_case(char32_t p_char) {
BSEARCH_CHAR_RANGE(uppercase_letter);
inline bool is_unicode_upper_case(char32_t p_char) {
BSEARCH_CHAR_RANGE(uppercase_letter, uppercase_letter_size);
}
constexpr bool is_unicode_lower_case(char32_t p_char) {
BSEARCH_CHAR_RANGE(lowercase_letter);
inline bool is_unicode_lower_case(char32_t p_char) {
BSEARCH_CHAR_RANGE(lowercase_letter, lowercase_letter_size);
}
constexpr bool is_unicode_letter(char32_t p_char) {
BSEARCH_CHAR_RANGE(unicode_letter);
inline bool is_unicode_letter(char32_t p_char) {
BSEARCH_CHAR_RANGE(unicode_letter, unicode_letter_size);
}
#undef BSEARCH_CHAR_RANGE

View file

@ -30,9 +30,10 @@
#include "fuzzy_search.h"
constexpr float cull_factor = 0.1f;
constexpr float cull_cutoff = 30.0f;
const String boundary_chars = "/\\-_.";
#include "core/object/class_db.h"
#include "core/variant/typed_array.h"
static const String boundary_chars = "/\\-_. ";
static bool _is_valid_interval(const Vector2i &p_interval) {
// Empty intervals are represented as (-1, -1).
@ -117,7 +118,7 @@ bool FuzzyTokenMatch::intersects(const Vector2i &p_other_interval) const {
return interval.y >= p_other_interval.x && interval.x <= p_other_interval.y;
}
bool FuzzySearchResult::can_add_token_match(const FuzzyTokenMatch &p_match) const {
bool FuzzySearchMatch::_can_add_token_match(const FuzzyTokenMatch &p_match) const {
if (p_match.get_miss_count() > miss_budget) {
return false;
}
@ -148,7 +149,7 @@ bool FuzzyTokenMatch::is_case_insensitive(const String &p_original, const String
return false;
}
void FuzzySearchResult::score_token_match(FuzzyTokenMatch &p_match, bool p_case_insensitive) const {
void FuzzySearchMatch::_score_token_match(FuzzyTokenMatch &p_match, bool p_case_insensitive) const {
// This can always be tweaked more. The intuition is that exact matches should almost always
// be prioritized over broken up matches, and other criteria more or less act as tie breakers.
@ -173,44 +174,80 @@ void FuzzySearchResult::score_token_match(FuzzyTokenMatch &p_match, bool p_case_
}
}
void FuzzySearchResult::maybe_apply_score_bonus() {
void FuzzySearchMatch::_maybe_apply_token_order_score_bonus() {
// This adds a small bonus to results which match tokens in the same order they appear in the query.
if (token_matches.is_empty()) {
return;
}
int *token_range_starts = (int *)alloca(sizeof(int) * token_matches.size());
for (const FuzzyTokenMatch &match : token_matches) {
token_range_starts[match.token_idx] = match.interval.x;
}
int last = token_range_starts[0];
for (int i = 1; i < token_matches.size(); i++) {
if (last > token_range_starts[i]) {
// Individual tokens can match without a range if the missed-character budget allows for it. If
// the i'th token matches in this manner, skip ahead so we check neither (i-1, i) nor (i, i+1).
// It's safe that this skips i=0 since any valid start will be > -1.
if (token_range_starts[i] == -1) {
i++;
continue;
}
if (token_range_starts[i - 1] > token_range_starts[i]) {
return;
}
last = token_range_starts[i];
}
score += 1;
}
void FuzzySearchResult::add_token_match(const FuzzyTokenMatch &p_match) {
void FuzzySearchMatch::_add_token_match(const FuzzyTokenMatch &p_match) {
score += p_match.score;
match_interval = _extend_interval(match_interval, p_match.interval);
miss_budget -= p_match.get_miss_count();
token_matches.append(p_match);
}
void remove_low_scores(Vector<FuzzySearchResult> &p_results, float p_cull_score) {
void FuzzySearchMatch::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_target", "target"), &FuzzySearchMatch::set_target);
ClassDB::bind_method(D_METHOD("get_target"), &FuzzySearchMatch::get_target);
ClassDB::bind_method(D_METHOD("set_score", "score"), &FuzzySearchMatch::set_score);
ClassDB::bind_method(D_METHOD("get_score"), &FuzzySearchMatch::get_score);
ClassDB::bind_method(D_METHOD("set_original_index", "original_index"), &FuzzySearchMatch::set_original_index);
ClassDB::bind_method(D_METHOD("get_original_index"), &FuzzySearchMatch::get_original_index);
ClassDB::bind_method(D_METHOD("get_matched_substrings"), &FuzzySearchMatch::get_matched_substrings);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "target"), "set_target", "get_target");
ADD_PROPERTY(PropertyInfo(Variant::INT, "score"), "set_score", "get_score");
ADD_PROPERTY(PropertyInfo(Variant::INT, "original_index"), "set_original_index", "get_original_index");
}
TypedArray<Vector2i> FuzzySearchMatch::get_matched_substrings() const {
TypedArray<Vector2i> substrings;
for (const FuzzyTokenMatch &match : token_matches) {
for (const Vector2i &substring : match.substrings) {
substrings.append(substring);
}
}
return substrings;
}
static void remove_low_scores(Vector<Ref<FuzzySearchMatch>> &p_results, float p_cull_score) {
// Removes all results with score < p_cull_score in-place.
int i = 0;
int j = p_results.size() - 1;
FuzzySearchResult *results = p_results.ptrw();
Ref<FuzzySearchMatch> *results = p_results.ptrw();
while (true) {
// Advances i to an element to remove and j to an element to keep.
while (j >= i && results[j].score < p_cull_score) {
while (j >= i && results[j]->get_score() < p_cull_score) {
j--;
}
while (i < j && results[i].score >= p_cull_score) {
while (i < j && results[i]->get_score() >= p_cull_score) {
i++;
}
if (i >= j) {
@ -222,60 +259,13 @@ void remove_low_scores(Vector<FuzzySearchResult> &p_results, float p_cull_score)
p_results.resize(j + 1);
}
void FuzzySearch::sort_and_filter(Vector<FuzzySearchResult> &p_results) const {
if (p_results.is_empty()) {
return;
}
float avg_score = 0;
float max_score = 0;
for (const FuzzySearchResult &result : p_results) {
avg_score += result.score;
max_score = MAX(max_score, result.score);
}
// TODO: Tune scoring and culling here to display fewer subsequence soup matches when good matches
// are available.
avg_score /= p_results.size();
float cull_score = MIN(cull_cutoff, Math::lerp(avg_score, max_score, cull_factor));
remove_low_scores(p_results, cull_score);
struct FuzzySearchResultComparator {
bool operator()(const FuzzySearchResult &p_lhs, const FuzzySearchResult &p_rhs) const {
// Sort on (score, length, alphanumeric) to ensure consistent ordering.
if (p_lhs.score == p_rhs.score) {
if (p_lhs.target.length() == p_rhs.target.length()) {
return p_lhs.target < p_rhs.target;
}
return p_lhs.target.length() < p_rhs.target.length();
}
return p_lhs.score > p_rhs.score;
}
};
SortArray<FuzzySearchResult, FuzzySearchResultComparator> sorter;
if (p_results.size() > max_results) {
sorter.partial_sort(0, p_results.size(), max_results, p_results.ptrw());
p_results.resize(max_results);
} else {
sorter.sort(p_results.ptrw(), p_results.size());
}
}
void FuzzySearch::set_query(const String &p_query) {
set_query(p_query, !p_query.is_lowercase());
}
void FuzzySearch::set_query(const String &p_query, bool p_case_sensitive) {
tokens.clear();
case_sensitive = p_case_sensitive;
Vector<FuzzySearchToken> FuzzySearch::_get_tokens(const String &p_query) const {
Vector<FuzzySearchToken> tokens;
for (const String &string : p_query.split(" ", false)) {
tokens.append({
static_cast<int>(tokens.size()),
p_case_sensitive ? string : string.to_lower(),
case_sensitive ? string : string.to_lower(),
});
}
@ -290,12 +280,60 @@ void FuzzySearch::set_query(const String &p_query, bool p_case_sensitive) {
// Prioritize matching longer tokens before shorter ones since match overlaps are not accepted.
tokens.sort_custom<TokenComparator>();
return tokens;
}
bool FuzzySearch::search(const String &p_target, FuzzySearchResult &p_result) const {
p_result.target = p_target;
p_result.dir_index = p_target.rfind_char('/');
p_result.miss_budget = max_misses;
void FuzzySearch::_sort_and_filter(Vector<Ref<FuzzySearchMatch>> &p_results) const {
if (p_results.is_empty()) {
return;
}
if (filter_low_scores) {
float avg_score = 0;
float max_score = 0;
for (const Ref<FuzzySearchMatch> &result : p_results) {
avg_score += result->get_score();
max_score = MAX(max_score, result->get_score());
}
avg_score /= p_results.size();
float cull_score = MIN(filter_cutoff, Math::lerp(avg_score, max_score, filter_factor));
remove_low_scores(p_results, cull_score);
}
struct FuzzySearchResultComparator {
bool operator()(const Ref<FuzzySearchMatch> &p_lhs, const Ref<FuzzySearchMatch> &p_rhs) const {
// Sort on (score, length, alphanumeric) to ensure consistent ordering.
if (p_lhs->score == p_rhs->score) {
if (p_lhs->target.length() == p_rhs->target.length()) {
return p_lhs->target < p_rhs->target;
}
return p_lhs->target.length() < p_rhs->target.length();
}
return p_lhs->score > p_rhs->score;
}
};
SortArray<Ref<FuzzySearchMatch>, FuzzySearchResultComparator> sorter;
if (p_results.size() > max_results) {
sorter.partial_sort(0, p_results.size(), max_results, p_results.ptrw());
p_results.resize(max_results);
} else {
sorter.sort(p_results.ptrw(), p_results.size());
}
}
void FuzzySearch::set_case_sensitive(bool p_case_sensitive) {
case_sensitive = p_case_sensitive;
}
bool FuzzySearch::_search_tokens(const Vector<FuzzySearchToken> &p_tokens, const String &p_target, Ref<FuzzySearchMatch> &r_result) const {
r_result->target = p_target;
r_result->dir_index = p_target.rfind_char('/');
r_result->miss_budget = max_misses;
r_result->token_matches.reserve(p_tokens.size());
String adjusted_target = case_sensitive ? p_target : p_target.to_lower();
@ -303,23 +341,23 @@ bool FuzzySearch::search(const String &p_target, FuzzySearchResult &p_result) co
// which does not conflict with prior token matches. This is not ensured to find the highest scoring
// combination of matches, or necessarily the highest scoring single subsequence, as it only considers
// eager subsequences for a given index, and likewise eagerly finds matches for each token in sequence.
for (const FuzzySearchToken &token : tokens) {
for (const FuzzySearchToken &token : p_tokens) {
FuzzyTokenMatch best_match;
int offset = start_offset;
while (true) {
FuzzyTokenMatch match;
if (allow_subsequences) {
if (!token.try_fuzzy_match(match, adjusted_target, offset, p_result.miss_budget)) {
break;
}
} else {
if (exact_token_matches) {
if (!token.try_exact_match(match, adjusted_target, offset)) {
break;
}
} else {
if (!token.try_fuzzy_match(match, adjusted_target, offset, r_result->miss_budget)) {
break;
}
}
if (p_result.can_add_token_match(match)) {
p_result.score_token_match(match, match.is_case_insensitive(p_target, adjusted_target));
if (r_result->_can_add_token_match(match)) {
r_result->_score_token_match(match, match.is_case_insensitive(p_target, adjusted_target));
if (best_match.token_idx == -1 || best_match.score < match.score) {
best_match = match;
}
@ -335,23 +373,89 @@ bool FuzzySearch::search(const String &p_target, FuzzySearchResult &p_result) co
return false;
}
p_result.add_token_match(best_match);
r_result->_add_token_match(best_match);
}
p_result.maybe_apply_score_bonus();
if (r_result->match_interval.x == -1) {
// Reject matches which rely entirely on misses.
return false;
}
r_result->_maybe_apply_token_order_score_bonus();
return true;
}
void FuzzySearch::search_all(const PackedStringArray &p_targets, Vector<FuzzySearchResult> &p_results) const {
p_results.clear();
Ref<FuzzySearchMatch> FuzzySearch::search(const String &p_query, const String &p_target) const {
Ref<FuzzySearchMatch> result;
result.instantiate();
if (_search_tokens(_get_tokens(p_query), p_target, result)) {
return result;
}
return nullptr;
}
Vector<Ref<FuzzySearchMatch>> FuzzySearch::search_all(const String &p_query, const PackedStringArray &p_targets) const {
Vector<Ref<FuzzySearchMatch>> results;
const Vector<FuzzySearchToken> tokens = _get_tokens(p_query);
for (int i = 0; i < p_targets.size(); i++) {
FuzzySearchResult result;
result.original_index = i;
if (search(p_targets[i], result)) {
p_results.append(result);
Ref<FuzzySearchMatch> result;
result.instantiate();
result->original_index = i;
if (_search_tokens(tokens, p_targets[i], result)) {
results.append(result);
}
}
sort_and_filter(p_results);
_sort_and_filter(results);
return results;
}
TypedArray<FuzzySearchMatch> FuzzySearch::_search_all_bind(const String &p_query, const PackedStringArray &p_targets) const {
Vector<Ref<FuzzySearchMatch>> results = search_all(p_query, p_targets);
TypedArray<FuzzySearchMatch> wrapped_results;
wrapped_results.reserve(results.size());
for (Ref<FuzzySearchMatch> &result : results) {
wrapped_results.append(result);
}
return wrapped_results;
}
void FuzzySearch::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_start_offset", "start_offset"), &FuzzySearch::set_start_offset);
ClassDB::bind_method(D_METHOD("get_start_offset"), &FuzzySearch::get_start_offset);
ClassDB::bind_method(D_METHOD("set_max_results", "max_results"), &FuzzySearch::set_max_results);
ClassDB::bind_method(D_METHOD("get_max_results"), &FuzzySearch::get_max_results);
ClassDB::bind_method(D_METHOD("set_max_misses", "max_misses"), &FuzzySearch::set_max_misses);
ClassDB::bind_method(D_METHOD("get_max_misses"), &FuzzySearch::get_max_misses);
ClassDB::bind_method(D_METHOD("set_use_exact_tokens", "use_exact_tokens"), &FuzzySearch::set_use_exact_tokens);
ClassDB::bind_method(D_METHOD("get_use_exact_tokens"), &FuzzySearch::get_use_exact_tokens);
ClassDB::bind_method(D_METHOD("set_case_sensitive", "case_sensitive"), &FuzzySearch::set_case_sensitive);
ClassDB::bind_method(D_METHOD("get_case_sensitive"), &FuzzySearch::get_case_sensitive);
ClassDB::bind_method(D_METHOD("set_filter_low_scores", "filter_low_scores"), &FuzzySearch::set_filter_low_scores);
ClassDB::bind_method(D_METHOD("get_filter_low_scores"), &FuzzySearch::get_filter_low_scores);
ClassDB::bind_method(D_METHOD("set_filter_factor", "filter_factor"), &FuzzySearch::set_filter_factor);
ClassDB::bind_method(D_METHOD("get_filter_factor"), &FuzzySearch::get_filter_factor);
ClassDB::bind_method(D_METHOD("set_filter_cutoff", "filter_cutoff"), &FuzzySearch::set_filter_cutoff);
ClassDB::bind_method(D_METHOD("get_filter_cutoff"), &FuzzySearch::get_filter_cutoff);
ClassDB::bind_method(D_METHOD("search", "query", "target"), &FuzzySearch::search);
ClassDB::bind_method(D_METHOD("search_all", "query", "targets"), &FuzzySearch::_search_all_bind);
ADD_PROPERTY(PropertyInfo(Variant::INT, "start_offset"), "set_start_offset", "get_start_offset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_results"), "set_max_results", "get_max_results");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_misses"), "set_max_misses", "get_max_misses");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_exact_tokens"), "set_use_exact_tokens", "get_use_exact_tokens");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "case_sensitive"), "set_case_sensitive", "get_case_sensitive");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_low_scores"), "set_filter_low_scores", "get_filter_low_scores");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "filter_factor"), "set_filter_factor", "get_filter_factor");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "filter_cutoff"), "set_filter_cutoff", "get_filter_cutoff");
}

View file

@ -30,6 +30,7 @@
#pragma once
#include "core/object/ref_counted.h"
#include "core/variant/variant.h"
class FuzzyTokenMatch;
@ -44,7 +45,7 @@ struct FuzzySearchToken {
class FuzzyTokenMatch {
friend struct FuzzySearchToken;
friend class FuzzySearchResult;
friend class FuzzySearchMatch;
friend class FuzzySearch;
int matched_length = 0;
@ -62,39 +63,93 @@ public:
Vector<Vector2i> substrings; // x is start index, y is length.
};
class FuzzySearchResult {
class FuzzySearchMatch : public RefCounted {
GDCLASS(FuzzySearchMatch, RefCounted)
friend class FuzzySearch;
int miss_budget = 0;
Vector2i match_interval = Vector2i(-1, -1);
bool can_add_token_match(const FuzzyTokenMatch &p_match) const;
void score_token_match(FuzzyTokenMatch &p_match, bool p_case_insensitive) const;
void add_token_match(const FuzzyTokenMatch &p_match);
void maybe_apply_score_bonus();
public:
String target;
int score = 0;
int original_index = -1;
int dir_index = -1;
Vector<FuzzyTokenMatch> token_matches;
};
int miss_budget = 0;
Vector2i match_interval = Vector2i(-1, -1);
class FuzzySearch {
Vector<FuzzySearchToken> tokens;
bool _can_add_token_match(const FuzzyTokenMatch &p_match) const;
void _score_token_match(FuzzyTokenMatch &p_match, bool p_case_insensitive) const;
void _add_token_match(const FuzzyTokenMatch &p_match);
void _maybe_apply_token_order_score_bonus();
bool case_sensitive = false;
void sort_and_filter(Vector<FuzzySearchResult> &p_results) const;
protected:
static void _bind_methods();
public:
void set_target(const String &p_target) { target = p_target; }
String get_target() const { return target; }
void set_score(int p_score) { score = p_score; }
int get_score() const { return score; }
void set_original_index(int p_original_index) { original_index = p_original_index; }
int get_original_index() const { return original_index; }
void set_dir_index(int p_dir_index) { dir_index = p_dir_index; }
int get_dir_index() const { return dir_index; }
Vector<FuzzyTokenMatch> get_token_matches() { return token_matches; }
TypedArray<Vector2i> get_matched_substrings() const;
};
class FuzzySearch : public RefCounted {
GDCLASS(FuzzySearch, RefCounted)
int start_offset = 0;
int max_results = 100;
int max_misses = 2;
bool allow_subsequences = true;
bool exact_token_matches = false;
bool case_sensitive = false;
bool filter_low_scores = true;
float filter_factor = 0.1f;
float filter_cutoff = 30.0f;
void set_query(const String &p_query);
void set_query(const String &p_query, bool p_case_sensitive);
bool search(const String &p_target, FuzzySearchResult &p_result) const;
void search_all(const PackedStringArray &p_targets, Vector<FuzzySearchResult> &p_results) const;
Vector<FuzzySearchToken> _get_tokens(const String &p_query) const;
void _sort_and_filter(Vector<Ref<FuzzySearchMatch>> &p_results) const;
bool _search_tokens(const Vector<FuzzySearchToken> &p_tokens, const String &p_target, Ref<FuzzySearchMatch> &r_result) const;
TypedArray<FuzzySearchMatch> _search_all_bind(const String &p_query, const PackedStringArray &p_targets) const;
protected:
static void _bind_methods();
public:
void set_start_offset(int p_offset) { start_offset = p_offset; }
int get_start_offset() const { return start_offset; }
void set_max_results(int p_max_results) { max_results = p_max_results; }
int get_max_results() const { return max_results; }
void set_max_misses(int p_max_misses) { max_misses = p_max_misses; }
int get_max_misses() const { return max_misses; }
void set_use_exact_tokens(bool p_use_exact_tokens) { exact_token_matches = p_use_exact_tokens; }
bool get_use_exact_tokens() const { return exact_token_matches; }
void set_case_sensitive(bool p_case_sensitive);
bool get_case_sensitive() const { return case_sensitive; }
void set_filter_low_scores(bool p_filter_low_scores) { filter_low_scores = p_filter_low_scores; }
bool get_filter_low_scores() const { return filter_low_scores; }
void set_filter_factor(float p_filter_factor) {
ERR_FAIL_COND_MSG(p_filter_factor < 0.0f || p_filter_factor > 1.0f, "filter_factor should be in the range [0, 1]");
filter_factor = p_filter_factor;
}
float get_filter_factor() const { return filter_factor; }
void set_filter_cutoff(float p_filter_cutoff) { filter_cutoff = p_filter_cutoff; }
float get_filter_cutoff() const { return filter_cutoff; }
Ref<FuzzySearchMatch> search(const String &p_query, const String &p_target) const;
Vector<Ref<FuzzySearchMatch>> search_all(const String &p_query, const PackedStringArray &p_targets) const;
};

View file

@ -913,6 +913,7 @@ static const char *language_list[][2] = {
{ "tmh", "Tamashek" },
{ "tn", "Tswana" },
{ "to", "Tongan" },
{ "tok", "Toki Pona" },
{ "tog", "Nyasa Tonga" },
{ "tpi", "Tok Pisin" },
{ "tr", "Turkish" },
@ -1537,6 +1538,7 @@ static const char *language_script_list[][2] = {
{ "tn", "Latn" },
{ "to", "Latn" },
{ "tog", "Latn" },
{ "tok", "Latn" },
{ "tpi", "Latn" },
{ "tr", "Latn Arab" },
{ "tru", "Latn Syrc" },

View file

@ -41,7 +41,8 @@ void NodePath::_update_hash_cache() const {
}
void NodePath::prepend_period() {
if (data->path.size() && data->path[0].operator String() != ".") {
if (data->path.size() && data->path[0].string() != ".") {
_copy_on_write();
data->path.insert(0, ".");
data->concatenated_path = StringName();
data->hash_cache_valid = false;
@ -167,6 +168,29 @@ void NodePath::operator=(const NodePath &p_path) {
}
}
void NodePath::_copy_on_write() {
if (!data) {
return; // No data; nothing to do.
}
if (data->refcount.get() == 1) {
return; // Already the only owner of data.
}
// Make a copy.
Data *new_data = memnew(Data);
new_data->refcount.init();
new_data->path = data->path;
new_data->subpath = data->subpath;
new_data->concatenated_path = data->concatenated_path;
new_data->concatenated_subpath = data->concatenated_subpath;
new_data->absolute = data->absolute;
new_data->hash_cache_valid = data->hash_cache_valid;
new_data->hash_cache = data->hash_cache;
unref();
data = new_data;
}
NodePath::operator String() const {
if (!data) {
return String();
@ -212,7 +236,7 @@ StringName NodePath::get_concatenated_names() const {
if (i > 0) {
concatenated += "/";
}
concatenated += sn[i].operator String();
concatenated += sn[i].string();
}
data->concatenated_path = concatenated;
}
@ -230,7 +254,7 @@ StringName NodePath::get_concatenated_subnames() const {
if (i > 0) {
concatenated += ":";
}
concatenated += ssn[i].operator String();
concatenated += ssn[i].string();
}
data->concatenated_subpath = concatenated;
}
@ -332,14 +356,15 @@ void NodePath::simplify() {
if (!data) {
return;
}
_copy_on_write();
for (int i = 0; i < data->path.size(); i++) {
if (data->path.size() == 1) {
break;
}
if (data->path[i].operator String() == ".") {
if (data->path[i].string() == ".") {
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() != "..") {
} else if (i > 0 && data->path[i].string() == ".." && data->path[i - 1].string() != "." && data->path[i - 1].string() != "..") {
//remove both
data->path.remove_at(i - 1);
data->path.remove_at(i - 1);

View file

@ -35,6 +35,10 @@
#include <climits>
// Represents a path to a node or property in a hierarchy of nodes
// Note that NodePath is (effectively) const: If you hold a NodePath,
// you can expect it to remain unchanged, even if you make copies of
// it. This is achieved through copy-on-write (CoW).
class [[nodiscard]] NodePath {
struct Data {
SafeRefCount refcount;
@ -52,6 +56,10 @@ class [[nodiscard]] NodePath {
void _update_hash_cache() const;
// Copies the underlying data.
// Every non-const function must call this before starting to mutate data.
void _copy_on_write();
public:
bool is_absolute() const;
int get_name_count() const;

View file

@ -0,0 +1,45 @@
/**************************************************************************/
/* optimized_translation.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 DISABLE_DEPRECATED
#include "optimized_translation.h"
#include "core/object/class_db.h"
void OptimizedTranslation::_generate_bind_compat_119563(const Ref<Translation> &p_from) {
generate(p_from);
}
void OptimizedTranslation::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("generate", "from"), &OptimizedTranslation::_generate_bind_compat_119563);
}
#endif

View file

@ -29,11 +29,13 @@
/**************************************************************************/
#include "optimized_translation.h"
#include "optimized_translation.compat.inc"
#include "core/object/class_db.h"
#include "core/templates/pair.h"
extern "C" {
#include "thirdparty/misc/smaz.h"
#include <thirdparty/misc/smaz.h>
}
struct CompressedString {
@ -42,11 +44,11 @@ struct CompressedString {
int offset = 0;
};
void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
bool OptimizedTranslation::generate(const Ref<Translation> &p_from) {
// This method compresses a Translation instance.
// Right now, it doesn't handle context or plurals.
#ifdef TOOLS_ENABLED
ERR_FAIL_COND(p_from.is_null());
ERR_FAIL_COND_V(p_from.is_null(), false);
List<StringName> keys;
{
@ -54,7 +56,7 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
p_from->get_message_list(&raw_keys);
for (const StringName &key : raw_keys) {
const String key_str = key.operator String();
const String key_str = key.string();
int p = key_str.find_char(0x04);
if (p == -1) {
keys.push_back(key);
@ -83,7 +85,7 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
for (const StringName &E : keys) {
//hash string
CharString cs = E.operator String().utf8();
CharString cs = E.string().utf8();
uint32_t h = hash(0, cs.get_data());
Pair<int, CharString> p;
p.first = idx;
@ -91,7 +93,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).operator String().utf8();
CharString src_s = p_from->get_message(E).string().utf8();
CompressedString ps;
ps.orig_len = src_s.size();
ps.offset = total_compression_size;
@ -149,7 +151,7 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
bucket_table_size += 2 + b.size() * 4;
}
ERR_FAIL_COND(bucket_table_size == 0);
ERR_FAIL_COND_V(bucket_table_size == 0, false);
hash_table.resize(size);
bucket_table.resize(bucket_table_size);
@ -188,14 +190,17 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
memcpy(&cw[compressed[i].offset], compressed[i].compressed.get_data(), compressed[i].compressed.size());
}
ERR_FAIL_COND(btindex != bucket_table_size);
ERR_FAIL_COND_V(btindex != bucket_table_size, false);
set_locale(p_from->get_locale());
return true;
#else
return false;
#endif
}
bool OptimizedTranslation::_set(const StringName &p_name, const Variant &p_value) {
String prop_name = p_name.operator String();
String prop_name = p_name.string();
if (prop_name == "hash_table") {
hash_table = p_value;
} else if (prop_name == "bucket_table") {
@ -212,7 +217,7 @@ bool OptimizedTranslation::_set(const StringName &p_name, const Variant &p_value
}
bool OptimizedTranslation::_get(const StringName &p_name, Variant &r_ret) const {
String prop_name = p_name.operator String();
String prop_name = p_name.string();
if (prop_name == "hash_table") {
r_ret = hash_table;
} else if (prop_name == "bucket_table") {
@ -235,7 +240,7 @@ StringName OptimizedTranslation::get_message(const StringName &p_src_text, const
return StringName();
}
CharString str = p_src_text.operator String().utf8();
CharString str = p_src_text.string().utf8();
uint32_t h = hash(0, str.get_data());
const int *htr = hash_table.ptr();
@ -332,7 +337,7 @@ void OptimizedTranslation::_get_property_list(List<PropertyInfo> *p_list) const
p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "hash_table", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "bucket_table", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
p_list->push_back(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "strings", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
p_list->push_back(PropertyInfo(Variant::OBJECT, "load_from", PROPERTY_HINT_RESOURCE_TYPE, "Translation", PROPERTY_USAGE_EDITOR));
p_list->push_back(PropertyInfo(Variant::OBJECT, "load_from", PROPERTY_HINT_RESOURCE_TYPE, Translation::get_class_static(), PROPERTY_USAGE_EDITOR));
}
void OptimizedTranslation::_bind_methods() {

View file

@ -91,11 +91,17 @@ protected:
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
void _generate_bind_compat_119563(const Ref<Translation> &p_from);
static void _bind_compatibility_methods();
#endif
public:
virtual StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const override; //overridable for other implementations
virtual StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const override;
virtual Vector<String> get_translated_message_list() const override;
void generate(const Ref<Translation> &p_from);
bool generate(const Ref<Translation> &p_from);
virtual void get_message_list(List<StringName> *r_messages) const override;
virtual int get_message_count() const override;

View file

@ -33,11 +33,17 @@
#include "core/core_globals.h"
#include "core/os/os.h"
#include <cstdio>
static PrintHandlerList *print_handler_list = nullptr;
static thread_local bool is_printing = false;
static void __print_fallback(const String &p_string, bool p_err) {
fprintf(p_err ? stderr : stdout, "While attempting to print a message, another message was printed:\n%s\n", p_string.utf8().get_data());
static void __print_fallback(const String &p_string, bool p_err, bool p_reentrance) {
if (p_reentrance) {
fprintf(p_err ? stderr : stdout, "While attempting to print an error, another error was printed:\n");
}
fprintf(p_err ? stderr : stdout, "%s\n", p_string.utf8().get_data());
}
void add_print_handler(PrintHandlerList *p_handler) {
@ -76,8 +82,13 @@ void __print_line(const String &p_string) {
return;
}
if (!CoreGlobals::print_ready) {
__print_fallback(p_string, false, false);
return;
}
if (is_printing) {
__print_fallback(p_string, false);
__print_fallback(p_string, false, true);
return;
}
@ -108,6 +119,7 @@ void __print_line_rich(const String &p_string) {
String output;
int pos = 0;
bool in_named_url = false;
while (pos <= p_string.length()) {
int brk_pos = p_string.find_char('[', pos);
@ -156,10 +168,24 @@ void __print_line_rich(const String &p_string) {
output += "\u001b[2m";
} else if (tag == "/code") {
output += "\u001b[22m";
} else if (tag.begins_with("url=")) {
// Support named URLs using OSC 8 escape codes:
// <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>
in_named_url = true;
const String url_link = tag.substr(strlen("url="));
output += vformat("\u001b]8;;%s\u001b\\", url_link);
} else if (tag == "url") {
output += "";
} else if (tag == "/url") {
output += "";
if (in_named_url) {
output += "\u001b]8;;\u001b\\";
in_named_url = false;
} else {
// While it's legal to close an URL that was never opened using OSC 8 escape codes,
// it would result in the code being printed to unsupported terminal emulators
// when using unnamed URLs.
output += "";
}
} else if (tag == "center") {
output += "\n\t\t\t";
} else if (tag == "/center") {
@ -277,8 +303,13 @@ void __print_line_rich(const String &p_string) {
}
output += "\u001b[0m"; // Reset.
if (!CoreGlobals::print_ready) {
__print_fallback(output, false, false);
return;
}
if (is_printing) {
__print_fallback(output, false);
__print_fallback(output, false, true);
return;
}
@ -299,8 +330,13 @@ void __print_line_rich(const String &p_string) {
}
void print_raw(const String &p_string) {
if (!CoreGlobals::print_ready) {
__print_fallback(p_string, false, false);
return;
}
if (is_printing) {
__print_fallback(p_string, true);
__print_fallback(p_string, true, true);
return;
}
@ -316,8 +352,13 @@ void print_error(const String &p_string) {
return;
}
if (!CoreGlobals::print_ready) {
__print_fallback(p_string, false, false);
return;
}
if (is_printing) {
__print_fallback(p_string, true);
__print_fallback(p_string, true, true);
return;
}

View file

@ -58,11 +58,11 @@ extern void print_error(const String &p_string);
extern bool is_print_verbose_enabled();
// This version avoids processing the text to be printed until it actually has to be printed, saving some CPU usage.
#define print_verbose(m_text) \
{ \
#define print_verbose(m_text) \
{ \
if (is_print_verbose_enabled()) { \
print_line(m_text); \
} \
print_line(m_text); \
} \
}
template <typename... Args>

View file

@ -30,6 +30,7 @@
#pragma once
#include "core/math/math_funcs_binary.h"
#include "core/string/print_string.h"
#include "core/string/ustring.h"
@ -126,7 +127,7 @@ StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::reserve(int p_
}
bool need_copy = string_length > 0 && buffer.is_empty();
buffer.resize_uninitialized(next_power_of_2((uint32_t)p_size));
buffer.resize_uninitialized(Math::next_power_of_2((uint32_t)p_size));
if (need_copy) {
memcpy(buffer.ptrw(), short_buffer, string_length * sizeof(char32_t));
}

View file

@ -33,6 +33,7 @@
#include "core/os/mutex.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
#include "core/templates/paged_allocator.h"
struct StringName::Table {
constexpr static uint32_t TABLE_BITS = 16;

View file

@ -125,12 +125,13 @@ public:
return (void *)_data;
}
_FORCE_INLINE_ operator String() const {
if (_data) {
return _data->name;
}
_FORCE_INLINE_ const String &string() const _LIFETIME_BOUND_ {
static const String EMPTY;
return _data ? _data->name : EMPTY;
}
return String();
_FORCE_INLINE_ operator const String &() const _LIFETIME_BOUND_ {
return string();
}
struct AlphCompare {

View file

@ -30,6 +30,7 @@
#include "translation.h"
#include "core/object/class_db.h"
#include "core/os/thread.h"
#include "core/string/plural_rules.h"
#include "core/string/translation_server.h"

View file

@ -31,7 +31,7 @@
#pragma once
#include "core/io/resource.h"
#include "core/object/gdvirtual.gen.inc"
#include "core/object/gdvirtual.gen.h"
class PluralRules;

View file

@ -30,6 +30,7 @@
#include "translation_domain.h"
#include "core/object/class_db.h"
#include "core/string/translation.h"
#include "core/string/translation_server.h"
#include "core/variant/typed_array.h"
@ -286,7 +287,7 @@ void TranslationDomain::clear() {
}
const HashSet<Ref<Translation>> TranslationDomain::get_translations() const {
return translations;
return HashSet<Ref<Translation>>(translations);
}
HashSet<Ref<Translation>> TranslationDomain::find_translations(const String &p_locale, bool p_exact) const {
@ -356,8 +357,9 @@ StringName TranslationDomain::translate(const StringName &p_message, const Strin
const String &locale = locale_override.is_empty() ? TranslationServer::get_singleton()->get_locale() : locale_override;
StringName res = get_message_from_translations(locale, p_message, p_context);
const bool &allow_fallback = TranslationServer::get_singleton()->is_fallback_allowed();
const String &fallback = TranslationServer::get_singleton()->get_fallback_locale();
if (!res && fallback.length() >= 2) {
if (!res && allow_fallback && fallback.length() >= 2) {
res = get_message_from_translations(fallback, p_message, p_context);
}
@ -375,8 +377,9 @@ StringName TranslationDomain::translate_plural(const StringName &p_message, cons
const String &locale = locale_override.is_empty() ? TranslationServer::get_singleton()->get_locale() : locale_override;
StringName res = get_message_from_translations(locale, p_message, p_message_plural, p_n, p_context);
const bool &allow_fallback = TranslationServer::get_singleton()->is_fallback_allowed();
const String &fallback = TranslationServer::get_singleton()->get_fallback_locale();
if (!res && fallback.length() >= 2) {
if (!res && allow_fallback && fallback.length() >= 2) {
res = get_message_from_translations(fallback, p_message, p_message_plural, p_n, p_context);
}

View file

@ -30,6 +30,10 @@
#ifndef DISABLE_DEPRECATED
#include "translation_server.h"
#include "core/object/class_db.h"
String TranslationServer::_standardize_locale_bind_compat_98972(const String &p_locale) const {
return standardize_locale(p_locale, false);
}

View file

@ -31,8 +31,10 @@
#include "translation_server.h"
#include "translation_server.compat.inc"
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/io/resource_loader.h"
#include "core/object/class_db.h"
#include "core/os/main_loop.h"
#include "core/os/os.h"
#include "core/string/locales.h"
@ -496,6 +498,14 @@ String TranslationServer::get_locale() const {
return locale;
}
void TranslationServer::set_fallback_allowed(const bool &p_allow) {
allow_fallback = p_allow;
}
bool TranslationServer::is_fallback_allowed() const {
return allow_fallback;
}
void TranslationServer::set_fallback_locale(const String &p_locale) {
fallback = p_locale;
}
@ -599,6 +609,7 @@ void TranslationServer::setup() {
}
fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
allow_fallback = GLOBAL_DEF("internationalization/locale/allow_fallback", true);
main_domain->set_pseudolocalization_enabled(GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false));
main_domain->set_pseudolocalization_accents_enabled(GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true));
main_domain->set_pseudolocalization_double_vowels_enabled(GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false));

View file

@ -37,6 +37,7 @@ class TranslationServer : public Object {
GDCLASS(TranslationServer, Object);
String locale = "en";
bool allow_fallback = true;
String fallback;
Ref<TranslationDomain> main_domain;
@ -109,6 +110,8 @@ public:
void set_locale(const String &p_locale);
String get_locale() const;
void set_fallback_allowed(const bool &p_allow);
bool is_fallback_allowed() const;
void set_fallback_locale(const String &p_locale);
String get_fallback_locale() const;

View file

@ -32,8 +32,8 @@
// This file was generated using the `misc/scripts/ucaps_fetch.py` script.
#define LTU_LEN 1477
#define UTL_LEN 1460
#define LTU_LEN 1505
#define UTL_LEN 1488
static const int caps_table[LTU_LEN][2] = {
{ 0x0061, 0x0041 },
@ -1119,7 +1119,10 @@ static const int caps_table[LTU_LEN][2] = {
{ 0xA7C8, 0xA7C7 },
{ 0xA7CA, 0xA7C9 },
{ 0xA7CD, 0xA7CC },
{ 0xA7CF, 0xA7CE },
{ 0xA7D1, 0xA7D0 },
{ 0xA7D3, 0xA7D2 },
{ 0xA7D5, 0xA7D4 },
{ 0xA7D7, 0xA7D6 },
{ 0xA7D9, 0xA7D8 },
{ 0xA7DB, 0xA7DA },
@ -1479,6 +1482,31 @@ static const int caps_table[LTU_LEN][2] = {
{ 0x16E7D, 0x16E5D },
{ 0x16E7E, 0x16E5E },
{ 0x16E7F, 0x16E5F },
{ 0x16EBB, 0x16EA0 },
{ 0x16EBC, 0x16EA1 },
{ 0x16EBD, 0x16EA2 },
{ 0x16EBE, 0x16EA3 },
{ 0x16EBF, 0x16EA4 },
{ 0x16EC0, 0x16EA5 },
{ 0x16EC1, 0x16EA6 },
{ 0x16EC2, 0x16EA7 },
{ 0x16EC3, 0x16EA8 },
{ 0x16EC4, 0x16EA9 },
{ 0x16EC5, 0x16EAA },
{ 0x16EC6, 0x16EAB },
{ 0x16EC7, 0x16EAC },
{ 0x16EC8, 0x16EAD },
{ 0x16EC9, 0x16EAE },
{ 0x16ECA, 0x16EAF },
{ 0x16ECB, 0x16EB0 },
{ 0x16ECC, 0x16EB1 },
{ 0x16ECD, 0x16EB2 },
{ 0x16ECE, 0x16EB3 },
{ 0x16ECF, 0x16EB4 },
{ 0x16ED0, 0x16EB5 },
{ 0x16ED1, 0x16EB6 },
{ 0x16ED2, 0x16EB7 },
{ 0x16ED3, 0x16EB8 },
{ 0x1E922, 0x1E900 },
{ 0x1E923, 0x1E901 },
{ 0x1E924, 0x1E902 },
@ -2662,7 +2690,10 @@ static const int reverse_caps_table[UTL_LEN][2] = {
{ 0xA7C9, 0xA7CA },
{ 0xA7CB, 0x0264 },
{ 0xA7CC, 0xA7CD },
{ 0xA7CE, 0xA7CF },
{ 0xA7D0, 0xA7D1 },
{ 0xA7D2, 0xA7D3 },
{ 0xA7D4, 0xA7D5 },
{ 0xA7D6, 0xA7D7 },
{ 0xA7D8, 0xA7D9 },
{ 0xA7DA, 0xA7DB },
@ -2942,6 +2973,31 @@ static const int reverse_caps_table[UTL_LEN][2] = {
{ 0x16E5D, 0x16E7D },
{ 0x16E5E, 0x16E7E },
{ 0x16E5F, 0x16E7F },
{ 0x16EA0, 0x16EBB },
{ 0x16EA1, 0x16EBC },
{ 0x16EA2, 0x16EBD },
{ 0x16EA3, 0x16EBE },
{ 0x16EA4, 0x16EBF },
{ 0x16EA5, 0x16EC0 },
{ 0x16EA6, 0x16EC1 },
{ 0x16EA7, 0x16EC2 },
{ 0x16EA8, 0x16EC3 },
{ 0x16EA9, 0x16EC4 },
{ 0x16EAA, 0x16EC5 },
{ 0x16EAB, 0x16EC6 },
{ 0x16EAC, 0x16EC7 },
{ 0x16EAD, 0x16EC8 },
{ 0x16EAE, 0x16EC9 },
{ 0x16EAF, 0x16ECA },
{ 0x16EB0, 0x16ECB },
{ 0x16EB1, 0x16ECC },
{ 0x16EB2, 0x16ECD },
{ 0x16EB3, 0x16ECE },
{ 0x16EB4, 0x16ECF },
{ 0x16EB5, 0x16ED0 },
{ 0x16EB6, 0x16ED1 },
{ 0x16EB7, 0x16ED2 },
{ 0x16EB8, 0x16ED3 },
{ 0x1E900, 0x1E922 },
{ 0x1E901, 0x1E923 },
{ 0x1E902, 0x1E924 },
@ -2978,7 +3034,7 @@ static const int reverse_caps_table[UTL_LEN][2] = {
{ 0x1E921, 0x1E943 },
};
static int _find_upper(int ch) {
inline int _find_upper(int ch) {
int low = 0;
int high = LTU_LEN - 1;
int middle;
@ -2998,7 +3054,7 @@ static int _find_upper(int ch) {
return ch;
}
static int _find_lower(int ch) {
inline int _find_lower(int ch) {
int low = 0;
int high = UTL_LEN - 1;
int middle;

View file

@ -39,7 +39,6 @@ STATIC_ASSERT_INCOMPLETE_TYPE(class, Object);
#include "core/math/color.h"
#include "core/math/math_funcs.h"
#include "core/object/object.h"
#include "core/os/memory.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
#include "core/string/string_name.h"
@ -48,7 +47,9 @@ STATIC_ASSERT_INCOMPLETE_TYPE(class, Object);
#include "core/variant/variant.h"
#include "core/version_generated.gen.h"
#include "thirdparty/grisu2/grisu2.h"
#include <thirdparty/grisu2/grisu2.h>
#include <cstdio>
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS // to disable build-time warning which suggested to use strcpy_s instead strcpy
@ -351,7 +352,7 @@ bool operator==(const wchar_t *p_chr, const String &p_str) {
// wchar_t is 16-bit
return p_str == String::utf16((const char16_t *)p_chr);
#else
// wchar_t is 32-bi
// wchar_t is 32-bit
return p_str == (const char32_t *)p_chr;
#endif
}
@ -365,7 +366,7 @@ bool operator!=(const wchar_t *p_chr, const String &p_str) {
// wchar_t is 16-bit
return !(p_str == String::utf16((const char16_t *)p_chr));
#else
// wchar_t is 32-bi
// wchar_t is 32-bit
return !(p_str == String((const char32_t *)p_chr));
#endif
}
@ -1657,16 +1658,16 @@ String String::hex_encode_buffer(const uint8_t *p_buffer, int p_len) {
Vector<uint8_t> String::hex_decode() const {
ERR_FAIL_COND_V_MSG(length() % 2 != 0, Vector<uint8_t>(), "Hexadecimal string of uneven length.");
#define HEX_TO_BYTE(m_output, m_index) \
uint8_t m_output; \
c = operator[](m_index); \
if (is_digit(c)) { \
m_output = c - '0'; \
} else if (c >= 'a' && c <= 'f') { \
m_output = c - 'a' + 10; \
} else if (c >= 'A' && c <= 'F') { \
m_output = c - 'A' + 10; \
} else { \
#define HEX_TO_BYTE(m_output, m_index) \
uint8_t m_output; \
c = operator[](m_index); \
if (is_digit(c)) { \
m_output = c - '0'; \
} else if (c >= 'a' && c <= 'f') { \
m_output = c - 'a' + 10; \
} else if (c >= 'A' && c <= 'F') { \
m_output = c - 'A' + 10; \
} else { \
ERR_FAIL_V_MSG(Vector<uint8_t>(), "Invalid hexadecimal character \"" + chr(c) + "\" at index " + m_index + "."); \
}
@ -2021,11 +2022,7 @@ Error String::append_utf16(const char16_t *p_utf16, int p_len, bool p_default_li
int cstr_size = 0;
int str_size = 0;
#ifdef BIG_ENDIAN_ENABLED
bool byteswap = p_default_little_endian;
#else
bool byteswap = !p_default_little_endian;
#endif
/* HANDLE BOM (Byte Order Mark) */
if (p_len < 0 || p_len >= 1) {
bool has_bom = false;
@ -5188,10 +5185,13 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
static const String MINUS("-");
static const String PLUS("+");
LocalVector<bool> used_args;
used_args.resize_initialized(values.size());
String formatted;
char32_t *self = (char32_t *)get_data();
bool in_format = false;
uint64_t value_index = 0;
int selected_index = -1;
int min_chars = 0;
int min_decimals = 0;
bool in_decimals = false;
@ -5218,15 +5218,16 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
case 'o': // Octal
case 'x': // Hexadecimal (lowercase)
case 'X': { // Hexadecimal (uppercase)
if (value_index >= values.size()) {
uint64_t index = (selected_index >= 0 ? selected_index : value_index);
if (index >= values.size()) {
return "not enough arguments for format string";
}
if (!values[value_index].is_num()) {
if (!values[index].is_num()) {
return "a number is required";
}
int64_t value = values[value_index];
int64_t value = values[index];
int base = 16;
bool capitalize = false;
switch (c) {
@ -5282,21 +5283,25 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
formatted += str;
++value_index;
if (selected_index == -1) {
++value_index;
}
used_args[index] = true;
in_format = false;
break;
}
case 'f': { // Float
if (value_index >= values.size()) {
uint64_t index = (selected_index >= 0 ? selected_index : value_index);
if (index >= values.size()) {
return "not enough arguments for format string";
}
if (!values[value_index].is_num()) {
if (!values[index].is_num()) {
return "a number is required";
}
double value = values[value_index];
double value = values[index];
bool is_negative = std::signbit(value);
String str = String::num(Math::abs(value), min_decimals);
const bool is_finite = Math::is_finite(value);
@ -5328,17 +5333,21 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
formatted += str;
++value_index;
if (selected_index == -1) {
++value_index;
}
used_args[index] = true;
in_format = false;
break;
}
case 'v': { // Vector2/3/4/2i/3i/4i
if (value_index >= values.size()) {
uint64_t index = (selected_index >= 0 ? selected_index : value_index);
if (index >= values.size()) {
return "not enough arguments for format string";
}
int count;
switch (values[value_index].get_type()) {
switch (values[index].get_type()) {
case Variant::VECTOR2:
case Variant::VECTOR2I: {
count = 2;
@ -5356,7 +5365,7 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
}
Vector4 vec = values[value_index];
Vector4 vec = values[index];
String str = "(";
for (int i = 0; i < count; i++) {
double val = vec[i];
@ -5398,16 +5407,20 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
str += ")";
formatted += str;
++value_index;
if (selected_index == -1) {
++value_index;
}
used_args[index] = true;
in_format = false;
break;
}
case 's': { // String
if (value_index >= values.size()) {
uint64_t index = (selected_index >= 0 ? selected_index : value_index);
if (index >= values.size()) {
return "not enough arguments for format string";
}
String str = values[value_index];
String str = values[index];
// Padding.
if (left_justified) {
str = str.rpad(min_chars);
@ -5416,19 +5429,23 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
formatted += str;
++value_index;
if (selected_index == -1) {
++value_index;
}
used_args[index] = true;
in_format = false;
break;
}
case 'c': {
if (value_index >= values.size()) {
uint64_t index = (selected_index >= 0 ? selected_index : value_index);
if (index >= values.size()) {
return "not enough arguments for format string";
}
// Convert to character.
String str;
if (values[value_index].is_num()) {
int value = values[value_index];
if (values[index].is_num()) {
int value = values[index];
if (value < 0) {
return "unsigned integer is lower than minimum";
} else if (value >= 0xd800 && value <= 0xdfff) {
@ -5436,9 +5453,9 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
} else if (value > 0x10ffff) {
return "unsigned integer is greater than maximum";
}
str = chr(values[value_index]);
} else if (values[value_index].get_type() == Variant::STRING) {
str = values[value_index];
str = chr(values[index]);
} else if (values[index].get_type() == Variant::STRING) {
str = values[index];
if (str.length() != 1) {
return "%c requires number or single-character string";
}
@ -5454,7 +5471,10 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
formatted += str;
++value_index;
if (selected_index == -1) {
++value_index;
}
used_args[index] = true;
in_format = false;
break;
}
@ -5498,6 +5518,14 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
break;
}
case '$': {
if (min_chars > 0) {
selected_index = min_chars - 1;
}
min_chars = 0;
pad_with_zeros = false;
break;
}
case '.': { // Float/Vector separator.
if (in_decimals) {
return "too many decimal points in format";
@ -5508,27 +5536,30 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
}
case '*': { // Dynamic width, based on value.
if (value_index >= values.size()) {
uint64_t index = (selected_index >= 0 ? selected_index : value_index);
if (index >= values.size()) {
return "not enough arguments for format string";
}
Variant::Type value_type = values[value_index].get_type();
if (!values[value_index].is_num() &&
Variant::Type value_type = values[index].get_type();
if (!values[index].is_num() &&
value_type != Variant::VECTOR2 && value_type != Variant::VECTOR2I &&
value_type != Variant::VECTOR3 && value_type != Variant::VECTOR3I &&
value_type != Variant::VECTOR4 && value_type != Variant::VECTOR4I) {
return "* wants number or vector";
}
int size = values[value_index];
int size = values[index];
if (in_decimals) {
min_decimals = size;
} else {
min_chars = size;
}
++value_index;
if (selected_index == -1) {
++value_index;
}
used_args[index] = true;
break;
}
@ -5547,6 +5578,7 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
left_justified = false;
show_sign = false;
in_decimals = false;
selected_index = -1;
break;
default:
formatted += c;
@ -5558,8 +5590,10 @@ String String::sprintf(const Span<Variant> &values, bool *error) const {
return "incomplete format";
}
if (value_index != values.size()) {
return "not all arguments converted during string formatting";
for (const bool &b : used_args) {
if (!b) {
return "not all arguments converted during string formatting";
}
}
if (error) {

View file

@ -30,8 +30,6 @@
#pragma once
// Note: _GODOT suffix added to header guard to avoid conflict with ICU header.
#include "core/string/char_utils.h" // IWYU pragma: export
#include "core/templates/cowdata.h"
#include "core/templates/hashfuncs.h"
@ -175,9 +173,9 @@ class [[nodiscard]] CharStringT {
static constexpr T _null = 0;
public:
_FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); }
_FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); }
_FORCE_INLINE_ const T *get_data() const { return size() ? ptr() : &_null; }
_FORCE_INLINE_ T *ptrw() _LIFETIME_BOUND_ { return _cowdata.ptrw(); }
_FORCE_INLINE_ const T *ptr() const _LIFETIME_BOUND_ { return _cowdata.ptr(); }
_FORCE_INLINE_ const T *get_data() const _LIFETIME_BOUND_ { return size() ? ptr() : &_null; }
// Returns the number of characters in the buffer, including the terminating NUL character.
// In most cases, length() should be used instead.
@ -186,8 +184,8 @@ public:
_FORCE_INLINE_ int length() const { return size() ? size() - 1 : 0; }
_FORCE_INLINE_ bool is_empty() const { return length() == 0; }
_FORCE_INLINE_ operator Span<T>() const { return Span(ptr(), length()); }
_FORCE_INLINE_ Span<T> span() const { return Span(ptr(), length()); }
_FORCE_INLINE_ operator Span<T>() const _LIFETIME_BOUND_ { return Span(ptr(), length()); }
_FORCE_INLINE_ Span<T> span() const _LIFETIME_BOUND_ { return Span(ptr(), length()); }
/// Resizes the string. The given size must include the null terminator.
/// New characters are not initialized, and should be set by the caller.
@ -295,9 +293,9 @@ public:
npos = -1 ///<for "some" compatibility with std::string (npos is a huge value in std::string)
};
_FORCE_INLINE_ char32_t *ptrw() { return _cowdata.ptrw(); }
_FORCE_INLINE_ const char32_t *ptr() const { return _cowdata.ptr(); }
_FORCE_INLINE_ const char32_t *get_data() const { return size() ? ptr() : &_null; }
_FORCE_INLINE_ char32_t *ptrw() _LIFETIME_BOUND_ { return _cowdata.ptrw(); }
_FORCE_INLINE_ const char32_t *ptr() const _LIFETIME_BOUND_ { return _cowdata.ptr(); }
_FORCE_INLINE_ const char32_t *get_data() const _LIFETIME_BOUND_ { return size() ? ptr() : &_null; }
// Returns the number of characters in the buffer, including the terminating NUL character.
// In most cases, length() should be used instead.
@ -306,8 +304,8 @@ public:
_FORCE_INLINE_ int length() const { return size() ? size() - 1 : 0; }
_FORCE_INLINE_ bool is_empty() const { return length() == 0; }
_FORCE_INLINE_ operator Span<char32_t>() const { return Span(ptr(), length()); }
_FORCE_INLINE_ Span<char32_t> span() const { return Span(ptr(), length()); }
_FORCE_INLINE_ operator Span<char32_t>() const _LIFETIME_BOUND_ { return Span(ptr(), length()); }
_FORCE_INLINE_ Span<char32_t> span() const _LIFETIME_BOUND_ { return Span(ptr(), length()); }
void remove_at(int p_index) { _cowdata.remove_at(p_index); }