diff options
Diffstat (limited to 'modules')
81 files changed, 6479 insertions, 2879 deletions
diff --git a/modules/arkit/register_types.h b/modules/arkit/register_types.h index 5c697baf68..f8939a1e3f 100644 --- a/modules/arkit/register_types.h +++ b/modules/arkit/register_types.h @@ -28,5 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#ifndef ARKIT_REGISTER_TYPES_H +#define ARKIT_REGISTER_TYPES_H + void register_arkit_types(); void unregister_arkit_types(); + +#endif // ARKIT_REGISTER_TYPES_H diff --git a/modules/basis_universal/texture_basisu.h b/modules/basis_universal/texture_basisu.h index 8de151ede0..20ecf15a59 100644 --- a/modules/basis_universal/texture_basisu.h +++ b/modules/basis_universal/texture_basisu.h @@ -28,6 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#ifndef BASIS_UNIVERSAL_TEXTURE_BASISU_H +#define BASIS_UNIVERSAL_TEXTURE_BASISU_H + #include "scene/resources/texture.h" #ifdef TOOLS_ENABLED @@ -75,3 +78,5 @@ public: }; #endif + +#endif // BASIS_UNIVERSAL_TEXTURE_BASISU_H diff --git a/modules/bullet/bullet_types_converter.cpp b/modules/bullet/bullet_types_converter.cpp index c9493d8892..7ecad9b78a 100644 --- a/modules/bullet/bullet_types_converter.cpp +++ b/modules/bullet/bullet_types_converter.cpp @@ -95,12 +95,61 @@ void G_TO_B(Transform const &inVal, btTransform &outVal) { } void UNSCALE_BT_BASIS(btTransform &scaledBasis) { - btMatrix3x3 &m(scaledBasis.getBasis()); - btVector3 column0(m[0][0], m[1][0], m[2][0]); - btVector3 column1(m[0][1], m[1][1], m[2][1]); - btVector3 column2(m[0][2], m[1][2], m[2][2]); + btMatrix3x3 &basis(scaledBasis.getBasis()); + btVector3 column0 = basis.getColumn(0); + btVector3 column1 = basis.getColumn(1); + btVector3 column2 = basis.getColumn(2); + + // Check for zero scaling. + if (btFuzzyZero(column0[0])) { + if (btFuzzyZero(column1[1])) { + if (btFuzzyZero(column2[2])) { + // All dimensions are fuzzy zero. Create a default basis. + column0 = btVector3(1, 0, 0); + column1 = btVector3(0, 1, 0); + column2 = btVector3(0, 0, 1); + } else { // Column 2 scale not fuzzy zero. + // Create two vectors orthogonal to row 2. + // Ensure that a default basis is created if row 2 = <0, 0, 1> + column1 = btVector3(0, column2[2], -column2[1]); + column0 = column1.cross(column2); + } + } else { // Column 1 scale not fuzzy zero. + if (btFuzzyZero(column2[2])) { + // Create two vectors othogonal to column 1. + // Ensure that a default basis is created if column 1 = <0, 1, 0> + column0 = btVector3(column1[1], -column1[0], 0); + column2 = column0.cross(column1); + } else { // Column 1 and column 2 scales not fuzzy zero. + // Create column 0 orthogonal to column 1 and column 2. + column0 = column1.cross(column2); + } + } + } else { // Column 0 scale not fuzzy zero. + if (btFuzzyZero(column1[1])) { + if (btFuzzyZero(column2[2])) { + // Create two vectors orthogonal to column 0. + // Ensure that a default basis is created if column 0 = <1, 0, 0> + column2 = btVector3(-column0[2], 0, column0[0]); + column1 = column2.cross(column0); + } else { // Column 0 and column 2 scales not fuzzy zero. + // Create column 1 orthogonal to column 0 and column 2. + column1 = column2.cross(column0); + } + } else { // Column 0 and column 1 scales not fuzzy zero. + if (btFuzzyZero(column2[2])) { + // Create column 2 orthogonal to column 0 and column 1. + column2 = column0.cross(column1); + } + } + } + + // Normalize column0.normalize(); column1.normalize(); column2.normalize(); - m.setValue(column0[0], column1[0], column2[0], column0[1], column1[1], column2[1], column0[2], column1[2], column2[2]); + + basis.setValue(column0[0], column1[0], column2[0], + column0[1], column1[1], column2[1], + column0[2], column1[2], column2[2]); } diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp index 32c3240a35..f517eecf64 100644 --- a/modules/bullet/rigid_body_bullet.cpp +++ b/modules/bullet/rigid_body_bullet.cpp @@ -849,8 +849,8 @@ void RigidBodyBullet::on_enter_area(AreaBullet *p_area) { } else { if (areasWhereIam[i]->get_spOv_priority() > p_area->get_spOv_priority()) { // The position was found, just shift all elements - for (int j = i; j < areaWhereIamCount; ++j) { - areasWhereIam[j + 1] = areasWhereIam[j]; + for (int j = areaWhereIamCount; j > i; j--) { + areasWhereIam[j] = areasWhereIam[j - 1]; } areasWhereIam[i] = p_area; break; diff --git a/modules/bullet/shape_bullet.cpp b/modules/bullet/shape_bullet.cpp index 274493ed17..74d6e073b3 100644 --- a/modules/bullet/shape_bullet.cpp +++ b/modules/bullet/shape_bullet.cpp @@ -308,7 +308,7 @@ void CapsuleShapeBullet::setup(real_t p_height, real_t p_radius) { } btCollisionShape *CapsuleShapeBullet::internal_create_bt_shape(const btVector3 &p_implicit_scale, real_t p_extra_edge) { - return prepare(ShapeBullet::create_shape_capsule(radius * p_implicit_scale[0] + p_extra_edge, height * p_implicit_scale[1] + p_extra_edge)); + return prepare(ShapeBullet::create_shape_capsule(radius * p_implicit_scale[0] + p_extra_edge, height * p_implicit_scale[1])); } /* Cylinder */ diff --git a/modules/camera/register_types.h b/modules/camera/register_types.h index f2753cb6d7..e34f84bf2c 100644 --- a/modules/camera/register_types.h +++ b/modules/camera/register_types.h @@ -28,5 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#ifndef CAMERA_REGISTER_TYPES_H +#define CAMERA_REGISTER_TYPES_H + void register_camera_types(); void unregister_camera_types(); + +#endif // CAMERA_REGISTER_TYPES_H diff --git a/modules/cvtt/register_types.h b/modules/cvtt/register_types.h index 8472980c6a..36b5e332d6 100644 --- a/modules/cvtt/register_types.h +++ b/modules/cvtt/register_types.h @@ -28,14 +28,14 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifdef TOOLS_ENABLED - #ifndef CVTT_REGISTER_TYPES_H #define CVTT_REGISTER_TYPES_H +#ifdef TOOLS_ENABLED + void register_cvtt_types(); void unregister_cvtt_types(); -#endif // CVTT_REGISTER_TYPES_H - #endif // TOOLS_ENABLED + +#endif // CVTT_REGISTER_TYPES_H diff --git a/modules/denoise/register_types.h b/modules/denoise/register_types.h index 2ffc36ee2c..f0f1f44bfe 100644 --- a/modules/denoise/register_types.h +++ b/modules/denoise/register_types.h @@ -28,5 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#ifndef DENOISE_REGISTER_TYPES_H +#define DENOISE_REGISTER_TYPES_H + void register_denoise_types(); void unregister_denoise_types(); + +#endif // DENOISE_REGISTER_TYPES_H diff --git a/modules/denoise/resource_to_cpp.py b/modules/denoise/resource_to_cpp.py index 4c0b67f701..6c83277355 100644 --- a/modules/denoise/resource_to_cpp.py +++ b/modules/denoise/resource_to_cpp.py @@ -17,8 +17,6 @@ ## ======================================================================== ## import os -import sys -import argparse from array import array # Generates a C++ file from the specified binary resource file diff --git a/modules/gdnative/gdnative/string.cpp b/modules/gdnative/gdnative/string.cpp index 26c40b625c..1fa19f4ff5 100644 --- a/modules/gdnative/gdnative/string.cpp +++ b/modules/gdnative/gdnative/string.cpp @@ -40,9 +40,10 @@ extern "C" { #endif +static_assert(sizeof(godot_char16_string) == sizeof(Char16String), "Char16String size mismatch"); static_assert(sizeof(godot_char_string) == sizeof(CharString), "CharString size mismatch"); static_assert(sizeof(godot_string) == sizeof(String), "String size mismatch"); -static_assert(sizeof(godot_char_type) == sizeof(CharType), "CharType size mismatch"); +static_assert(sizeof(godot_char_type) == sizeof(char32_t), "char32_t size mismatch"); godot_int GDAPI godot_char_string_length(const godot_char_string *p_cs) { const CharString *cs = (const CharString *)p_cs; @@ -62,6 +63,24 @@ void GDAPI godot_char_string_destroy(godot_char_string *p_cs) { cs->~CharString(); } +godot_int GDAPI godot_char16_string_length(const godot_char16_string *p_cs) { + const Char16String *cs = (const Char16String *)p_cs; + + return cs->length(); +} + +const char16_t GDAPI *godot_char16_string_get_data(const godot_char16_string *p_cs) { + const Char16String *cs = (const Char16String *)p_cs; + + return cs->get_data(); +} + +void GDAPI godot_char16_string_destroy(godot_char16_string *p_cs) { + Char16String *cs = (Char16String *)p_cs; + + cs->~Char16String(); +} + void GDAPI godot_string_new(godot_string *r_dest) { String *dest = (String *)r_dest; memnew_placement(dest, String); @@ -70,27 +89,97 @@ void GDAPI godot_string_new(godot_string *r_dest) { void GDAPI godot_string_new_copy(godot_string *r_dest, const godot_string *p_src) { String *dest = (String *)r_dest; const String *src = (const String *)p_src; - memnew_placement(dest, String(*src)); + memnew_placement(dest, String); + *dest = String(*src); +} + +void GDAPI godot_string_new_with_latin1_chars(godot_string *r_dest, const char *p_contents) { + String *dest = (String *)r_dest; + memnew_placement(dest, String); + *dest = String(p_contents); +} + +void GDAPI godot_string_new_with_utf8_chars(godot_string *r_dest, const char *p_contents) { + String *dest = (String *)r_dest; + memnew_placement(dest, String); + dest->parse_utf8(p_contents); +} + +void GDAPI godot_string_new_with_utf16_chars(godot_string *r_dest, const char16_t *p_contents) { + String *dest = (String *)r_dest; + memnew_placement(dest, String); + dest->parse_utf16(p_contents); +} + +void GDAPI godot_string_new_with_utf32_chars(godot_string *r_dest, const char32_t *p_contents) { + String *dest = (String *)r_dest; + memnew_placement(dest, String); + *dest = String((const char32_t *)p_contents); +} + +void GDAPI godot_string_new_with_wide_chars(godot_string *r_dest, const wchar_t *p_contents) { + String *dest = (String *)r_dest; + if (sizeof(wchar_t) == 2) { + // wchar_t is 16 bit, parse. + memnew_placement(dest, String); + dest->parse_utf16((const char16_t *)p_contents); + } else { + // wchar_t is 32 bit, copy. + memnew_placement(dest, String); + *dest = String((const char32_t *)p_contents); + } +} + +void GDAPI godot_string_new_with_latin1_chars_and_len(godot_string *r_dest, const char *p_contents, const int p_size) { + String *dest = (String *)r_dest; + memnew_placement(dest, String); + *dest = String(p_contents, p_size); +} + +void GDAPI godot_string_new_with_utf8_chars_and_len(godot_string *r_dest, const char *p_contents, const int p_size) { + String *dest = (String *)r_dest; + memnew_placement(dest, String); + dest->parse_utf8(p_contents, p_size); +} + +void GDAPI godot_string_new_with_utf16_chars_and_len(godot_string *r_dest, const char16_t *p_contents, const int p_size) { + String *dest = (String *)r_dest; + memnew_placement(dest, String); + dest->parse_utf16(p_contents, p_size); +} + +void GDAPI godot_string_new_with_utf32_chars_and_len(godot_string *r_dest, const char32_t *p_contents, const int p_size) { + String *dest = (String *)r_dest; + memnew_placement(dest, String); + *dest = String((const char32_t *)p_contents, p_size); } -void GDAPI godot_string_new_with_wide_string(godot_string *r_dest, const wchar_t *p_contents, const int p_size) { +void GDAPI godot_string_new_with_wide_chars_and_len(godot_string *r_dest, const wchar_t *p_contents, const int p_size) { String *dest = (String *)r_dest; - memnew_placement(dest, String(p_contents, p_size)); + if (sizeof(wchar_t) == 2) { + // wchar_t is 16 bit, parse. + memnew_placement(dest, String); + dest->parse_utf16((const char16_t *)p_contents, p_size); + } else { + // wchar_t is 32 bit, copy. + memnew_placement(dest, String); + *dest = String((const char32_t *)p_contents, p_size); + } } -const wchar_t GDAPI *godot_string_operator_index(godot_string *p_self, const godot_int p_idx) { +const godot_char_type GDAPI *godot_string_operator_index(godot_string *p_self, const godot_int p_idx) { String *self = (String *)p_self; return &(self->operator[](p_idx)); } -wchar_t GDAPI godot_string_operator_index_const(const godot_string *p_self, const godot_int p_idx) { +godot_char_type GDAPI godot_string_operator_index_const(const godot_string *p_self, const godot_int p_idx) { const String *self = (const String *)p_self; return self->operator[](p_idx); } -const wchar_t GDAPI *godot_string_wide_str(const godot_string *p_self) { +const godot_char_type GDAPI *godot_string_get_data(const godot_string *p_self) { const String *self = (const String *)p_self; - return self->c_str(); + return self->get_data(); } godot_bool GDAPI godot_string_operator_equal(const godot_string *p_self, const godot_string *p_b) { @@ -162,22 +251,14 @@ godot_bool GDAPI godot_string_begins_with_char_array(const godot_string *p_self, return self->begins_with(p_char_array); } -godot_array GDAPI godot_string_bigrams(const godot_string *p_self) { +godot_packed_string_array GDAPI godot_string_bigrams(const godot_string *p_self) { const String *self = (const String *)p_self; - Vector<String> return_value = self->bigrams(); - - godot_array result; - memnew_placement(&result, Array); - Array *proxy = (Array *)&result; - proxy->resize(return_value.size()); - for (int i = 0; i < return_value.size(); i++) { - (*proxy)[i] = return_value[i]; - } - - return result; + godot_packed_string_array ret; + memnew_placement(&ret, Vector<String>(self->bigrams())); + return ret; }; -godot_string GDAPI godot_string_chr(wchar_t p_character) { +godot_string GDAPI godot_string_chr(godot_char_type p_character) { godot_string result; memnew_placement(&result, String(String::chr(p_character))); @@ -191,88 +272,73 @@ godot_bool GDAPI godot_string_ends_with(const godot_string *p_self, const godot_ return self->ends_with(*string); } -godot_int GDAPI godot_string_count(const godot_string *p_self, godot_string p_what, godot_int p_from, godot_int p_to) { +godot_bool GDAPI godot_string_ends_with_char_array(const godot_string *p_self, const char *p_char_array) { const String *self = (const String *)p_self; - String *what = (String *)&p_what; + + return self->ends_with(p_char_array); +} + +godot_int GDAPI godot_string_count(const godot_string *p_self, const godot_string *p_what, godot_int p_from, godot_int p_to) { + const String *self = (const String *)p_self; + const String *what = (const String *)p_what; return self->count(*what, p_from, p_to); } -godot_int GDAPI godot_string_countn(const godot_string *p_self, godot_string p_what, godot_int p_from, godot_int p_to) { +godot_int GDAPI godot_string_countn(const godot_string *p_self, const godot_string *p_what, godot_int p_from, godot_int p_to) { const String *self = (const String *)p_self; - String *what = (String *)&p_what; + const String *what = (const String *)p_what; return self->countn(*what, p_from, p_to); } -godot_int GDAPI godot_string_find(const godot_string *p_self, godot_string p_what) { +godot_int GDAPI godot_string_find(const godot_string *p_self, const godot_string *p_what) { const String *self = (const String *)p_self; - String *what = (String *)&p_what; + const String *what = (const String *)p_what; return self->find(*what); } -godot_int GDAPI godot_string_find_from(const godot_string *p_self, godot_string p_what, godot_int p_from) { +godot_int GDAPI godot_string_find_from(const godot_string *p_self, const godot_string *p_what, godot_int p_from) { const String *self = (const String *)p_self; - String *what = (String *)&p_what; + const String *what = (const String *)p_what; return self->find(*what, p_from); } -godot_int GDAPI godot_string_findmk(const godot_string *p_self, const godot_array *p_keys) { +godot_int GDAPI godot_string_findmk(const godot_string *p_self, const godot_packed_string_array *p_keys) { const String *self = (const String *)p_self; - - Vector<String> keys; - Array *keys_proxy = (Array *)p_keys; - keys.resize(keys_proxy->size()); - for (int i = 0; i < keys_proxy->size(); i++) { - keys.write[i] = (*keys_proxy)[i]; - } - - return self->findmk(keys); + const Vector<String> *keys = (const Vector<String> *)p_keys; + return self->findmk(*keys); } -godot_int GDAPI godot_string_findmk_from(const godot_string *p_self, const godot_array *p_keys, godot_int p_from) { +godot_int GDAPI godot_string_findmk_from(const godot_string *p_self, const godot_packed_string_array *p_keys, godot_int p_from) { const String *self = (const String *)p_self; - - Vector<String> keys; - Array *keys_proxy = (Array *)p_keys; - keys.resize(keys_proxy->size()); - for (int i = 0; i < keys_proxy->size(); i++) { - keys.write[i] = (*keys_proxy)[i]; - } - - return self->findmk(keys, p_from); + const Vector<String> *keys = (const Vector<String> *)p_keys; + return self->findmk(*keys, p_from); } -godot_int GDAPI godot_string_findmk_from_in_place(const godot_string *p_self, const godot_array *p_keys, godot_int p_from, godot_int *r_key) { +godot_int GDAPI godot_string_findmk_from_in_place(const godot_string *p_self, const godot_packed_string_array *p_keys, godot_int p_from, godot_int *r_key) { const String *self = (const String *)p_self; - - Vector<String> keys; - Array *keys_proxy = (Array *)p_keys; - keys.resize(keys_proxy->size()); - for (int i = 0; i < keys_proxy->size(); i++) { - keys.write[i] = (*keys_proxy)[i]; - } - + const Vector<String> *keys = (const Vector<String> *)p_keys; int key; - int ret = self->findmk(keys, p_from, &key); + int ret = self->findmk(*keys, p_from, &key); if (r_key) { *r_key = key; } return ret; } -godot_int GDAPI godot_string_findn(const godot_string *p_self, godot_string p_what) { +godot_int GDAPI godot_string_findn(const godot_string *p_self, const godot_string *p_what) { const String *self = (const String *)p_self; - String *what = (String *)&p_what; + const String *what = (const String *)p_what; return self->findn(*what); } -godot_int GDAPI godot_string_findn_from(const godot_string *p_self, godot_string p_what, godot_int p_from) { +godot_int GDAPI godot_string_findn_from(const godot_string *p_self, const godot_string *p_what, godot_int p_from) { const String *self = (const String *)p_self; - String *what = (String *)&p_what; + const String *what = (const String *)p_what; return self->findn(*what, p_from); } @@ -303,21 +369,9 @@ godot_string GDAPI godot_string_hex_encode_buffer(const uint8_t *p_buffer, godot return result; } -godot_int GDAPI godot_string_hex_to_int(const godot_string *p_self) { - const String *self = (const String *)p_self; - - return self->hex_to_int(); -} - -godot_int GDAPI godot_string_hex_to_int_without_prefix(const godot_string *p_self) { - const String *self = (const String *)p_self; - - return self->hex_to_int(true); -} - -godot_string GDAPI godot_string_insert(const godot_string *p_self, godot_int p_at_pos, godot_string p_string) { +godot_string GDAPI godot_string_insert(const godot_string *p_self, godot_int p_at_pos, const godot_string *p_string) { const String *self = (const String *)p_self; - String *content = (String *)&p_string; + const String *content = (const String *)p_string; godot_string result; memnew_placement(&result, String(self->insert(p_at_pos, *content))); @@ -440,58 +494,58 @@ godot_string GDAPI godot_string_pad_zeros(const godot_string *p_self, godot_int return result; } -godot_string GDAPI godot_string_replace(const godot_string *p_self, godot_string p_key, godot_string p_with) { +godot_string GDAPI godot_string_replace(const godot_string *p_self, const godot_string *p_key, const godot_string *p_with) { const String *self = (const String *)p_self; - String *key = (String *)&p_key; - String *with = (String *)&p_with; + const String *key = (const String *)p_key; + const String *with = (const String *)p_with; godot_string result; memnew_placement(&result, String(self->replace(*key, *with))); return result; } -godot_string GDAPI godot_string_replacen(const godot_string *p_self, godot_string p_key, godot_string p_with) { +godot_string GDAPI godot_string_replacen(const godot_string *p_self, const godot_string *p_key, const godot_string *p_with) { const String *self = (const String *)p_self; - String *key = (String *)&p_key; - String *with = (String *)&p_with; + const String *key = (const String *)p_key; + const String *with = (const String *)p_with; godot_string result; memnew_placement(&result, String(self->replacen(*key, *with))); return result; } -godot_int GDAPI godot_string_rfind(const godot_string *p_self, godot_string p_what) { +godot_int GDAPI godot_string_rfind(const godot_string *p_self, const godot_string *p_what) { const String *self = (const String *)p_self; - String *what = (String *)&p_what; + const String *what = (const String *)p_what; return self->rfind(*what); } -godot_int GDAPI godot_string_rfindn(const godot_string *p_self, godot_string p_what) { +godot_int GDAPI godot_string_rfindn(const godot_string *p_self, const godot_string *p_what) { const String *self = (const String *)p_self; - String *what = (String *)&p_what; + const String *what = (const String *)p_what; return self->rfindn(*what); } -godot_int GDAPI godot_string_rfind_from(const godot_string *p_self, godot_string p_what, godot_int p_from) { +godot_int GDAPI godot_string_rfind_from(const godot_string *p_self, const godot_string *p_what, godot_int p_from) { const String *self = (const String *)p_self; - String *what = (String *)&p_what; + const String *what = (const String *)p_what; return self->rfind(*what, p_from); } -godot_int GDAPI godot_string_rfindn_from(const godot_string *p_self, godot_string p_what, godot_int p_from) { +godot_int GDAPI godot_string_rfindn_from(const godot_string *p_self, const godot_string *p_what, godot_int p_from) { const String *self = (const String *)p_self; - String *what = (String *)&p_what; + const String *what = (const String *)p_what; return self->rfindn(*what, p_from); } -godot_string GDAPI godot_string_replace_first(const godot_string *p_self, godot_string p_key, godot_string p_with) { +godot_string GDAPI godot_string_replace_first(const godot_string *p_self, const godot_string *p_key, const godot_string *p_with) { const String *self = (const String *)p_self; - String *key = (String *)&p_key; - String *with = (String *)&p_with; + const String *key = (const String *)p_key; + const String *with = (const String *)p_with; godot_string result; memnew_placement(&result, String(self->replace_first(*key, *with))); @@ -541,16 +595,16 @@ godot_string GDAPI godot_string_substr(const godot_string *p_self, godot_int p_f return result; } -double GDAPI godot_string_to_float(const godot_string *p_self) { +godot_int GDAPI godot_string_to_int(const godot_string *p_self) { const String *self = (const String *)p_self; - return self->to_float(); + return self->to_int(); } -godot_int GDAPI godot_string_to_int(const godot_string *p_self) { +double GDAPI godot_string_to_float(const godot_string *p_self) { const String *self = (const String *)p_self; - return self->to_int(); + return self->to_float(); } godot_string GDAPI godot_string_capitalize(const godot_string *p_self) { @@ -581,11 +635,15 @@ double GDAPI godot_string_char_to_float(const char *p_what) { return String::to_float(p_what); } +double GDAPI godot_string_wchar_to_float(const wchar_t *p_str, const wchar_t **r_end) { + return String::to_float(p_str, r_end); +} + godot_int GDAPI godot_string_char_to_int(const char *p_what) { return String::to_int(p_what); } -int64_t GDAPI godot_string_wchar_to_int(const wchar_t *p_str) { +godot_int GDAPI godot_string_wchar_to_int(const wchar_t *p_str) { return String::to_int(p_str); } @@ -593,42 +651,32 @@ godot_int GDAPI godot_string_char_to_int_with_len(const char *p_what, godot_int return String::to_int(p_what, p_len); } -int64_t GDAPI godot_string_char_to_int64_with_len(const wchar_t *p_str, int p_len) { +godot_int GDAPI godot_string_wchar_to_int_with_len(const wchar_t *p_str, int p_len) { return String::to_int(p_str, p_len); } -int64_t GDAPI godot_string_hex_to_int64(const godot_string *p_self) { +godot_int GDAPI godot_string_hex_to_int(const godot_string *p_self) { const String *self = (const String *)p_self; return self->hex_to_int(false); } -int64_t GDAPI godot_string_hex_to_int64_with_prefix(const godot_string *p_self) { +godot_int GDAPI godot_string_hex_to_int_with_prefix(const godot_string *p_self) { const String *self = (const String *)p_self; return self->hex_to_int(); } -int64_t GDAPI godot_string_to_int64(const godot_string *p_self) { +godot_string GDAPI godot_string_get_slice(const godot_string *p_self, const godot_string *p_splitter, godot_int p_slice) { const String *self = (const String *)p_self; - - return self->to_int(); -} - -double GDAPI godot_string_unicode_char_to_float(const wchar_t *p_str, const wchar_t **r_end) { - return String::to_float(p_str, r_end); -} - -godot_string GDAPI godot_string_get_slice(const godot_string *p_self, godot_string p_splitter, godot_int p_slice) { - const String *self = (const String *)p_self; - String *splitter = (String *)&p_splitter; + const String *splitter = (const String *)p_splitter; godot_string result; memnew_placement(&result, String(self->get_slice(*splitter, p_slice))); return result; } -godot_string GDAPI godot_string_get_slicec(const godot_string *p_self, wchar_t p_splitter, godot_int p_slice) { +godot_string GDAPI godot_string_get_slicec(const godot_string *p_self, godot_char_type p_splitter, godot_int p_slice) { const String *self = (const String *)p_self; godot_string result; memnew_placement(&result, String(self->get_slicec(p_splitter, p_slice))); @@ -636,221 +684,149 @@ godot_string GDAPI godot_string_get_slicec(const godot_string *p_self, wchar_t p return result; } -godot_array GDAPI godot_string_split(const godot_string *p_self, const godot_string *p_splitter) { +godot_packed_string_array GDAPI godot_string_split(const godot_string *p_self, const godot_string *p_splitter) { const String *self = (const String *)p_self; const String *splitter = (const String *)p_splitter; - godot_array result; - memnew_placement(&result, Array); - Array *proxy = (Array *)&result; - Vector<String> return_value = self->split(*splitter, false); - - proxy->resize(return_value.size()); - for (int i = 0; i < return_value.size(); i++) { - (*proxy)[i] = return_value[i]; - } - - return result; + godot_packed_string_array ret; + memnew_placement(&ret, Vector<String>(self->split(*splitter, false))); + return ret; } -godot_array GDAPI godot_string_split_allow_empty(const godot_string *p_self, const godot_string *p_splitter) { +godot_packed_string_array GDAPI godot_string_split_allow_empty(const godot_string *p_self, const godot_string *p_splitter) { const String *self = (const String *)p_self; const String *splitter = (const String *)p_splitter; - godot_array result; - memnew_placement(&result, Array); - Array *proxy = (Array *)&result; - Vector<String> return_value = self->split(*splitter); - - proxy->resize(return_value.size()); - for (int i = 0; i < return_value.size(); i++) { - (*proxy)[i] = return_value[i]; - } + godot_packed_string_array ret; + memnew_placement(&ret, Vector<String>(self->split(*splitter, true))); + return ret; +} - return result; +godot_packed_string_array GDAPI godot_string_split_with_maxsplit(const godot_string *p_self, const godot_string *p_splitter, const godot_bool p_allow_empty, const godot_int p_maxsplit) { + const String *self = (const String *)p_self; + const String *splitter = (const String *)p_splitter; + godot_packed_string_array ret; + memnew_placement(&ret, Vector<String>(self->split(*splitter, p_allow_empty, p_maxsplit))); + return ret; } -godot_array GDAPI godot_string_split_floats(const godot_string *p_self, const godot_string *p_splitter) { +godot_packed_string_array GDAPI godot_string_rsplit(const godot_string *p_self, const godot_string *p_splitter) { const String *self = (const String *)p_self; const String *splitter = (const String *)p_splitter; - godot_array result; - memnew_placement(&result, Array); - Array *proxy = (Array *)&result; - Vector<float> return_value = self->split_floats(*splitter, false); - - proxy->resize(return_value.size()); - for (int i = 0; i < return_value.size(); i++) { - (*proxy)[i] = return_value[i]; - } - return result; + godot_packed_string_array ret; + memnew_placement(&ret, Vector<String>(self->rsplit(*splitter, false))); + return ret; } -godot_array GDAPI godot_string_split_floats_allows_empty(const godot_string *p_self, const godot_string *p_splitter) { +godot_packed_string_array GDAPI godot_string_rsplit_allow_empty(const godot_string *p_self, const godot_string *p_splitter) { const String *self = (const String *)p_self; const String *splitter = (const String *)p_splitter; - godot_array result; - memnew_placement(&result, Array); - Array *proxy = (Array *)&result; - Vector<float> return_value = self->split_floats(*splitter); - - proxy->resize(return_value.size()); - for (int i = 0; i < return_value.size(); i++) { - (*proxy)[i] = return_value[i]; - } - return result; + godot_packed_string_array ret; + memnew_placement(&ret, Vector<String>(self->rsplit(*splitter, true))); + return ret; } -godot_array GDAPI godot_string_split_floats_mk(const godot_string *p_self, const godot_array *p_splitters) { +godot_packed_string_array GDAPI godot_string_rsplit_with_maxsplit(const godot_string *p_self, const godot_string *p_splitter, const godot_bool p_allow_empty, const godot_int p_maxsplit) { const String *self = (const String *)p_self; + const String *splitter = (const String *)p_splitter; - Vector<String> splitters; - Array *splitter_proxy = (Array *)p_splitters; - splitters.resize(splitter_proxy->size()); - for (int i = 0; i < splitter_proxy->size(); i++) { - splitters.write[i] = (*splitter_proxy)[i]; - } - - godot_array result; - memnew_placement(&result, Array); - Array *proxy = (Array *)&result; - Vector<float> return_value = self->split_floats_mk(splitters, false); + godot_packed_string_array ret; + memnew_placement(&ret, Vector<String>(self->rsplit(*splitter, p_allow_empty, p_maxsplit))); + return ret; +} - proxy->resize(return_value.size()); - for (int i = 0; i < return_value.size(); i++) { - (*proxy)[i] = return_value[i]; - } +godot_packed_float32_array GDAPI godot_string_split_floats(const godot_string *p_self, const godot_string *p_splitter) { + const String *self = (const String *)p_self; + const String *splitter = (const String *)p_splitter; - return result; + godot_packed_float32_array ret; + memnew_placement(&ret, Vector<float>(self->split_floats(*splitter, false))); + return ret; } -godot_array GDAPI godot_string_split_floats_mk_allows_empty(const godot_string *p_self, const godot_array *p_splitters) { +godot_packed_float32_array GDAPI godot_string_split_floats_allow_empty(const godot_string *p_self, const godot_string *p_splitter) { const String *self = (const String *)p_self; + const String *splitter = (const String *)p_splitter; - Vector<String> splitters; - Array *splitter_proxy = (Array *)p_splitters; - splitters.resize(splitter_proxy->size()); - for (int i = 0; i < splitter_proxy->size(); i++) { - splitters.write[i] = (*splitter_proxy)[i]; - } - - godot_array result; - memnew_placement(&result, Array); - Array *proxy = (Array *)&result; - Vector<float> return_value = self->split_floats_mk(splitters); + godot_packed_float32_array ret; + memnew_placement(&ret, Vector<float>(self->split_floats(*splitter, true))); + return ret; +} - proxy->resize(return_value.size()); - for (int i = 0; i < return_value.size(); i++) { - (*proxy)[i] = return_value[i]; - } +godot_packed_float32_array GDAPI godot_string_split_floats_mk(const godot_string *p_self, const godot_packed_string_array *p_splitters) { + const String *self = (const String *)p_self; + const Vector<String> *splitters = (const Vector<String> *)p_splitters; - return result; + godot_packed_float32_array ret; + memnew_placement(&ret, Vector<float>(self->split_floats_mk(*splitters, false))); + return ret; } -godot_array GDAPI godot_string_split_ints(const godot_string *p_self, const godot_string *p_splitter) { +godot_packed_float32_array GDAPI godot_string_split_floats_mk_allow_empty(const godot_string *p_self, const godot_packed_string_array *p_splitters) { const String *self = (const String *)p_self; - const String *splitter = (const String *)p_splitter; - godot_array result; - memnew_placement(&result, Array); - Array *proxy = (Array *)&result; - Vector<int> return_value = self->split_ints(*splitter, false); - - proxy->resize(return_value.size()); - for (int i = 0; i < return_value.size(); i++) { - (*proxy)[i] = return_value[i]; - } + const Vector<String> *splitters = (const Vector<String> *)p_splitters; - return result; + godot_packed_float32_array ret; + memnew_placement(&ret, Vector<float>(self->split_floats_mk(*splitters, true))); + return ret; } -godot_array GDAPI godot_string_split_ints_allows_empty(const godot_string *p_self, const godot_string *p_splitter) { +godot_packed_int32_array GDAPI godot_string_split_ints(const godot_string *p_self, const godot_string *p_splitter) { const String *self = (const String *)p_self; const String *splitter = (const String *)p_splitter; - godot_array result; - memnew_placement(&result, Array); - Array *proxy = (Array *)&result; - Vector<int> return_value = self->split_ints(*splitter); - - proxy->resize(return_value.size()); - for (int i = 0; i < return_value.size(); i++) { - (*proxy)[i] = return_value[i]; - } - return result; + godot_packed_int32_array ret; + memnew_placement(&ret, Vector<int>(self->split_ints(*splitter, false))); + return ret; } -godot_array GDAPI godot_string_split_ints_mk(const godot_string *p_self, const godot_array *p_splitters) { +godot_packed_int32_array GDAPI godot_string_split_ints_allow_empty(const godot_string *p_self, const godot_string *p_splitter) { const String *self = (const String *)p_self; + const String *splitter = (const String *)p_splitter; - Vector<String> splitters; - Array *splitter_proxy = (Array *)p_splitters; - splitters.resize(splitter_proxy->size()); - for (int i = 0; i < splitter_proxy->size(); i++) { - splitters.write[i] = (*splitter_proxy)[i]; - } - - godot_array result; - memnew_placement(&result, Array); - Array *proxy = (Array *)&result; - Vector<int> return_value = self->split_ints_mk(splitters, false); - - proxy->resize(return_value.size()); - for (int i = 0; i < return_value.size(); i++) { - (*proxy)[i] = return_value[i]; - } - - return result; + godot_packed_int32_array ret; + memnew_placement(&ret, Vector<int>(self->split_ints(*splitter, true))); + return ret; } -godot_array GDAPI godot_string_split_ints_mk_allows_empty(const godot_string *p_self, const godot_array *p_splitters) { +godot_packed_int32_array GDAPI godot_string_split_ints_mk(const godot_string *p_self, const godot_packed_string_array *p_splitters) { const String *self = (const String *)p_self; + const Vector<String> *splitters = (const Vector<String> *)p_splitters; - Vector<String> splitters; - Array *splitter_proxy = (Array *)p_splitters; - splitters.resize(splitter_proxy->size()); - for (int i = 0; i < splitter_proxy->size(); i++) { - splitters.write[i] = (*splitter_proxy)[i]; - } - - godot_array result; - memnew_placement(&result, Array); - Array *proxy = (Array *)&result; - Vector<int> return_value = self->split_ints_mk(splitters); + godot_packed_int32_array ret; + memnew_placement(&ret, Vector<int>(self->split_ints_mk(*splitters, false))); + return ret; +} - proxy->resize(return_value.size()); - for (int i = 0; i < return_value.size(); i++) { - (*proxy)[i] = return_value[i]; - } +godot_packed_int32_array GDAPI godot_string_split_ints_mk_allow_empty(const godot_string *p_self, const godot_packed_string_array *p_splitters) { + const String *self = (const String *)p_self; + const Vector<String> *splitters = (const Vector<String> *)p_splitters; - return result; + godot_packed_int32_array ret; + memnew_placement(&ret, Vector<int>(self->split_ints_mk(*splitters, true))); + return ret; } -godot_array GDAPI godot_string_split_spaces(const godot_string *p_self) { +godot_packed_string_array GDAPI godot_string_split_spaces(const godot_string *p_self) { const String *self = (const String *)p_self; - godot_array result; - memnew_placement(&result, Array); - Array *proxy = (Array *)&result; - Vector<String> return_value = self->split_spaces(); - proxy->resize(return_value.size()); - for (int i = 0; i < return_value.size(); i++) { - (*proxy)[i] = return_value[i]; - } - - return result; + godot_packed_string_array ret; + memnew_placement(&ret, Vector<String>(self->split_spaces())); + return ret; } -godot_int GDAPI godot_string_get_slice_count(const godot_string *p_self, godot_string p_splitter) { +godot_int GDAPI godot_string_get_slice_count(const godot_string *p_self, const godot_string *p_splitter) { const String *self = (const String *)p_self; - String *splitter = (String *)&p_splitter; + const String *splitter = (const String *)p_splitter; return self->get_slice_count(*splitter); } -wchar_t GDAPI godot_string_char_lowercase(wchar_t p_char) { +godot_char_type GDAPI godot_string_char_lowercase(godot_char_type p_char) { return String::char_lowercase(p_char); } -wchar_t GDAPI godot_string_char_uppercase(wchar_t p_char) { +godot_char_type GDAPI godot_string_char_uppercase(godot_char_type p_char) { return String::char_uppercase(p_char); } @@ -894,7 +870,7 @@ godot_string GDAPI godot_string_left(const godot_string *p_self, godot_int p_pos return result; } -wchar_t GDAPI godot_string_ord_at(const godot_string *p_self, godot_int p_idx) { +godot_char_type GDAPI godot_string_ord_at(const godot_string *p_self, godot_int p_idx) { const String *self = (const String *)p_self; return self->ord_at(p_idx); @@ -917,6 +893,14 @@ godot_string GDAPI godot_string_right(const godot_string *p_self, godot_int p_po return result; } +godot_string GDAPI godot_string_repeat(const godot_string *p_self, godot_int p_count) { + const String *self = (const String *)p_self; + godot_string result; + memnew_placement(&result, String(self->repeat(p_count))); + + return result; +} + godot_string GDAPI godot_string_strip_edges(const godot_string *p_self, godot_bool p_left, godot_bool p_right) { const String *self = (const String *)p_self; godot_string result; @@ -948,7 +932,7 @@ godot_char_string GDAPI godot_string_ascii(const godot_string *p_self) { return result; } -godot_char_string GDAPI godot_string_ascii_extended(const godot_string *p_self) { +godot_char_string GDAPI godot_string_latin1(const godot_string *p_self) { const String *self = (const String *)p_self; godot_char_string result; @@ -994,6 +978,42 @@ godot_string GDAPI godot_string_chars_to_utf8_with_len(const char *p_utf8, godot return result; } +godot_char16_string GDAPI godot_string_utf16(const godot_string *p_self) { + const String *self = (const String *)p_self; + + godot_char16_string result; + + memnew_placement(&result, Char16String(self->utf16())); + + return result; +} + +godot_bool GDAPI godot_string_parse_utf16(godot_string *p_self, const char16_t *p_utf16) { + String *self = (String *)p_self; + + return self->parse_utf16(p_utf16); +} + +godot_bool GDAPI godot_string_parse_utf16_with_len(godot_string *p_self, const char16_t *p_utf16, godot_int p_len) { + String *self = (String *)p_self; + + return self->parse_utf16(p_utf16, p_len); +} + +godot_string GDAPI godot_string_chars_to_utf16(const char16_t *p_utf16) { + godot_string result; + memnew_placement(&result, String(String::utf16(p_utf16))); + + return result; +} + +godot_string GDAPI godot_string_chars_to_utf16_with_len(const char16_t *p_utf16, godot_int p_len) { + godot_string result; + memnew_placement(&result, String(String::utf16(p_utf16, p_len))); + + return result; +} + uint32_t GDAPI godot_string_hash(const godot_string *p_self) { const String *self = (const String *)p_self; @@ -1014,28 +1034,18 @@ uint32_t GDAPI godot_string_hash_chars_with_len(const char *p_cstr, godot_int p_ return String::hash(p_cstr, p_len); } -uint32_t GDAPI godot_string_hash_utf8_chars(const wchar_t *p_str) { +uint32_t GDAPI godot_string_hash_wide_chars(const wchar_t *p_str) { return String::hash(p_str); } -uint32_t GDAPI godot_string_hash_utf8_chars_with_len(const wchar_t *p_str, godot_int p_len) { +uint32_t GDAPI godot_string_hash_wide_chars_with_len(const wchar_t *p_str, godot_int p_len) { return String::hash(p_str, p_len); } godot_packed_byte_array GDAPI godot_string_md5_buffer(const godot_string *p_self) { const String *self = (const String *)p_self; - Vector<uint8_t> tmp_result = self->md5_buffer(); - godot_packed_byte_array result; - memnew_placement(&result, PackedByteArray); - PackedByteArray *proxy = (PackedByteArray *)&result; - uint8_t *proxy_writer = proxy->ptrw(); - proxy->resize(tmp_result.size()); - - for (int i = 0; i < tmp_result.size(); i++) { - proxy_writer[i] = tmp_result[i]; - } - + memnew_placement(&result, PackedByteArray(self->md5_buffer())); return result; } @@ -1047,23 +1057,28 @@ godot_string GDAPI godot_string_md5_text(const godot_string *p_self) { return result; } -godot_packed_byte_array GDAPI godot_string_sha256_buffer(const godot_string *p_self) { +godot_packed_byte_array GDAPI godot_string_sha1_buffer(const godot_string *p_self) { const String *self = (const String *)p_self; - Vector<uint8_t> tmp_result = self->sha256_buffer(); - godot_packed_byte_array result; - memnew_placement(&result, PackedByteArray); - PackedByteArray *proxy = (PackedByteArray *)&result; - uint8_t *proxy_writer = proxy->ptrw(); - proxy->resize(tmp_result.size()); + memnew_placement(&result, PackedByteArray(self->sha1_buffer())); + return result; +} - for (int i = 0; i < tmp_result.size(); i++) { - proxy_writer[i] = tmp_result[i]; - } +godot_string GDAPI godot_string_sha1_text(const godot_string *p_self) { + const String *self = (const String *)p_self; + godot_string result; + memnew_placement(&result, String(self->sha1_text())); return result; } +godot_packed_byte_array GDAPI godot_string_sha256_buffer(const godot_string *p_self) { + const String *self = (const String *)p_self; + godot_packed_byte_array result; + memnew_placement(&result, PackedByteArray(self->sha256_buffer())); + return result; +} + godot_string GDAPI godot_string_sha256_text(const godot_string *p_self) { const String *self = (const String *)p_self; godot_string result; @@ -1206,15 +1221,6 @@ godot_string GDAPI godot_string_json_escape(const godot_string *p_self) { return result; } -godot_string GDAPI godot_string_word_wrap(const godot_string *p_self, godot_int p_chars_per_line) { - const String *self = (const String *)p_self; - godot_string result; - String return_value = self->word_wrap(p_chars_per_line); - memnew_placement(&result, String(return_value)); - - return result; -} - godot_string GDAPI godot_string_xml_escape(const godot_string *p_self) { const String *self = (const String *)p_self; godot_string result; @@ -1260,6 +1266,22 @@ godot_string GDAPI godot_string_percent_encode(const godot_string *p_self) { return result; } +godot_string GDAPI godot_string_join(const godot_string *p_self, const godot_packed_string_array *p_parts) { + const String *self = (const String *)p_self; + const Vector<String> *parts = (const Vector<String> *)p_parts; + godot_string result; + String return_value = self->join(*parts); + memnew_placement(&result, String(return_value)); + + return result; +} + +godot_bool GDAPI godot_string_is_valid_filename(const godot_string *p_self) { + const String *self = (const String *)p_self; + + return self->is_valid_filename(); +} + godot_bool GDAPI godot_string_is_valid_float(const godot_string *p_self) { const String *self = (const String *)p_self; @@ -1325,31 +1347,22 @@ godot_string GDAPI godot_string_trim_suffix(const godot_string *p_self, const go return result; } -godot_string GDAPI godot_string_rstrip(const godot_string *p_self, const godot_string *p_chars) { +godot_string GDAPI godot_string_lstrip(const godot_string *p_self, const godot_string *p_chars) { const String *self = (const String *)p_self; String *chars = (String *)p_chars; godot_string result; - String return_value = self->rstrip(*chars); + String return_value = self->lstrip(*chars); memnew_placement(&result, String(return_value)); return result; } -godot_packed_string_array GDAPI godot_string_rsplit(const godot_string *p_self, const godot_string *p_divisor, - const godot_bool p_allow_empty, const godot_int p_maxsplit) { +godot_string GDAPI godot_string_rstrip(const godot_string *p_self, const godot_string *p_chars) { const String *self = (const String *)p_self; - String *divisor = (String *)p_divisor; - - godot_packed_string_array result; - memnew_placement(&result, PackedStringArray); - PackedStringArray *proxy = (PackedStringArray *)&result; - String *proxy_writer = proxy->ptrw(); - Vector<String> tmp_result = self->rsplit(*divisor, p_allow_empty, p_maxsplit); - proxy->resize(tmp_result.size()); - - for (int i = 0; i < tmp_result.size(); i++) { - proxy_writer[i] = tmp_result[i]; - } + String *chars = (String *)p_chars; + godot_string result; + String return_value = self->rstrip(*chars); + memnew_placement(&result, String(return_value)); return result; } diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 8ccf44ff1a..82bfbd23de 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -3732,6 +3732,27 @@ ] }, { + "name": "godot_char16_string_length", + "return_type": "godot_int", + "arguments": [ + ["const godot_char16_string *", "p_cs"] + ] + }, + { + "name": "godot_char16_string_get_data", + "return_type": "const char16_t *", + "arguments": [ + ["const godot_char16_string *", "p_cs"] + ] + }, + { + "name": "godot_char16_string_destroy", + "return_type": "void", + "arguments": [ + ["godot_char16_string *", "p_cs"] + ] + }, + { "name": "godot_string_new", "return_type": "void", "arguments": [ @@ -3747,7 +3768,83 @@ ] }, { - "name": "godot_string_new_with_wide_string", + "name": "godot_string_new_with_latin1_chars", + "return_type": "void", + "arguments": [ + ["godot_string *", "r_dest"], + ["const char *", "p_contents"] + ] + }, + { + "name": "godot_string_new_with_utf8_chars", + "return_type": "void", + "arguments": [ + ["godot_string *", "r_dest"], + ["const char *", "p_contents"] + ] + }, + { + "name": "godot_string_new_with_utf16_chars", + "return_type": "void", + "arguments": [ + ["godot_string *", "r_dest"], + ["const char16_t *", "p_contents"] + ] + }, + { + "name": "godot_string_new_with_utf32_chars", + "return_type": "void", + "arguments": [ + ["godot_string *", "r_dest"], + ["const char32_t *", "p_contents"] + ] + }, + { + "name": "godot_string_new_with_wide_chars", + "return_type": "void", + "arguments": [ + ["godot_string *", "r_dest"], + ["const wchar_t *", "p_contents"] + ] + }, + { + "name": "godot_string_new_with_latin1_chars_and_len", + "return_type": "void", + "arguments": [ + ["godot_string *", "r_dest"], + ["const char *", "p_contents"], + ["const int", "p_size"] + ] + }, + { + "name": "godot_string_new_with_utf8_chars_and_len", + "return_type": "void", + "arguments": [ + ["godot_string *", "r_dest"], + ["const char *", "p_contents"], + ["const int", "p_size"] + ] + }, + { + "name": "godot_string_new_with_utf16_chars_and_len", + "return_type": "void", + "arguments": [ + ["godot_string *", "r_dest"], + ["const char16_t *", "p_contents"], + ["const int", "p_size"] + ] + }, + { + "name": "godot_string_new_with_utf32_chars_and_len", + "return_type": "void", + "arguments": [ + ["godot_string *", "r_dest"], + ["const char32_t *", "p_contents"], + ["const int", "p_size"] + ] + }, + { + "name": "godot_string_new_with_wide_chars_and_len", "return_type": "void", "arguments": [ ["godot_string *", "r_dest"], @@ -3757,7 +3854,7 @@ }, { "name": "godot_string_operator_index", - "return_type": "const wchar_t *", + "return_type": "const godot_char_type *", "arguments": [ ["godot_string *", "p_self"], ["const godot_int", "p_idx"] @@ -3765,15 +3862,15 @@ }, { "name": "godot_string_operator_index_const", - "return_type": "wchar_t", + "return_type": "godot_char_type", "arguments": [ ["const godot_string *", "p_self"], ["const godot_int", "p_idx"] ] }, { - "name": "godot_string_wide_str", - "return_type": "const wchar_t *", + "name": "godot_string_get_data", + "return_type": "const godot_char_type *", "arguments": [ ["const godot_string *", "p_self"] ] @@ -3807,7 +3904,7 @@ "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_what"], + ["const godot_string *", "p_what"], ["godot_int", "p_from"], ["godot_int", "p_to"] ] @@ -3817,7 +3914,7 @@ "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_what"], + ["const godot_string *", "p_what"], ["godot_int", "p_from"], ["godot_int", "p_to"] ] @@ -3878,7 +3975,7 @@ }, { "name": "godot_string_bigrams", - "return_type": "godot_array", + "return_type": "godot_packed_string_array", "arguments": [ ["const godot_string *", "p_self"] ] @@ -3887,7 +3984,7 @@ "name": "godot_string_chr", "return_type": "godot_string", "arguments": [ - ["wchar_t", "p_character"] + ["godot_char_type", "p_character"] ] }, { @@ -3899,11 +3996,19 @@ ] }, { + "name": "godot_string_ends_with_char_array", + "return_type": "godot_bool", + "arguments": [ + ["const godot_string *", "p_self"], + ["const char *", "p_char_array"] + ] + }, + { "name": "godot_string_find", "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_what"] + ["const godot_string *", "p_what"] ] }, { @@ -3911,7 +4016,7 @@ "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_what"], + ["const godot_string *", "p_what"], ["godot_int", "p_from"] ] }, @@ -3920,7 +4025,7 @@ "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["const godot_array *", "p_keys"] + ["const godot_packed_string_array *", "p_keys"] ] }, { @@ -3928,7 +4033,7 @@ "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["const godot_array *", "p_keys"], + ["const godot_packed_string_array *", "p_keys"], ["godot_int", "p_from"] ] }, @@ -3937,7 +4042,7 @@ "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["const godot_array *", "p_keys"], + ["const godot_packed_string_array *", "p_keys"], ["godot_int", "p_from"], ["godot_int *", "r_key"] ] @@ -3947,7 +4052,7 @@ "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_what"] + ["const godot_string *", "p_what"] ] }, { @@ -3955,7 +4060,7 @@ "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_what"], + ["const godot_string *", "p_what"], ["godot_int", "p_from"] ] }, @@ -3985,26 +4090,12 @@ ] }, { - "name": "godot_string_hex_to_int", - "return_type": "godot_int", - "arguments": [ - ["const godot_string *", "p_self"] - ] - }, - { - "name": "godot_string_hex_to_int_without_prefix", - "return_type": "godot_int", - "arguments": [ - ["const godot_string *", "p_self"] - ] - }, - { "name": "godot_string_insert", "return_type": "godot_string", "arguments": [ ["const godot_string *", "p_self"], ["godot_int", "p_at_pos"], - ["godot_string", "p_string"] + ["const godot_string *", "p_string"] ] }, { @@ -4137,8 +4228,8 @@ "return_type": "godot_string", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_key"], - ["godot_string", "p_with"] + ["const godot_string *", "p_key"], + ["const godot_string *", "p_with"] ] }, { @@ -4146,8 +4237,8 @@ "return_type": "godot_string", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_key"], - ["godot_string", "p_with"] + ["const godot_string *", "p_key"], + ["const godot_string *", "p_with"] ] }, { @@ -4155,8 +4246,8 @@ "return_type": "godot_string", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_key"], - ["godot_string", "p_with"] + ["const godot_string *", "p_key"], + ["const godot_string *", "p_with"] ] }, { @@ -4164,7 +4255,7 @@ "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_what"] + ["const godot_string *", "p_what"] ] }, { @@ -4172,7 +4263,7 @@ "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_what"] + ["const godot_string *", "p_what"] ] }, { @@ -4180,7 +4271,7 @@ "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_what"], + ["const godot_string *", "p_what"], ["godot_int", "p_from"] ] }, @@ -4189,7 +4280,7 @@ "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_what"], + ["const godot_string *", "p_what"], ["godot_int", "p_from"] ] }, @@ -4237,15 +4328,15 @@ ] }, { - "name": "godot_string_to_float", - "return_type": "double", + "name": "godot_string_to_int", + "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"] ] }, { - "name": "godot_string_to_int", - "return_type": "godot_int", + "name": "godot_string_to_float", + "return_type": "double", "arguments": [ ["const godot_string *", "p_self"] ] @@ -4279,6 +4370,14 @@ ] }, { + "name": "godot_string_wchar_to_float", + "return_type": "double", + "arguments": [ + ["const wchar_t *", "p_str"], + ["const wchar_t **", "r_end"] + ] + }, + { "name": "godot_string_char_to_int", "return_type": "godot_int", "arguments": [ @@ -4287,7 +4386,7 @@ }, { "name": "godot_string_wchar_to_int", - "return_type": "int64_t", + "return_type": "godot_int", "arguments": [ ["const wchar_t *", "p_str"] ] @@ -4301,48 +4400,33 @@ ] }, { - "name": "godot_string_char_to_int64_with_len", - "return_type": "int64_t", + "name": "godot_string_wchar_to_int_with_len", + "return_type": "godot_int", "arguments": [ ["const wchar_t *", "p_str"], ["int", "p_len"] ] }, { - "name": "godot_string_hex_to_int64", - "return_type": "int64_t", - "arguments": [ - ["const godot_string *", "p_self"] - ] - }, - { - "name": "godot_string_hex_to_int64_with_prefix", - "return_type": "int64_t", + "name": "godot_string_hex_to_int", + "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"] ] }, { - "name": "godot_string_to_int64", - "return_type": "int64_t", + "name": "godot_string_hex_to_int_with_prefix", + "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"] ] }, { - "name": "godot_string_unicode_char_to_float", - "return_type": "double", - "arguments": [ - ["const wchar_t *", "p_str"], - ["const wchar_t **", "r_end"] - ] - }, - { "name": "godot_string_get_slice_count", "return_type": "godot_int", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_splitter"] + ["const godot_string *", "p_splitter"] ] }, { @@ -4350,7 +4434,7 @@ "return_type": "godot_string", "arguments": [ ["const godot_string *", "p_self"], - ["godot_string", "p_splitter"], + ["const godot_string *", "p_splitter"], ["godot_int", "p_slice"] ] }, @@ -4359,13 +4443,13 @@ "return_type": "godot_string", "arguments": [ ["const godot_string *", "p_self"], - ["wchar_t", "p_splitter"], + ["godot_char_type", "p_splitter"], ["godot_int", "p_slice"] ] }, { "name": "godot_string_split", - "return_type": "godot_array", + "return_type": "godot_packed_string_array", "arguments": [ ["const godot_string *", "p_self"], ["const godot_string *", "p_splitter"] @@ -4373,23 +4457,59 @@ }, { "name": "godot_string_split_allow_empty", - "return_type": "godot_array", + "return_type": "godot_packed_string_array", "arguments": [ ["const godot_string *", "p_self"], ["const godot_string *", "p_splitter"] ] }, { + "name": "godot_string_split_with_maxsplit", + "return_type": "godot_packed_string_array", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_string *", "p_splitter"], + ["const godot_bool", "p_allow_empty"], + ["const godot_int", "p_maxsplit"] + ] + }, + { + "name": "godot_string_rsplit", + "return_type": "godot_packed_string_array", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_string *", "p_splitter"] + ] + }, + { + "name": "godot_string_rsplit_allow_empty", + "return_type": "godot_packed_string_array", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_string *", "p_splitter"] + ] + }, + { + "name": "godot_string_rsplit_with_maxsplit", + "return_type": "godot_packed_string_array", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_string *", "p_splitter"], + ["const godot_bool", "p_allow_empty"], + ["const godot_int", "p_maxsplit"] + ] + }, + { "name": "godot_string_split_floats", - "return_type": "godot_array", + "return_type": "godot_packed_float32_array", "arguments": [ ["const godot_string *", "p_self"], ["const godot_string *", "p_splitter"] ] }, { - "name": "godot_string_split_floats_allows_empty", - "return_type": "godot_array", + "name": "godot_string_split_floats_allow_empty", + "return_type": "godot_packed_float32_array", "arguments": [ ["const godot_string *", "p_self"], ["const godot_string *", "p_splitter"] @@ -4397,31 +4517,31 @@ }, { "name": "godot_string_split_floats_mk", - "return_type": "godot_array", + "return_type": "godot_packed_float32_array", "arguments": [ ["const godot_string *", "p_self"], - ["const godot_array *", "p_splitters"] + ["const godot_packed_string_array *", "p_splitters"] ] }, { - "name": "godot_string_split_floats_mk_allows_empty", - "return_type": "godot_array", + "name": "godot_string_split_floats_mk_allow_empty", + "return_type": "godot_packed_float32_array", "arguments": [ ["const godot_string *", "p_self"], - ["const godot_array *", "p_splitters"] + ["const godot_packed_string_array *", "p_splitters"] ] }, { "name": "godot_string_split_ints", - "return_type": "godot_array", + "return_type": "godot_packed_int32_array", "arguments": [ ["const godot_string *", "p_self"], ["const godot_string *", "p_splitter"] ] }, { - "name": "godot_string_split_ints_allows_empty", - "return_type": "godot_array", + "name": "godot_string_split_ints_allow_empty", + "return_type": "godot_packed_int32_array", "arguments": [ ["const godot_string *", "p_self"], ["const godot_string *", "p_splitter"] @@ -4429,29 +4549,29 @@ }, { "name": "godot_string_split_ints_mk", - "return_type": "godot_array", + "return_type": "godot_packed_int32_array", "arguments": [ ["const godot_string *", "p_self"], - ["const godot_array *", "p_splitters"] + ["const godot_packed_string_array *", "p_splitters"] ] }, { - "name": "godot_string_split_ints_mk_allows_empty", - "return_type": "godot_array", + "name": "godot_string_split_ints_mk_allow_empty", + "return_type": "godot_packed_int32_array", "arguments": [ ["const godot_string *", "p_self"], - ["const godot_array *", "p_splitters"] + ["const godot_packed_string_array *", "p_splitters"] ] }, { "name": "godot_string_split_spaces", - "return_type": "godot_array", + "return_type": "godot_packed_string_array", "arguments": [ ["const godot_string *", "p_self"] ] }, { - "name": "godot_string_rstrip", + "name": "godot_string_lstrip", "return_type": "godot_string", "arguments": [ ["const godot_string *", "p_self"], @@ -4459,13 +4579,11 @@ ] }, { - "name": "godot_string_rsplit", - "return_type": "godot_packed_string_array", + "name": "godot_string_rstrip", + "return_type": "godot_string", "arguments": [ ["const godot_string *", "p_self"], - ["const godot_string *", "p_divisor"], - ["const godot_bool", "p_allow_empty"], - ["const godot_int", "p_maxsplit"] + ["const godot_string *", "p_chars"] ] }, { @@ -4486,16 +4604,16 @@ }, { "name": "godot_string_char_lowercase", - "return_type": "wchar_t", + "return_type": "godot_char_type", "arguments": [ - ["wchar_t", "p_char"] + ["godot_char_type", "p_char"] ] }, { "name": "godot_string_char_uppercase", - "return_type": "wchar_t", + "return_type": "godot_char_type", "arguments": [ - ["wchar_t", "p_char"] + ["godot_char_type", "p_char"] ] }, { @@ -4536,7 +4654,7 @@ }, { "name": "godot_string_ord_at", - "return_type": "wchar_t", + "return_type": "godot_char_type", "arguments": [ ["const godot_string *", "p_self"], ["godot_int", "p_idx"] @@ -4559,6 +4677,14 @@ ] }, { + "name": "godot_string_repeat", + "return_type": "godot_string", + "arguments": [ + ["const godot_string *", "p_self"], + ["godot_int", "p_count"] + ] + }, + { "name": "godot_string_strip_edges", "return_type": "godot_string", "arguments": [ @@ -4591,7 +4717,7 @@ ] }, { - "name": "godot_string_ascii_extended", + "name": "godot_string_latin1", "return_type": "godot_char_string", "arguments": [ ["const godot_string *", "p_self"] @@ -4622,17 +4748,26 @@ ] }, { - "name": "godot_string_chars_to_utf8", - "return_type": "godot_string", + "name": "godot_string_utf16", + "return_type": "godot_char16_string", "arguments": [ - ["const char *", "p_utf8"] + ["const godot_string *", "p_self"] ] }, { - "name": "godot_string_chars_to_utf8_with_len", - "return_type": "godot_string", + "name": "godot_string_parse_utf16", + "return_type": "godot_bool", "arguments": [ - ["const char *", "p_utf8"], + ["godot_string *", "p_self"], + ["const char16_t *", "p_utf16"] + ] + }, + { + "name": "godot_string_parse_utf16_with_len", + "return_type": "godot_bool", + "arguments": [ + ["godot_string *", "p_self"], + ["const char16_t *", "p_utf16"], ["godot_int", "p_len"] ] }, @@ -4666,14 +4801,14 @@ ] }, { - "name": "godot_string_hash_utf8_chars", + "name": "godot_string_hash_wide_chars", "return_type": "uint32_t", "arguments": [ ["const wchar_t *", "p_str"] ] }, { - "name": "godot_string_hash_utf8_chars_with_len", + "name": "godot_string_hash_wide_chars_with_len", "return_type": "uint32_t", "arguments": [ ["const wchar_t *", "p_str"], @@ -4695,6 +4830,20 @@ ] }, { + "name": "godot_string_sha1_buffer", + "return_type": "godot_packed_byte_array", + "arguments": [ + ["const godot_string *", "p_self"] + ] + }, + { + "name": "godot_string_sha1_text", + "return_type": "godot_string", + "arguments": [ + ["const godot_string *", "p_self"] + ] + }, + { "name": "godot_string_sha256_buffer", "return_type": "godot_packed_byte_array", "arguments": [ @@ -4823,14 +4972,6 @@ ] }, { - "name": "godot_string_word_wrap", - "return_type": "godot_string", - "arguments": [ - ["const godot_string *", "p_self"], - ["godot_int", "p_chars_per_line"] - ] - }, - { "name": "godot_string_xml_escape", "return_type": "godot_string", "arguments": [ @@ -4866,6 +5007,21 @@ ] }, { + "name": "godot_string_join", + "return_type": "godot_string", + "arguments": [ + ["const godot_string *", "p_self"], + ["const godot_packed_string_array *", "p_parts"] + ] + }, + { + "name": "godot_string_is_valid_filename", + "return_type": "godot_bool", + "arguments": [ + ["const godot_string *", "p_self"] + ] + }, + { "name": "godot_string_is_valid_float", "return_type": "godot_bool", "arguments": [ diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp index fdd755845f..f0f095ddf5 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.cpp +++ b/modules/gdnative/gdnative_library_editor_plugin.cpp @@ -318,6 +318,7 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { platform_ios.name = "iOS"; platform_ios.entries.push_back("armv7"); platform_ios.entries.push_back("arm64"); + platform_ios.entries.push_back("x86_64"); // iOS can use both Static and Dynamic libraries. // Frameworks is actually a folder with files. platform_ios.library_extension = "*.framework; Framework, *.xcframework; Binary Framework, *.a; Static Library, *.dylib; Dynamic Library"; diff --git a/modules/gdnative/include/gdnative/string.h b/modules/gdnative/include/gdnative/string.h index d89383dc1b..0582d95f63 100644 --- a/modules/gdnative/include/gdnative/string.h +++ b/modules/gdnative/include/gdnative/string.h @@ -38,10 +38,11 @@ extern "C" { #include <stdint.h> #include <wchar.h> -typedef wchar_t godot_char_type; +typedef char32_t godot_char_type; #define GODOT_STRING_SIZE sizeof(void *) #define GODOT_CHAR_STRING_SIZE sizeof(void *) +#define GODOT_CHAR16_STRING_SIZE sizeof(void *) #ifndef GODOT_CORE_API_GODOT_STRING_TYPE_DEFINED #define GODOT_CORE_API_GODOT_STRING_TYPE_DEFINED @@ -58,6 +59,13 @@ typedef struct { } godot_char_string; #endif +#ifndef GODOT_CORE_API_GODOT_CHAR16_STRING_TYPE_DEFINED +#define GODOT_CORE_API_GODOT_CHAR16_STRING_TYPE_DEFINED +typedef struct { + uint8_t _dont_touch_that[GODOT_CHAR16_STRING_SIZE]; +} godot_char16_string; +#endif + // reduce extern "C" nesting for VS2013 #ifdef __cplusplus } @@ -75,13 +83,28 @@ godot_int GDAPI godot_char_string_length(const godot_char_string *p_cs); const char GDAPI *godot_char_string_get_data(const godot_char_string *p_cs); void GDAPI godot_char_string_destroy(godot_char_string *p_cs); +godot_int GDAPI godot_char16_string_length(const godot_char16_string *p_cs); +const char16_t GDAPI *godot_char16_string_get_data(const godot_char16_string *p_cs); +void GDAPI godot_char16_string_destroy(godot_char16_string *p_cs); + void GDAPI godot_string_new(godot_string *r_dest); void GDAPI godot_string_new_copy(godot_string *r_dest, const godot_string *p_src); -void GDAPI godot_string_new_with_wide_string(godot_string *r_dest, const wchar_t *p_contents, const int p_size); -const wchar_t GDAPI *godot_string_operator_index(godot_string *p_self, const godot_int p_idx); -wchar_t GDAPI godot_string_operator_index_const(const godot_string *p_self, const godot_int p_idx); -const wchar_t GDAPI *godot_string_wide_str(const godot_string *p_self); +void GDAPI godot_string_new_with_latin1_chars(godot_string *r_dest, const char *p_contents); +void GDAPI godot_string_new_with_utf8_chars(godot_string *r_dest, const char *p_contents); +void GDAPI godot_string_new_with_utf16_chars(godot_string *r_dest, const char16_t *p_contents); +void GDAPI godot_string_new_with_utf32_chars(godot_string *r_dest, const char32_t *p_contents); +void GDAPI godot_string_new_with_wide_chars(godot_string *r_dest, const wchar_t *p_contents); + +void GDAPI godot_string_new_with_latin1_chars_and_len(godot_string *r_dest, const char *p_contents, const int p_size); +void GDAPI godot_string_new_with_utf8_chars_and_len(godot_string *r_dest, const char *p_contents, const int p_size); +void GDAPI godot_string_new_with_utf16_chars_and_len(godot_string *r_dest, const char16_t *p_contents, const int p_size); +void GDAPI godot_string_new_with_utf32_chars_and_len(godot_string *r_dest, const char32_t *p_contents, const int p_size); +void GDAPI godot_string_new_with_wide_chars_and_len(godot_string *r_dest, const wchar_t *p_contents, const int p_size); + +const godot_char_type GDAPI *godot_string_operator_index(godot_string *p_self, const godot_int p_idx); +godot_char_type GDAPI godot_string_operator_index_const(const godot_string *p_self, const godot_int p_idx); +const godot_char_type GDAPI *godot_string_get_data(const godot_string *p_self); godot_bool GDAPI godot_string_operator_equal(const godot_string *p_self, const godot_string *p_b); godot_bool GDAPI godot_string_operator_less(const godot_string *p_self, const godot_string *p_b); @@ -89,7 +112,7 @@ godot_string GDAPI godot_string_operator_plus(const godot_string *p_self, const /* Standard size stuff */ -godot_int GDAPI godot_string_length(const godot_string *p_self); +/*+++*/ godot_int GDAPI godot_string_length(const godot_string *p_self); /* Helpers */ @@ -99,24 +122,25 @@ signed char GDAPI godot_string_naturalnocasecmp_to(const godot_string *p_self, c godot_bool GDAPI godot_string_begins_with(const godot_string *p_self, const godot_string *p_string); godot_bool GDAPI godot_string_begins_with_char_array(const godot_string *p_self, const char *p_char_array); -godot_array GDAPI godot_string_bigrams(const godot_string *p_self); -godot_string GDAPI godot_string_chr(wchar_t p_character); +godot_packed_string_array GDAPI godot_string_bigrams(const godot_string *p_self); +godot_string GDAPI godot_string_chr(godot_char_type p_character); godot_bool GDAPI godot_string_ends_with(const godot_string *p_self, const godot_string *p_string); -godot_int GDAPI godot_string_count(const godot_string *p_self, godot_string p_what, godot_int p_from, godot_int p_to); -godot_int GDAPI godot_string_countn(const godot_string *p_self, godot_string p_what, godot_int p_from, godot_int p_to); -godot_int GDAPI godot_string_find(const godot_string *p_self, godot_string p_what); -godot_int GDAPI godot_string_find_from(const godot_string *p_self, godot_string p_what, godot_int p_from); -godot_int GDAPI godot_string_findmk(const godot_string *p_self, const godot_array *p_keys); -godot_int GDAPI godot_string_findmk_from(const godot_string *p_self, const godot_array *p_keys, godot_int p_from); -godot_int GDAPI godot_string_findmk_from_in_place(const godot_string *p_self, const godot_array *p_keys, godot_int p_from, godot_int *r_key); -godot_int GDAPI godot_string_findn(const godot_string *p_self, godot_string p_what); -godot_int GDAPI godot_string_findn_from(const godot_string *p_self, godot_string p_what, godot_int p_from); +godot_bool GDAPI godot_string_ends_with_char_array(const godot_string *p_self, const char *p_char_array); +godot_int GDAPI godot_string_count(const godot_string *p_self, const godot_string *p_what, godot_int p_from, godot_int p_to); +godot_int GDAPI godot_string_countn(const godot_string *p_self, const godot_string *p_what, godot_int p_from, godot_int p_to); +godot_int GDAPI godot_string_find(const godot_string *p_self, const godot_string *p_what); +godot_int GDAPI godot_string_find_from(const godot_string *p_self, const godot_string *p_what, godot_int p_from); +godot_int GDAPI godot_string_findmk(const godot_string *p_self, const godot_packed_string_array *p_keys); +godot_int GDAPI godot_string_findmk_from(const godot_string *p_self, const godot_packed_string_array *p_keys, godot_int p_from); +godot_int GDAPI godot_string_findmk_from_in_place(const godot_string *p_self, const godot_packed_string_array *p_keys, godot_int p_from, godot_int *r_key); +godot_int GDAPI godot_string_findn(const godot_string *p_self, const godot_string *p_what); +godot_int GDAPI godot_string_findn_from(const godot_string *p_self, const godot_string *p_what, godot_int p_from); godot_string GDAPI godot_string_format(const godot_string *p_self, const godot_variant *p_values); godot_string GDAPI godot_string_format_with_custom_placeholder(const godot_string *p_self, const godot_variant *p_values, const char *p_placeholder); godot_string GDAPI godot_string_hex_encode_buffer(const uint8_t *p_buffer, godot_int p_len); godot_int GDAPI godot_string_hex_to_int(const godot_string *p_self); -godot_int GDAPI godot_string_hex_to_int_without_prefix(const godot_string *p_self); -godot_string GDAPI godot_string_insert(const godot_string *p_self, godot_int p_at_pos, godot_string p_string); +godot_int GDAPI godot_string_hex_to_int_with_prefix(const godot_string *p_self); +godot_string GDAPI godot_string_insert(const godot_string *p_self, godot_int p_at_pos, const godot_string *p_string); godot_bool GDAPI godot_string_is_numeric(const godot_string *p_self); godot_bool GDAPI godot_string_is_subsequence_of(const godot_string *p_self, const godot_string *p_string); godot_bool GDAPI godot_string_is_subsequence_ofi(const godot_string *p_self, const godot_string *p_string); @@ -133,13 +157,13 @@ godot_string GDAPI godot_string_num_scientific(double p_num); godot_string GDAPI godot_string_num_with_decimals(double p_num, godot_int p_decimals); godot_string GDAPI godot_string_pad_decimals(const godot_string *p_self, godot_int p_digits); godot_string GDAPI godot_string_pad_zeros(const godot_string *p_self, godot_int p_digits); -godot_string GDAPI godot_string_replace_first(const godot_string *p_self, godot_string p_key, godot_string p_with); -godot_string GDAPI godot_string_replace(const godot_string *p_self, godot_string p_key, godot_string p_with); -godot_string GDAPI godot_string_replacen(const godot_string *p_self, godot_string p_key, godot_string p_with); -godot_int GDAPI godot_string_rfind(const godot_string *p_self, godot_string p_what); -godot_int GDAPI godot_string_rfindn(const godot_string *p_self, godot_string p_what); -godot_int GDAPI godot_string_rfind_from(const godot_string *p_self, godot_string p_what, godot_int p_from); -godot_int GDAPI godot_string_rfindn_from(const godot_string *p_self, godot_string p_what, godot_int p_from); +godot_string GDAPI godot_string_replace_first(const godot_string *p_self, const godot_string *p_key, const godot_string *p_with); +godot_string GDAPI godot_string_replace(const godot_string *p_self, const godot_string *p_key, const godot_string *p_with); +godot_string GDAPI godot_string_replacen(const godot_string *p_self, const godot_string *p_key, const godot_string *p_with); +godot_int GDAPI godot_string_rfind(const godot_string *p_self, const godot_string *p_what); +godot_int GDAPI godot_string_rfindn(const godot_string *p_self, const godot_string *p_what); +godot_int GDAPI godot_string_rfind_from(const godot_string *p_self, const godot_string *p_what, godot_int p_from); +godot_int GDAPI godot_string_rfindn_from(const godot_string *p_self, const godot_string *p_what, godot_int p_from); godot_string GDAPI godot_string_rpad(const godot_string *p_self, godot_int p_min_length); godot_string GDAPI godot_string_rpad_with_custom_character(const godot_string *p_self, godot_int p_min_length, const godot_string *p_character); godot_real GDAPI godot_string_similarity(const godot_string *p_self, const godot_string *p_string); @@ -151,64 +175,79 @@ godot_int GDAPI godot_string_to_int(const godot_string *p_self); godot_string GDAPI godot_string_camelcase_to_underscore(const godot_string *p_self); godot_string GDAPI godot_string_camelcase_to_underscore_lowercased(const godot_string *p_self); godot_string GDAPI godot_string_capitalize(const godot_string *p_self); + double GDAPI godot_string_char_to_float(const char *p_what); +double GDAPI godot_string_wchar_to_float(const wchar_t *p_str, const wchar_t **r_end); + godot_int GDAPI godot_string_char_to_int(const char *p_what); -int64_t GDAPI godot_string_wchar_to_int(const wchar_t *p_str); +godot_int GDAPI godot_string_wchar_to_int(const wchar_t *p_str); + godot_int GDAPI godot_string_char_to_int_with_len(const char *p_what, godot_int p_len); -int64_t GDAPI godot_string_char_to_int64_with_len(const wchar_t *p_str, int p_len); -int64_t GDAPI godot_string_hex_to_int64(const godot_string *p_self); -int64_t GDAPI godot_string_hex_to_int64_with_prefix(const godot_string *p_self); -int64_t GDAPI godot_string_to_int64(const godot_string *p_self); -double GDAPI godot_string_unicode_char_to_float(const wchar_t *p_str, const wchar_t **r_end); - -godot_int GDAPI godot_string_get_slice_count(const godot_string *p_self, godot_string p_splitter); -godot_string GDAPI godot_string_get_slice(const godot_string *p_self, godot_string p_splitter, godot_int p_slice); -godot_string GDAPI godot_string_get_slicec(const godot_string *p_self, wchar_t p_splitter, godot_int p_slice); - -godot_array GDAPI godot_string_split(const godot_string *p_self, const godot_string *p_splitter); -godot_array GDAPI godot_string_split_allow_empty(const godot_string *p_self, const godot_string *p_splitter); -godot_array GDAPI godot_string_split_floats(const godot_string *p_self, const godot_string *p_splitter); -godot_array GDAPI godot_string_split_floats_allows_empty(const godot_string *p_self, const godot_string *p_splitter); -godot_array GDAPI godot_string_split_floats_mk(const godot_string *p_self, const godot_array *p_splitters); -godot_array GDAPI godot_string_split_floats_mk_allows_empty(const godot_string *p_self, const godot_array *p_splitters); -godot_array GDAPI godot_string_split_ints(const godot_string *p_self, const godot_string *p_splitter); -godot_array GDAPI godot_string_split_ints_allows_empty(const godot_string *p_self, const godot_string *p_splitter); -godot_array GDAPI godot_string_split_ints_mk(const godot_string *p_self, const godot_array *p_splitters); -godot_array GDAPI godot_string_split_ints_mk_allows_empty(const godot_string *p_self, const godot_array *p_splitters); -godot_array GDAPI godot_string_split_spaces(const godot_string *p_self); - -wchar_t GDAPI godot_string_char_lowercase(wchar_t p_char); -wchar_t GDAPI godot_string_char_uppercase(wchar_t p_char); +godot_int GDAPI godot_string_wchar_to_int_with_len(const wchar_t *p_str, int p_len); + +godot_int GDAPI godot_string_get_slice_count(const godot_string *p_self, const godot_string *p_splitter); +godot_string GDAPI godot_string_get_slice(const godot_string *p_self, const godot_string *p_splitter, godot_int p_slice); +godot_string GDAPI godot_string_get_slicec(const godot_string *p_self, godot_char_type p_splitter, godot_int p_slice); + +godot_packed_string_array GDAPI godot_string_split(const godot_string *p_self, const godot_string *p_splitter); +godot_packed_string_array GDAPI godot_string_split_allow_empty(const godot_string *p_self, const godot_string *p_splitter); +godot_packed_string_array GDAPI godot_string_split_with_maxsplit(const godot_string *p_self, const godot_string *p_splitter, const godot_bool p_allow_empty, const godot_int p_maxsplit); + +godot_packed_string_array GDAPI godot_string_rsplit(const godot_string *p_self, const godot_string *p_splitter); +godot_packed_string_array GDAPI godot_string_rsplit_allow_empty(const godot_string *p_self, const godot_string *p_splitter); +godot_packed_string_array GDAPI godot_string_rsplit_with_maxsplit(const godot_string *p_self, const godot_string *p_splitter, const godot_bool p_allow_empty, const godot_int p_maxsplit); + +godot_packed_float32_array GDAPI godot_string_split_floats(const godot_string *p_self, const godot_string *p_splitter); +godot_packed_float32_array GDAPI godot_string_split_floats_allow_empty(const godot_string *p_self, const godot_string *p_splitter); +godot_packed_float32_array GDAPI godot_string_split_floats_mk(const godot_string *p_self, const godot_packed_string_array *p_splitters); +godot_packed_float32_array GDAPI godot_string_split_floats_mk_allow_empty(const godot_string *p_self, const godot_packed_string_array *p_splitters); +godot_packed_int32_array GDAPI godot_string_split_ints(const godot_string *p_self, const godot_string *p_splitter); +godot_packed_int32_array GDAPI godot_string_split_ints_allow_empty(const godot_string *p_self, const godot_string *p_splitter); +godot_packed_int32_array GDAPI godot_string_split_ints_mk(const godot_string *p_self, const godot_packed_string_array *p_splitters); +godot_packed_int32_array GDAPI godot_string_split_ints_mk_allow_empty(const godot_string *p_self, const godot_packed_string_array *p_splitters); + +godot_packed_string_array GDAPI godot_string_split_spaces(const godot_string *p_self); + +godot_char_type GDAPI godot_string_char_lowercase(godot_char_type p_char); +godot_char_type GDAPI godot_string_char_uppercase(godot_char_type p_char); godot_string GDAPI godot_string_to_lower(const godot_string *p_self); godot_string GDAPI godot_string_to_upper(const godot_string *p_self); godot_string GDAPI godot_string_get_basename(const godot_string *p_self); godot_string GDAPI godot_string_get_extension(const godot_string *p_self); godot_string GDAPI godot_string_left(const godot_string *p_self, godot_int p_pos); -wchar_t GDAPI godot_string_ord_at(const godot_string *p_self, godot_int p_idx); +godot_char_type GDAPI godot_string_ord_at(const godot_string *p_self, godot_int p_idx); godot_string GDAPI godot_string_plus_file(const godot_string *p_self, const godot_string *p_file); godot_string GDAPI godot_string_right(const godot_string *p_self, godot_int p_pos); +godot_string GDAPI godot_string_repeat(const godot_string *p_self, godot_int p_count); godot_string GDAPI godot_string_strip_edges(const godot_string *p_self, godot_bool p_left, godot_bool p_right); godot_string GDAPI godot_string_strip_escapes(const godot_string *p_self); void GDAPI godot_string_erase(godot_string *p_self, godot_int p_pos, godot_int p_chars); godot_char_string GDAPI godot_string_ascii(const godot_string *p_self); -godot_char_string GDAPI godot_string_ascii_extended(const godot_string *p_self); +godot_char_string GDAPI godot_string_latin1(const godot_string *p_self); + godot_char_string GDAPI godot_string_utf8(const godot_string *p_self); godot_bool GDAPI godot_string_parse_utf8(godot_string *p_self, const char *p_utf8); godot_bool GDAPI godot_string_parse_utf8_with_len(godot_string *p_self, const char *p_utf8, godot_int p_len); -godot_string GDAPI godot_string_chars_to_utf8(const char *p_utf8); -godot_string GDAPI godot_string_chars_to_utf8_with_len(const char *p_utf8, godot_int p_len); + +godot_char16_string GDAPI godot_string_utf16(const godot_string *p_self); +godot_bool GDAPI godot_string_parse_utf16(godot_string *p_self, const char16_t *p_utf16); +godot_bool GDAPI godot_string_parse_utf16_with_len(godot_string *p_self, const char16_t *p_utf16, godot_int p_len); uint32_t GDAPI godot_string_hash(const godot_string *p_self); uint64_t GDAPI godot_string_hash64(const godot_string *p_self); + uint32_t GDAPI godot_string_hash_chars(const char *p_cstr); uint32_t GDAPI godot_string_hash_chars_with_len(const char *p_cstr, godot_int p_len); -uint32_t GDAPI godot_string_hash_utf8_chars(const wchar_t *p_str); -uint32_t GDAPI godot_string_hash_utf8_chars_with_len(const wchar_t *p_str, godot_int p_len); +uint32_t GDAPI godot_string_hash_wide_chars(const wchar_t *p_str); +uint32_t GDAPI godot_string_hash_wide_chars_with_len(const wchar_t *p_str, godot_int p_len); + godot_packed_byte_array GDAPI godot_string_md5_buffer(const godot_string *p_self); godot_string GDAPI godot_string_md5_text(const godot_string *p_self); +godot_packed_byte_array GDAPI godot_string_sha1_buffer(const godot_string *p_self); +godot_string GDAPI godot_string_sha1_text(const godot_string *p_self); godot_packed_byte_array GDAPI godot_string_sha256_buffer(const godot_string *p_self); godot_string GDAPI godot_string_sha256_text(const godot_string *p_self); @@ -231,14 +270,15 @@ godot_string GDAPI godot_string_c_unescape(const godot_string *p_self); godot_string GDAPI godot_string_http_escape(const godot_string *p_self); godot_string GDAPI godot_string_http_unescape(const godot_string *p_self); godot_string GDAPI godot_string_json_escape(const godot_string *p_self); -godot_string GDAPI godot_string_word_wrap(const godot_string *p_self, godot_int p_chars_per_line); godot_string GDAPI godot_string_xml_escape(const godot_string *p_self); godot_string GDAPI godot_string_xml_escape_with_quotes(const godot_string *p_self); godot_string GDAPI godot_string_xml_unescape(const godot_string *p_self); godot_string GDAPI godot_string_percent_decode(const godot_string *p_self); godot_string GDAPI godot_string_percent_encode(const godot_string *p_self); +godot_string GDAPI godot_string_join(const godot_string *p_self, const godot_packed_string_array *p_parts); +godot_bool GDAPI godot_string_is_valid_filename(const godot_string *p_self); godot_bool GDAPI godot_string_is_valid_float(const godot_string *p_self); godot_bool GDAPI godot_string_is_valid_hex_number(const godot_string *p_self, godot_bool p_with_prefix); godot_bool GDAPI godot_string_is_valid_html_color(const godot_string *p_self); @@ -249,8 +289,8 @@ godot_bool GDAPI godot_string_is_valid_ip_address(const godot_string *p_self); godot_string GDAPI godot_string_dedent(const godot_string *p_self); godot_string GDAPI godot_string_trim_prefix(const godot_string *p_self, const godot_string *p_prefix); godot_string GDAPI godot_string_trim_suffix(const godot_string *p_self, const godot_string *p_suffix); +godot_string GDAPI godot_string_lstrip(const godot_string *p_self, const godot_string *p_chars); godot_string GDAPI godot_string_rstrip(const godot_string *p_self, const godot_string *p_chars); -godot_packed_string_array GDAPI godot_string_rsplit(const godot_string *p_self, const godot_string *p_divisor, const godot_bool p_allow_empty, const godot_int p_maxsplit); void GDAPI godot_string_destroy(godot_string *p_self); diff --git a/modules/gdnative/nativescript/api_generator.cpp b/modules/gdnative/nativescript/api_generator.cpp index 019fa0d1f8..8dbaec4e75 100644 --- a/modules/gdnative/nativescript/api_generator.cpp +++ b/modules/gdnative/nativescript/api_generator.cpp @@ -176,10 +176,10 @@ List<ClassAPI> generate_c_api_classes() { // Register global constants as a fake GlobalConstants singleton class { ClassAPI global_constants_api; - global_constants_api.class_name = L"GlobalConstants"; + global_constants_api.class_name = "GlobalConstants"; global_constants_api.api_type = ClassDB::API_CORE; global_constants_api.is_singleton = true; - global_constants_api.singleton_name = L"GlobalConstants"; + global_constants_api.singleton_name = "GlobalConstants"; global_constants_api.is_instanciable = false; const int constants_count = GlobalConstants::get_global_constant_count(); for (int i = 0; i < constants_count; ++i) { diff --git a/modules/gdnative/tests/test_string.h b/modules/gdnative/tests/test_string.h new file mode 100644 index 0000000000..aeb855a1c4 --- /dev/null +++ b/modules/gdnative/tests/test_string.h @@ -0,0 +1,1980 @@ +/*************************************************************************/ +/* test_string.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_GDNATIVE_STRING_H +#define TEST_GDNATIVE_STRING_H + +namespace TestGDNativeString { + +#include "gdnative/string.h" + +#include "tests/test_macros.h" + +int u32scmp(const char32_t *l, const char32_t *r) { + for (; *l == *r && *l && *r; l++, r++) + ; + return *l - *r; +} + +TEST_CASE("[GDNative String] Construct from Latin-1 char string") { + godot_string s; + + godot_string_new_with_latin1_chars(&s, "Hello"); + CHECK(u32scmp(godot_string_get_data(&s), U"Hello") == 0); + godot_string_destroy(&s); + + godot_string_new_with_latin1_chars_and_len(&s, "Hello", 3); + CHECK(u32scmp(godot_string_get_data(&s), U"Hel") == 0); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Construct from wchar_t string") { + godot_string s; + + godot_string_new_with_wide_chars(&s, L"Give me"); + CHECK(u32scmp(godot_string_get_data(&s), U"Give me") == 0); + godot_string_destroy(&s); + + godot_string_new_with_wide_chars_and_len(&s, L"Give me", 3); + CHECK(u32scmp(godot_string_get_data(&s), U"Giv") == 0); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Construct from UTF-8 char string") { + static const char32_t u32str[] = { 0x0045, 0x0020, 0x304A, 0x360F, 0x3088, 0x3046, 0x1F3A4, 0 }; + static const char32_t u32str_short[] = { 0x0045, 0x0020, 0x304A, 0 }; + static const uint8_t u8str[] = { 0x45, 0x20, 0xE3, 0x81, 0x8A, 0xE3, 0x98, 0x8F, 0xE3, 0x82, 0x88, 0xE3, 0x81, 0x86, 0xF0, 0x9F, 0x8E, 0xA4, 0 }; + + godot_string s; + + godot_string_new_with_utf8_chars(&s, (const char *)u8str); + CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0); + godot_string_destroy(&s); + + godot_string_new_with_utf8_chars_and_len(&s, (const char *)u8str, 5); + CHECK(u32scmp(godot_string_get_data(&s), u32str_short) == 0); + godot_string_destroy(&s); + + godot_string_new_with_utf32_chars(&s, u32str); + godot_char_string cs = godot_string_utf8(&s); + godot_string_parse_utf8(&s, godot_char_string_get_data(&cs)); + CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0); + godot_string_destroy(&s); + godot_char_string_destroy(&cs); + + godot_string_new_with_utf32_chars(&s, u32str); + cs = godot_string_utf8(&s); + godot_string_parse_utf8_with_len(&s, godot_char_string_get_data(&cs), godot_char_string_length(&cs)); + CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0); + godot_string_destroy(&s); + godot_char_string_destroy(&cs); +} + +TEST_CASE("[GDNative String] Construct from UTF-8 string with BOM") { + static const char32_t u32str[] = { 0x0045, 0x0020, 0x304A, 0x360F, 0x3088, 0x3046, 0x1F3A4, 0 }; + static const char32_t u32str_short[] = { 0x0045, 0x0020, 0x304A, 0 }; + static const uint8_t u8str[] = { 0xEF, 0xBB, 0xBF, 0x45, 0x20, 0xE3, 0x81, 0x8A, 0xE3, 0x98, 0x8F, 0xE3, 0x82, 0x88, 0xE3, 0x81, 0x86, 0xF0, 0x9F, 0x8E, 0xA4, 0 }; + + godot_string s; + + godot_string_new_with_utf8_chars(&s, (const char *)u8str); + CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0); + godot_string_destroy(&s); + + godot_string_new_with_utf8_chars_and_len(&s, (const char *)u8str, 8); + CHECK(u32scmp(godot_string_get_data(&s), u32str_short) == 0); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Construct from UTF-16 string") { + static const char32_t u32str[] = { 0x0045, 0x0020, 0x1F3A4, 0x360F, 0x3088, 0x3046, 0x1F3A4, 0 }; + static const char32_t u32str_short[] = { 0x0045, 0x0020, 0x1F3A4, 0 }; + static const char16_t u16str[] = { 0x0045, 0x0020, 0xD83C, 0xDFA4, 0x360F, 0x3088, 0x3046, 0xD83C, 0xDFA4, 0 }; + + godot_string s; + + godot_string_new_with_utf16_chars(&s, u16str); + CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0); + godot_string_destroy(&s); + + godot_string_new_with_utf16_chars_and_len(&s, u16str, 4); + CHECK(u32scmp(godot_string_get_data(&s), u32str_short) == 0); + godot_string_destroy(&s); + + godot_string_new_with_utf32_chars(&s, u32str); + godot_char16_string cs = godot_string_utf16(&s); + godot_string_parse_utf16(&s, godot_char16_string_get_data(&cs)); + CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0); + godot_string_destroy(&s); + godot_char16_string_destroy(&cs); + + godot_string_new_with_utf32_chars(&s, u32str); + cs = godot_string_utf16(&s); + godot_string_parse_utf16_with_len(&s, godot_char16_string_get_data(&cs), godot_char16_string_length(&cs)); + CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0); + godot_string_destroy(&s); + godot_char16_string_destroy(&cs); +} + +TEST_CASE("[GDNative String] Construct from UTF-16 string with BOM ") { + static const char32_t u32str[] = { 0x0045, 0x0020, 0x1F3A4, 0x360F, 0x3088, 0x3046, 0x1F3A4, 0 }; + static const char32_t u32str_short[] = { 0x0045, 0x0020, 0x1F3A4, 0 }; + static const char16_t u16str[] = { 0xFEFF, 0x0045, 0x0020, 0xD83C, 0xDFA4, 0x360F, 0x3088, 0x3046, 0xD83C, 0xDFA4, 0 }; + static const char16_t u16str_swap[] = { 0xFFFE, 0x4500, 0x2000, 0x3CD8, 0xA4DF, 0x0F36, 0x8830, 0x4630, 0x3CD8, 0xA4DF, 0 }; + + godot_string s; + + godot_string_new_with_utf16_chars(&s, u16str); + CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0); + godot_string_destroy(&s); + + godot_string_new_with_utf16_chars(&s, u16str_swap); + CHECK(u32scmp(godot_string_get_data(&s), u32str) == 0); + godot_string_destroy(&s); + + godot_string_new_with_utf16_chars_and_len(&s, u16str, 5); + CHECK(u32scmp(godot_string_get_data(&s), u32str_short) == 0); + godot_string_destroy(&s); + + godot_string_new_with_utf16_chars_and_len(&s, u16str_swap, 5); + CHECK(u32scmp(godot_string_get_data(&s), u32str_short) == 0); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Construct string copy") { + godot_string s, t; + + godot_string_new_with_latin1_chars(&s, "Hello"); + godot_string_new_copy(&t, &s); + CHECK(u32scmp(godot_string_get_data(&t), U"Hello") == 0); + godot_string_destroy(&t); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Construct empty string") { + godot_string s; + + godot_string_new(&s); + CHECK(u32scmp(godot_string_get_data(&s), U"") == 0); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] ASCII/Latin-1") { + godot_string s; + godot_string_new_with_utf32_chars(&s, U"Primero Leche"); + + godot_char_string cs = godot_string_ascii(&s); + CHECK(strcmp(godot_char_string_get_data(&cs), "Primero Leche") == 0); + godot_char_string_destroy(&cs); + + cs = godot_string_latin1(&s); + CHECK(strcmp(godot_char_string_get_data(&cs), "Primero Leche") == 0); + godot_char_string_destroy(&cs); + + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Comparisons (equal)") { + godot_string s, t; + + godot_string_new_with_latin1_chars(&s, "Test Compare"); + godot_string_new_with_latin1_chars(&t, "Test Compare"); + CHECK(godot_string_operator_equal(&s, &t)); + godot_string_destroy(&s); + godot_string_destroy(&t); +} + +TEST_CASE("[GDNative String] Comparisons (operator <)") { + godot_string s, t; + + godot_string_new_with_latin1_chars(&s, "Bees"); + + godot_string_new_with_latin1_chars(&t, "Elephant"); + CHECK(godot_string_operator_less(&s, &t)); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "Amber"); + CHECK(!godot_string_operator_less(&s, &t)); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "Beatrix"); + CHECK(!godot_string_operator_less(&s, &t)); + godot_string_destroy(&t); + + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Concatenation (operator +)") { + godot_string s, t, x; + + godot_string_new_with_latin1_chars(&s, "Hel"); + godot_string_new_with_latin1_chars(&t, "lo"); + x = godot_string_operator_plus(&s, &t); + CHECK(u32scmp(godot_string_get_data(&x), U"Hello") == 0); + godot_string_destroy(&x); + godot_string_destroy(&s); + godot_string_destroy(&t); +} + +TEST_CASE("[GDNative String] Testing size and length of string") { + godot_string s; + + godot_string_new_with_latin1_chars(&s, "Mellon"); + CHECK(godot_string_length(&s) == 6); + godot_string_destroy(&s); + + godot_string_new_with_latin1_chars(&s, "Mellon1"); + CHECK(godot_string_length(&s) == 7); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Testing for empty string") { + godot_string s; + + godot_string_new_with_latin1_chars(&s, "Mellon"); + CHECK(!godot_string_empty(&s)); + godot_string_destroy(&s); + + godot_string_new_with_latin1_chars(&s, ""); + CHECK(godot_string_empty(&s)); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Test chr") { + godot_string s; + + s = godot_string_chr('H'); + CHECK(u32scmp(godot_string_get_data(&s), U"H") == 0); + godot_string_destroy(&s); + + s = godot_string_chr(0x3012); + CHECK(godot_string_operator_index_const(&s, 0) == 0x3012); + godot_string_destroy(&s); + + ERR_PRINT_OFF + s = godot_string_chr(0xd812); + CHECK(godot_string_operator_index_const(&s, 0) == 0xfffd); // Unpaired UTF-16 surrogate + godot_string_destroy(&s); + + s = godot_string_chr(0x20d812); + CHECK(godot_string_operator_index_const(&s, 0) == 0xfffd); // Outside UTF-32 range + godot_string_destroy(&s); + ERR_PRINT_ON +} + +TEST_CASE("[GDNative String] Operator []") { + godot_string s; + + godot_string_new_with_latin1_chars(&s, "Hello"); + CHECK(*godot_string_operator_index(&s, 1) == 'e'); + CHECK(godot_string_operator_index_const(&s, 0) == 'H'); + CHECK(godot_string_ord_at(&s, 0) == 'H'); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Case function test") { + godot_string s, t; + + godot_string_new_with_latin1_chars(&s, "MoMoNgA"); + + t = godot_string_to_upper(&s); + CHECK(u32scmp(godot_string_get_data(&t), U"MOMONGA") == 0); + godot_string_destroy(&t); + + t = godot_string_to_lower(&s); + CHECK(u32scmp(godot_string_get_data(&t), U"momonga") == 0); + godot_string_destroy(&t); + + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Case compare function test") { + godot_string s, t; + + godot_string_new_with_latin1_chars(&s, "MoMoNgA"); + godot_string_new_with_latin1_chars(&t, "momonga"); + + CHECK(godot_string_casecmp_to(&s, &t) != 0); + CHECK(godot_string_nocasecmp_to(&s, &t) == 0); + + godot_string_destroy(&s); + godot_string_destroy(&t); +} + +TEST_CASE("[GDNative String] Natural compare function test") { + godot_string s, t; + + godot_string_new_with_latin1_chars(&s, "img2.png"); + godot_string_new_with_latin1_chars(&t, "img10.png"); + + CHECK(godot_string_nocasecmp_to(&s, &t) > 0); + CHECK(godot_string_naturalnocasecmp_to(&s, &t) < 0); + + godot_string_destroy(&s); + godot_string_destroy(&t); +} + +TEST_CASE("[GDNative String] hex_encode_buffer") { + static const uint8_t u8str[] = { 0x45, 0xE3, 0x81, 0x8A, 0x8F, 0xE3 }; + godot_string s = godot_string_hex_encode_buffer(u8str, 6); + CHECK(u32scmp(godot_string_get_data(&s), U"45e3818a8fe3") == 0); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Substr") { + godot_string s, t; + godot_string_new_with_latin1_chars(&s, "Killer Baby"); + t = godot_string_substr(&s, 3, 4); + CHECK(u32scmp(godot_string_get_data(&t), U"ler ") == 0); + godot_string_destroy(&t); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Find") { + godot_string s, t; + godot_string_new_with_latin1_chars(&s, "Pretty Woman Woman"); + + godot_string_new_with_latin1_chars(&t, "Revenge of the Monster Truck"); + CHECK(godot_string_find(&s, &t) == -1); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "tty"); + CHECK(godot_string_find(&s, &t) == 3); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "Wo"); + CHECK(godot_string_find_from(&s, &t, 9) == 13); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "man"); + CHECK(godot_string_rfind(&s, &t) == 15); + godot_string_destroy(&t); + + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Find no case") { + godot_string s, t; + godot_string_new_with_latin1_chars(&s, "Pretty Whale Whale"); + + godot_string_new_with_latin1_chars(&t, "WHA"); + CHECK(godot_string_findn(&s, &t) == 7); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "WHA"); + CHECK(godot_string_findn_from(&s, &t, 9) == 13); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "WHA"); + CHECK(godot_string_rfindn(&s, &t) == 13); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "Revenge of the Monster SawFish"); + CHECK(godot_string_findn(&s, &t) == -1); + godot_string_destroy(&t); + + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Find MK") { + godot_packed_string_array keys; + godot_packed_string_array_new(&keys); + +#define PUSH_KEY(x) \ + { \ + godot_string t; \ + godot_string_new_with_latin1_chars(&t, x); \ + godot_packed_string_array_push_back(&keys, &t); \ + godot_string_destroy(&t); \ + } + + PUSH_KEY("sty") + PUSH_KEY("tty") + PUSH_KEY("man") + + godot_string s; + godot_string_new_with_latin1_chars(&s, "Pretty Woman"); + godot_int key = 0; + + CHECK(godot_string_findmk(&s, &keys) == 3); + CHECK(godot_string_findmk_from_in_place(&s, &keys, 0, &key) == 3); + CHECK(key == 1); + + CHECK(godot_string_findmk_from(&s, &keys, 5) == 9); + CHECK(godot_string_findmk_from_in_place(&s, &keys, 5, &key) == 9); + CHECK(key == 2); + + godot_string_destroy(&s); + godot_packed_string_array_destroy(&keys); + +#undef PUSH_KEY +} + +TEST_CASE("[GDNative String] Find and replace") { + godot_string s, c, w; + godot_string_new_with_latin1_chars(&s, "Happy Birthday, Anna!"); + godot_string_new_with_latin1_chars(&c, "Birthday"); + godot_string_new_with_latin1_chars(&w, "Halloween"); + godot_string t = godot_string_replace(&s, &c, &w); + CHECK(u32scmp(godot_string_get_data(&t), U"Happy Halloween, Anna!") == 0); + godot_string_destroy(&s); + godot_string_destroy(&c); + godot_string_destroy(&w); + + godot_string_new_with_latin1_chars(&c, "H"); + godot_string_new_with_latin1_chars(&w, "W"); + s = godot_string_replace_first(&t, &c, &w); + godot_string_destroy(&t); + godot_string_destroy(&c); + godot_string_destroy(&w); + + CHECK(u32scmp(godot_string_get_data(&s), U"Wappy Halloween, Anna!") == 0); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Insertion") { + godot_string s, t, r, u; + godot_string_new_with_latin1_chars(&s, "Who is Frederic?"); + godot_string_new_with_latin1_chars(&t, "?"); + godot_string_new_with_latin1_chars(&r, " Chopin"); + + u = godot_string_insert(&s, godot_string_find(&s, &t), &r); + CHECK(u32scmp(godot_string_get_data(&u), U"Who is Frederic Chopin?") == 0); + + godot_string_destroy(&s); + godot_string_destroy(&t); + godot_string_destroy(&r); + godot_string_destroy(&u); +} + +TEST_CASE("[GDNative String] Number to string") { + godot_string s; + s = godot_string_num(3.141593); + CHECK(u32scmp(godot_string_get_data(&s), U"3.141593") == 0); + godot_string_destroy(&s); + + s = godot_string_num_with_decimals(3.141593, 3); + CHECK(u32scmp(godot_string_get_data(&s), U"3.142") == 0); + godot_string_destroy(&s); + + s = godot_string_num_real(3.141593); + CHECK(u32scmp(godot_string_get_data(&s), U"3.141593") == 0); + godot_string_destroy(&s); + + s = godot_string_num_scientific(30000000); + CHECK(u32scmp(godot_string_get_data(&s), U"3e+07") == 0); + godot_string_destroy(&s); + + s = godot_string_num_int64(3141593, 10); + CHECK(u32scmp(godot_string_get_data(&s), U"3141593") == 0); + godot_string_destroy(&s); + + s = godot_string_num_int64(0xA141593, 16); + CHECK(u32scmp(godot_string_get_data(&s), U"a141593") == 0); + godot_string_destroy(&s); + + s = godot_string_num_int64_capitalized(0xA141593, 16, true); + CHECK(u32scmp(godot_string_get_data(&s), U"A141593") == 0); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] String to integer") { + static const wchar_t *wnums[4] = { L"1237461283", L"- 22", L"0", L" - 1123412" }; + static const char *nums[4] = { "1237461283", "- 22", "0", " - 1123412" }; + static const int num[4] = { 1237461283, -22, 0, -1123412 }; + + for (int i = 0; i < 4; i++) { + godot_string s; + godot_string_new_with_latin1_chars(&s, nums[i]); + CHECK(godot_string_to_int(&s) == num[i]); + godot_string_destroy(&s); + + CHECK(godot_string_char_to_int(nums[i]) == num[i]); + CHECK(godot_string_wchar_to_int(wnums[i]) == num[i]); + } +} + +TEST_CASE("[GDNative String] Hex to integer") { + static const char *nums[4] = { "0xFFAE", "22", "0", "AADDAD" }; + static const int64_t num[4] = { 0xFFAE, 0x22, 0, 0xAADDAD }; + static const bool wo_prefix[4] = { false, true, true, true }; + static const bool w_prefix[4] = { true, false, true, false }; + + for (int i = 0; i < 4; i++) { + godot_string s; + godot_string_new_with_latin1_chars(&s, nums[i]); + CHECK((godot_string_hex_to_int_with_prefix(&s) == num[i]) == w_prefix[i]); + CHECK((godot_string_hex_to_int(&s) == num[i]) == wo_prefix[i]); + godot_string_destroy(&s); + } +} + +TEST_CASE("[GDNative String] String to float") { + static const wchar_t *wnums[4] = { L"-12348298412.2", L"0.05", L"2.0002", L" -0.0001" }; + static const char *nums[4] = { "-12348298412.2", "0.05", "2.0002", " -0.0001" }; + static const double num[4] = { -12348298412.2, 0.05, 2.0002, -0.0001 }; + + for (int i = 0; i < 4; i++) { + godot_string s; + godot_string_new_with_latin1_chars(&s, nums[i]); + CHECK(!(ABS(godot_string_to_float(&s) - num[i]) > 0.00001)); + godot_string_destroy(&s); + + CHECK(!(ABS(godot_string_char_to_float(nums[i]) - num[i]) > 0.00001)); + CHECK(!(ABS(godot_string_wchar_to_float(wnums[i], nullptr) - num[i]) > 0.00001)); + } +} + +TEST_CASE("[GDNative String] CamelCase to underscore") { + godot_string s, t; + godot_string_new_with_latin1_chars(&s, "TestTestStringGD"); + + t = godot_string_camelcase_to_underscore(&s); + CHECK(u32scmp(godot_string_get_data(&t), U"Test_Test_String_GD") == 0); + godot_string_destroy(&t); + + t = godot_string_camelcase_to_underscore_lowercased(&s); + CHECK(u32scmp(godot_string_get_data(&t), U"test_test_string_gd") == 0); + godot_string_destroy(&t); + + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Slicing") { + godot_string s, c; + godot_string_new_with_latin1_chars(&s, "Mars,Jupiter,Saturn,Uranus"); + godot_string_new_with_latin1_chars(&c, ","); + + const char32_t *slices[4] = { U"Mars", U"Jupiter", U"Saturn", U"Uranus" }; + for (int i = 0; i < godot_string_get_slice_count(&s, &c); i++) { + godot_string t; + t = godot_string_get_slice(&s, &c, i); + CHECK(u32scmp(godot_string_get_data(&t), slices[i]) == 0); + godot_string_destroy(&t); + + t = godot_string_get_slicec(&s, U',', i); + CHECK(u32scmp(godot_string_get_data(&t), slices[i]) == 0); + godot_string_destroy(&t); + } + + godot_string_destroy(&c); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Splitting") { + godot_string s, c; + godot_string_new_with_latin1_chars(&s, "Mars,Jupiter,Saturn,Uranus"); + godot_string_new_with_latin1_chars(&c, ","); + + godot_packed_string_array l; + + const char32_t *slices_l[3] = { U"Mars", U"Jupiter", U"Saturn,Uranus" }; + const char32_t *slices_r[3] = { U"Mars,Jupiter", U"Saturn", U"Uranus" }; + + l = godot_string_split_with_maxsplit(&s, &c, true, 2); + CHECK(godot_packed_string_array_size(&l) == 3); + for (int i = 0; i < godot_packed_string_array_size(&l); i++) { + godot_string t = godot_packed_string_array_get(&l, i); + CHECK(u32scmp(godot_string_get_data(&t), slices_l[i]) == 0); + godot_string_destroy(&t); + } + godot_packed_string_array_destroy(&l); + + l = godot_string_rsplit_with_maxsplit(&s, &c, true, 2); + CHECK(godot_packed_string_array_size(&l) == 3); + for (int i = 0; i < godot_packed_string_array_size(&l); i++) { + godot_string t = godot_packed_string_array_get(&l, i); + CHECK(u32scmp(godot_string_get_data(&t), slices_r[i]) == 0); + godot_string_destroy(&t); + } + godot_packed_string_array_destroy(&l); + godot_string_destroy(&s); + + godot_string_new_with_latin1_chars(&s, "Mars Jupiter Saturn Uranus"); + const char32_t *slices_s[4] = { U"Mars", U"Jupiter", U"Saturn", U"Uranus" }; + l = godot_string_split_spaces(&s); + for (int i = 0; i < godot_packed_string_array_size(&l); i++) { + godot_string t = godot_packed_string_array_get(&l, i); + CHECK(u32scmp(godot_string_get_data(&t), slices_s[i]) == 0); + godot_string_destroy(&t); + } + godot_packed_string_array_destroy(&l); + godot_string_destroy(&s); + + godot_string c1, c2; + godot_string_new_with_latin1_chars(&c1, ";"); + godot_string_new_with_latin1_chars(&c2, " "); + + godot_string_new_with_latin1_chars(&s, "1.2;2.3 4.5"); + const double slices_d[3] = { 1.2, 2.3, 4.5 }; + + godot_packed_float32_array lf = godot_string_split_floats_allow_empty(&s, &c1); + CHECK(godot_packed_float32_array_size(&lf) == 2); + for (int i = 0; i < godot_packed_float32_array_size(&lf); i++) { + CHECK(ABS(godot_packed_float32_array_get(&lf, i) - slices_d[i]) <= 0.00001); + } + godot_packed_float32_array_destroy(&lf); + + godot_packed_string_array keys; + godot_packed_string_array_new(&keys); + godot_packed_string_array_push_back(&keys, &c1); + godot_packed_string_array_push_back(&keys, &c2); + + lf = godot_string_split_floats_mk_allow_empty(&s, &keys); + CHECK(godot_packed_float32_array_size(&lf) == 3); + for (int i = 0; i < godot_packed_float32_array_size(&lf); i++) { + CHECK(ABS(godot_packed_float32_array_get(&lf, i) - slices_d[i]) <= 0.00001); + } + godot_packed_float32_array_destroy(&lf); + + godot_string_destroy(&s); + godot_string_new_with_latin1_chars(&s, "1;2 4"); + const int slices_i[3] = { 1, 2, 4 }; + + godot_packed_int32_array li = godot_string_split_ints_allow_empty(&s, &c1); + CHECK(godot_packed_int32_array_size(&li) == 2); + for (int i = 0; i < godot_packed_int32_array_size(&li); i++) { + CHECK(godot_packed_int32_array_get(&li, i) == slices_i[i]); + } + godot_packed_int32_array_destroy(&li); + + li = godot_string_split_ints_mk_allow_empty(&s, &keys); + CHECK(godot_packed_int32_array_size(&li) == 3); + for (int i = 0; i < godot_packed_int32_array_size(&li); i++) { + CHECK(godot_packed_int32_array_get(&li, i) == slices_i[i]); + } + godot_packed_int32_array_destroy(&li); + + godot_string_destroy(&s); + godot_string_destroy(&c); + godot_string_destroy(&c1); + godot_string_destroy(&c2); + godot_packed_string_array_destroy(&keys); +} + +TEST_CASE("[GDNative String] Erasing") { + godot_string s, t; + godot_string_new_with_latin1_chars(&s, "Josephine is such a cute girl!"); + godot_string_new_with_latin1_chars(&t, "cute "); + + godot_string_erase(&s, godot_string_find(&s, &t), godot_string_length(&t)); + + CHECK(u32scmp(godot_string_get_data(&s), U"Josephine is such a girl!") == 0); + + godot_string_destroy(&s); + godot_string_destroy(&t); +} + +struct test_27_data { + char const *data; + char const *part; + bool expected; +}; + +TEST_CASE("[GDNative String] Begins with") { + test_27_data tc[] = { + { "res://foobar", "res://", true }, + { "res", "res://", false }, + { "abc", "abc", true } + }; + size_t count = sizeof(tc) / sizeof(tc[0]); + bool state = true; + for (size_t i = 0; state && i < count; ++i) { + godot_string s; + godot_string_new_with_latin1_chars(&s, tc[i].data); + + state = godot_string_begins_with_char_array(&s, tc[i].part) == tc[i].expected; + if (state) { + godot_string t; + godot_string_new_with_latin1_chars(&t, tc[i].part); + state = godot_string_begins_with(&s, &t) == tc[i].expected; + godot_string_destroy(&t); + } + godot_string_destroy(&s); + + CHECK(state); + if (!state) { + break; + } + }; + CHECK(state); +} + +TEST_CASE("[GDNative String] Ends with") { + test_27_data tc[] = { + { "res://foobar", "foobar", true }, + { "res", "res://", false }, + { "abc", "abc", true } + }; + size_t count = sizeof(tc) / sizeof(tc[0]); + bool state = true; + for (size_t i = 0; state && i < count; ++i) { + godot_string s; + godot_string_new_with_latin1_chars(&s, tc[i].data); + + state = godot_string_ends_with_char_array(&s, tc[i].part) == tc[i].expected; + if (state) { + godot_string t; + godot_string_new_with_latin1_chars(&t, tc[i].part); + state = godot_string_ends_with(&s, &t) == tc[i].expected; + godot_string_destroy(&t); + } + godot_string_destroy(&s); + + CHECK(state); + if (!state) { + break; + } + }; + CHECK(state); +} + +TEST_CASE("[GDNative String] format") { + godot_string value_format, t; + godot_string_new_with_latin1_chars(&value_format, "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\""); + + godot_variant key_v, val_v; + godot_dictionary value_dictionary; + godot_dictionary_new(&value_dictionary); + + godot_string_new_with_latin1_chars(&t, "red"); + godot_variant_new_string(&key_v, &t); + godot_string_destroy(&t); + godot_variant_new_int(&val_v, 10); + godot_dictionary_set(&value_dictionary, &key_v, &val_v); + godot_variant_destroy(&key_v); + godot_variant_destroy(&val_v); + + godot_string_new_with_latin1_chars(&t, "green"); + godot_variant_new_string(&key_v, &t); + godot_string_destroy(&t); + godot_variant_new_int(&val_v, 20); + godot_dictionary_set(&value_dictionary, &key_v, &val_v); + godot_variant_destroy(&key_v); + godot_variant_destroy(&val_v); + + godot_string_new_with_latin1_chars(&t, "blue"); + godot_variant_new_string(&key_v, &t); + godot_string_destroy(&t); + godot_string_new_with_latin1_chars(&t, "bla"); + godot_variant_new_string(&val_v, &t); + godot_string_destroy(&t); + godot_dictionary_set(&value_dictionary, &key_v, &val_v); + godot_variant_destroy(&key_v); + godot_variant_destroy(&val_v); + + godot_string_new_with_latin1_chars(&t, "alpha"); + godot_variant_new_string(&key_v, &t); + godot_string_destroy(&t); + godot_variant_new_real(&val_v, 0.4); + godot_dictionary_set(&value_dictionary, &key_v, &val_v); + godot_variant_destroy(&key_v); + godot_variant_destroy(&val_v); + + godot_variant dict_v; + godot_variant_new_dictionary(&dict_v, &value_dictionary); + godot_string s = godot_string_format_with_custom_placeholder(&value_format, &dict_v, "$_"); + + CHECK(u32scmp(godot_string_get_data(&s), U"red=\"10\" green=\"20\" blue=\"bla\" alpha=\"0.4\"") == 0); + + godot_dictionary_destroy(&value_dictionary); + godot_string_destroy(&s); + godot_variant_destroy(&dict_v); + godot_string_destroy(&value_format); +} + +TEST_CASE("[GDNative String] sprintf") { + //godot_string GDAPI (const godot_string *p_self, const godot_array *p_values, godot_bool *p_error); + godot_string format, output; + godot_array args; + bool error; + +#define ARRAY_PUSH_STRING(x) \ + { \ + godot_variant v; \ + godot_string t; \ + godot_string_new_with_latin1_chars(&t, x); \ + godot_variant_new_string(&v, &t); \ + godot_string_destroy(&t); \ + godot_array_push_back(&args, &v); \ + godot_variant_destroy(&v); \ + } + +#define ARRAY_PUSH_INT(x) \ + { \ + godot_variant v; \ + godot_variant_new_int(&v, x); \ + godot_array_push_back(&args, &v); \ + godot_variant_destroy(&v); \ + } + +#define ARRAY_PUSH_REAL(x) \ + { \ + godot_variant v; \ + godot_variant_new_real(&v, x); \ + godot_array_push_back(&args, &v); \ + godot_variant_destroy(&v); \ + } + + godot_array_new(&args); + + // %% + godot_string_new_with_latin1_chars(&format, "fish %% frog"); + godot_array_clear(&args); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish % frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + //////// INTS + + // Int + godot_string_new_with_latin1_chars(&format, "fish %d frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(5); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 5 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Int left padded with zeroes. + godot_string_new_with_latin1_chars(&format, "fish %05d frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(5); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 00005 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Int left padded with spaces. + godot_string_new_with_latin1_chars(&format, "fish %5d frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(5); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 5 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Int right padded with spaces. + godot_string_new_with_latin1_chars(&format, "fish %-5d frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(5); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 5 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Int with sign (positive). + godot_string_new_with_latin1_chars(&format, "fish %+d frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(5); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish +5 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Negative int. + godot_string_new_with_latin1_chars(&format, "fish %d frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(-5); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish -5 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Hex (lower) + godot_string_new_with_latin1_chars(&format, "fish %x frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(45); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 2d frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Hex (upper) + godot_string_new_with_latin1_chars(&format, "fish %X frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(45); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 2D frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Octal + godot_string_new_with_latin1_chars(&format, "fish %o frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 143 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + ////// REALS + + // Real + godot_string_new_with_latin1_chars(&format, "fish %f frog"); + godot_array_clear(&args); + ARRAY_PUSH_REAL(99.99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 99.990000 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Real left-padded + godot_string_new_with_latin1_chars(&format, "fish %11f frog"); + godot_array_clear(&args); + ARRAY_PUSH_REAL(99.99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 99.990000 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Real right-padded + godot_string_new_with_latin1_chars(&format, "fish %-11f frog"); + godot_array_clear(&args); + ARRAY_PUSH_REAL(99.99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 99.990000 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Real given int. + godot_string_new_with_latin1_chars(&format, "fish %f frog"); + godot_array_clear(&args); + ARRAY_PUSH_REAL(99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 99.000000 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Real with sign (positive). + godot_string_new_with_latin1_chars(&format, "fish %+f frog"); + godot_array_clear(&args); + ARRAY_PUSH_REAL(99.99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish +99.990000 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Real with 1 decimals. + godot_string_new_with_latin1_chars(&format, "fish %.1f frog"); + godot_array_clear(&args); + ARRAY_PUSH_REAL(99.99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 100.0 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Real with 12 decimals. + godot_string_new_with_latin1_chars(&format, "fish %.12f frog"); + godot_array_clear(&args); + ARRAY_PUSH_REAL(99.99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 99.990000000000 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Real with no decimals. + godot_string_new_with_latin1_chars(&format, "fish %.f frog"); + godot_array_clear(&args); + ARRAY_PUSH_REAL(99.99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 100 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + /////// Strings. + + // String + godot_string_new_with_latin1_chars(&format, "fish %s frog"); + godot_array_clear(&args); + ARRAY_PUSH_STRING("cheese"); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish cheese frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // String left-padded + godot_string_new_with_latin1_chars(&format, "fish %10s frog"); + godot_array_clear(&args); + ARRAY_PUSH_STRING("cheese"); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish cheese frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // String right-padded + godot_string_new_with_latin1_chars(&format, "fish %-10s frog"); + godot_array_clear(&args); + ARRAY_PUSH_STRING("cheese"); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish cheese frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + ///// Characters + + // Character as string. + godot_string_new_with_latin1_chars(&format, "fish %c frog"); + godot_array_clear(&args); + ARRAY_PUSH_STRING("A"); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish A frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Character as int. + godot_string_new_with_latin1_chars(&format, "fish %c frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(65); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish A frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + ///// Dynamic width + + // String dynamic width + godot_string_new_with_latin1_chars(&format, "fish %*s frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(10); + ARRAY_PUSH_STRING("cheese"); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + REQUIRE(u32scmp(godot_string_get_data(&output), U"fish cheese frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Int dynamic width + godot_string_new_with_latin1_chars(&format, "fish %*d frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(10); + ARRAY_PUSH_INT(99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + REQUIRE(u32scmp(godot_string_get_data(&output), U"fish 99 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Float dynamic width + godot_string_new_with_latin1_chars(&format, "fish %*.*f frog"); + godot_array_clear(&args); + ARRAY_PUSH_INT(10); + ARRAY_PUSH_INT(3); + ARRAY_PUSH_REAL(99.99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error == false); + CHECK(u32scmp(godot_string_get_data(&output), U"fish 99.990 frog") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + ///// Errors + + // More formats than arguments. + godot_string_new_with_latin1_chars(&format, "fish %s %s frog"); + godot_array_clear(&args); + ARRAY_PUSH_STRING("cheese"); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error); + CHECK(u32scmp(godot_string_get_data(&output), U"not enough arguments for format string") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // More arguments than formats. + godot_string_new_with_latin1_chars(&format, "fish %s frog"); + godot_array_clear(&args); + ARRAY_PUSH_STRING("hello"); + ARRAY_PUSH_STRING("cheese"); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error); + CHECK(u32scmp(godot_string_get_data(&output), U"not all arguments converted during string formatting") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Incomplete format. + godot_string_new_with_latin1_chars(&format, "fish %10"); + godot_array_clear(&args); + ARRAY_PUSH_STRING("cheese"); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error); + CHECK(u32scmp(godot_string_get_data(&output), U"incomplete format") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Bad character in format string + godot_string_new_with_latin1_chars(&format, "fish %&f frog"); + godot_array_clear(&args); + ARRAY_PUSH_STRING("cheese"); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error); + CHECK(u32scmp(godot_string_get_data(&output), U"unsupported format character") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Too many decimals. + godot_string_new_with_latin1_chars(&format, "fish %2.2.2f frog"); + godot_array_clear(&args); + ARRAY_PUSH_REAL(99.99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error); + CHECK(u32scmp(godot_string_get_data(&output), U"too many decimal points in format") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // * not a number + godot_string_new_with_latin1_chars(&format, "fish %*f frog"); + godot_array_clear(&args); + ARRAY_PUSH_STRING("cheese"); + ARRAY_PUSH_REAL(99.99); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error); + CHECK(u32scmp(godot_string_get_data(&output), U"* wants number") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Character too long. + godot_string_new_with_latin1_chars(&format, "fish %c frog"); + godot_array_clear(&args); + ARRAY_PUSH_STRING("sc"); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error); + CHECK(u32scmp(godot_string_get_data(&output), U"%c requires number or single-character string") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + // Character bad type. + godot_string_new_with_latin1_chars(&format, "fish %c frog"); + godot_array_clear(&args); + godot_array t; + godot_array_new(&t); + godot_variant v; + godot_variant_new_array(&v, &t); + godot_array_destroy(&t); + godot_array_push_back(&args, &v); + godot_variant_destroy(&v); + output = godot_string_sprintf(&format, &args, &error); + REQUIRE(error); + CHECK(u32scmp(godot_string_get_data(&output), U"%c requires number or single-character string") == 0); + godot_string_destroy(&format); + godot_string_destroy(&output); + + godot_array_destroy(&args); +#undef ARRAY_PUSH_INT +#undef ARRAY_PUSH_REAL +#undef ARRAY_PUSH_STRING +} + +TEST_CASE("[GDNative String] is_numeric") { +#define IS_NUM_TEST(x, r) \ + { \ + godot_string t; \ + godot_string_new_with_latin1_chars(&t, x); \ + CHECK(godot_string_is_numeric(&t) == r); \ + godot_string_destroy(&t); \ + } + + IS_NUM_TEST("12", true); + IS_NUM_TEST("1.2", true); + IS_NUM_TEST("AF", false); + IS_NUM_TEST("-12", true); + IS_NUM_TEST("-1.2", true); + +#undef IS_NUM_TEST +} + +TEST_CASE("[GDNative String] pad") { + godot_string s, c; + godot_string_new_with_latin1_chars(&s, "test"); + godot_string_new_with_latin1_chars(&c, "x"); + + godot_string l = godot_string_lpad_with_custom_character(&s, 10, &c); + CHECK(u32scmp(godot_string_get_data(&l), U"xxxxxxtest") == 0); + godot_string_destroy(&l); + + godot_string r = godot_string_rpad_with_custom_character(&s, 10, &c); + CHECK(u32scmp(godot_string_get_data(&r), U"testxxxxxx") == 0); + godot_string_destroy(&r); + + godot_string_destroy(&s); + godot_string_destroy(&c); + + godot_string_new_with_latin1_chars(&s, "10.10"); + c = godot_string_pad_decimals(&s, 4); + CHECK(u32scmp(godot_string_get_data(&c), U"10.1000") == 0); + godot_string_destroy(&c); + c = godot_string_pad_zeros(&s, 4); + CHECK(u32scmp(godot_string_get_data(&c), U"0010.10") == 0); + godot_string_destroy(&c); + + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] is_subsequence_of") { + godot_string a, t; + godot_string_new_with_latin1_chars(&a, "is subsequence of"); + + godot_string_new_with_latin1_chars(&t, "sub"); + CHECK(godot_string_is_subsequence_of(&t, &a)); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "Sub"); + CHECK(!godot_string_is_subsequence_of(&t, &a)); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "Sub"); + CHECK(godot_string_is_subsequence_ofi(&t, &a)); + godot_string_destroy(&t); + + godot_string_destroy(&a); +} + +TEST_CASE("[GDNative String] match") { + godot_string s, t; + godot_string_new_with_latin1_chars(&s, "*.png"); + + godot_string_new_with_latin1_chars(&t, "img1.png"); + CHECK(godot_string_match(&t, &s)); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "img1.jpeg"); + CHECK(!godot_string_match(&t, &s)); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "img1.Png"); + CHECK(!godot_string_match(&t, &s)); + CHECK(godot_string_matchn(&t, &s)); + godot_string_destroy(&t); + + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] IPVX address to string") { + godot_string ip; + + godot_string_new_with_latin1_chars(&ip, "192.168.0.1"); + CHECK(godot_string_is_valid_ip_address(&ip)); + godot_string_destroy(&ip); + + godot_string_new_with_latin1_chars(&ip, "192.368.0.1"); + CHECK(!godot_string_is_valid_ip_address(&ip)); + godot_string_destroy(&ip); + + godot_string_new_with_latin1_chars(&ip, "2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + CHECK(godot_string_is_valid_ip_address(&ip)); + godot_string_destroy(&ip); + + godot_string_new_with_latin1_chars(&ip, "2001:0db8:85j3:0000:0000:8a2e:0370:7334"); + CHECK(!godot_string_is_valid_ip_address(&ip)); + godot_string_destroy(&ip); + + godot_string_new_with_latin1_chars(&ip, "2001:0db8:85f345:0000:0000:8a2e:0370:7334"); + CHECK(!godot_string_is_valid_ip_address(&ip)); + godot_string_destroy(&ip); + + godot_string_new_with_latin1_chars(&ip, "2001:0db8::0:8a2e:370:7334"); + CHECK(godot_string_is_valid_ip_address(&ip)); + godot_string_destroy(&ip); + + godot_string_new_with_latin1_chars(&ip, "::ffff:192.168.0.1"); + CHECK(godot_string_is_valid_ip_address(&ip)); + godot_string_destroy(&ip); +} + +TEST_CASE("[GDNative String] Capitalize against many strings") { +#define CAP_TEST(i, o) \ + godot_string_new_with_latin1_chars(&input, i); \ + godot_string_new_with_latin1_chars(&output, o); \ + test = godot_string_capitalize(&input); \ + CHECK(u32scmp(godot_string_get_data(&output), godot_string_get_data(&test)) == 0); \ + godot_string_destroy(&input); \ + godot_string_destroy(&output); \ + godot_string_destroy(&test); + + godot_string input, output, test; + + CAP_TEST("bytes2var", "Bytes 2 Var"); + CAP_TEST("linear2db", "Linear 2 Db"); + CAP_TEST("vector3", "Vector 3"); + CAP_TEST("sha256", "Sha 256"); + CAP_TEST("2db", "2 Db"); + CAP_TEST("PascalCase", "Pascal Case"); + CAP_TEST("PascalPascalCase", "Pascal Pascal Case"); + CAP_TEST("snake_case", "Snake Case"); + CAP_TEST("snake_snake_case", "Snake Snake Case"); + CAP_TEST("sha256sum", "Sha 256 Sum"); + CAP_TEST("cat2dog", "Cat 2 Dog"); + CAP_TEST("function(name)", "Function(name)"); + CAP_TEST("snake_case_function(snake_case_arg)", "Snake Case Function(snake Case Arg)"); + CAP_TEST("snake_case_function( snake_case_arg )", "Snake Case Function( Snake Case Arg )"); + +#undef CAP_TEST +} + +TEST_CASE("[GDNative String] lstrip and rstrip") { +#define LSTRIP_TEST(x, y, z) \ + { \ + godot_string xx, yy, zz, rr; \ + godot_string_new_with_latin1_chars(&xx, x); \ + godot_string_new_with_latin1_chars(&yy, y); \ + godot_string_new_with_latin1_chars(&zz, z); \ + rr = godot_string_lstrip(&xx, &yy); \ + state = state && (u32scmp(godot_string_get_data(&rr), godot_string_get_data(&zz)) == 0); \ + godot_string_destroy(&xx); \ + godot_string_destroy(&yy); \ + godot_string_destroy(&zz); \ + godot_string_destroy(&rr); \ + } + +#define RSTRIP_TEST(x, y, z) \ + { \ + godot_string xx, yy, zz, rr; \ + godot_string_new_with_latin1_chars(&xx, x); \ + godot_string_new_with_latin1_chars(&yy, y); \ + godot_string_new_with_latin1_chars(&zz, z); \ + rr = godot_string_rstrip(&xx, &yy); \ + state = state && (u32scmp(godot_string_get_data(&rr), godot_string_get_data(&zz)) == 0); \ + godot_string_destroy(&xx); \ + godot_string_destroy(&yy); \ + godot_string_destroy(&zz); \ + godot_string_destroy(&rr); \ + } + +#define LSTRIP_UTF8_TEST(x, y, z) \ + { \ + godot_string xx, yy, zz, rr; \ + godot_string_new_with_utf8_chars(&xx, x); \ + godot_string_new_with_utf8_chars(&yy, y); \ + godot_string_new_with_utf8_chars(&zz, z); \ + rr = godot_string_lstrip(&xx, &yy); \ + state = state && (u32scmp(godot_string_get_data(&rr), godot_string_get_data(&zz)) == 0); \ + godot_string_destroy(&xx); \ + godot_string_destroy(&yy); \ + godot_string_destroy(&zz); \ + godot_string_destroy(&rr); \ + } + +#define RSTRIP_UTF8_TEST(x, y, z) \ + { \ + godot_string xx, yy, zz, rr; \ + godot_string_new_with_utf8_chars(&xx, x); \ + godot_string_new_with_utf8_chars(&yy, y); \ + godot_string_new_with_utf8_chars(&zz, z); \ + rr = godot_string_rstrip(&xx, &yy); \ + state = state && (u32scmp(godot_string_get_data(&rr), godot_string_get_data(&zz)) == 0); \ + godot_string_destroy(&xx); \ + godot_string_destroy(&yy); \ + godot_string_destroy(&zz); \ + godot_string_destroy(&rr); \ + } + + bool state = true; + + // strip none + LSTRIP_TEST("abc", "", "abc"); + RSTRIP_TEST("abc", "", "abc"); + // strip one + LSTRIP_TEST("abc", "a", "bc"); + RSTRIP_TEST("abc", "c", "ab"); + // strip lots + LSTRIP_TEST("bababbababccc", "ab", "ccc"); + RSTRIP_TEST("aaabcbcbcbbcbbc", "cb", "aaa"); + // strip empty string + LSTRIP_TEST("", "", ""); + RSTRIP_TEST("", "", ""); + // strip to empty string + LSTRIP_TEST("abcabcabc", "bca", ""); + RSTRIP_TEST("abcabcabc", "bca", ""); + // don't strip wrong end + LSTRIP_TEST("abc", "c", "abc"); + LSTRIP_TEST("abca", "a", "bca"); + RSTRIP_TEST("abc", "a", "abc"); + RSTRIP_TEST("abca", "a", "abc"); + // in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5) + // and the same second as "ÿ" (\u00ff) + LSTRIP_UTF8_TEST("¿", "µÿ", "¿"); + RSTRIP_UTF8_TEST("¿", "µÿ", "¿"); + LSTRIP_UTF8_TEST("µ¿ÿ", "µÿ", "¿ÿ"); + RSTRIP_UTF8_TEST("µ¿ÿ", "µÿ", "µ¿"); + + // the above tests repeated with additional superfluous strip chars + + // strip none + LSTRIP_TEST("abc", "qwjkl", "abc"); + RSTRIP_TEST("abc", "qwjkl", "abc"); + // strip one + LSTRIP_TEST("abc", "qwajkl", "bc"); + RSTRIP_TEST("abc", "qwcjkl", "ab"); + // strip lots + LSTRIP_TEST("bababbababccc", "qwabjkl", "ccc"); + RSTRIP_TEST("aaabcbcbcbbcbbc", "qwcbjkl", "aaa"); + // strip empty string + LSTRIP_TEST("", "qwjkl", ""); + RSTRIP_TEST("", "qwjkl", ""); + // strip to empty string + LSTRIP_TEST("abcabcabc", "qwbcajkl", ""); + RSTRIP_TEST("abcabcabc", "qwbcajkl", ""); + // don't strip wrong end + LSTRIP_TEST("abc", "qwcjkl", "abc"); + LSTRIP_TEST("abca", "qwajkl", "bca"); + RSTRIP_TEST("abc", "qwajkl", "abc"); + RSTRIP_TEST("abca", "qwajkl", "abc"); + // in utf-8 "¿" (\u00bf) has the same first byte as "µ" (\u00b5) + // and the same second as "ÿ" (\u00ff) + LSTRIP_UTF8_TEST("¿", "qwaµÿjkl", "¿"); + RSTRIP_UTF8_TEST("¿", "qwaµÿjkl", "¿"); + LSTRIP_UTF8_TEST("µ¿ÿ", "qwaµÿjkl", "¿ÿ"); + RSTRIP_UTF8_TEST("µ¿ÿ", "qwaµÿjkl", "µ¿"); + + CHECK(state); + +#undef LSTRIP_TEST +#undef RSTRIP_TEST +#undef LSTRIP_UTF8_TEST +#undef RSTRIP_UTF8_TEST +} + +TEST_CASE("[GDNative String] Cyrillic to_lower()") { + godot_string upper, lower, test; + godot_string_new_with_utf8_chars(&upper, "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"); + godot_string_new_with_utf8_chars(&lower, "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"); + + test = godot_string_to_lower(&upper); + + CHECK((u32scmp(godot_string_get_data(&test), godot_string_get_data(&lower)) == 0)); + + godot_string_destroy(&upper); + godot_string_destroy(&lower); + godot_string_destroy(&test); +} + +TEST_CASE("[GDNative String] Count and countn functionality") { +#define COUNT_TEST(x, y, r) \ + { \ + godot_string s, t; \ + godot_string_new_with_latin1_chars(&s, x); \ + godot_string_new_with_latin1_chars(&t, y); \ + state = state && (godot_string_count(&s, &t, 0, 0) == r); \ + godot_string_destroy(&s); \ + godot_string_destroy(&t); \ + } + +#define COUNTR_TEST(x, y, a, b, r) \ + { \ + godot_string s, t; \ + godot_string_new_with_latin1_chars(&s, x); \ + godot_string_new_with_latin1_chars(&t, y); \ + state = state && (godot_string_count(&s, &t, a, b) == r); \ + godot_string_destroy(&s); \ + godot_string_destroy(&t); \ + } + +#define COUNTN_TEST(x, y, r) \ + { \ + godot_string s, t; \ + godot_string_new_with_latin1_chars(&s, x); \ + godot_string_new_with_latin1_chars(&t, y); \ + state = state && (godot_string_countn(&s, &t, 0, 0) == r); \ + godot_string_destroy(&s); \ + godot_string_destroy(&t); \ + } + +#define COUNTNR_TEST(x, y, a, b, r) \ + { \ + godot_string s, t; \ + godot_string_new_with_latin1_chars(&s, x); \ + godot_string_new_with_latin1_chars(&t, y); \ + state = state && (godot_string_countn(&s, &t, a, b) == r); \ + godot_string_destroy(&s); \ + godot_string_destroy(&t); \ + } + bool state = true; + + COUNT_TEST("", "Test", 0); + COUNT_TEST("Test", "", 0); + COUNT_TEST("Test", "test", 0); + COUNT_TEST("Test", "TEST", 0); + COUNT_TEST("TEST", "TEST", 1); + COUNT_TEST("Test", "Test", 1); + COUNT_TEST("aTest", "Test", 1); + COUNT_TEST("Testa", "Test", 1); + COUNT_TEST("TestTestTest", "Test", 3); + COUNT_TEST("TestTestTest", "TestTest", 1); + COUNT_TEST("TestGodotTestGodotTestGodot", "Test", 3); + + COUNTR_TEST("TestTestTestTest", "Test", 4, 8, 1); + COUNTR_TEST("TestTestTestTest", "Test", 4, 12, 2); + COUNTR_TEST("TestTestTestTest", "Test", 4, 16, 3); + COUNTR_TEST("TestTestTestTest", "Test", 4, 0, 3); + + COUNTN_TEST("Test", "test", 1); + COUNTN_TEST("Test", "TEST", 1); + COUNTN_TEST("testTest-Testatest", "tEst", 4); + COUNTNR_TEST("testTest-TeStatest", "tEsT", 4, 16, 2); + + CHECK(state); + +#undef COUNT_TEST +#undef COUNTR_TEST +#undef COUNTN_TEST +#undef COUNTNR_TEST +} + +TEST_CASE("[GDNative String] Bigrams") { + godot_string s, t; + godot_string_new_with_latin1_chars(&s, "abcd"); + godot_packed_string_array bigr = godot_string_bigrams(&s); + godot_string_destroy(&s); + + CHECK(godot_packed_string_array_size(&bigr) == 3); + + t = godot_packed_string_array_get(&bigr, 0); + CHECK(u32scmp(godot_string_get_data(&t), U"ab") == 0); + godot_string_destroy(&t); + + t = godot_packed_string_array_get(&bigr, 1); + CHECK(u32scmp(godot_string_get_data(&t), U"bc") == 0); + godot_string_destroy(&t); + + t = godot_packed_string_array_get(&bigr, 2); + CHECK(u32scmp(godot_string_get_data(&t), U"cd") == 0); + godot_string_destroy(&t); + + godot_packed_string_array_destroy(&bigr); +} + +TEST_CASE("[GDNative String] c-escape/unescape") { + godot_string s; + godot_string_new_with_latin1_chars(&s, "\\1\a2\b\f3\n45\r6\t7\v8\'9\?0\""); + godot_string t = godot_string_c_escape(&s); + godot_string u = godot_string_c_unescape(&t); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&s)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] dedent") { + godot_string s, t; + godot_string_new_with_latin1_chars(&s, " aaa\n bbb"); + godot_string_new_with_latin1_chars(&t, "aaa\nbbb"); + godot_string u = godot_string_dedent(&s); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Path functions") { + static const char *path[4] = { "C:\\Godot\\project\\test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot\\test.doc" }; + static const char *base_dir[4] = { "C:\\Godot\\project", "/Godot/project", "../Godot/project", "Godot" }; + static const char *base_name[4] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test" }; + static const char *ext[4] = { "tscn", "xscn", "scn", "doc" }; + static const char *file[4] = { "test.tscn", "test.xscn", "test.scn", "test.doc" }; + static const bool abs[4] = { true, true, false, false }; + + for (int i = 0; i < 4; i++) { + godot_string s, t, u, f; + godot_string_new_with_latin1_chars(&s, path[i]); + + t = godot_string_get_base_dir(&s); + godot_string_new_with_latin1_chars(&u, base_dir[i]); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + + t = godot_string_get_basename(&s); + godot_string_new_with_latin1_chars(&u, base_name[i]); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + + t = godot_string_get_extension(&s); + godot_string_new_with_latin1_chars(&u, ext[i]); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + + t = godot_string_get_file(&s); + godot_string_new_with_latin1_chars(&u, file[i]); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + + godot_string s_simp; + s_simp = godot_string_simplify_path(&s); + t = godot_string_get_base_dir(&s_simp); + godot_string_new_with_latin1_chars(&u, file[i]); + f = godot_string_plus_file(&t, &u); + CHECK(u32scmp(godot_string_get_data(&f), godot_string_get_data(&s_simp)) == 0); + godot_string_destroy(&f); + godot_string_destroy(&u); + godot_string_destroy(&t); + godot_string_destroy(&s_simp); + + CHECK(godot_string_is_abs_path(&s) == abs[i]); + CHECK(godot_string_is_rel_path(&s) != abs[i]); + + godot_string_destroy(&s); + } + + static const char *file_name[3] = { "test.tscn", "test://.xscn", "?tes*t.scn" }; + static const bool valid[3] = { true, false, false }; + for (int i = 0; i < 3; i++) { + godot_string s; + godot_string_new_with_latin1_chars(&s, file_name[i]); + CHECK(godot_string_is_valid_filename(&s) == valid[i]); + godot_string_destroy(&s); + } +} + +TEST_CASE("[GDNative String] hash") { + godot_string a, b, c; + godot_string_new_with_latin1_chars(&a, "Test"); + godot_string_new_with_latin1_chars(&b, "Test"); + godot_string_new_with_latin1_chars(&c, "West"); + CHECK(godot_string_hash(&a) == godot_string_hash(&b)); + CHECK(godot_string_hash(&a) != godot_string_hash(&c)); + + CHECK(godot_string_hash64(&a) == godot_string_hash64(&b)); + CHECK(godot_string_hash64(&a) != godot_string_hash64(&c)); + + godot_string_destroy(&a); + godot_string_destroy(&b); + godot_string_destroy(&c); +} + +TEST_CASE("[GDNative String] http_escape/unescape") { + godot_string s, t, u; + godot_string_new_with_latin1_chars(&s, "Godot Engine:'docs'"); + godot_string_new_with_latin1_chars(&t, "Godot%20Engine%3A%27docs%27"); + + u = godot_string_http_escape(&s); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + + u = godot_string_http_unescape(&t); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&s)) == 0); + godot_string_destroy(&u); + + godot_string_destroy(&s); + godot_string_destroy(&t); +} + +TEST_CASE("[GDNative String] percent_encode/decode") { + godot_string s, t, u; + godot_string_new_with_latin1_chars(&s, "Godot Engine:'docs'"); + godot_string_new_with_latin1_chars(&t, "Godot%20Engine%3a%27docs%27"); + + u = godot_string_percent_encode(&s); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + + u = godot_string_percent_decode(&t); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&s)) == 0); + godot_string_destroy(&u); + + godot_string_destroy(&s); + godot_string_destroy(&t); +} + +TEST_CASE("[GDNative String] xml_escape/unescape") { + godot_string s, t, u; + godot_string_new_with_latin1_chars(&s, "\"Test\" <test@test&'test'>"); + + t = godot_string_xml_escape_with_quotes(&s); + u = godot_string_xml_unescape(&t); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&s)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + + t = godot_string_xml_escape(&s); + u = godot_string_xml_unescape(&t); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&s)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Strip escapes") { + godot_string s, t, u; + godot_string_new_with_latin1_chars(&s, "\t\tTest Test\r\n Test"); + godot_string_new_with_latin1_chars(&t, "Test Test Test"); + + u = godot_string_strip_escapes(&s); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + + godot_string_destroy(&t); + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Strip edges") { + godot_string s, t, u; + godot_string_new_with_latin1_chars(&s, "\t Test Test "); + + godot_string_new_with_latin1_chars(&t, "Test Test "); + u = godot_string_strip_edges(&s, true, false); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "\t Test Test"); + u = godot_string_strip_edges(&s, false, true); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "Test Test"); + u = godot_string_strip_edges(&s, true, true); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Similarity") { + godot_string a, b, c; + godot_string_new_with_latin1_chars(&a, "Test"); + godot_string_new_with_latin1_chars(&b, "West"); + godot_string_new_with_latin1_chars(&c, "Toad"); + + CHECK(godot_string_similarity(&a, &b) > godot_string_similarity(&a, &c)); + + godot_string_destroy(&a); + godot_string_destroy(&b); + godot_string_destroy(&c); +} + +TEST_CASE("[GDNative String] Trim") { + godot_string s, t, u, p; + godot_string_new_with_latin1_chars(&s, "aaaTestbbb"); + + godot_string_new_with_latin1_chars(&p, "aaa"); + godot_string_new_with_latin1_chars(&t, "Testbbb"); + u = godot_string_trim_prefix(&s, &p); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + godot_string_destroy(&p); + + godot_string_new_with_latin1_chars(&p, "bbb"); + godot_string_new_with_latin1_chars(&t, "aaaTest"); + u = godot_string_trim_suffix(&s, &p); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + godot_string_destroy(&p); + + godot_string_new_with_latin1_chars(&p, "Test"); + u = godot_string_trim_suffix(&s, &p); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&s)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&p); + + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Right/Left") { + godot_string s, t, u; + godot_string_new_with_latin1_chars(&s, "aaaTestbbb"); + // ^ + + godot_string_new_with_latin1_chars(&t, "tbbb"); + u = godot_string_right(&s, 6); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&t, "aaaTes"); + u = godot_string_left(&s, 6); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + + godot_string_destroy(&s); +} + +TEST_CASE("[GDNative String] Repeat") { + godot_string t, u; + godot_string_new_with_latin1_chars(&t, "ab"); + + u = godot_string_repeat(&t, 4); + CHECK(u32scmp(godot_string_get_data(&u), U"abababab") == 0); + godot_string_destroy(&u); + + godot_string_destroy(&t); +} + +TEST_CASE("[GDNative String] SHA1/SHA256/MD5") { + godot_string s, t, sha1, sha256, md5; + godot_string_new_with_latin1_chars(&s, "Godot"); + godot_string_new_with_latin1_chars(&sha1, "a1e91f39b9fce6a9998b14bdbe2aa2b39dc2d201"); + static uint8_t sha1_buf[20] = { + 0xA1, 0xE9, 0x1F, 0x39, 0xB9, 0xFC, 0xE6, 0xA9, 0x99, 0x8B, 0x14, 0xBD, 0xBE, 0x2A, 0xA2, 0xB3, + 0x9D, 0xC2, 0xD2, 0x01 + }; + godot_string_new_with_latin1_chars(&sha256, "2a02b2443f7985d89d09001086ae3dcfa6eb0f55c6ef170715d42328e16e6cb8"); + static uint8_t sha256_buf[32] = { + 0x2A, 0x02, 0xB2, 0x44, 0x3F, 0x79, 0x85, 0xD8, 0x9D, 0x09, 0x00, 0x10, 0x86, 0xAE, 0x3D, 0xCF, + 0xA6, 0xEB, 0x0F, 0x55, 0xC6, 0xEF, 0x17, 0x07, 0x15, 0xD4, 0x23, 0x28, 0xE1, 0x6E, 0x6C, 0xB8 + }; + godot_string_new_with_latin1_chars(&md5, "4a336d087aeb0390da10ee2ea7cb87f8"); + static uint8_t md5_buf[16] = { + 0x4A, 0x33, 0x6D, 0x08, 0x7A, 0xEB, 0x03, 0x90, 0xDA, 0x10, 0xEE, 0x2E, 0xA7, 0xCB, 0x87, 0xF8 + }; + + godot_packed_byte_array buf = godot_string_sha1_buffer(&s); + CHECK(memcmp(sha1_buf, godot_packed_byte_array_ptr(&buf), 20) == 0); + godot_packed_byte_array_destroy(&buf); + + t = godot_string_sha1_text(&s); + CHECK(u32scmp(godot_string_get_data(&t), godot_string_get_data(&sha1)) == 0); + godot_string_destroy(&t); + + buf = godot_string_sha256_buffer(&s); + CHECK(memcmp(sha256_buf, godot_packed_byte_array_ptr(&buf), 32) == 0); + godot_packed_byte_array_destroy(&buf); + + t = godot_string_sha256_text(&s); + CHECK(u32scmp(godot_string_get_data(&t), godot_string_get_data(&sha256)) == 0); + godot_string_destroy(&t); + + buf = godot_string_md5_buffer(&s); + CHECK(memcmp(md5_buf, godot_packed_byte_array_ptr(&buf), 16) == 0); + godot_packed_byte_array_destroy(&buf); + + t = godot_string_md5_text(&s); + CHECK(u32scmp(godot_string_get_data(&t), godot_string_get_data(&md5)) == 0); + godot_string_destroy(&t); + + godot_string_destroy(&s); + godot_string_destroy(&sha1); + godot_string_destroy(&sha256); + godot_string_destroy(&md5); +} + +TEST_CASE("[GDNative String] Join") { + godot_string s, t, u; + godot_string_new_with_latin1_chars(&s, ", "); + + godot_packed_string_array parts; + godot_packed_string_array_new(&parts); + godot_string_new_with_latin1_chars(&t, "One"); + godot_packed_string_array_push_back(&parts, &t); + godot_string_destroy(&t); + godot_string_new_with_latin1_chars(&t, "B"); + godot_packed_string_array_push_back(&parts, &t); + godot_string_destroy(&t); + godot_string_new_with_latin1_chars(&t, "C"); + godot_packed_string_array_push_back(&parts, &t); + godot_string_destroy(&t); + + godot_string_new_with_latin1_chars(&u, "One, B, C"); + t = godot_string_join(&s, &parts); + CHECK(u32scmp(godot_string_get_data(&u), godot_string_get_data(&t)) == 0); + godot_string_destroy(&u); + godot_string_destroy(&t); + + godot_string_destroy(&s); + godot_packed_string_array_destroy(&parts); +} + +TEST_CASE("[GDNative String] Is_*") { + static const char *data[12] = { "-30", "100", "10.1", "10,1", "1e2", "1e-2", "1e2e3", "0xAB", "AB", "Test1", "1Test", "Test*1" }; + static bool isnum[12] = { true, true, true, false, false, false, false, false, false, false, false, false }; + static bool isint[12] = { true, true, false, false, false, false, false, false, false, false, false, false }; + static bool ishex[12] = { true, true, false, false, true, false, true, false, true, false, false, false }; + static bool ishex_p[12] = { false, false, false, false, false, false, false, true, false, false, false, false }; + static bool isflt[12] = { true, true, true, false, true, true, false, false, false, false, false, false }; + static bool isid[12] = { false, false, false, false, false, false, false, false, true, true, false, false }; + + for (int i = 0; i < 12; i++) { + godot_string s; + godot_string_new_with_latin1_chars(&s, data[i]); + CHECK(godot_string_is_numeric(&s) == isnum[i]); + CHECK(godot_string_is_valid_integer(&s) == isint[i]); + CHECK(godot_string_is_valid_hex_number(&s, false) == ishex[i]); + CHECK(godot_string_is_valid_hex_number(&s, true) == ishex_p[i]); + CHECK(godot_string_is_valid_float(&s) == isflt[i]); + CHECK(godot_string_is_valid_identifier(&s) == isid[i]); + godot_string_destroy(&s); + } +} + +TEST_CASE("[GDNative String] humanize_size") { + godot_string s; + + s = godot_string_humanize_size(1000); + CHECK(u32scmp(godot_string_get_data(&s), U"1000 B") == 0); + godot_string_destroy(&s); + + s = godot_string_humanize_size(1025); + CHECK(u32scmp(godot_string_get_data(&s), U"1.00 KiB") == 0); + godot_string_destroy(&s); + + s = godot_string_humanize_size(1025300); + CHECK(u32scmp(godot_string_get_data(&s), U"1001.2 KiB") == 0); + godot_string_destroy(&s); + + s = godot_string_humanize_size(100523550); + CHECK(u32scmp(godot_string_get_data(&s), U"95.86 MiB") == 0); + godot_string_destroy(&s); + + s = godot_string_humanize_size(5345555000); + CHECK(u32scmp(godot_string_get_data(&s), U"4.97 GiB") == 0); + godot_string_destroy(&s); +} + +} // namespace TestGDNativeString + +#endif // TEST_GDNATIVE_STRING_H diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub index e58a1d8edc..5c8cbdf869 100644 --- a/modules/gdscript/SCsub +++ b/modules/gdscript/SCsub @@ -17,3 +17,7 @@ if env["tools"]: # Using a define in the disabled case, to avoid having an extra define # in regular builds where all modules are enabled. env_gdscript.Append(CPPDEFINES=["GDSCRIPT_NO_LSP"]) + +if env["tests"]: + env_gdscript.Append(CPPDEFINES=["TESTS_ENABLED"]) + env_gdscript.add_source_files(env.modules_sources, "./tests/*.cpp") diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index e528fc6623..613039754f 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -369,24 +369,19 @@ <description> Returns the floating-point modulus of [code]a/b[/code] that wraps equally in positive and negative. [codeblock] - var i = -6 - while i < 5: - prints(i, fposmod(i, 3)) - i += 1 + for i in 7: + var x = 0.5 * i - 1.5 + print("%4.1f %4.1f %4.1f" % [x, fmod(x, 1.5), fposmod(x, 1.5)]) [/codeblock] Produces: [codeblock] - -6 0 - -5 1 - -4 2 - -3 0 - -2 1 - -1 2 - 0 0 - 1 1 - 2 2 - 3 0 - 4 1 + -1.5 -0.0 0.0 + -1.0 -1.0 0.5 + -0.5 -0.5 1.0 + 0.0 0.0 0.0 + 0.5 0.5 0.5 + 1.0 1.0 1.0 + 1.5 0.0 0.0 [/codeblock] </description> </method> @@ -771,24 +766,18 @@ <description> Returns the integer modulus of [code]a/b[/code] that wraps equally in positive and negative. [codeblock] - var i = -6 - while i < 5: - prints(i, posmod(i, 3)) - i += 1 + for i in range(-3, 4): + print("%2.0f %2.0f %2.0f" % [i, i % 3, posmod(i, 3)]) [/codeblock] Produces: [codeblock] - -6 0 - -5 1 - -4 2 - -3 0 - -2 1 - -1 2 - 0 0 - 1 1 - 2 2 - 3 0 - 4 1 + -3 0 0 + -2 -2 1 + -1 -1 2 + 0 0 0 + 1 1 1 + 2 2 2 + 3 0 0 [/codeblock] </description> </method> @@ -829,6 +818,7 @@ a = [1, 2, 3] print("a", "b", a) # Prints ab[1, 2, 3] [/codeblock] + [b]Note:[/b] Consider using [method push_error] and [method push_warning] to print error and warning messages instead of [method print]. This distinguishes them from print messages used for debugging purposes, while also displaying a stack trace when an error or warning is printed. </description> </method> <method name="print_debug" qualifiers="vararg"> @@ -902,6 +892,7 @@ [codeblock] push_error("test error") # Prints "test error" to debugger and terminal as error call [/codeblock] + [b]Note:[/b] Errors printed this way will not pause project execution. To print an error message and pause project execution in debug builds, use [code]assert(false, "test error")[/code] instead. </description> </method> <method name="push_warning"> @@ -991,27 +982,15 @@ <description> Returns an array with the given range. Range can be 1 argument N (0 to N-1), two arguments (initial, final-1) or three arguments (initial, final-1, increment). [codeblock] - for i in range(4): - print(i) - for i in range(2, 5): - print(i) - for i in range(0, 6, 2): - print(i) + print(range(4)) + print(range(2, 5)) + print(range(0, 6, 2)) [/codeblock] Output: [codeblock] - 0 - 1 - 2 - 3 - - 2 - 3 - 4 - - 0 - 2 - 4 + [0, 1, 2, 3] + [2, 3, 4] + [0, 2, 4] [/codeblock] </description> </method> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index ae1f2893f1..9a3273d201 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -33,15 +33,15 @@ #include "../gdscript_tokenizer.h" #include "editor/editor_settings.h" -static bool _is_char(CharType c) { +static bool _is_char(char32_t c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } -static bool _is_hex_symbol(CharType c) { +static bool _is_hex_symbol(char32_t c) { return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } -static bool _is_bin_symbol(CharType c) { +static bool _is_bin_symbol(char32_t c) { return (c == '0' || c == '1'); } @@ -119,7 +119,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) /* search the line */ bool match = true; - const CharType *start_key = color_regions[c].start_key.c_str(); + const char32_t *start_key = color_regions[c].start_key.get_data(); for (int k = 0; k < start_key_length; k++) { if (start_key[k] != str[from + k]) { match = false; @@ -153,18 +153,16 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) /* if we are in one find the end key */ if (in_region != -1) { - /* check there is enough room */ - int chars_left = line_length - from; - int end_key_length = color_regions[in_region].end_key.length(); - if (chars_left < end_key_length) { - continue; - } - /* search the line */ int region_end_index = -1; - const CharType *end_key = color_regions[in_region].start_key.c_str(); + int end_key_length = color_regions[in_region].end_key.length(); + const char32_t *end_key = color_regions[in_region].end_key.get_data(); for (; from < line_length; from++) { - if (!is_a_symbol) { + if (line_length - from < end_key_length) { + break; + } + + if (!is_symbol(str[from])) { continue; } @@ -173,9 +171,10 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) continue; } + region_end_index = from; for (int k = 0; k < end_key_length; k++) { - if (end_key[k] == str[from + k]) { - region_end_index = from; + if (end_key[k] != str[from + k]) { + region_end_index = -1; break; } } @@ -192,7 +191,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) previous_type = REGION; previous_text = ""; previous_column = j; - j = from; + j = from + (end_key_length - 1); if (region_end_index == -1) { color_region_cache[p_line] = in_region; } @@ -572,8 +571,12 @@ void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, cons } } + int at = 0; for (int i = 0; i < color_regions.size(); i++) { ERR_FAIL_COND_MSG(color_regions[i].start_key == p_start_key, "color region with start key '" + p_start_key + "' already exists."); + if (p_start_key.length() < color_regions[i].start_key.length()) { + at++; + } } ColorRegion color_region; @@ -581,7 +584,8 @@ void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, cons color_region.start_key = p_start_key; color_region.end_key = p_end_key; color_region.line_only = p_line_only; - color_regions.push_back(color_region); + color_regions.insert(at, color_region); + clear_highlighting_cache(); } Ref<EditorSyntaxHighlighter> GDScriptSyntaxHighlighter::_create() const { diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 0263e32c5b..7f303a966d 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1053,7 +1053,9 @@ GDScript::~GDScript() { memdelete(E->get()); } - GDScriptCache::remove_script(get_path()); + if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. + GDScriptCache::remove_script(get_path()); + } _save_orphaned_subclasses(); @@ -2035,7 +2037,23 @@ GDScriptLanguage::~GDScriptLanguage() { if (_call_stack) { memdelete_arr(_call_stack); } - singleton = nullptr; + + // Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit). + while (script_list.first()) { + GDScript *script = script_list.first()->self(); + for (Map<StringName, GDScriptFunction *>::Element *E = script->member_functions.front(); E; E = E->next()) { + GDScriptFunction *func = E->get(); + for (int i = 0; i < func->argument_types.size(); i++) { + func->argument_types.write[i].script_type_ref = Ref<Script>(); + } + func->return_type.script_type_ref = Ref<Script>(); + } + for (Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.front(); E; E = E->next()) { + E->get().data_type.script_type_ref = Ref<Script>(); + } + } + + singleton = NULL; } void GDScriptLanguage::add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass) { diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index c25307ed7f..943a49060f 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -505,6 +505,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas member.variable->set_datatype(datatype); // Allow recursive usage. reduce_expression(member.variable->initializer); datatype = member.variable->initializer->get_datatype(); + if (datatype.type_source != GDScriptParser::DataType::UNDETECTED) { + datatype.type_source = GDScriptParser::DataType::INFERRED; + } } if (member.variable->datatype_specifier != nullptr) { @@ -540,6 +543,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } else if (datatype.builtin_type == Variant::NIL) { push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is "null".)", member.variable->identifier->name), member.variable->initializer); } + datatype.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; } datatype.is_constant = false; @@ -790,7 +794,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { resolve_match_branch(static_cast<GDScriptParser::MatchBranchNode *>(p_node), nullptr); break; case GDScriptParser::Node::PARAMETER: - resolve_pararameter(static_cast<GDScriptParser::ParameterNode *>(p_node)); + resolve_parameter(static_cast<GDScriptParser::ParameterNode *>(p_node)); break; case GDScriptParser::Node::PATTERN: resolve_match_pattern(static_cast<GDScriptParser::PatternNode *>(p_node), nullptr); @@ -844,7 +848,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * parser->current_function = p_function; for (int i = 0; i < p_function->parameters.size(); i++) { - resolve_pararameter(p_function->parameters[i]); + resolve_parameter(p_function->parameters[i]); #ifdef DEBUG_ENABLED if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) { parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, p_function->identifier->name, p_function->parameters[i]->identifier->name); @@ -914,6 +918,7 @@ void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScript p_suite->datatype.type_source = GDScriptParser::DataType::UNDETECTED; } else { p_suite->set_datatype(p_statement->get_datatype()); + p_suite->datatype.type_source = GDScriptParser::DataType::INFERRED; } break; default: @@ -1259,14 +1264,18 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc p_match_pattern->set_datatype(result); } -void GDScriptAnalyzer::resolve_pararameter(GDScriptParser::ParameterNode *p_parameter) { +void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parameter) { GDScriptParser::DataType result; result.kind = GDScriptParser::DataType::VARIANT; if (p_parameter->default_value != nullptr) { reduce_expression(p_parameter->default_value); result = p_parameter->default_value->get_datatype(); - result.type_source = GDScriptParser::DataType::INFERRED; + if (p_parameter->infer_datatype) { + result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + } else { + result.type_source = GDScriptParser::DataType::INFERRED; + } result.is_constant = false; } @@ -1277,7 +1286,7 @@ void GDScriptAnalyzer::resolve_pararameter(GDScriptParser::ParameterNode *p_para if (p_parameter->default_value != nullptr) { if (!is_type_compatible(result, p_parameter->default_value->get_datatype())) { - push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with paremeter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value); + push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with parameter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value); } else if (p_parameter->default_value->get_datatype().is_variant()) { mark_node_unsafe(p_parameter); } @@ -2068,18 +2077,32 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier); } } else { - Callable::CallError temp; - Variant dummy = Variant::construct(base.builtin_type, nullptr, 0, temp); - List<PropertyInfo> properties; - dummy.get_property_list(&properties); - for (const List<PropertyInfo>::Element *E = properties.front(); E != nullptr; E = E->next()) { - const PropertyInfo &prop = E->get(); - if (prop.name == name) { - p_identifier->set_datatype(type_from_property(prop)); + switch (base.builtin_type) { + case Variant::NIL: { + push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier); + return; + } + case Variant::DICTIONARY: { + GDScriptParser::DataType dummy; + dummy.kind = GDScriptParser::DataType::VARIANT; + p_identifier->set_datatype(dummy); return; } + default: { + Callable::CallError temp; + Variant dummy = Variant::construct(base.builtin_type, nullptr, 0, temp); + List<PropertyInfo> properties; + dummy.get_property_list(&properties); + for (const List<PropertyInfo>::Element *E = properties.front(); E != nullptr; E = E->next()) { + const PropertyInfo &prop = E->get(); + if (prop.name == name) { + p_identifier->set_datatype(type_from_property(prop)); + return; + } + } + push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier); + } } - push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier); } return; } @@ -2964,7 +2987,7 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p if (arg_type.is_variant()) { // Argument can be anything, so this is unsafe. mark_node_unsafe(p_call->arguments[i]); - } else if (!is_type_compatible(par_type, arg_type, true)) { + } else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) { // Supertypes are acceptable for dynamic compliance, but it's unsafe. mark_node_unsafe(p_call); if (!is_type_compatible(arg_type, par_type)) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 4e06e0a530..c3911cce76 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -67,7 +67,7 @@ class GDScriptAnalyzer { void resolve_match(GDScriptParser::MatchNode *p_match); void resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test); void resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test); - void resolve_pararameter(GDScriptParser::ParameterNode *p_parameter); + void resolve_parameter(GDScriptParser::ParameterNode *p_parameter); void resolve_return(GDScriptParser::ReturnNode *p_return); // Reduction functions. diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp new file mode 100644 index 0000000000..8f0ce99de6 --- /dev/null +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -0,0 +1,736 @@ +/*************************************************************************/ +/* gdscript_byte_codegen.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_byte_codegen.h" + +#include "core/debugger/engine_debugger.h" +#include "gdscript.h" + +uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) { +#ifdef TOOLS_ENABLED + function->arg_names.push_back(p_name); +#endif + function->_argument_count++; + function->argument_types.push_back(p_type); + if (p_is_optional) { + if (function->_default_arg_count == 0) { + append(GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT); + } + function->default_arguments.push_back(opcodes.size()); + function->_default_arg_count++; + } + + return add_local(p_name, p_type); +} + +uint32_t GDScriptByteCodeGenerator::add_local(const StringName &p_name, const GDScriptDataType &p_type) { + int stack_pos = increase_stack(); + add_stack_identifier(p_name, stack_pos); + return stack_pos; +} + +uint32_t GDScriptByteCodeGenerator::add_local_constant(const StringName &p_name, const Variant &p_constant) { + int index = add_or_get_constant(p_constant); + local_constants[p_name] = index; + return index; +} + +uint32_t GDScriptByteCodeGenerator::add_or_get_constant(const Variant &p_constant) { + if (constant_map.has(p_constant)) { + return constant_map[p_constant]; + } + int index = constant_map.size(); + constant_map[p_constant] = index; + return index; +} + +uint32_t GDScriptByteCodeGenerator::add_or_get_name(const StringName &p_name) { + return get_name_map_pos(p_name); +} + +uint32_t GDScriptByteCodeGenerator::add_temporary() { + current_temporaries++; + return increase_stack(); +} + +void GDScriptByteCodeGenerator::pop_temporary() { + current_stack_size--; + current_temporaries--; +} + +void GDScriptByteCodeGenerator::start_parameters() {} + +void GDScriptByteCodeGenerator::end_parameters() { + function->default_arguments.invert(); +} + +void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) { + function = memnew(GDScriptFunction); + debug_stack = EngineDebugger::is_active(); + + function->name = p_function_name; + function->_script = p_script; + function->source = p_script->get_path(); + +#ifdef DEBUG_ENABLED + function->func_cname = (String(function->source) + " - " + String(p_function_name)).utf8(); + function->_func_cname = function->func_cname.get_data(); +#endif + + function->_static = p_static; + function->return_type = p_return_type; + function->rpc_mode = p_rpc_mode; + function->_argument_count = 0; +} + +GDScriptFunction *GDScriptByteCodeGenerator::write_end() { + append(GDScriptFunction::OPCODE_END); + + if (constant_map.size()) { + function->_constant_count = constant_map.size(); + function->constants.resize(constant_map.size()); + function->_constants_ptr = function->constants.ptrw(); + const Variant *K = nullptr; + while ((K = constant_map.next(K))) { + int idx = constant_map[*K]; + function->constants.write[idx] = *K; + } + } else { + function->_constants_ptr = nullptr; + function->_constant_count = 0; + } + + if (name_map.size()) { + function->global_names.resize(name_map.size()); + function->_global_names_ptr = &function->global_names[0]; + for (Map<StringName, int>::Element *E = name_map.front(); E; E = E->next()) { + function->global_names.write[E->get()] = E->key(); + } + function->_global_names_count = function->global_names.size(); + + } else { + function->_global_names_ptr = nullptr; + function->_global_names_count = 0; + } + + if (opcodes.size()) { + function->code = opcodes; + function->_code_ptr = &function->code[0]; + function->_code_size = opcodes.size(); + + } else { + function->_code_ptr = nullptr; + function->_code_size = 0; + } + + if (function->default_arguments.size()) { + function->_default_arg_count = function->default_arguments.size(); + function->_default_arg_ptr = &function->default_arguments[0]; + } else { + function->_default_arg_count = 0; + function->_default_arg_ptr = nullptr; + } + + if (debug_stack) { + function->stack_debug = stack_debug; + } + function->_stack_size = stack_max; + function->_call_size = call_max; + + ended = true; + return function; +} + +#ifdef DEBUG_ENABLED +void GDScriptByteCodeGenerator::set_signature(const String &p_signature) { + function->profile.signature = p_signature; +} +#endif + +void GDScriptByteCodeGenerator::set_initial_line(int p_line) { + function->_initial_line = p_line; +} + +void GDScriptByteCodeGenerator::write_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) { + append(GDScriptFunction::OPCODE_OPERATOR); + append(p_operator); + append(p_left_operand); + append(p_right_operand); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) { + append(GDScriptFunction::OPCODE_EXTENDS_TEST); + append(p_source); + append(p_type); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) { + append(GDScriptFunction::OPCODE_IS_BUILTIN); + append(p_source); + append(p_type); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_and_left_operand(const Address &p_left_operand) { + append(GDScriptFunction::OPCODE_JUMP_IF_NOT); + append(p_left_operand); + logic_op_jump_pos1.push_back(opcodes.size()); + append(0); // Jump target, will be patched. +} + +void GDScriptByteCodeGenerator::write_and_right_operand(const Address &p_right_operand) { + append(GDScriptFunction::OPCODE_JUMP_IF_NOT); + append(p_right_operand); + logic_op_jump_pos2.push_back(opcodes.size()); + append(0); // Jump target, will be patched. +} + +void GDScriptByteCodeGenerator::write_end_and(const Address &p_target) { + // If here means both operands are true. + append(GDScriptFunction::OPCODE_ASSIGN_TRUE); + append(p_target); + // Jump away from the fail condition. + append(GDScriptFunction::OPCODE_JUMP); + append(opcodes.size() + 3); + // Here it means one of operands is false. + patch_jump(logic_op_jump_pos1.back()->get()); + patch_jump(logic_op_jump_pos2.back()->get()); + logic_op_jump_pos1.pop_back(); + logic_op_jump_pos2.pop_back(); + append(GDScriptFunction::OPCODE_ASSIGN_FALSE); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_or_left_operand(const Address &p_left_operand) { + append(GDScriptFunction::OPCODE_JUMP_IF); + append(p_left_operand); + logic_op_jump_pos1.push_back(opcodes.size()); + append(0); // Jump target, will be patched. +} + +void GDScriptByteCodeGenerator::write_or_right_operand(const Address &p_right_operand) { + append(GDScriptFunction::OPCODE_JUMP_IF); + append(p_right_operand); + logic_op_jump_pos2.push_back(opcodes.size()); + append(0); // Jump target, will be patched. +} + +void GDScriptByteCodeGenerator::write_end_or(const Address &p_target) { + // If here means both operands are false. + append(GDScriptFunction::OPCODE_ASSIGN_FALSE); + append(p_target); + // Jump away from the success condition. + append(GDScriptFunction::OPCODE_JUMP); + append(opcodes.size() + 3); + // Here it means one of operands is false. + patch_jump(logic_op_jump_pos1.back()->get()); + patch_jump(logic_op_jump_pos2.back()->get()); + logic_op_jump_pos1.pop_back(); + logic_op_jump_pos2.pop_back(); + append(GDScriptFunction::OPCODE_ASSIGN_TRUE); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_start_ternary(const Address &p_target) { + ternary_result.push_back(p_target); +} + +void GDScriptByteCodeGenerator::write_ternary_condition(const Address &p_condition) { + append(GDScriptFunction::OPCODE_JUMP_IF_NOT); + append(p_condition); + ternary_jump_fail_pos.push_back(opcodes.size()); + append(0); // Jump target, will be patched. +} + +void GDScriptByteCodeGenerator::write_ternary_true_expr(const Address &p_expr) { + append(GDScriptFunction::OPCODE_ASSIGN); + append(ternary_result.back()->get()); + append(p_expr); + // Jump away from the false path. + append(GDScriptFunction::OPCODE_JUMP); + ternary_jump_skip_pos.push_back(opcodes.size()); + append(0); + // Fail must jump here. + patch_jump(ternary_jump_fail_pos.back()->get()); + ternary_jump_fail_pos.pop_back(); +} + +void GDScriptByteCodeGenerator::write_ternary_false_expr(const Address &p_expr) { + append(GDScriptFunction::OPCODE_ASSIGN); + append(ternary_result.back()->get()); + append(p_expr); +} + +void GDScriptByteCodeGenerator::write_end_ternary() { + patch_jump(ternary_jump_skip_pos.back()->get()); + ternary_jump_skip_pos.pop_back(); +} + +void GDScriptByteCodeGenerator::write_set(const Address &p_target, const Address &p_index, const Address &p_source) { + append(GDScriptFunction::OPCODE_SET); + append(p_target); + append(p_index); + append(p_source); +} + +void GDScriptByteCodeGenerator::write_get(const Address &p_target, const Address &p_index, const Address &p_source) { + append(GDScriptFunction::OPCODE_GET); + append(p_source); + append(p_index); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_set_named(const Address &p_target, const StringName &p_name, const Address &p_source) { + append(GDScriptFunction::OPCODE_SET_NAMED); + append(p_target); + append(p_name); + append(p_source); +} + +void GDScriptByteCodeGenerator::write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) { + append(GDScriptFunction::OPCODE_GET_NAMED); + append(p_source); + append(p_name); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_set_member(const Address &p_value, const StringName &p_name) { + append(GDScriptFunction::OPCODE_SET_MEMBER); + append(p_name); + append(p_value); +} + +void GDScriptByteCodeGenerator::write_get_member(const Address &p_target, const StringName &p_name) { + append(GDScriptFunction::OPCODE_GET_MEMBER); + append(p_name); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) { + if (p_target.type.has_type && !p_source.type.has_type) { + // Typed assignment. + switch (p_target.type.kind) { + case GDScriptDataType::BUILTIN: { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); + append(p_target.type.builtin_type); + append(p_target); + append(p_source); + } break; + case GDScriptDataType::NATIVE: { + int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type]; + class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); + append(class_idx); + append(p_target); + append(p_source); + } break; + case GDScriptDataType::SCRIPT: + case GDScriptDataType::GDSCRIPT: { + Variant script = p_target.type.script_type; + int idx = get_constant_pos(script); + + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); + append(idx); + append(p_target); + append(p_source); + } break; + default: { + ERR_PRINT("Compiler bug: unresolved assign."); + + // Shouldn't get here, but fail-safe to a regular assignment + append(GDScriptFunction::OPCODE_ASSIGN); + append(p_target); + append(p_source); + } + } + } else { + if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { + // Need conversion.. + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); + append(p_target.type.builtin_type); + append(p_target); + append(p_source); + } else { + // Either untyped assignment or already type-checked by the parser + append(GDScriptFunction::OPCODE_ASSIGN); + append(p_target); + append(p_source); + } + } +} + +void GDScriptByteCodeGenerator::write_assign_true(const Address &p_target) { + append(GDScriptFunction::OPCODE_ASSIGN_TRUE); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_assign_false(const Address &p_target) { + append(GDScriptFunction::OPCODE_ASSIGN_FALSE); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) { + switch (p_type.kind) { + case GDScriptDataType::BUILTIN: { + append(GDScriptFunction::OPCODE_CAST_TO_BUILTIN); + append(p_type.builtin_type); + } break; + case GDScriptDataType::NATIVE: { + int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_type.native_type]; + class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); + append(GDScriptFunction::OPCODE_CAST_TO_NATIVE); + append(class_idx); + } break; + case GDScriptDataType::SCRIPT: + case GDScriptDataType::GDSCRIPT: { + Variant script = p_type.script_type; + int idx = get_constant_pos(script); + + append(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); + append(idx); + } break; + default: { + return; + } + } + + append(p_source); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { + append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); + append(p_arguments.size()); + append(p_base); + append(p_function_name); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + alloc_call(p_arguments.size()); +} + +void GDScriptByteCodeGenerator::write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CALL_SELF_BASE); + append(p_function_name); + append(p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + alloc_call(p_arguments.size()); +} + +void GDScriptByteCodeGenerator::write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CALL_ASYNC); + append(p_arguments.size()); + append(p_base); + append(p_function_name); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + alloc_call(p_arguments.size()); +} + +void GDScriptByteCodeGenerator::write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CALL_BUILT_IN); + append(p_function); + append(p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + alloc_call(p_arguments.size()); +} + +void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target, const Address &p_base, const MethodBind *p_method, const Vector<Address> &p_arguments) { + append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); + append(p_arguments.size()); + append(p_base); + append(p_method->get_name()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + alloc_call(p_arguments.size()); +} + +void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, const Address &p_base, const MethodBind *p_method, const Vector<Address> &p_arguments) { + append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); + append(p_arguments.size()); + append(p_base); + append(p_method->get_name()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + alloc_call(p_arguments.size()); +} + +void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { + append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); + append(p_arguments.size()); + append(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); + append(p_function_name); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + alloc_call(p_arguments.size()); +} + +void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { + append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); + append(p_arguments.size()); + append(p_base); + append(p_function_name); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + alloc_call(p_arguments.size()); +} + +void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CONSTRUCT); + append(p_type); + append(p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); +} + +void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CONSTRUCT_ARRAY); + append(p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); +} + +void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY); + append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments. + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); +} + +void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) { + append(GDScriptFunction::OPCODE_AWAIT); + append(p_operand); + append(GDScriptFunction::OPCODE_AWAIT_RESUME); + append(p_target); +} + +void GDScriptByteCodeGenerator::write_if(const Address &p_condition) { + append(GDScriptFunction::OPCODE_JUMP_IF_NOT); + append(p_condition); + if_jmp_addrs.push_back(opcodes.size()); + append(0); // Jump destination, will be patched. +} + +void GDScriptByteCodeGenerator::write_else() { + append(GDScriptFunction::OPCODE_JUMP); // Jump from true if block; + int else_jmp_addr = opcodes.size(); + append(0); // Jump destination, will be patched. + + patch_jump(if_jmp_addrs.back()->get()); + if_jmp_addrs.pop_back(); + if_jmp_addrs.push_back(else_jmp_addr); +} + +void GDScriptByteCodeGenerator::write_endif() { + patch_jump(if_jmp_addrs.back()->get()); + if_jmp_addrs.pop_back(); +} + +void GDScriptByteCodeGenerator::write_for(const Address &p_variable, const Address &p_list) { + int counter_pos = increase_stack() | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + int container_pos = increase_stack() | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + + current_breaks_to_patch.push_back(List<int>()); + + // Assign container. + append(GDScriptFunction::OPCODE_ASSIGN); + append(container_pos); + append(p_list); + + // Begin loop. + append(GDScriptFunction::OPCODE_ITERATE_BEGIN); + append(counter_pos); + append(container_pos); + for_jmp_addrs.push_back(opcodes.size()); + append(0); // End of loop address, will be patched. + append(p_variable); + append(GDScriptFunction::OPCODE_JUMP); + append(opcodes.size() + 6); // Skip over 'continue' code. + + // Next iteration. + int continue_addr = opcodes.size(); + continue_addrs.push_back(continue_addr); + append(GDScriptFunction::OPCODE_ITERATE); + append(counter_pos); + append(container_pos); + for_jmp_addrs.push_back(opcodes.size()); + append(0); // Jump destination, will be patched. + append(p_variable); +} + +void GDScriptByteCodeGenerator::write_endfor() { + // Jump back to loop check. + append(GDScriptFunction::OPCODE_JUMP); + append(continue_addrs.back()->get()); + continue_addrs.pop_back(); + + // Patch end jumps (two of them). + for (int i = 0; i < 2; i++) { + patch_jump(for_jmp_addrs.back()->get()); + for_jmp_addrs.pop_back(); + } + + // Patch break statements. + for (const List<int>::Element *E = current_breaks_to_patch.back()->get().front(); E; E = E->next()) { + patch_jump(E->get()); + } + current_breaks_to_patch.pop_back(); + + current_stack_size -= 2; // Remove loop temporaries. +} + +void GDScriptByteCodeGenerator::start_while_condition() { + current_breaks_to_patch.push_back(List<int>()); + continue_addrs.push_back(opcodes.size()); +} + +void GDScriptByteCodeGenerator::write_while(const Address &p_condition) { + // Condition check. + append(GDScriptFunction::OPCODE_JUMP_IF_NOT); + append(p_condition); + while_jmp_addrs.push_back(opcodes.size()); + append(0); // End of loop address, will be patched. +} + +void GDScriptByteCodeGenerator::write_endwhile() { + // Jump back to loop check. + append(GDScriptFunction::OPCODE_JUMP); + append(continue_addrs.back()->get()); + continue_addrs.pop_back(); + + // Patch end jump. + patch_jump(while_jmp_addrs.back()->get()); + while_jmp_addrs.pop_back(); + + // Patch break statements. + for (const List<int>::Element *E = current_breaks_to_patch.back()->get().front(); E; E = E->next()) { + patch_jump(E->get()); + } + current_breaks_to_patch.pop_back(); +} + +void GDScriptByteCodeGenerator::start_match() { + match_continues_to_patch.push_back(List<int>()); +} + +void GDScriptByteCodeGenerator::start_match_branch() { + // Patch continue statements. + for (const List<int>::Element *E = match_continues_to_patch.back()->get().front(); E; E = E->next()) { + patch_jump(E->get()); + } + match_continues_to_patch.pop_back(); + // Start a new list for next branch. + match_continues_to_patch.push_back(List<int>()); +} + +void GDScriptByteCodeGenerator::end_match() { + // Patch continue statements. + for (const List<int>::Element *E = match_continues_to_patch.back()->get().front(); E; E = E->next()) { + patch_jump(E->get()); + } + match_continues_to_patch.pop_back(); +} + +void GDScriptByteCodeGenerator::write_break() { + append(GDScriptFunction::OPCODE_JUMP); + current_breaks_to_patch.back()->get().push_back(opcodes.size()); + append(0); +} + +void GDScriptByteCodeGenerator::write_continue() { + append(GDScriptFunction::OPCODE_JUMP); + append(continue_addrs.back()->get()); +} + +void GDScriptByteCodeGenerator::write_continue_match() { + append(GDScriptFunction::OPCODE_JUMP); + match_continues_to_patch.back()->get().push_back(opcodes.size()); + append(0); +} + +void GDScriptByteCodeGenerator::write_breakpoint() { + append(GDScriptFunction::OPCODE_BREAKPOINT); +} + +void GDScriptByteCodeGenerator::write_newline(int p_line) { + append(GDScriptFunction::OPCODE_LINE); + append(p_line); + current_line = p_line; +} + +void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { + append(GDScriptFunction::OPCODE_RETURN); + append(p_return_value); +} + +void GDScriptByteCodeGenerator::write_assert(const Address &p_test, const Address &p_message) { + append(GDScriptFunction::OPCODE_ASSERT); + append(p_test); + append(p_message); +} + +void GDScriptByteCodeGenerator::start_block() { + push_stack_identifiers(); +} + +void GDScriptByteCodeGenerator::end_block() { + pop_stack_identifiers(); +} + +GDScriptByteCodeGenerator::~GDScriptByteCodeGenerator() { + if (!ended && function != nullptr) { + memdelete(function); + } +} diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h new file mode 100644 index 0000000000..62438b6dd2 --- /dev/null +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -0,0 +1,277 @@ +/*************************************************************************/ +/* gdscript_byte_codegen.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_BYTE_CODEGEN +#define GDSCRIPT_BYTE_CODEGEN + +#include "gdscript_codegen.h" + +class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { + bool ended = false; + GDScriptFunction *function = nullptr; + bool debug_stack = false; + + Vector<int> opcodes; + List<Map<StringName, int>> stack_id_stack; + Map<StringName, int> stack_identifiers; + Map<StringName, int> local_constants; + + List<GDScriptFunction::StackDebug> stack_debug; + List<Map<StringName, int>> block_identifier_stack; + Map<StringName, int> block_identifiers; + + int current_stack_size = 0; + int current_temporaries = 0; + + HashMap<Variant, int, VariantHasher, VariantComparator> constant_map; + Map<StringName, int> name_map; +#ifdef TOOLS_ENABLED + Vector<StringName> named_globals; +#endif + int current_line = 0; + int stack_max = 0; + int call_max = 0; + + List<int> if_jmp_addrs; // List since this can be nested. + List<int> for_jmp_addrs; + List<int> while_jmp_addrs; + List<int> continue_addrs; + + // Used to patch jumps with `and` and `or` operators with short-circuit. + List<int> logic_op_jump_pos1; + List<int> logic_op_jump_pos2; + + List<Address> ternary_result; + List<int> ternary_jump_fail_pos; + List<int> ternary_jump_skip_pos; + + List<List<int>> current_breaks_to_patch; + List<List<int>> match_continues_to_patch; + + void add_stack_identifier(const StringName &p_id, int p_stackpos) { + stack_identifiers[p_id] = p_stackpos; + if (debug_stack) { + block_identifiers[p_id] = p_stackpos; + GDScriptFunction::StackDebug sd; + sd.added = true; + sd.line = current_line; + sd.identifier = p_id; + sd.pos = p_stackpos; + stack_debug.push_back(sd); + } + } + + void push_stack_identifiers() { + stack_id_stack.push_back(stack_identifiers); + if (debug_stack) { + block_identifier_stack.push_back(block_identifiers); + block_identifiers.clear(); + } + } + + void pop_stack_identifiers() { + stack_identifiers = stack_id_stack.back()->get(); + current_stack_size = stack_identifiers.size() + current_temporaries; + stack_id_stack.pop_back(); + + if (debug_stack) { + for (Map<StringName, int>::Element *E = block_identifiers.front(); E; E = E->next()) { + GDScriptFunction::StackDebug sd; + sd.added = false; + sd.identifier = E->key(); + sd.line = current_line; + sd.pos = E->get(); + stack_debug.push_back(sd); + } + block_identifiers = block_identifier_stack.back()->get(); + block_identifier_stack.pop_back(); + } + } + + int get_name_map_pos(const StringName &p_identifier) { + int ret; + if (!name_map.has(p_identifier)) { + ret = name_map.size(); + name_map[p_identifier] = ret; + } else { + ret = name_map[p_identifier]; + } + return ret; + } + + int get_constant_pos(const Variant &p_constant) { + if (constant_map.has(p_constant)) + return constant_map[p_constant]; + int pos = constant_map.size(); + constant_map[p_constant] = pos; + return pos; + } + + void alloc_stack(int p_level) { + if (p_level >= stack_max) + stack_max = p_level + 1; + } + + void alloc_call(int p_params) { + if (p_params >= call_max) + call_max = p_params; + } + + int increase_stack() { + int top = current_stack_size++; + alloc_stack(current_stack_size); + return top; + } + + int address_of(const Address &p_address) { + switch (p_address.mode) { + case Address::SELF: + return GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS; + case Address::CLASS: + return GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS; + case Address::MEMBER: + return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); + case Address::CLASS_CONSTANT: + return p_address.address | (GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS); + case Address::LOCAL_CONSTANT: + case Address::CONSTANT: + return p_address.address | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); + case Address::LOCAL_VARIABLE: + case Address::TEMPORARY: + case Address::FUNCTION_PARAMETER: + return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + case Address::GLOBAL: + return p_address.address | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); + case Address::NAMED_GLOBAL: + return p_address.address | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS); + case Address::NIL: + return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; + } + return -1; // Unreachable. + } + + void append(int code) { + opcodes.push_back(code); + } + + void append(const Address &p_address) { + opcodes.push_back(address_of(p_address)); + } + + void append(const StringName &p_name) { + opcodes.push_back(get_name_map_pos(p_name)); + } + + void patch_jump(int p_address) { + opcodes.write[p_address] = opcodes.size(); + } + +public: + virtual uint32_t add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) override; + virtual uint32_t add_local(const StringName &p_name, const GDScriptDataType &p_type) override; + virtual uint32_t add_local_constant(const StringName &p_name, const Variant &p_constant) override; + virtual uint32_t add_or_get_constant(const Variant &p_constant) override; + virtual uint32_t add_or_get_name(const StringName &p_name) override; + virtual uint32_t add_temporary() override; + virtual void pop_temporary() override; + + virtual void start_parameters() override; + virtual void end_parameters() override; + + virtual void start_block() override; + virtual void end_block() override; + + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) override; + virtual GDScriptFunction *write_end() override; + +#ifdef DEBUG_ENABLED + virtual void set_signature(const String &p_signature) override; +#endif + virtual void set_initial_line(int p_line) override; + + virtual void write_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) override; + virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) override; + virtual void write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) override; + virtual void write_and_left_operand(const Address &p_left_operand) override; + virtual void write_and_right_operand(const Address &p_right_operand) override; + virtual void write_end_and(const Address &p_target) override; + virtual void write_or_left_operand(const Address &p_left_operand) override; + virtual void write_or_right_operand(const Address &p_right_operand) override; + virtual void write_end_or(const Address &p_target) override; + virtual void write_start_ternary(const Address &p_target) override; + virtual void write_ternary_condition(const Address &p_condition) override; + virtual void write_ternary_true_expr(const Address &p_expr) override; + virtual void write_ternary_false_expr(const Address &p_expr) override; + virtual void write_end_ternary() override; + virtual void write_set(const Address &p_target, const Address &p_index, const Address &p_source) override; + virtual void write_get(const Address &p_target, const Address &p_index, const Address &p_source) override; + virtual void write_set_named(const Address &p_target, const StringName &p_name, const Address &p_source) override; + virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) override; + virtual void write_set_member(const Address &p_value, const StringName &p_name) override; + virtual void write_get_member(const Address &p_target, const StringName &p_name) override; + virtual void write_assign(const Address &p_target, const Address &p_source) override; + virtual void write_assign_true(const Address &p_target) override; + virtual void write_assign_false(const Address &p_target) override; + virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override; + virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) override; + virtual void write_call_method_bind(const Address &p_target, const Address &p_base, const MethodBind *p_method, const Vector<Address> &p_arguments) override; + virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, const MethodBind *p_method, const Vector<Address> &p_arguments) override; + virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) override; + virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override; + virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override; + virtual void write_await(const Address &p_target, const Address &p_operand) override; + virtual void write_if(const Address &p_condition) override; + virtual void write_else() override; + virtual void write_endif() override; + virtual void write_for(const Address &p_variable, const Address &p_list) override; + virtual void write_endfor() override; + virtual void start_while_condition() override; + virtual void write_while(const Address &p_condition) override; + virtual void write_endwhile() override; + virtual void start_match() override; + virtual void start_match_branch() override; + virtual void end_match() override; + virtual void write_break() override; + virtual void write_continue() override; + virtual void write_continue_match() override; + virtual void write_breakpoint() override; + virtual void write_newline(int p_line) override; + virtual void write_return(const Address &p_return_value) override; + virtual void write_assert(const Address &p_test, const Address &p_message) override; + + virtual ~GDScriptByteCodeGenerator(); +}; + +#endif // GDSCRIPT_BYTE_CODEGEN diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 992f8f4b58..57b95f5b21 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -108,20 +108,20 @@ GDScriptParserRef::~GDScriptParserRef() { if (analyzer != nullptr) { memdelete(analyzer); } - MutexLock(GDScriptCache::singleton->lock); + MutexLock lock(GDScriptCache::singleton->lock); GDScriptCache::singleton->parser_map.erase(path); } GDScriptCache *GDScriptCache::singleton = nullptr; void GDScriptCache::remove_script(const String &p_path) { - MutexLock(singleton->lock); + MutexLock lock(singleton->lock); singleton->shallow_gdscript_cache.erase(p_path); singleton->full_gdscript_cache.erase(p_path); } Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) { - MutexLock(singleton->lock); + MutexLock lock(singleton->lock); Ref<GDScriptParserRef> ref; if (p_owner != String()) { singleton->dependencies[p_owner].insert(p_path); @@ -168,7 +168,7 @@ String GDScriptCache::get_source_code(const String &p_path) { } Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const String &p_owner) { - MutexLock(singleton->lock); + MutexLock lock(singleton->lock); if (p_owner != String()) { singleton->dependencies[p_owner].insert(p_path); } @@ -190,7 +190,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri } Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner) { - MutexLock(singleton->lock); + MutexLock lock(singleton->lock); if (p_owner != String()) { singleton->dependencies[p_owner].insert(p_path); diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h new file mode 100644 index 0000000000..31e1e6ba23 --- /dev/null +++ b/modules/gdscript/gdscript_codegen.h @@ -0,0 +1,160 @@ +/*************************************************************************/ +/* gdscript_codegen.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_CODEGEN +#define GDSCRIPT_CODEGEN + +#include "core/io/multiplayer_api.h" +#include "core/string_name.h" +#include "core/variant.h" +#include "gdscript_function.h" +#include "gdscript_functions.h" + +class GDScriptCodeGenerator { +public: + struct Address { + enum AddressMode { + SELF, + CLASS, + MEMBER, + CONSTANT, + CLASS_CONSTANT, + LOCAL_CONSTANT, + LOCAL_VARIABLE, + FUNCTION_PARAMETER, + TEMPORARY, + GLOBAL, + NAMED_GLOBAL, + NIL, + }; + AddressMode mode = NIL; + uint32_t address = 0; + GDScriptDataType type; + + Address() {} + Address(AddressMode p_mode, const GDScriptDataType &p_type = GDScriptDataType()) { + mode = p_mode; + type = p_type; + } + Address(AddressMode p_mode, uint32_t p_address, const GDScriptDataType &p_type = GDScriptDataType()) { + mode = p_mode, + address = p_address; + type = p_type; + } + }; + + virtual uint32_t add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) = 0; + virtual uint32_t add_local(const StringName &p_name, const GDScriptDataType &p_type) = 0; + virtual uint32_t add_local_constant(const StringName &p_name, const Variant &p_constant) = 0; + virtual uint32_t add_or_get_constant(const Variant &p_constant) = 0; + virtual uint32_t add_or_get_name(const StringName &p_name) = 0; + virtual uint32_t add_temporary() = 0; + virtual void pop_temporary() = 0; + + virtual void start_parameters() = 0; + virtual void end_parameters() = 0; + + virtual void start_block() = 0; + virtual void end_block() = 0; + + // virtual int get_max_stack_level() = 0; + // virtual int get_max_function_arguments() = 0; + + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) = 0; + virtual GDScriptFunction *write_end() = 0; + +#ifdef DEBUG_ENABLED + virtual void set_signature(const String &p_signature) = 0; +#endif + virtual void set_initial_line(int p_line) = 0; + + // virtual void alloc_stack(int p_level) = 0; // Is this needed? + // virtual void alloc_call(int p_arg_count) = 0; // This might be automatic from other functions. + + virtual void write_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) = 0; + virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) = 0; + virtual void write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) = 0; + virtual void write_and_left_operand(const Address &p_left_operand) = 0; + virtual void write_and_right_operand(const Address &p_right_operand) = 0; + virtual void write_end_and(const Address &p_target) = 0; + virtual void write_or_left_operand(const Address &p_left_operand) = 0; + virtual void write_or_right_operand(const Address &p_right_operand) = 0; + virtual void write_end_or(const Address &p_target) = 0; + virtual void write_start_ternary(const Address &p_target) = 0; + virtual void write_ternary_condition(const Address &p_condition) = 0; + virtual void write_ternary_true_expr(const Address &p_expr) = 0; + virtual void write_ternary_false_expr(const Address &p_expr) = 0; + virtual void write_end_ternary() = 0; + virtual void write_set(const Address &p_target, const Address &p_index, const Address &p_source) = 0; + virtual void write_get(const Address &p_target, const Address &p_index, const Address &p_source) = 0; + virtual void write_set_named(const Address &p_target, const StringName &p_name, const Address &p_source) = 0; + virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) = 0; + virtual void write_set_member(const Address &p_value, const StringName &p_name) = 0; + virtual void write_get_member(const Address &p_target, const StringName &p_name) = 0; + virtual void write_assign(const Address &p_target, const Address &p_source) = 0; + virtual void write_assign_true(const Address &p_target) = 0; + virtual void write_assign_false(const Address &p_target) = 0; + virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0; + virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_call_builtin(const Address &p_target, GDScriptFunctions::Function p_function, const Vector<Address> &p_arguments) = 0; + virtual void write_call_method_bind(const Address &p_target, const Address &p_base, const MethodBind *p_method, const Vector<Address> &p_arguments) = 0; + virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, const MethodBind *p_method, const Vector<Address> &p_arguments) = 0; + virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) = 0; + virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0; + virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0; + virtual void write_await(const Address &p_target, const Address &p_operand) = 0; + virtual void write_if(const Address &p_condition) = 0; + // virtual void write_elseif(const Address &p_condition) = 0; This kind of makes things more difficult for no real benefit. + virtual void write_else() = 0; + virtual void write_endif() = 0; + virtual void write_for(const Address &p_variable, const Address &p_list) = 0; + virtual void write_endfor() = 0; + virtual void start_while_condition() = 0; // Used to allow a jump to the expression evaluation. + virtual void write_while(const Address &p_condition) = 0; + virtual void write_endwhile() = 0; + virtual void start_match() = 0; + virtual void start_match_branch() = 0; + virtual void end_match() = 0; + virtual void write_break() = 0; + virtual void write_continue() = 0; + virtual void write_continue_match() = 0; + virtual void write_breakpoint() = 0; + virtual void write_newline(int p_line) = 0; + virtual void write_return(const Address &p_return_value) = 0; + virtual void write_assert(const Address &p_test, const Address &p_message) = 0; + + virtual ~GDScriptCodeGenerator() {} +}; + +#endif // GDSCRIPT_CODEGEN diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 67a894912f..bad450c9f9 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -31,6 +31,7 @@ #include "gdscript_compiler.h" #include "gdscript.h" +#include "gdscript_byte_codegen.h" #include "gdscript_cache.h" bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) { @@ -38,7 +39,7 @@ bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringN return false; } - if (codegen.stack_identifiers.has(p_name)) { + if (codegen.locals.has(p_name)) { return false; //shadowed } @@ -75,46 +76,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N } } -bool GDScriptCompiler::_create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level) { - int src_address_a = _parse_expression(codegen, on->operand, p_stack_level); - if (src_address_a < 0) { - return false; - } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); // perform operator - codegen.opcodes.push_back(op); //which operator - codegen.opcodes.push_back(src_address_a); // argument 1 - codegen.opcodes.push_back(src_address_a); // argument 2 (repeated) - //codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_NIL); // argument 2 (unary only takes one parameter) - return true; -} - -bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer, int p_index_addr) { - int src_address_a = _parse_expression(codegen, p_left_operand, p_stack_level, false, p_initializer, p_index_addr); - if (src_address_a < 0) { - return false; - } - if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - p_stack_level++; //uses stack for return, increase stack - } - - int src_address_b = _parse_expression(codegen, p_right_operand, p_stack_level, false, p_initializer); - if (src_address_b < 0) { - return false; - } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); // perform operator - codegen.opcodes.push_back(op); //which operator - codegen.opcodes.push_back(src_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) - return true; -} - -bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer, int p_index_addr) { - return _create_binary_operator(codegen, on->left_operand, on->right_operand, op, p_stack_level, p_initializer, p_index_addr); -} - -GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const { +GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) const { if (!p_datatype.is_set() || !p_datatype.is_hard_type()) { return GDScriptDataType(); } @@ -136,7 +98,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } break; case GDScriptParser::DataType::SCRIPT: { result.kind = GDScriptDataType::SCRIPT; - result.script_type = p_datatype.script_type; + result.script_type = Ref<Script>(p_datatype.script_type).ptr(); result.native_type = result.script_type->get_instance_base_type(); } break; case GDScriptParser::DataType::CLASS: { @@ -162,11 +124,11 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D names.pop_back(); } result.kind = GDScriptDataType::GDSCRIPT; - result.script_type = script; + result.script_type = script.ptr(); result.native_type = script->get_instance_base_type(); } else { result.kind = GDScriptDataType::GDSCRIPT; - result.script_type = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path); + result.script_type = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path).ptr(); result.native_type = p_datatype.native_type; } } @@ -187,202 +149,73 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } - return result; -} - -int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::AssignmentNode *p_assignment, int p_stack_level, int p_index_addr) { - Variant::Operator var_op = Variant::OP_MAX; - - switch (p_assignment->operation) { - case GDScriptParser::AssignmentNode::OP_ADDITION: - var_op = Variant::OP_ADD; - break; - case GDScriptParser::AssignmentNode::OP_SUBTRACTION: - var_op = Variant::OP_SUBTRACT; - break; - case GDScriptParser::AssignmentNode::OP_MULTIPLICATION: - var_op = Variant::OP_MULTIPLY; - break; - case GDScriptParser::AssignmentNode::OP_DIVISION: - var_op = Variant::OP_DIVIDE; - break; - case GDScriptParser::AssignmentNode::OP_MODULO: - var_op = Variant::OP_MODULE; - break; - case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_LEFT: - var_op = Variant::OP_SHIFT_LEFT; - break; - case GDScriptParser::AssignmentNode::OP_BIT_SHIFT_RIGHT: - var_op = Variant::OP_SHIFT_RIGHT; - break; - case GDScriptParser::AssignmentNode::OP_BIT_AND: - var_op = Variant::OP_BIT_AND; - break; - case GDScriptParser::AssignmentNode::OP_BIT_OR: - var_op = Variant::OP_BIT_OR; - break; - case GDScriptParser::AssignmentNode::OP_BIT_XOR: - var_op = Variant::OP_BIT_XOR; - break; - case GDScriptParser::AssignmentNode::OP_NONE: { - //none - } break; - default: { - ERR_FAIL_V(-1); - } + // Only hold strong reference to the script if it's not the owner of the + // element qualified with this type, to avoid cyclic references (leaks). + if (result.script_type && result.script_type != p_owner) { + result.script_type_ref = Ref<Script>(result.script_type); } - // bool initializer = p_expression->op == GDScriptParser::OperatorNode::OP_INIT_ASSIGN; - - if (var_op == Variant::OP_MAX) { - return _parse_expression(codegen, p_assignment->assigned_value, p_stack_level, false, false); - } - - if (!_create_binary_operator(codegen, p_assignment->assignee, p_assignment->assigned_value, var_op, p_stack_level, false, p_index_addr)) { - return -1; - } - - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; -} - -bool GDScriptCompiler::_generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type) { - if (p_datatype.has_type && p_value_type.is_variant()) { - // Typed assignment - switch (p_datatype.kind) { - case GDScriptDataType::BUILTIN: { - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator - codegen.opcodes.push_back(p_datatype.builtin_type); // variable type - codegen.opcodes.push_back(p_dst_address); // argument 1 - codegen.opcodes.push_back(p_src_address); // argument 2 - } break; - case GDScriptDataType::NATIVE: { - int class_idx; - if (GDScriptLanguage::get_singleton()->get_global_map().has(p_datatype.native_type)) { - class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_datatype.native_type]; - class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) - } else { - // _set_error("Invalid native class type '" + String(p_datatype.native_type) + "'.", on->arguments[0]); - return false; - } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator - codegen.opcodes.push_back(class_idx); // variable type - codegen.opcodes.push_back(p_dst_address); // argument 1 - codegen.opcodes.push_back(p_src_address); // argument 2 - } break; - case GDScriptDataType::SCRIPT: - case GDScriptDataType::GDSCRIPT: { - Variant script = p_datatype.script_type; - int idx = codegen.get_constant_pos(script); //make it a local constant (faster access) - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator - codegen.opcodes.push_back(idx); // variable type - codegen.opcodes.push_back(p_dst_address); // argument 1 - codegen.opcodes.push_back(p_src_address); // argument 2 - } break; - default: { - ERR_PRINT("Compiler bug: unresolved assign."); - - // Shouldn't get here, but fail-safe to a regular assignment - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator - codegen.opcodes.push_back(p_dst_address); // argument 1 - codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter) - } - } - } else { - if (p_datatype.kind == GDScriptDataType::BUILTIN && p_value_type.kind == GDScriptParser::DataType::BUILTIN && p_datatype.builtin_type != p_value_type.builtin_type) { - // Need conversion. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator - codegen.opcodes.push_back(p_datatype.builtin_type); // variable type - codegen.opcodes.push_back(p_dst_address); // argument 1 - codegen.opcodes.push_back(p_src_address); // argument 2 - } else { - // Either untyped assignment or already type-checked by the parser - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator - codegen.opcodes.push_back(p_dst_address); // argument 1 - codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter) - } - } - return true; + return result; } -int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root, bool p_initializer, int p_index_addr) { +GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) { if (p_expression->is_constant) { - return codegen.get_constant_pos(p_expression->reduced_value); + return codegen.add_constant(p_expression->reduced_value); } + GDScriptCodeGenerator *gen = codegen.generator; + switch (p_expression->type) { - //should parse variable declaration and adjust stack accordingly... case GDScriptParser::Node::IDENTIFIER: { - //return identifier - //wait, identifier could be a local variable or something else... careful here, must reference properly - //as stack may be more interesting to work with - - //This could be made much simpler by just indexing "self", but done this way (with custom self-addressing modes) increases performance a lot. - + // Look for identifiers in current scope. const GDScriptParser::IdentifierNode *in = static_cast<const GDScriptParser::IdentifierNode *>(p_expression); StringName identifier = in->name; - // TRY STACK! - if (!p_initializer && codegen.stack_identifiers.has(identifier)) { - int pos = codegen.stack_identifiers[identifier]; - return pos | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); + // Try function parameters. + if (codegen.parameters.has(identifier)) { + return codegen.parameters[identifier]; } - // TRY LOCAL CONSTANTS! - if (codegen.local_named_constants.has(identifier)) { - return codegen.local_named_constants[identifier] | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); + // Try local variables and constants. + if (!p_initializer && codegen.locals.has(identifier)) { + return codegen.locals[identifier]; } - // TRY CLASS MEMBER + // Try class members. if (_is_class_member_property(codegen, identifier)) { - //get property - codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET_MEMBER); // perform operator - codegen.opcodes.push_back(codegen.get_name_map_pos(identifier)); // argument 2 (unary only takes one parameter) - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; + // Get property. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Could get the type of the class member here. + gen->write_get_member(temp, identifier); + return temp; } - //TRY MEMBERS! + // Try members. if (!codegen.function_node || !codegen.function_node->is_static) { - // TRY MEMBER VARIABLES! - //static function + // Try member variables. if (codegen.script->member_indices.has(identifier)) { if (codegen.script->member_indices[identifier].getter != StringName() && codegen.script->member_indices[identifier].getter != codegen.function_name) { // Perform getter. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_RETURN); - codegen.opcodes.push_back(0); // Argument count. - codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Base (self). - codegen.opcodes.push_back(codegen.get_name_map_pos(codegen.script->member_indices[identifier].getter)); // Method name. - // Destination. - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; + GDScriptCodeGenerator::Address temp = codegen.add_temporary(); + Vector<GDScriptCodeGenerator::Address> args; // No argument needed. + gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args); + return temp; } else { - // No getter or inside getter: direct member access. + // No getter or inside getter: direct member access., int idx = codegen.script->member_indices[identifier].index; - return idx | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); //argument (stack root) + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier)); } } } - //TRY CLASS CONSTANTS - + // Try class constants. GDScript *owner = codegen.script; while (owner) { GDScript *scr = owner; GDScriptNativeClass *nc = nullptr; while (scr) { if (scr->constants.has(identifier)) { - //int idx=scr->constants[identifier]; - int idx = codegen.get_name_map_pos(identifier); - return idx | (GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS); //argument (stack root) + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS_CONSTANT, gen->add_or_get_name(identifier)); // TODO: Get type here. } if (scr->native.is_valid()) { nc = scr->native.ptr(); @@ -390,52 +223,37 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: scr = scr->_base; } - // CLASS C++ Integer Constant - + // Class C++ integer constant. if (nc) { bool success = false; int constant = ClassDB::get_integer_constant(nc->get_name(), identifier, &success); if (success) { - Variant key = constant; - int idx; - - if (!codegen.constant_map.has(key)) { - idx = codegen.constant_map.size(); - codegen.constant_map[key] = idx; - - } else { - idx = codegen.constant_map[key]; - } - - return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access) + return codegen.add_constant(constant); } } owner = owner->_owner; } - // TRY SIGNALS AND METHODS (can be made callables) + // Try signals and methods (can be made callables); if (codegen.class_node->members_indices.has(identifier)) { const GDScriptParser::ClassNode::Member &member = codegen.class_node->members[codegen.class_node->members_indices[identifier]]; if (member.type == GDScriptParser::ClassNode::Member::FUNCTION || member.type == GDScriptParser::ClassNode::Member::SIGNAL) { // Get like it was a property. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET_NAMED); // perform operator - codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Self. - codegen.opcodes.push_back(codegen.get_name_map_pos(identifier)); // argument 2 (unary only takes one parameter) - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; + GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here. + GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF); + + gen->write_get_named(temp, identifier, self); + return temp; } } if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; - return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::GLOBAL, idx); // TODO: Get type. } - /* TRY GLOBAL CLASSES */ - + // Try global classes. if (ScriptServer::is_global_class(identifier)) { const GDScriptParser::ClassNode *class_node = codegen.class_node; while (class_node->outer) { @@ -450,356 +268,209 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); if (res.is_null()) { _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); - return -1; + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } } - Variant key = res; - int idx; - - if (!codegen.constant_map.has(key)) { - idx = codegen.constant_map.size(); - codegen.constant_map[key] = idx; - - } else { - idx = codegen.constant_map[key]; - } - - return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access) + return codegen.add_constant(res); } #ifdef TOOLS_ENABLED if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { - int idx = codegen.named_globals.find(identifier); - if (idx == -1) { - idx = codegen.named_globals.size(); - codegen.named_globals.push_back(identifier); - } - return idx | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NAMED_GLOBAL, gen->add_or_get_name(identifier)); // TODO: Get type. } #endif - //not found, error - + // Not found, error. _set_error("Identifier not found: " + String(identifier), p_expression); - - return -1; - + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } break; case GDScriptParser::Node::LITERAL: { - //return constant + // Return constant. const GDScriptParser::LiteralNode *cn = static_cast<const GDScriptParser::LiteralNode *>(p_expression); - int idx; - - if (!codegen.constant_map.has(cn->value)) { - idx = codegen.constant_map.size(); - codegen.constant_map[cn->value] = idx; - - } else { - idx = codegen.constant_map[cn->value]; - } - - return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //argument (stack root) - + return codegen.add_constant(cn->value); } break; case GDScriptParser::Node::SELF: { //return constant if (codegen.function_node && codegen.function_node->is_static) { _set_error("'self' not present in static function!", p_expression); - return -1; + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } - return (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF); } break; case GDScriptParser::Node::ARRAY: { const GDScriptParser::ArrayNode *an = static_cast<const GDScriptParser::ArrayNode *>(p_expression); - Vector<int> values; + Vector<GDScriptCodeGenerator::Address> values; - int slevel = p_stack_level; + // Create the result temporary first since it's the last to be killed. + GDScriptDataType array_type; + array_type.has_type = true; + array_type.kind = GDScriptDataType::BUILTIN; + array_type.builtin_type = Variant::ARRAY; + GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type); for (int i = 0; i < an->elements.size(); i++) { - int ret = _parse_expression(codegen, an->elements[i], slevel); - if (ret < 0) { - return ret; + GDScriptCodeGenerator::Address val = _parse_expression(codegen, r_error, an->elements[i]); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } - - values.push_back(ret); + values.push_back(val); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT_ARRAY); - codegen.opcodes.push_back(values.size()); + gen->write_construct_array(result, values); + for (int i = 0; i < values.size(); i++) { - codegen.opcodes.push_back(values[i]); + if (values[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; - + return result; } break; case GDScriptParser::Node::DICTIONARY: { const GDScriptParser::DictionaryNode *dn = static_cast<const GDScriptParser::DictionaryNode *>(p_expression); - Vector<int> elements; + Vector<GDScriptCodeGenerator::Address> elements; - int slevel = p_stack_level; + // Create the result temporary first since it's the last to be killed. + GDScriptDataType dict_type; + dict_type.has_type = true; + dict_type.kind = GDScriptDataType::BUILTIN; + dict_type.builtin_type = Variant::DICTIONARY; + GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type); for (int i = 0; i < dn->elements.size(); i++) { // Key. - int ret = -1; + GDScriptCodeGenerator::Address element; switch (dn->style) { case GDScriptParser::DictionaryNode::PYTHON_DICT: // Python-style: key is any expression. - ret = _parse_expression(codegen, dn->elements[i].key, slevel); - if (ret < 0) { - return ret; - } - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + element = _parse_expression(codegen, r_error, dn->elements[i].key); + if (r_error) { + return GDScriptCodeGenerator::Address(); } break; case GDScriptParser::DictionaryNode::LUA_TABLE: // Lua-style: key is an identifier interpreted as string. String key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name; - ret = codegen.get_constant_pos(key); + element = codegen.add_constant(key); break; } - elements.push_back(ret); + elements.push_back(element); - ret = _parse_expression(codegen, dn->elements[i].value, slevel); - if (ret < 0) { - return ret; - } - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + element = _parse_expression(codegen, r_error, dn->elements[i].value); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - elements.push_back(ret); + elements.push_back(element); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY); - codegen.opcodes.push_back(dn->elements.size()); + gen->write_construct_dictionary(result, elements); + for (int i = 0; i < elements.size(); i++) { - codegen.opcodes.push_back(elements[i]); + if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; - + return result; } break; case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); + GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype()); - int slevel = p_stack_level; - int src_addr = _parse_expression(codegen, cn->operand, slevel); - if (src_addr < 0) { - return src_addr; - } - if (src_addr & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } + // Create temporary for result first since it will be deleted last. + GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type); - GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype()); + GDScriptCodeGenerator::Address source = _parse_expression(codegen, r_error, cn->operand); - switch (cast_type.kind) { - case GDScriptDataType::BUILTIN: { - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_BUILTIN); - codegen.opcodes.push_back(cast_type.builtin_type); - } break; - case GDScriptDataType::NATIVE: { - int class_idx; - if (GDScriptLanguage::get_singleton()->get_global_map().has(cast_type.native_type)) { - class_idx = GDScriptLanguage::get_singleton()->get_global_map()[cast_type.native_type]; - class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) - } else { - _set_error("Invalid native class type '" + String(cast_type.native_type) + "'.", cn); - return -1; - } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_NATIVE); // perform operator - codegen.opcodes.push_back(class_idx); // variable type - } break; - case GDScriptDataType::SCRIPT: - case GDScriptDataType::GDSCRIPT: { - Variant script = cast_type.script_type; - int idx = codegen.get_constant_pos(script); //make it a local constant (faster access) + gen->write_cast(result, source, cast_type); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); // perform operator - codegen.opcodes.push_back(idx); // variable type - } break; - default: { - _set_error("Parser bug: unresolved data type.", cn); - return -1; - } + if (source.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - codegen.opcodes.push_back(src_addr); // source address - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(dst_addr); // append the stack level as destination address of the opcode - codegen.alloc_stack(p_stack_level); - return dst_addr; - + return source; } break; - //hell breaks loose - -#define OPERATOR_RETURN \ - int dst_addr = (p_stack_level) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); \ - codegen.opcodes.push_back(dst_addr); \ - codegen.alloc_stack(p_stack_level); \ - return dst_addr - case GDScriptParser::Node::CALL: { const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression); - if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name) != Variant::VARIANT_MAX) { - //construct a basic type - - Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); + GDScriptDataType type = _gdtype_from_datatype(call->get_datatype()); + GDScriptCodeGenerator::Address result = codegen.add_temporary(type); - Vector<int> arguments; - int slevel = p_stack_level; - for (int i = 0; i < call->arguments.size(); i++) { - int ret = _parse_expression(codegen, call->arguments[i], slevel); - if (ret < 0) { - return ret; - } - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } - arguments.push_back(ret); + Vector<GDScriptCodeGenerator::Address> arguments; + for (int i = 0; i < call->arguments.size(); i++) { + GDScriptCodeGenerator::Address arg = _parse_expression(codegen, r_error, call->arguments[i]); + if (r_error) { + return GDScriptCodeGenerator::Address(); } + arguments.push_back(arg); + } - //push call bytecode - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CONSTRUCT); // basic type constructor - codegen.opcodes.push_back(vtype); //instance - codegen.opcodes.push_back(arguments.size()); //argument count - codegen.alloc_call(arguments.size()); - for (int i = 0; i < arguments.size(); i++) { - codegen.opcodes.push_back(arguments[i]); //arguments - } + if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name) != Variant::VARIANT_MAX) { + // Construct a built-in type. + Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); + gen->write_construct(result, vtype, arguments); } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_function(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name) != GDScriptFunctions::FUNC_MAX) { - //built in function - - Vector<int> arguments; - int slevel = p_stack_level; - for (int i = 0; i < call->arguments.size(); i++) { - int ret = _parse_expression(codegen, call->arguments[i], slevel); - if (ret < 0) { - return ret; - } - - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } - - arguments.push_back(ret); - } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptParser::get_builtin_function(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name)); - codegen.opcodes.push_back(arguments.size()); - codegen.alloc_call(arguments.size()); - for (int i = 0; i < arguments.size(); i++) { - codegen.opcodes.push_back(arguments[i]); - } - + // Built-in function. + GDScriptFunctions::Function func = GDScriptParser::get_builtin_function(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); + gen->write_call_builtin(result, func, arguments); } else { - //regular function - + // Regular function. const GDScriptParser::ExpressionNode *callee = call->callee; - Vector<int> arguments; - int slevel = p_stack_level; - - // TODO: Use callables when possible if needed. - int ret = -1; - int super_address = -1; if (call->is_super) { // Super call. - if (call->callee == nullptr) { - // Implicit super function call. - super_address = codegen.get_name_map_pos(codegen.function_node->identifier->name); - } else { - super_address = codegen.get_name_map_pos(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); - } + gen->write_super_call(result, call->function_name, arguments); } else { if (callee->type == GDScriptParser::Node::IDENTIFIER) { // Self function call. if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") { - ret = (GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS); + GDScriptCodeGenerator::Address self; + self.mode = GDScriptCodeGenerator::Address::CLASS; + gen->write_call(result, self, call->function_name, arguments); } else { - ret = (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); + gen->write_call_self(result, call->function_name, arguments); } - arguments.push_back(ret); - ret = codegen.get_name_map_pos(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); - arguments.push_back(ret); } else if (callee->type == GDScriptParser::Node::SUBSCRIPT) { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee); if (subscript->is_attribute) { - ret = _parse_expression(codegen, subscript->base, slevel); - if (ret < 0) { - return ret; + GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + if (within_await) { + gen->write_call_async(result, base, call->function_name, arguments); + } else { + gen->write_call(result, base, call->function_name, arguments); + } + if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - arguments.push_back(ret); - arguments.push_back(codegen.get_name_map_pos(subscript->attribute->name)); } else { _set_error("Cannot call something that isn't a function.", call->callee); - return -1; + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } } else { - _set_error("Cannot call something that isn't a function.", call->callee); - return -1; - } - } - - for (int i = 0; i < call->arguments.size(); i++) { - ret = _parse_expression(codegen, call->arguments[i], slevel); - if (ret < 0) { - return ret; + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); - } - arguments.push_back(ret); - } - - int opcode = GDScriptFunction::OPCODE_CALL_RETURN; - if (call->is_super) { - opcode = GDScriptFunction::OPCODE_CALL_SELF_BASE; - } else if (within_await) { - opcode = GDScriptFunction::OPCODE_CALL_ASYNC; - } else if (p_root) { - opcode = GDScriptFunction::OPCODE_CALL; } + } - codegen.opcodes.push_back(opcode); // perform operator - if (call->is_super) { - codegen.opcodes.push_back(super_address); - } - codegen.opcodes.push_back(call->arguments.size()); - codegen.alloc_call(call->arguments.size()); - for (int i = 0; i < arguments.size(); i++) { - codegen.opcodes.push_back(arguments[i]); + for (int i = 0; i < arguments.size(); i++) { + if (arguments[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } } - OPERATOR_RETURN; + return result; } break; case GDScriptParser::Node::GET_NODE: { const GDScriptParser::GetNodeNode *get_node = static_cast<const GDScriptParser::GetNodeNode *>(p_expression); @@ -816,59 +487,55 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: } } - int arg_address = codegen.get_constant_pos(NodePath(node_name)); + Vector<GDScriptCodeGenerator::Address> args; + args.push_back(codegen.add_constant(NodePath(node_name))); - codegen.opcodes.push_back(p_root ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN); - codegen.opcodes.push_back(1); // number of arguments. - codegen.alloc_call(1); - codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // self. - codegen.opcodes.push_back(codegen.get_name_map_pos("get_node")); // function. - codegen.opcodes.push_back(arg_address); // argument (NodePath). - OPERATOR_RETURN; + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype())); + + MethodBind *get_node_method = ClassDB::get_method("Node", "get_node"); + gen->write_call_method_bind(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args); + + return result; } break; case GDScriptParser::Node::PRELOAD: { const GDScriptParser::PreloadNode *preload = static_cast<const GDScriptParser::PreloadNode *>(p_expression); // Add resource as constant. - return codegen.get_constant_pos(preload->resource); + return codegen.add_constant(preload->resource); } break; case GDScriptParser::Node::AWAIT: { const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression); - int slevel = p_stack_level; + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype())); within_await = true; - int argument = _parse_expression(codegen, await->to_await, slevel); + GDScriptCodeGenerator::Address argument = _parse_expression(codegen, r_error, await->to_await); within_await = false; - if (argument < 0) { - return argument; + if (r_error) { + return GDScriptCodeGenerator::Address(); } - if ((argument >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + + gen->write_await(result, argument); + + if (argument.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - //push call bytecode - codegen.opcodes.push_back(GDScriptFunction::OPCODE_AWAIT); - codegen.opcodes.push_back(argument); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_AWAIT_RESUME); - //next will be where to place the result :) - OPERATOR_RETURN; + return result; } break; - - //indexing operator + // Indexing operator. case GDScriptParser::Node::SUBSCRIPT: { - int slevel = p_stack_level; - const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); - int from = _parse_expression(codegen, subscript->base, slevel); - if (from < 0) { - return from; + GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); + if (r_error) { + return GDScriptCodeGenerator::Address(); } bool named = subscript->is_attribute; - int index; - if (p_index_addr != 0) { + StringName name; + GDScriptCodeGenerator::Address index; + if (p_index_addr.mode != GDScriptCodeGenerator::Address::NIL) { index = p_index_addr; } else if (subscript->is_attribute) { if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { @@ -879,306 +546,179 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: if (MI && MI->get().getter == codegen.function_name) { String n = identifier->name; _set_error("Must use '" + n + "' instead of 'self." + n + "' in getter.", identifier); - return -1; + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } #endif if (MI && MI->get().getter == "") { - // Faster than indexing self (as if no self. had been used) - return (MI->get().index) | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); + // Remove result temp as we don't need it. + gen->pop_temporary(); + // Faster than indexing self (as if no self. had been used). + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->get().index, _gdtype_from_datatype(subscript->get_datatype())); } } - index = codegen.get_name_map_pos(subscript->attribute->name); - + name = subscript->attribute->name; + named = true; } else { if (subscript->index->type == GDScriptParser::Node::LITERAL && static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value.get_type() == Variant::STRING) { - //also, somehow, named (speed up anyway) - StringName name = static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value; - index = codegen.get_name_map_pos(name); + // Also, somehow, named (speed up anyway). + name = static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value; named = true; - } else { - //regular indexing - if (from & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } - - index = _parse_expression(codegen, subscript->index, slevel); - if (index < 0) { - return index; + // Regular indexing. + index = _parse_expression(codegen, r_error, subscript->index); + if (r_error) { + return GDScriptCodeGenerator::Address(); } } } - codegen.opcodes.push_back(named ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET); // perform operator - codegen.opcodes.push_back(from); // argument 1 - codegen.opcodes.push_back(index); // argument 2 (unary only takes one parameter) - OPERATOR_RETURN; + if (named) { + gen->write_get_named(result, name, base); + } else { + gen->write_get(result, index, base); + } + + if (index.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + + return result; } break; case GDScriptParser::Node::UNARY_OPERATOR: { - //unary operators const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression); - switch (unary->operation) { - case GDScriptParser::UnaryOpNode::OP_NEGATIVE: { - if (!_create_unary_operator(codegen, unary, Variant::OP_NEGATE, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::UnaryOpNode::OP_POSITIVE: { - if (!_create_unary_operator(codegen, unary, Variant::OP_POSITIVE, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::UnaryOpNode::OP_LOGIC_NOT: { - if (!_create_unary_operator(codegen, unary, Variant::OP_NOT, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::UnaryOpNode::OP_COMPLEMENT: { - if (!_create_unary_operator(codegen, unary, Variant::OP_BIT_NEGATE, p_stack_level)) { - return -1; - } - } break; + + GDScriptCodeGenerator::Address result = codegen.add_temporary(); + + GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, unary->operand); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + + gen->write_operator(result, unary->variant_op, operand, GDScriptCodeGenerator::Address()); + + if (operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - OPERATOR_RETURN; + + return result; } case GDScriptParser::Node::BINARY_OPERATOR: { - //binary operators (in precedence order) const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); + GDScriptCodeGenerator::Address result = codegen.add_temporary(); + switch (binary->operation) { case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: { - // AND operator with early out on failure + // AND operator with early out on failure. + GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand); + gen->write_and_left_operand(left_operand); + GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand); + gen->write_and_right_operand(right_operand); - int res = _parse_expression(codegen, binary->left_operand, p_stack_level); - if (res < 0) { - return res; + gen->write_end_and(result); + + if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(res); - int jump_fail_pos = codegen.opcodes.size(); - codegen.opcodes.push_back(0); - - res = _parse_expression(codegen, binary->right_operand, p_stack_level); - if (res < 0) { - return res; + if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(res); - int jump_fail_pos2 = codegen.opcodes.size(); - codegen.opcodes.push_back(0); - - codegen.alloc_stack(p_stack_level); //it will be used.. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TRUE); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(codegen.opcodes.size() + 3); - codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size(); - codegen.opcodes.write[jump_fail_pos2] = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_FALSE); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - } break; case GDScriptParser::BinaryOpNode::OP_LOGIC_OR: { - // OR operator with early out on success + // OR operator with early out on success. + GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand); + gen->write_or_left_operand(left_operand); + GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand); + gen->write_or_right_operand(right_operand); + + gen->write_end_or(result); - int res = _parse_expression(codegen, binary->left_operand, p_stack_level); - if (res < 0) { - return res; + if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF); - codegen.opcodes.push_back(res); - int jump_success_pos = codegen.opcodes.size(); - codegen.opcodes.push_back(0); - - res = _parse_expression(codegen, binary->right_operand, p_stack_level); - if (res < 0) { - return res; + if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF); - codegen.opcodes.push_back(res); - int jump_success_pos2 = codegen.opcodes.size(); - codegen.opcodes.push_back(0); - - codegen.alloc_stack(p_stack_level); //it will be used.. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_FALSE); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(codegen.opcodes.size() + 3); - codegen.opcodes.write[jump_success_pos] = codegen.opcodes.size(); - codegen.opcodes.write[jump_success_pos2] = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TRUE); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - } break; case GDScriptParser::BinaryOpNode::OP_TYPE_TEST: { - int slevel = p_stack_level; + GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, binary->left_operand); - int src_address_a = _parse_expression(codegen, binary->left_operand, slevel); - if (src_address_a < 0) { - return -1; - } - - if (src_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; //uses stack for return, increase stack - } - - int src_address_b = -1; - bool builtin = false; if (binary->right_operand->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name) != Variant::VARIANT_MAX) { - // `is` with builtin type - builtin = true; - src_address_b = (int)GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name); + // `is` with builtin type) + Variant::Type type = GDScriptParser::get_builtin_type(static_cast<const GDScriptParser::IdentifierNode *>(binary->right_operand)->name); + gen->write_type_test_builtin(result, operand, type); } else { - src_address_b = _parse_expression(codegen, binary->right_operand, slevel); - if (src_address_b < 0) { - return -1; + GDScriptCodeGenerator::Address type = _parse_expression(codegen, r_error, binary->right_operand); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + gen->write_type_test(result, operand, type); + if (type.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } } + } break; + default: { + GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand); + GDScriptCodeGenerator::Address right_operand = _parse_expression(codegen, r_error, binary->right_operand); - codegen.opcodes.push_back(builtin ? GDScriptFunction::OPCODE_IS_BUILTIN : GDScriptFunction::OPCODE_EXTENDS_TEST); // perform operator - codegen.opcodes.push_back(src_address_a); // argument 1 - codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter) + gen->write_operator(result, binary->variant_op, left_operand, right_operand); - } break; - case GDScriptParser::BinaryOpNode::OP_CONTENT_TEST: { - if (!_create_binary_operator(codegen, binary, Variant::OP_IN, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_COMP_EQUAL: { - if (!_create_binary_operator(codegen, binary, Variant::OP_EQUAL, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_COMP_NOT_EQUAL: { - if (!_create_binary_operator(codegen, binary, Variant::OP_NOT_EQUAL, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_COMP_LESS: { - if (!_create_binary_operator(codegen, binary, Variant::OP_LESS, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_COMP_LESS_EQUAL: { - if (!_create_binary_operator(codegen, binary, Variant::OP_LESS_EQUAL, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_COMP_GREATER: { - if (!_create_binary_operator(codegen, binary, Variant::OP_GREATER, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_COMP_GREATER_EQUAL: { - if (!_create_binary_operator(codegen, binary, Variant::OP_GREATER_EQUAL, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_ADDITION: { - if (!_create_binary_operator(codegen, binary, Variant::OP_ADD, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_SUBTRACTION: { - if (!_create_binary_operator(codegen, binary, Variant::OP_SUBTRACT, p_stack_level)) { - return -1; + if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - } break; - case GDScriptParser::BinaryOpNode::OP_MULTIPLICATION: { - if (!_create_binary_operator(codegen, binary, Variant::OP_MULTIPLY, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_DIVISION: { - if (!_create_binary_operator(codegen, binary, Variant::OP_DIVIDE, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_MODULO: { - if (!_create_binary_operator(codegen, binary, Variant::OP_MODULE, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_BIT_AND: { - if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_AND, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_BIT_OR: { - if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_OR, p_stack_level)) { - return -1; + if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - } break; - case GDScriptParser::BinaryOpNode::OP_BIT_XOR: { - if (!_create_binary_operator(codegen, binary, Variant::OP_BIT_XOR, p_stack_level)) { - return -1; - } - } break; - //shift - case GDScriptParser::BinaryOpNode::OP_BIT_LEFT_SHIFT: { - if (!_create_binary_operator(codegen, binary, Variant::OP_SHIFT_LEFT, p_stack_level)) { - return -1; - } - } break; - case GDScriptParser::BinaryOpNode::OP_BIT_RIGHT_SHIFT: { - if (!_create_binary_operator(codegen, binary, Variant::OP_SHIFT_RIGHT, p_stack_level)) { - return -1; - } - } break; + } } - OPERATOR_RETURN; + return result; } break; - // ternary operators case GDScriptParser::Node::TERNARY_OPERATOR: { - // x IF a ELSE y operator with early out on failure - + // x IF a ELSE y operator with early out on failure. const GDScriptParser::TernaryOpNode *ternary = static_cast<const GDScriptParser::TernaryOpNode *>(p_expression); - int res = _parse_expression(codegen, ternary->condition, p_stack_level); - if (res < 0) { - return res; - } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(res); - int jump_fail_pos = codegen.opcodes.size(); - codegen.opcodes.push_back(0); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype())); - res = _parse_expression(codegen, ternary->true_expr, p_stack_level); - if (res < 0) { - return res; - } + gen->write_start_ternary(result); - codegen.alloc_stack(p_stack_level); //it will be used.. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(res); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - int jump_past_pos = codegen.opcodes.size(); - codegen.opcodes.push_back(0); + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, r_error, ternary->condition); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + gen->write_ternary_condition(condition); - codegen.opcodes.write[jump_fail_pos] = codegen.opcodes.size(); - res = _parse_expression(codegen, ternary->false_expr, p_stack_level); - if (res < 0) { - return res; + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.opcodes.push_back(res); + GDScriptCodeGenerator::Address true_expr = _parse_expression(codegen, r_error, ternary->true_expr); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + gen->write_ternary_true_expr(true_expr); + if (true_expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } - codegen.opcodes.write[jump_past_pos] = codegen.opcodes.size(); + GDScriptCodeGenerator::Address false_expr = _parse_expression(codegen, r_error, ternary->false_expr); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + gen->write_ternary_false_expr(false_expr); + if (false_expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } - return p_stack_level | GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + gen->write_end_ternary(); + return result; } break; - //assignment operators case GDScriptParser::Node::ASSIGNMENT: { const GDScriptParser::AssignmentNode *assignment = static_cast<const GDScriptParser::AssignmentNode *>(p_expression); @@ -1186,20 +726,16 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: // SET (chained) MODE! const GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(assignment->assignee); #ifdef DEBUG_ENABLED - if (subscript->is_attribute) { - if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { - const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(subscript->attribute->name); - if (MI && MI->get().setter == codegen.function_name) { - String n = subscript->attribute->name; - _set_error("Must use '" + n + "' instead of 'self." + n + "' in setter.", subscript); - return -1; - } + if (subscript->is_attribute && subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { + const Map<StringName, GDScript::MemberInfo>::Element *MI = codegen.script->member_indices.find(subscript->attribute->name); + if (MI && MI->get().setter == codegen.function_name) { + String n = subscript->attribute->name; + _set_error("Must use '" + n + "' instead of 'self." + n + "' in setter.", subscript); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); } } #endif - - int slevel = p_stack_level; - /* Find chain of sets */ StringName assign_property; @@ -1207,13 +743,12 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: List<const GDScriptParser::SubscriptNode *> chain; { - //create get/set chain + // Create get/set chain. const GDScriptParser::SubscriptNode *n = subscript; while (true) { chain.push_back(n); - if (n->base->type != GDScriptParser::Node::SUBSCRIPT) { - //check for a built-in property + // Check for a built-in property. if (n->base->type == GDScriptParser::Node::IDENTIFIER) { GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(n->base); if (_is_class_member_property(codegen, identifier->name)) { @@ -1228,366 +763,396 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: /* Chain of gets */ - //get at (potential) root stack pos, so it can be returned - int prev_pos = _parse_expression(codegen, chain.back()->get()->base, slevel); - if (prev_pos < 0) { - return prev_pos; + // Get at (potential) root stack pos, so it can be returned. + GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, chain.back()->get()->base); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - int retval = prev_pos; - if (retval & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } + GDScriptCodeGenerator::Address prev_base = base; - Vector<int> setchain; + struct ChainInfo { + bool is_named = false; + GDScriptCodeGenerator::Address base; + GDScriptCodeGenerator::Address key; + StringName name; + }; - if (assign_property != StringName()) { - // recover and assign at the end, this allows stuff like - // position.x+=2.0 - // in Node2D - setchain.push_back(prev_pos); - setchain.push_back(codegen.get_name_map_pos(assign_property)); - setchain.push_back(GDScriptFunction::OPCODE_SET_MEMBER); - } + List<ChainInfo> set_chain; for (List<const GDScriptParser::SubscriptNode *>::Element *E = chain.back(); E; E = E->prev()) { - if (E == chain.front()) { //ignore first + if (E == chain.front()) { + // Skip the main subscript, since we'll assign to that. break; } - const GDScriptParser::SubscriptNode *subscript_elem = E->get(); - int key_idx; + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype())); + GDScriptCodeGenerator::Address key; + StringName name; if (subscript_elem->is_attribute) { - key_idx = codegen.get_name_map_pos(subscript_elem->attribute->name); - //printf("named key %x\n",key_idx); - + name = subscript_elem->attribute->name; + gen->write_get_named(value, name, prev_base); } else { - if (prev_pos & (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS)) { - slevel++; - codegen.alloc_stack(slevel); + key = _parse_expression(codegen, r_error, subscript_elem->index); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - - GDScriptParser::ExpressionNode *key = subscript_elem->index; - key_idx = _parse_expression(codegen, key, slevel); - //printf("expr key %x\n",key_idx); - - //stack was raised here if retval was stack but.. + gen->write_get(value, key, prev_base); } - if (key_idx < 0) { //error - return key_idx; - } - - codegen.opcodes.push_back(subscript_elem->is_attribute ? GDScriptFunction::OPCODE_GET_NAMED : GDScriptFunction::OPCODE_GET); - codegen.opcodes.push_back(prev_pos); - codegen.opcodes.push_back(key_idx); - slevel++; - codegen.alloc_stack(slevel); - int dst_pos = (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) | slevel; - - codegen.opcodes.push_back(dst_pos); - - //add in reverse order, since it will be reverted - - setchain.push_back(dst_pos); - setchain.push_back(key_idx); - setchain.push_back(prev_pos); - setchain.push_back(subscript_elem->is_attribute ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET); - - prev_pos = dst_pos; + // Store base and key for setting it back later. + set_chain.push_front({ subscript_elem->is_attribute, prev_base, key, name }); // Push to front to invert the list. + prev_base = value; } - setchain.invert(); - - int set_index; - + // Get value to assign. + GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + // Get the key if needed. + GDScriptCodeGenerator::Address key; + StringName name; if (subscript->is_attribute) { - set_index = codegen.get_name_map_pos(subscript->attribute->name); + name = subscript->attribute->name; } else { - set_index = _parse_expression(codegen, subscript->index, slevel + 1); + key = _parse_expression(codegen, r_error, subscript->index); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } } - if (set_index < 0) { //error - return set_index; + // Perform operator if any. + if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + GDScriptCodeGenerator::Address value = codegen.add_temporary(); + if (subscript->is_attribute) { + gen->write_get_named(value, name, prev_base); + } else { + gen->write_get(value, key, prev_base); + } + gen->write_operator(value, assignment->variant_op, value, assigned); + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + assigned = value; } - if (set_index & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); + // Perform assignment. + if (subscript->is_attribute) { + gen->write_set_named(prev_base, name, assigned); + } else { + gen->write_set(prev_base, key, assigned); } - - int set_value = _parse_assign_right_expression(codegen, assignment, slevel + 1, subscript->is_attribute ? 0 : set_index); - if (set_value < 0) { //error - return set_value; + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - codegen.opcodes.push_back(subscript->is_attribute ? GDScriptFunction::OPCODE_SET_NAMED : GDScriptFunction::OPCODE_SET); - codegen.opcodes.push_back(prev_pos); - codegen.opcodes.push_back(set_index); - codegen.opcodes.push_back(set_value); + assigned = prev_base; - for (int i = 0; i < setchain.size(); i++) { - codegen.opcodes.push_back(setchain[i]); + // Set back the values into their bases. + for (List<ChainInfo>::Element *E = set_chain.front(); E; E = E->next()) { + const ChainInfo &info = E->get(); + if (!info.is_named) { + gen->write_set(info.base, info.key, assigned); + if (info.key.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } else { + gen->write_set_named(info.base, info.name, assigned); + } + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + assigned = info.base; } - return retval; + // If this is a local member, also assign to it. + // This allow things like: position.x += 2.0 + if (assign_property != StringName()) { + gen->write_set_member(assigned, assign_property); + } + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } else if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER && _is_class_member_property(codegen, static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name)) { - //assignment to member property - - int slevel = p_stack_level; - - int src_address = _parse_assign_right_expression(codegen, assignment, slevel); - if (src_address < 0) { - return -1; + // Assignment to member property. + GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); + if (r_error) { + return GDScriptCodeGenerator::Address(); } + GDScriptCodeGenerator::Address assign_temp = assigned; StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - codegen.opcodes.push_back(GDScriptFunction::OPCODE_SET_MEMBER); - codegen.opcodes.push_back(codegen.get_name_map_pos(name)); - codegen.opcodes.push_back(src_address); + if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + GDScriptCodeGenerator::Address member = codegen.add_temporary(); + gen->write_get_member(member, name); + gen->write_operator(assigned, assignment->variant_op, member, assigned); + gen->pop_temporary(); + } - return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; - } else { - //REGULAR ASSIGNMENT MODE!! + gen->write_set_member(assigned, name); - int slevel = p_stack_level; - int dst_address_a = -1; + if (assign_temp.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } else { + // Regular assignment. + GDScriptCodeGenerator::Address target; bool has_setter = false; bool is_in_setter = false; StringName setter_function; if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (!codegen.stack_identifiers.has(var_name) && codegen.script->member_indices.has(var_name)) { + if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { setter_function = codegen.script->member_indices[var_name].setter; if (setter_function != StringName()) { has_setter = true; is_in_setter = setter_function == codegen.function_name; - dst_address_a = codegen.script->member_indices[var_name].index; + target.mode = GDScriptCodeGenerator::Address::MEMBER; + target.address = codegen.script->member_indices[var_name].index; } } } if (has_setter) { - if (is_in_setter) { - // Use direct member access. - dst_address_a |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; - } else { + if (!is_in_setter) { // Store stack slot for the temp value. - dst_address_a = slevel++; - codegen.alloc_stack(slevel); - dst_address_a |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; + target = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype())); } } else { - dst_address_a = _parse_expression(codegen, assignment->assignee, slevel); - if (dst_address_a < 0) { - return -1; + target = _parse_expression(codegen, r_error, assignment->assignee); + if (r_error) { + return GDScriptCodeGenerator::Address(); } + } - if (dst_address_a & GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS) { - slevel++; - codegen.alloc_stack(slevel); - } + GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); + GDScriptCodeGenerator::Address op_result; + if (r_error) { + return GDScriptCodeGenerator::Address(); } - int src_address_b = _parse_assign_right_expression(codegen, assignment, slevel); - if (src_address_b < 0) { - return -1; + if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + // Perform operation. + op_result = codegen.add_temporary(); + gen->write_operator(op_result, assignment->variant_op, target, assigned); + } else { + op_result = assigned; + assigned = GDScriptCodeGenerator::Address(); } GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); if (has_setter && !is_in_setter) { // Call setter. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL); - codegen.opcodes.push_back(1); // Argument count. - codegen.opcodes.push_back(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); // Base (self). - codegen.opcodes.push_back(codegen.get_name_map_pos(setter_function)); // Method name. - codegen.opcodes.push_back(dst_address_a); // Argument. - codegen.opcodes.push_back(dst_address_a); // Result address (won't be used here). - codegen.alloc_call(1); - } else if (!_generate_typed_assign(codegen, src_address_b, dst_address_a, assign_type, assignment->assigned_value->get_datatype())) { - return -1; + Vector<GDScriptCodeGenerator::Address> args; + args.push_back(op_result); + gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args); + } else { + // Just assign. + gen->write_assign(target, op_result); } - return dst_address_a; //if anything, returns wathever was assigned or correct stack position + if (op_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + if (target.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } + return GDScriptCodeGenerator::Address(); // Assignment does not return a value. } break; -#undef OPERATOR_RETURN - //TYPE_TYPE, default: { - ERR_FAIL_V_MSG(-1, "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); //unreachable code + ERR_FAIL_V_MSG(GDScriptCodeGenerator::Address(), "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); // Unreachable code. } break; } } -Error GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector<int> &r_patch_addresses, Vector<int> &r_block_patch_address) { - // TODO: Many "repeated" code here that could be abstracted. This compiler is going away when new VM arrives though, so... +GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested) { switch (p_pattern->pattern_type) { case GDScriptParser::PatternNode::PT_LITERAL: { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } + // Get literal type into constant map. - int literal_type_addr = -1; - if (!codegen.constant_map.has((int)p_pattern->literal->value.get_type())) { - literal_type_addr = codegen.constant_map.size(); - codegen.constant_map[(int)p_pattern->literal->value.get_type()] = literal_type_addr; + GDScriptCodeGenerator::Address literal_type_addr = codegen.add_constant((int)p_pattern->literal->value.get_type()); - } else { - literal_type_addr = codegen.constant_map[(int)p_pattern->literal->value.get_type()]; - } - literal_type_addr |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; + // Equality is always a boolean. + GDScriptDataType equality_type; + equality_type.has_type = true; + equality_type.kind = GDScriptDataType::BUILTIN; + equality_type.builtin_type = Variant::BOOL; // Check type equality. - int equality_addr = p_stack_level++; - equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(p_stack_level); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(Variant::OP_EQUAL); - codegen.opcodes.push_back(p_type_addr); - codegen.opcodes.push_back(literal_type_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if not the same type. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + GDScriptCodeGenerator::Address type_equality_addr = codegen.add_temporary(equality_type); + codegen.generator->write_operator(type_equality_addr, Variant::OP_EQUAL, p_type_addr, literal_type_addr); + codegen.generator->write_and_left_operand(type_equality_addr); // Get literal. - int literal_addr = _parse_expression(codegen, p_pattern->literal, p_stack_level); + GDScriptCodeGenerator::Address literal_addr = _parse_expression(codegen, r_error, p_pattern->literal); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } // Check value equality. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(Variant::OP_EQUAL); - codegen.opcodes.push_back(p_value_addr); - codegen.opcodes.push_back(literal_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if doesn't match. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. - - // Jump to the actual block since it matches. This is needed to take multi-pattern into account. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - r_block_patch_address.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + GDScriptCodeGenerator::Address equality_addr = codegen.add_temporary(equality_type); + codegen.generator->write_operator(equality_addr, Variant::OP_EQUAL, p_value_addr, literal_addr); + codegen.generator->write_and_right_operand(equality_addr); + + // AND both together (reuse temporary location). + codegen.generator->write_end_and(type_equality_addr); + + codegen.generator->pop_temporary(); // Remove equality_addr from stack. + + if (literal_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_and_right_operand(type_equality_addr); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_or_right_operand(type_equality_addr); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign(p_previous_test, type_equality_addr); + } + codegen.generator->pop_temporary(); // Remove type_equality_addr. + + return p_previous_test; } break; case GDScriptParser::PatternNode::PT_EXPRESSION: { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } + // Create the result temps first since it's the last to go away. + GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address equality_test_addr = codegen.add_temporary(); + // Evaluate expression. - int expr_addr = _parse_expression(codegen, p_pattern->expression, p_stack_level); - if ((expr_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - p_stack_level++; - codegen.alloc_stack(p_stack_level); + GDScriptCodeGenerator::Address expr_addr; + expr_addr = _parse_expression(codegen, r_error, p_pattern->expression); + if (r_error) { + return GDScriptCodeGenerator::Address(); } // Evaluate expression type. - int expr_type_addr = p_stack_level++; - expr_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(p_stack_level); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(expr_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(expr_type_addr); // Address to result. + Vector<GDScriptCodeGenerator::Address> typeof_args; + typeof_args.push_back(expr_addr); + codegen.generator->write_call_builtin(result_addr, GDScriptFunctions::TYPE_OF, typeof_args); // Check type equality. - int equality_addr = p_stack_level++; - equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(p_stack_level); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(Variant::OP_EQUAL); - codegen.opcodes.push_back(p_type_addr); - codegen.opcodes.push_back(expr_type_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if not the same type. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + codegen.generator->write_operator(result_addr, Variant::OP_EQUAL, p_type_addr, result_addr); + codegen.generator->write_and_left_operand(result_addr); // Check value equality. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(Variant::OP_EQUAL); - codegen.opcodes.push_back(p_value_addr); - codegen.opcodes.push_back(expr_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if doesn't match. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. - - // Jump to the actual block since it matches. This is needed to take multi-pattern into account. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - r_block_patch_address.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. - } break; - case GDScriptParser::PatternNode::PT_BIND: { - // Create new stack variable. - int bind_addr = p_stack_level | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); - codegen.add_stack_identifier(p_pattern->bind->name, p_stack_level++); - codegen.alloc_stack(p_stack_level); - r_bound_variables++; + codegen.generator->write_operator(result_addr, Variant::OP_EQUAL, p_value_addr, expr_addr); + codegen.generator->write_and_right_operand(equality_test_addr); - // Assign value to bound variable. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(bind_addr); // Destination. - codegen.opcodes.push_back(p_value_addr); // Source. - // Not need to block jump because bind happens only once. + // AND both type and value equality. + codegen.generator->write_end_and(result_addr); + + // We don't need the expression temporary anymore. + if (expr_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + codegen.generator->pop_temporary(); // Remove type equality temporary. + + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_and_right_operand(result_addr); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_or_right_operand(result_addr); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign(p_previous_test, result_addr); + } + codegen.generator->pop_temporary(); // Remove temp result addr. + + return p_previous_test; } break; case GDScriptParser::PatternNode::PT_ARRAY: { - int slevel = p_stack_level; - + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } // Get array type into constant map. - int array_type_addr = codegen.get_constant_pos(Variant::ARRAY); + GDScriptCodeGenerator::Address array_type_addr = codegen.add_constant((int)Variant::ARRAY); + + // Equality is always a boolean. + GDScriptDataType temp_type; + temp_type.has_type = true; + temp_type.kind = GDScriptDataType::BUILTIN; + temp_type.builtin_type = Variant::BOOL; // Check type equality. - int equality_addr = slevel++; - equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(slevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(Variant::OP_EQUAL); - codegen.opcodes.push_back(p_type_addr); - codegen.opcodes.push_back(array_type_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if not the same type. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(temp_type); + codegen.generator->write_operator(result_addr, Variant::OP_EQUAL, p_type_addr, array_type_addr); + codegen.generator->write_and_left_operand(result_addr); // Store pattern length in constant map. - int array_length_addr = codegen.get_constant_pos(p_pattern->rest_used ? p_pattern->array.size() - 1 : p_pattern->array.size()); + GDScriptCodeGenerator::Address array_length_addr = codegen.add_constant(p_pattern->rest_used ? p_pattern->array.size() - 1 : p_pattern->array.size()); // Get value length. - int value_length_addr = slevel++; - codegen.alloc_stack(slevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::LEN); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(p_value_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(value_length_addr); // Address to result. + temp_type.builtin_type = Variant::INT; + GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type); + Vector<GDScriptCodeGenerator::Address> len_args; + len_args.push_back(p_value_addr); + codegen.generator->write_call_builtin(value_length_addr, GDScriptFunctions::LEN, len_args); // Test length compatibility. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL); - codegen.opcodes.push_back(value_length_addr); - codegen.opcodes.push_back(array_length_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if length is not compatible. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + temp_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address length_compat_addr = codegen.add_temporary(temp_type); + codegen.generator->write_operator(length_compat_addr, p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL, value_length_addr, array_length_addr); + codegen.generator->write_and_right_operand(length_compat_addr); + + // AND type and length check. + codegen.generator->write_end_and(result_addr); + + // Remove length temporaries. + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); + + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_and_right_operand(result_addr); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_or_right_operand(result_addr); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign(p_previous_test, result_addr); + } + codegen.generator->pop_temporary(); // Remove temp result addr. + + // Create temporaries outside the loop so they can be reused. + GDScriptCodeGenerator::Address element_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address element_type_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address test_addr = p_previous_test; // Evaluate element by element. for (int i = 0; i < p_pattern->array.size(); i++) { @@ -1596,494 +1161,461 @@ Error GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, const GDScriptPar break; } - int stlevel = p_stack_level; - Vector<int> element_block_patches; // I want to internal patterns try the next element instead of going to the block. + // Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get). + codegen.generator->write_and_left_operand(test_addr); + // Add index to constant map. - int index_addr = codegen.get_constant_pos(i); + GDScriptCodeGenerator::Address index_addr = codegen.add_constant(i); // Get the actual element from the user-sent array. - int element_addr = stlevel++; - codegen.alloc_stack(stlevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET); - codegen.opcodes.push_back(p_value_addr); // Source. - codegen.opcodes.push_back(index_addr); // Index. - codegen.opcodes.push_back(element_addr); // Destination. + codegen.generator->write_get(element_addr, index_addr, p_value_addr); // Also get type of element. - int element_type_addr = stlevel++; - element_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(stlevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(element_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(element_type_addr); // Address to result. + Vector<GDScriptCodeGenerator::Address> typeof_args; + typeof_args.push_back(element_addr); + codegen.generator->write_call_builtin(element_type_addr, GDScriptFunctions::TYPE_OF, typeof_args); // Try the pattern inside the element. - Error err = _parse_match_pattern(codegen, p_pattern->array[i], stlevel, element_addr, element_type_addr, r_bound_variables, r_patch_addresses, element_block_patches); - if (err != OK) { - return err; + test_addr = _parse_match_pattern(codegen, r_error, p_pattern->array[i], element_addr, element_type_addr, p_previous_test, false, true); + if (r_error != OK) { + return GDScriptCodeGenerator::Address(); } - // Patch jumps to block to try the next element. - for (int j = 0; j < element_block_patches.size(); j++) { - codegen.opcodes.write[element_block_patches[j]] = codegen.opcodes.size(); - } + codegen.generator->write_and_right_operand(test_addr); + codegen.generator->write_end_and(test_addr); } + // Remove element temporaries. + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); - // Jump to the actual block since it matches. This is needed to take multi-pattern into account. - // Also here for the case of empty arrays. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - r_block_patch_address.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + return test_addr; } break; case GDScriptParser::PatternNode::PT_DICTIONARY: { - int slevel = p_stack_level; - + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } // Get dictionary type into constant map. - int dict_type_addr = codegen.get_constant_pos(Variant::DICTIONARY); + GDScriptCodeGenerator::Address dict_type_addr = codegen.add_constant((int)Variant::DICTIONARY); + + // Equality is always a boolean. + GDScriptDataType temp_type; + temp_type.has_type = true; + temp_type.kind = GDScriptDataType::BUILTIN; + temp_type.builtin_type = Variant::BOOL; // Check type equality. - int equality_addr = slevel++; - equality_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(slevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(Variant::OP_EQUAL); - codegen.opcodes.push_back(p_type_addr); - codegen.opcodes.push_back(dict_type_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if not the same type. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(temp_type); + codegen.generator->write_operator(result_addr, Variant::OP_EQUAL, p_type_addr, dict_type_addr); + codegen.generator->write_and_left_operand(result_addr); // Store pattern length in constant map. - int dict_length_addr = codegen.get_constant_pos(p_pattern->rest_used ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size()); + GDScriptCodeGenerator::Address dict_length_addr = codegen.add_constant(p_pattern->rest_used ? p_pattern->dictionary.size() - 1 : p_pattern->dictionary.size()); // Get user's dictionary length. - int value_length_addr = slevel++; - codegen.alloc_stack(slevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::LEN); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(p_value_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(value_length_addr); // Address to result. + temp_type.builtin_type = Variant::INT; + GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type); + Vector<GDScriptCodeGenerator::Address> func_args; + func_args.push_back(p_value_addr); + codegen.generator->write_call_builtin(value_length_addr, GDScriptFunctions::LEN, func_args); // Test length compatibility. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_OPERATOR); - codegen.opcodes.push_back(p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL); - codegen.opcodes.push_back(value_length_addr); - codegen.opcodes.push_back(dict_length_addr); - codegen.opcodes.push_back(equality_addr); // Address to result. - - // Jump away if length is not compatible. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(equality_addr); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + temp_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address length_compat_addr = codegen.add_temporary(temp_type); + codegen.generator->write_operator(length_compat_addr, p_pattern->rest_used ? Variant::OP_GREATER_EQUAL : Variant::OP_EQUAL, value_length_addr, dict_length_addr); + codegen.generator->write_and_right_operand(length_compat_addr); + + // AND type and length check. + codegen.generator->write_end_and(result_addr); + + // Remove length temporaries. + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); + + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_and_right_operand(result_addr); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the previous value as target, since we only need one temporary variable. + codegen.generator->write_or_right_operand(result_addr); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign(p_previous_test, result_addr); + } + codegen.generator->pop_temporary(); // Remove temp result addr. + + // Create temporaries outside the loop so they can be reused. + temp_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address test_result = codegen.add_temporary(temp_type); + GDScriptCodeGenerator::Address element_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address element_type_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address test_addr = p_previous_test; // Evaluate element by element. for (int i = 0; i < p_pattern->dictionary.size(); i++) { const GDScriptParser::PatternNode::Pair &element = p_pattern->dictionary[i]; if (element.value_pattern && element.value_pattern->pattern_type == GDScriptParser::PatternNode::PT_REST) { // Ignore rest pattern. - continue; + break; } - int stlevel = p_stack_level; - Vector<int> element_block_patches; // I want to internal patterns try the next element instead of going to the block. + + // Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get). + codegen.generator->write_and_left_operand(test_addr); // Get the pattern key. - int pattern_key_addr = _parse_expression(codegen, element.key, stlevel); - if (pattern_key_addr < 0) { - return ERR_PARSE_ERROR; - } - if ((pattern_key_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - stlevel++; - codegen.alloc_stack(stlevel); + GDScriptCodeGenerator::Address pattern_key_addr = _parse_expression(codegen, r_error, element.key); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - // Create stack slot for test result. - int test_result = stlevel++; - test_result |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(stlevel); - - // Check if pattern key exists in user's dictionary. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_RETURN); - codegen.opcodes.push_back(1); // Argument count. - codegen.opcodes.push_back(p_value_addr); // Base (user dictionary). - codegen.opcodes.push_back(codegen.get_name_map_pos("has")); // Function name. - codegen.opcodes.push_back(pattern_key_addr); // Argument (pattern key). - codegen.opcodes.push_back(test_result); // Return address. - - // Jump away if key doesn't exist. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(test_result); - r_patch_addresses.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + // Check if pattern key exists in user's dictionary. This will be AND-ed with next result. + func_args.clear(); + func_args.push_back(pattern_key_addr); + codegen.generator->write_call(test_result, p_value_addr, "has", func_args); if (element.value_pattern != nullptr) { + // Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get). + codegen.generator->write_and_left_operand(test_result); + // Get actual value from user dictionary. - int value_addr = stlevel++; - codegen.alloc_stack(stlevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET); - codegen.opcodes.push_back(p_value_addr); // Source. - codegen.opcodes.push_back(pattern_key_addr); // Index. - codegen.opcodes.push_back(value_addr); // Destination. + codegen.generator->write_get(element_addr, pattern_key_addr, p_value_addr); // Also get type of value. - int value_type_addr = stlevel++; - value_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(stlevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(value_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(value_type_addr); // Address to result. + func_args.clear(); + func_args.push_back(element_addr); + codegen.generator->write_call_builtin(element_type_addr, GDScriptFunctions::TYPE_OF, func_args); // Try the pattern inside the value. - Error err = _parse_match_pattern(codegen, element.value_pattern, stlevel, value_addr, value_type_addr, r_bound_variables, r_patch_addresses, element_block_patches); - if (err != OK) { - return err; + test_addr = _parse_match_pattern(codegen, r_error, element.value_pattern, element_addr, element_type_addr, test_addr, false, true); + if (r_error != OK) { + return GDScriptCodeGenerator::Address(); } + codegen.generator->write_and_right_operand(test_addr); + codegen.generator->write_end_and(test_addr); } - // Patch jumps to block to try the next element. - for (int j = 0; j < element_block_patches.size(); j++) { - codegen.opcodes.write[element_block_patches[j]] = codegen.opcodes.size(); + codegen.generator->write_and_right_operand(test_addr); + codegen.generator->write_end_and(test_addr); + + // Remove pattern key temporary. + if (pattern_key_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } } - // Jump to the actual block since it matches. This is needed to take multi-pattern into account. - // Also here for the case of empty dictionaries. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - r_block_patch_address.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + // Remove element temporaries. + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); + codegen.generator->pop_temporary(); + return test_addr; } break; case GDScriptParser::PatternNode::PT_REST: // Do nothing. + return p_previous_test; break; + case GDScriptParser::PatternNode::PT_BIND: { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } + // Get the bind address. + GDScriptCodeGenerator::Address bind = codegen.locals[p_pattern->bind->name]; + + // Assign value to bound variable. + codegen.generator->write_assign(bind, p_value_addr); + } + [[fallthrough]]; // Act like matching anything too. case GDScriptParser::PatternNode::PT_WILDCARD: - // This matches anything so just do the jump. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - r_block_patch_address.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be replaced. + // If this is a fall through we don't want to do this again. + if (p_pattern->pattern_type != GDScriptParser::PatternNode::PT_BIND) { + if (p_is_nested) { + codegen.generator->write_and_left_operand(p_previous_test); + } else if (!p_is_first) { + codegen.generator->write_or_left_operand(p_previous_test); + } + } + // This matches anything so just do the same as `if(true)`. + // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. + if (p_is_nested) { + // Use the operator with the `true` constant so it works as always matching. + GDScriptCodeGenerator::Address constant = codegen.add_constant(true); + codegen.generator->write_and_right_operand(constant); + codegen.generator->write_end_and(p_previous_test); + } else if (!p_is_first) { + // Use the operator with the `true` constant so it works as always matching. + GDScriptCodeGenerator::Address constant = codegen.add_constant(true); + codegen.generator->write_or_right_operand(constant); + codegen.generator->write_end_or(p_previous_test); + } else { + // Just assign this value to the accumulator temporary. + codegen.generator->write_assign_true(p_previous_test); + } + return p_previous_test; } - return OK; + ERR_FAIL_V_MSG(p_previous_test, "Reaching the end of pattern compilation without matching a pattern."); } -Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level, int p_break_addr, int p_continue_addr) { - codegen.push_stack_identifiers(); - int new_identifiers = 0; - codegen.current_line = p_block->start_line; +void GDScriptCompiler::_add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block) { + for (int i = 0; i < p_block->locals.size(); i++) { + if (p_block->locals[i].type == GDScriptParser::SuiteNode::Local::PARAMETER || p_block->locals[i].type == GDScriptParser::SuiteNode::Local::FOR_VARIABLE) { + // Parameters are added directly from function and loop variables are declared explicitly. + continue; + } + codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype())); + } +} + +Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals) { + Error error = OK; + GDScriptCodeGenerator *gen = codegen.generator; + + codegen.start_block(); + + if (p_add_locals) { + _add_locals_in_block(codegen, p_block); + } for (int i = 0; i < p_block->statements.size(); i++) { const GDScriptParser::Node *s = p_block->statements[i]; #ifdef DEBUG_ENABLED // Add a newline before each statement, since the debugger needs those. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); - codegen.opcodes.push_back(s->start_line); - codegen.current_line = s->start_line; + gen->write_newline(s->start_line); #endif switch (s->type) { case GDScriptParser::Node::MATCH: { const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s); - int slevel = p_stack_level; + gen->start_match(); + codegen.start_block(); - // First, let's save the addres of the value match. - int temp_addr = _parse_expression(codegen, match->test, slevel); - if (temp_addr < 0) { - return ERR_PARSE_ERROR; - } - if ((temp_addr >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + // Evaluate the match expression. + GDScriptCodeGenerator::Address value = _parse_expression(codegen, error, match->test); + if (error) { + return error; } // Then, let's save the type of the value in the stack too, so we can reuse for later comparisons. - int type_addr = slevel++; - type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; - codegen.alloc_stack(slevel); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); - codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); - codegen.opcodes.push_back(1); // One argument. - codegen.opcodes.push_back(temp_addr); // Argument is the value we want to test. - codegen.opcodes.push_back(type_addr); // Address to result. - - Vector<int> patch_match_end; // Will patch the jump to the end of match. + GDScriptCodeGenerator::Address type = codegen.add_temporary(); + Vector<GDScriptCodeGenerator::Address> typeof_args; + typeof_args.push_back(value); + gen->write_call_builtin(type, GDScriptFunctions::TYPE_OF, typeof_args); // Now we can actually start testing. // For each branch. for (int j = 0; j < match->branches.size(); j++) { + if (j > 0) { + // Use `else` to not check the next branch after matching. + gen->write_else(); + } + const GDScriptParser::MatchBranchNode *branch = match->branches[j]; - int bound_variables = 0; - codegen.push_stack_identifiers(); // Create an extra block around for binds. + gen->start_match_branch(); // Need so lower level code can patch 'continue' jumps. + codegen.start_block(); // Create an extra block around for binds. + + // Add locals in block before patterns, so temporaries don't use the stack address for binds. + _add_locals_in_block(codegen, branch->block); #ifdef DEBUG_ENABLED // Add a newline before each branch, since the debugger needs those. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); - codegen.opcodes.push_back(s->start_line); - codegen.current_line = s->start_line; + gen->write_newline(branch->start_line); #endif - Vector<int> patch_addrs; // Will patch with end of pattern to jump. - Vector<int> block_patch_addrs; // Will patch with start of block to jump. - // For each pattern in branch. + GDScriptCodeGenerator::Address pattern_result = codegen.add_temporary(); for (int k = 0; k < branch->patterns.size(); k++) { - if (k > 0) { - // Patch jumps per pattern to allow for multipattern. If a pattern fails it just tries the next. - for (int l = 0; l < patch_addrs.size(); l++) { - codegen.opcodes.write[patch_addrs[l]] = codegen.opcodes.size(); - } - patch_addrs.clear(); - } - Error err = _parse_match_pattern(codegen, branch->patterns[k], slevel, temp_addr, type_addr, bound_variables, patch_addrs, block_patch_addrs); - if (err != OK) { - return err; + pattern_result = _parse_match_pattern(codegen, error, branch->patterns[k], value, type, pattern_result, k == 0, false); + if (error != OK) { + return error; } } - // Patch jumps to the block. - for (int k = 0; k < block_patch_addrs.size(); k++) { - codegen.opcodes.write[block_patch_addrs[k]] = codegen.opcodes.size(); - } - // Leave space for bound variables. - slevel += bound_variables; - codegen.alloc_stack(slevel); + // Check if pattern did match. + gen->write_if(pattern_result); - // Parse the branch block. - _parse_block(codegen, branch->block, slevel, p_break_addr, p_continue_addr); + // Remove the result from stack. + gen->pop_temporary(); - // Jump to end of match. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - patch_match_end.push_back(codegen.opcodes.size()); - codegen.opcodes.push_back(0); // Will be patched. - - // Patch the addresses of last pattern to jump to the end of the branch, into the next one. - for (int k = 0; k < patch_addrs.size(); k++) { - codegen.opcodes.write[patch_addrs[k]] = codegen.opcodes.size(); + // Parse the branch block. + error = _parse_block(codegen, branch->block, false); // Don't add locals again. + if (error) { + return error; } - codegen.pop_stack_identifiers(); // Get out of extra block. + codegen.end_block(); // Get out of extra block. + } + + // End all nested `if`s. + for (int j = 0; j < match->branches.size(); j++) { + gen->write_endif(); } - // Patch the addresses to jump to the end of the match statement. - for (int j = 0; j < patch_match_end.size(); j++) { - codegen.opcodes.write[patch_match_end[j]] = codegen.opcodes.size(); + + gen->pop_temporary(); + + if (value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } - } break; + gen->end_match(); + } break; case GDScriptParser::Node::IF: { const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s); - int ret2 = _parse_expression(codegen, if_n->condition, p_stack_level, false); - if (ret2 < 0) { - return ERR_PARSE_ERROR; + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, if_n->condition); + if (error) { + return error; } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(ret2); - int else_addr = codegen.opcodes.size(); - codegen.opcodes.push_back(0); //temporary + gen->write_if(condition); + + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } - Error err = _parse_block(codegen, if_n->true_block, p_stack_level, p_break_addr, p_continue_addr); - if (err) { - return err; + error = _parse_block(codegen, if_n->true_block); + if (error) { + return error; } if (if_n->false_block) { - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - int end_addr = codegen.opcodes.size(); - codegen.opcodes.push_back(0); - codegen.opcodes.write[else_addr] = codegen.opcodes.size(); - - Error err2 = _parse_block(codegen, if_n->false_block, p_stack_level, p_break_addr, p_continue_addr); - if (err2) { - return err2; - } + gen->write_else(); - codegen.opcodes.write[end_addr] = codegen.opcodes.size(); - } else { - //end without else - codegen.opcodes.write[else_addr] = codegen.opcodes.size(); + error = _parse_block(codegen, if_n->false_block); + if (error) { + return error; + } } + gen->write_endif(); } break; case GDScriptParser::Node::FOR: { const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s); - int slevel = p_stack_level; - int iter_stack_pos = slevel; - int iterator_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - int counter_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - int container_pos = (slevel++) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - codegen.alloc_stack(slevel); - - codegen.push_stack_identifiers(); - codegen.add_stack_identifier(for_n->variable->name, iter_stack_pos); - - int ret2 = _parse_expression(codegen, for_n->list, slevel, false); - if (ret2 < 0) { - return ERR_COMPILATION_FAILED; - } - - //assign container - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); - codegen.opcodes.push_back(container_pos); - codegen.opcodes.push_back(ret2); - - //begin loop - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ITERATE_BEGIN); - codegen.opcodes.push_back(counter_pos); - codegen.opcodes.push_back(container_pos); - codegen.opcodes.push_back(codegen.opcodes.size() + 4); - codegen.opcodes.push_back(iterator_pos); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); //skip code for next - codegen.opcodes.push_back(codegen.opcodes.size() + 8); - //break loop - int break_pos = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); //skip code for next - codegen.opcodes.push_back(0); //skip code for next - //next loop - int continue_pos = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ITERATE); - codegen.opcodes.push_back(counter_pos); - codegen.opcodes.push_back(container_pos); - codegen.opcodes.push_back(break_pos); - codegen.opcodes.push_back(iterator_pos); - - Error err = _parse_block(codegen, for_n->loop, slevel, break_pos, continue_pos); - if (err) { - return err; - } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(continue_pos); - codegen.opcodes.write[break_pos + 1] = codegen.opcodes.size(); - - codegen.pop_stack_identifiers(); + codegen.start_block(); + GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype())); + + GDScriptCodeGenerator::Address list = _parse_expression(codegen, error, for_n->list); + if (error) { + return error; + } + + gen->write_for(iterator, list); + + error = _parse_block(codegen, for_n->loop); + if (error) { + return error; + } + + gen->write_endfor(); + + if (list.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + + codegen.end_block(); } break; case GDScriptParser::Node::WHILE: { const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(codegen.opcodes.size() + 3); - int break_addr = codegen.opcodes.size(); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(0); - int continue_addr = codegen.opcodes.size(); - - int ret2 = _parse_expression(codegen, while_n->condition, p_stack_level, false); - if (ret2 < 0) { - return ERR_PARSE_ERROR; + + gen->start_while_condition(); + + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, while_n->condition); + if (error) { + return error; } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_IF_NOT); - codegen.opcodes.push_back(ret2); - codegen.opcodes.push_back(break_addr); - Error err = _parse_block(codegen, while_n->loop, p_stack_level, break_addr, continue_addr); - if (err) { - return err; + + gen->write_while(condition); + + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(continue_addr); - codegen.opcodes.write[break_addr + 1] = codegen.opcodes.size(); + error = _parse_block(codegen, while_n->loop); + if (error) { + return error; + } + gen->write_endwhile(); } break; case GDScriptParser::Node::BREAK: { - if (p_break_addr < 0) { - _set_error("'break'' not within loop", s); - return ERR_COMPILATION_FAILED; - } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(p_break_addr); - + gen->write_break(); } break; case GDScriptParser::Node::CONTINUE: { - if (p_continue_addr < 0) { - _set_error("'continue' not within loop", s); - return ERR_COMPILATION_FAILED; + const GDScriptParser::ContinueNode *cont = static_cast<const GDScriptParser::ContinueNode *>(s); + if (cont->is_for_match) { + gen->write_continue_match(); + } else { + gen->write_continue(); } - - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP); - codegen.opcodes.push_back(p_continue_addr); - } break; case GDScriptParser::Node::RETURN: { const GDScriptParser::ReturnNode *return_n = static_cast<const GDScriptParser::ReturnNode *>(s); - int ret2; + + GDScriptCodeGenerator::Address return_value; if (return_n->return_value != nullptr) { - ret2 = _parse_expression(codegen, return_n->return_value, p_stack_level, false); - if (ret2 < 0) { - return ERR_PARSE_ERROR; + return_value = _parse_expression(codegen, error, return_n->return_value); + if (error) { + return error; } - - } else { - ret2 = GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_RETURN); - codegen.opcodes.push_back(ret2); - + gen->write_return(return_value); + if (return_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } } break; case GDScriptParser::Node::ASSERT: { #ifdef DEBUG_ENABLED - // try subblocks - const GDScriptParser::AssertNode *as = static_cast<const GDScriptParser::AssertNode *>(s); - int ret2 = _parse_expression(codegen, as->condition, p_stack_level, false); - if (ret2 < 0) { - return ERR_PARSE_ERROR; + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, error, as->condition); + if (error) { + return error; } - int message_ret = 0; + GDScriptCodeGenerator::Address message; + if (as->message) { - message_ret = _parse_expression(codegen, as->message, p_stack_level + 1, false); - if (message_ret < 0) { - return ERR_PARSE_ERROR; + message = _parse_expression(codegen, error, as->message); + if (error) { + return error; } } + gen->write_assert(condition, message); - codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSERT); - codegen.opcodes.push_back(ret2); - codegen.opcodes.push_back(message_ret); + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + if (message.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } #endif } break; case GDScriptParser::Node::BREAKPOINT: { #ifdef DEBUG_ENABLED - // try subblocks - codegen.opcodes.push_back(GDScriptFunction::OPCODE_BREAKPOINT); + gen->write_breakpoint(); #endif } break; case GDScriptParser::Node::VARIABLE: { const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s); - - // since we are using properties now for most class access, allow shadowing of class members to make user's life easier. - // - //if (_is_class_member_property(codegen, lv->name)) { - // _set_error("Name for local variable '" + String(lv->name) + "' can't shadow class property of the same name.", lv); - // return ERR_ALREADY_EXISTS; - //} - - codegen.add_stack_identifier(lv->identifier->name, p_stack_level++); - codegen.alloc_stack(p_stack_level); - new_identifiers++; + // Should be already in stack when the block began. + GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name]; if (lv->initializer != nullptr) { - int dst_address = codegen.stack_identifiers[lv->identifier->name]; - dst_address |= GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS; - - int src_address = _parse_expression(codegen, lv->initializer, p_stack_level); - if (src_address < 0) { - return ERR_PARSE_ERROR; + GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, lv->initializer); + if (error) { + return error; } - if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(lv->get_datatype()), lv->initializer->get_datatype())) { - return ERR_PARSE_ERROR; + gen->write_assign(local, src_address); + if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } } } break; @@ -2094,281 +1626,159 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui _set_error("Local constant must have a constant value as initializer.", lc->initializer); return ERR_PARSE_ERROR; } - codegen.local_named_constants[lc->identifier->name] = codegen.get_constant_pos(lc->initializer->reduced_value); + + codegen.add_local_constant(lc->identifier->name, lc->initializer->reduced_value); } break; case GDScriptParser::Node::PASS: // Nothing to do. break; default: { - //expression + // Expression. if (s->is_expression()) { - int ret2 = _parse_expression(codegen, static_cast<const GDScriptParser::ExpressionNode *>(s), p_stack_level, true); - if (ret2 < 0) { - return ERR_PARSE_ERROR; + GDScriptCodeGenerator::Address expr = _parse_expression(codegen, error, static_cast<const GDScriptParser::ExpressionNode *>(s), true); + if (error) { + return error; + } + if (expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } } else { - ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Bug in bytecode compiler, unexpected node in parse tree while parsing statement."); //unreachable code + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Bug in bytecode compiler, unexpected node in parse tree while parsing statement."); // Unreachable code. } } break; } } - codegen.pop_stack_identifiers(); + codegen.end_block(); return OK; } Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready) { - Vector<int> bytecode; + Error error = OK; CodeGen codegen; + codegen.generator = memnew(GDScriptByteCodeGenerator); codegen.class_node = p_class; codegen.script = p_script; codegen.function_node = p_func; - codegen.stack_max = 0; - codegen.current_line = 0; - codegen.call_max = 0; - codegen.debug_stack = EngineDebugger::is_active(); - Vector<StringName> argnames; - int stack_level = 0; + StringName func_name; + bool is_static = false; + MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + GDScriptDataType return_type; + return_type.has_type = true; + return_type.kind = GDScriptDataType::BUILTIN; + return_type.builtin_type = Variant::NIL; + + if (p_func) { + func_name = p_func->identifier->name; + is_static = p_func->is_static; + rpc_mode = p_func->rpc_mode; + return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script); + } else { + if (p_for_ready) { + func_name = "_ready"; + } else { + func_name = "@implicit_new"; + } + } + + codegen.function_name = func_name; + codegen.generator->write_start(p_script, func_name, is_static, rpc_mode, return_type); + int optional_parameters = 0; if (p_func) { for (int i = 0; i < p_func->parameters.size(); i++) { - // since we are using properties now for most class access, allow shadowing of class members to make user's life easier. - // - //if (_is_class_member_property(p_script, p_func->arguments[i])) { - // _set_error("Name for argument '" + String(p_func->arguments[i]) + "' can't shadow class property of the same name.", p_func); - // return ERR_ALREADY_EXISTS; - //} - - codegen.add_stack_identifier(p_func->parameters[i]->identifier->name, i); -#ifdef TOOLS_ENABLED - argnames.push_back(p_func->parameters[i]->identifier->name); -#endif + const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; + GDScriptDataType par_type = _gdtype_from_datatype(parameter->get_datatype(), p_script); + uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->default_value != nullptr, par_type); + codegen.parameters[parameter->identifier->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, par_type); + if (p_func->parameters[i]->default_value != nullptr) { optional_parameters++; } } - stack_level = p_func->parameters.size(); } - codegen.alloc_stack(stack_level); - - /* Parse initializer -if applies- */ - + // Parse initializer if applies. bool is_implicit_initializer = !p_for_ready && !p_func; bool is_initializer = p_func && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init; + bool is_for_ready = p_for_ready || (p_func && String(p_func->identifier->name) == "_ready"); - if (is_implicit_initializer) { + if (is_implicit_initializer || is_for_ready) { // Initialize class fields. for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { continue; } const GDScriptParser::VariableNode *field = p_class->members[i].variable; - if (field->onready) { + if (field->onready != is_for_ready) { // Only initialize in _ready. continue; } if (field->initializer) { // Emit proper line change. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); - codegen.opcodes.push_back(field->initializer->start_line); - - int src_address = _parse_expression(codegen, field->initializer, stack_level, false, true); - if (src_address < 0) { - return ERR_PARSE_ERROR; - } - int dst_address = codegen.script->member_indices[field->identifier->name].index; - dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; - - if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) { - return ERR_PARSE_ERROR; - } - } - } - } - - if (p_for_ready || (p_func && String(p_func->identifier->name) == "_ready")) { - // Initialize class fields on ready. - for (int i = 0; i < p_class->members.size(); i++) { - if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { - continue; - } - const GDScriptParser::VariableNode *field = p_class->members[i].variable; - if (!field->onready) { - continue; - } - - if (field->initializer) { - // Emit proper line change. - codegen.opcodes.push_back(GDScriptFunction::OPCODE_LINE); - codegen.opcodes.push_back(field->initializer->start_line); + codegen.generator->write_newline(field->initializer->start_line); - int src_address = _parse_expression(codegen, field->initializer, stack_level, false, true); - if (src_address < 0) { - return ERR_PARSE_ERROR; + GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, field->initializer, false, true); + if (error) { + memdelete(codegen.generator); + return error; } - int dst_address = codegen.script->member_indices[field->identifier->name].index; - dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; + GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype())); - if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) { - return ERR_PARSE_ERROR; + codegen.generator->write_assign(dst_address, src_address); + if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } } } } - /* Parse default argument code -if applies- */ - - Vector<int> defarg_addr; - StringName func_name; - + // Parse default argument code if applies. if (p_func) { if (optional_parameters > 0) { - codegen.opcodes.push_back(GDScriptFunction::OPCODE_JUMP_TO_DEF_ARGUMENT); - defarg_addr.push_back(codegen.opcodes.size()); + codegen.generator->start_parameters(); for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) { - int src_addr = _parse_expression(codegen, p_func->parameters[i]->default_value, stack_level, true); - if (src_addr < 0) { - return ERR_PARSE_ERROR; + const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; + GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, error, parameter->default_value, true); + if (error) { + memdelete(codegen.generator); + return error; } - int dst_addr = codegen.stack_identifiers[p_func->parameters[i]->identifier->name] | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS); - if (!_generate_typed_assign(codegen, src_addr, dst_addr, _gdtype_from_datatype(p_func->parameters[i]->get_datatype()), p_func->parameters[i]->default_value->get_datatype())) { - return ERR_PARSE_ERROR; + GDScriptCodeGenerator::Address dst_addr = codegen.parameters[parameter->identifier->name]; + codegen.generator->write_assign(dst_addr, src_addr); + if (src_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); } - defarg_addr.push_back(codegen.opcodes.size()); } - defarg_addr.invert(); + codegen.generator->end_parameters(); } - func_name = p_func->identifier->name; - codegen.function_name = func_name; - Error err = _parse_block(codegen, p_func->body, stack_level); + Error err = _parse_block(codegen, p_func->body); if (err) { + memdelete(codegen.generator); return err; } - - } else { - if (p_for_ready) { - func_name = "_ready"; - } else { - func_name = "@implicit_new"; - } - } - - codegen.function_name = func_name; - codegen.opcodes.push_back(GDScriptFunction::OPCODE_END); - - /* - if (String(p_func->name)=="") { //initializer func - gdfunc = &p_script->initializer; - */ - //} else { //regular func - p_script->member_functions[func_name] = memnew(GDScriptFunction); - GDScriptFunction *gdfunc = p_script->member_functions[func_name]; - //} - - if (p_func) { - gdfunc->_static = p_func->is_static; - gdfunc->rpc_mode = p_func->rpc_mode; - gdfunc->argument_types.resize(p_func->parameters.size()); - for (int i = 0; i < p_func->parameters.size(); i++) { - gdfunc->argument_types.write[i] = _gdtype_from_datatype(p_func->parameters[i]->get_datatype()); - } - gdfunc->return_type = _gdtype_from_datatype(p_func->get_datatype()); - } else { - gdfunc->_static = false; - gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; - gdfunc->return_type = GDScriptDataType(); - gdfunc->return_type.has_type = true; - gdfunc->return_type.kind = GDScriptDataType::BUILTIN; - gdfunc->return_type.builtin_type = Variant::NIL; - } - -#ifdef TOOLS_ENABLED - gdfunc->arg_names = argnames; -#endif - //constants - if (codegen.constant_map.size()) { - gdfunc->_constant_count = codegen.constant_map.size(); - gdfunc->constants.resize(codegen.constant_map.size()); - gdfunc->_constants_ptr = gdfunc->constants.ptrw(); - const Variant *K = nullptr; - while ((K = codegen.constant_map.next(K))) { - int idx = codegen.constant_map[*K]; - gdfunc->constants.write[idx] = *K; - } - } else { - gdfunc->_constants_ptr = nullptr; - gdfunc->_constant_count = 0; - } - //global names - if (codegen.name_map.size()) { - gdfunc->global_names.resize(codegen.name_map.size()); - gdfunc->_global_names_ptr = &gdfunc->global_names[0]; - for (Map<StringName, int>::Element *E = codegen.name_map.front(); E; E = E->next()) { - gdfunc->global_names.write[E->get()] = E->key(); - } - gdfunc->_global_names_count = gdfunc->global_names.size(); - - } else { - gdfunc->_global_names_ptr = nullptr; - gdfunc->_global_names_count = 0; - } - -#ifdef TOOLS_ENABLED - // Named globals - if (codegen.named_globals.size()) { - gdfunc->named_globals.resize(codegen.named_globals.size()); - gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr(); - for (int i = 0; i < codegen.named_globals.size(); i++) { - gdfunc->named_globals.write[i] = codegen.named_globals[i]; - } - gdfunc->_named_globals_count = gdfunc->named_globals.size(); - } -#endif - - if (codegen.opcodes.size()) { - gdfunc->code = codegen.opcodes; - gdfunc->_code_ptr = &gdfunc->code[0]; - gdfunc->_code_size = codegen.opcodes.size(); - - } else { - gdfunc->_code_ptr = nullptr; - gdfunc->_code_size = 0; } - if (defarg_addr.size()) { - gdfunc->default_arguments = defarg_addr; - gdfunc->_default_arg_count = defarg_addr.size() - 1; - gdfunc->_default_arg_ptr = &gdfunc->default_arguments[0]; - } else { - gdfunc->_default_arg_count = 0; - gdfunc->_default_arg_ptr = nullptr; - } - - gdfunc->_argument_count = p_func ? p_func->parameters.size() : 0; - gdfunc->_stack_size = codegen.stack_max; - gdfunc->_call_size = codegen.call_max; - gdfunc->name = func_name; #ifdef DEBUG_ENABLED if (EngineDebugger::is_active()) { String signature; - //path + // Path. if (p_script->get_path() != String()) { signature += p_script->get_path(); } - //loc + // Location. if (p_func) { signature += "::" + itos(p_func->body->start_line); } else { signature += "::0"; } - //function and class + // Function and class. if (p_class->identifier) { signature += "::" + String(p_class->identifier->name) + "." + String(func_name); @@ -2376,65 +1786,41 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser signature += "::" + String(func_name); } - gdfunc->profile.signature = signature; + codegen.generator->set_signature(signature); } #endif - gdfunc->_script = p_script; - gdfunc->source = source; - -#ifdef DEBUG_ENABLED - { - gdfunc->func_cname = (String(source) + " - " + String(func_name)).utf8(); - gdfunc->_func_cname = gdfunc->func_cname.get_data(); - } - -#endif if (p_func) { - gdfunc->_initial_line = p_func->start_line; + codegen.generator->set_initial_line(p_func->start_line); #ifdef TOOLS_ENABLED - p_script->member_lines[func_name] = p_func->start_line; #endif } else { - gdfunc->_initial_line = 0; + codegen.generator->set_initial_line(0); } - if (codegen.debug_stack) { - gdfunc->stack_debug = codegen.stack_debug; - } + GDScriptFunction *gd_function = codegen.generator->write_end(); if (is_initializer) { - p_script->initializer = gdfunc; - } - if (is_implicit_initializer) { - p_script->implicit_initializer = gdfunc; + p_script->initializer = gd_function; + } else if (is_implicit_initializer) { + p_script->implicit_initializer = gd_function; } + p_script->member_functions[func_name] = gd_function; + + memdelete(codegen.generator); + return OK; } Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) { - Vector<int> bytecode; + Error error = OK; CodeGen codegen; + codegen.generator = memnew(GDScriptByteCodeGenerator); codegen.class_node = p_class; codegen.script = p_script; - codegen.function_node = nullptr; - codegen.stack_max = 0; - codegen.current_line = 0; - codegen.call_max = 0; - codegen.debug_stack = EngineDebugger::is_active(); - Vector<StringName> argnames; - - int stack_level = 0; - - if (p_is_setter) { - codegen.add_stack_identifier(p_variable->setter_parameter->name, stack_level++); - argnames.push_back(p_variable->setter_parameter->name); - } - - codegen.alloc_stack(stack_level); StringName func_name; @@ -2443,76 +1829,33 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP } else { func_name = "@" + p_variable->identifier->name + "_getter"; } - codegen.function_name = func_name; - Error err = _parse_block(codegen, p_is_setter ? p_variable->setter : p_variable->getter, stack_level); - if (err != OK) { - return err; + GDScriptDataType return_type; + if (p_is_setter) { + return_type.has_type = true; + return_type.kind = GDScriptDataType::BUILTIN; + return_type.builtin_type = Variant::NIL; + } else { + return_type = _gdtype_from_datatype(p_variable->get_datatype(), p_script); } - codegen.opcodes.push_back(GDScriptFunction::OPCODE_END); - - p_script->member_functions[func_name] = memnew(GDScriptFunction); - GDScriptFunction *gdfunc = p_script->member_functions[func_name]; - - gdfunc->_static = false; - gdfunc->rpc_mode = p_variable->rpc_mode; - gdfunc->argument_types.resize(p_is_setter ? 1 : 0); - gdfunc->return_type = _gdtype_from_datatype(p_variable->get_datatype()); -#ifdef TOOLS_ENABLED - gdfunc->arg_names = argnames; -#endif + codegen.generator->write_start(p_script, func_name, false, p_variable->rpc_mode, return_type); - // TODO: Unify this with function compiler. - //constants - if (codegen.constant_map.size()) { - gdfunc->_constant_count = codegen.constant_map.size(); - gdfunc->constants.resize(codegen.constant_map.size()); - gdfunc->_constants_ptr = gdfunc->constants.ptrw(); - const Variant *K = nullptr; - while ((K = codegen.constant_map.next(K))) { - int idx = codegen.constant_map[*K]; - gdfunc->constants.write[idx] = *K; - } - } else { - gdfunc->_constants_ptr = nullptr; - gdfunc->_constant_count = 0; + if (p_is_setter) { + uint32_t par_addr = codegen.generator->add_parameter(p_variable->setter_parameter->name, false, _gdtype_from_datatype(p_variable->get_datatype())); + codegen.parameters[p_variable->setter_parameter->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, _gdtype_from_datatype(p_variable->get_datatype())); } - //global names - if (codegen.name_map.size()) { - gdfunc->global_names.resize(codegen.name_map.size()); - gdfunc->_global_names_ptr = &gdfunc->global_names[0]; - for (Map<StringName, int>::Element *E = codegen.name_map.front(); E; E = E->next()) { - gdfunc->global_names.write[E->get()] = E->key(); - } - gdfunc->_global_names_count = gdfunc->global_names.size(); - } else { - gdfunc->_global_names_ptr = nullptr; - gdfunc->_global_names_count = 0; + error = _parse_block(codegen, p_is_setter ? p_variable->setter : p_variable->getter); + if (error) { + memdelete(codegen.generator); + return error; } -#ifdef TOOLS_ENABLED - // Named globals - if (codegen.named_globals.size()) { - gdfunc->named_globals.resize(codegen.named_globals.size()); - gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr(); - for (int i = 0; i < codegen.named_globals.size(); i++) { - gdfunc->named_globals.write[i] = codegen.named_globals[i]; - } - gdfunc->_named_globals_count = gdfunc->named_globals.size(); - } -#endif + GDScriptFunction *gd_function = codegen.generator->write_end(); + + p_script->member_functions[func_name] = gd_function; - gdfunc->code = codegen.opcodes; - gdfunc->_code_ptr = &gdfunc->code[0]; - gdfunc->_code_size = codegen.opcodes.size(); - gdfunc->_default_arg_count = 0; - gdfunc->_default_arg_ptr = nullptr; - gdfunc->_argument_count = argnames.size(); - gdfunc->_stack_size = codegen.stack_max; - gdfunc->_call_size = codegen.call_max; - gdfunc->name = func_name; #ifdef DEBUG_ENABLED if (EngineDebugger::is_active()) { String signature; @@ -2531,29 +1874,15 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP signature += "::" + String(func_name); } - gdfunc->profile.signature = signature; + codegen.generator->set_signature(signature); } #endif - gdfunc->_script = p_script; - gdfunc->source = source; - -#ifdef DEBUG_ENABLED - - { - gdfunc->func_cname = (String(source) + " - " + String(func_name)).utf8(); - gdfunc->_func_cname = gdfunc->func_cname.get_data(); - } + codegen.generator->set_initial_line(p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line); -#endif - gdfunc->_initial_line = p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line; #ifdef TOOLS_ENABLED - - p_script->member_lines[func_name] = gdfunc->_initial_line; + p_script->member_lines[func_name] = p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line; #endif - - if (codegen.debug_stack) { - gdfunc->stack_debug = codegen.stack_debug; - } + memdelete(codegen.generator); return OK; } @@ -2604,7 +1933,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->native = native; } break; case GDScriptDataType::GDSCRIPT: { - Ref<GDScript> base = base_type.script_type; + Ref<GDScript> base = Ref<GDScript>(base_type.script_type); p_script->base = base; p_script->_base = base.ptr(); @@ -2671,7 +2000,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar break; } minfo.rpc_mode = variable->rpc_mode; - minfo.data_type = _gdtype_from_datatype(variable->get_datatype()); + minfo.data_type = _gdtype_from_datatype(variable->get_datatype(), p_script); PropertyInfo prop_info = minfo.data_type; prop_info.name = name; diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index e8601f69c7..db02079d26 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -33,109 +33,88 @@ #include "core/set.h" #include "gdscript.h" +#include "gdscript_codegen.h" #include "gdscript_function.h" #include "gdscript_parser.h" class GDScriptCompiler { - const GDScriptParser *parser; + const GDScriptParser *parser = nullptr; Set<GDScript *> parsed_classes; Set<GDScript *> parsing_classes; - GDScript *main_script; + GDScript *main_script = nullptr; + struct CodeGen { - GDScript *script; - const GDScriptParser::ClassNode *class_node; - const GDScriptParser::FunctionNode *function_node; + GDScript *script = nullptr; + const GDScriptParser::ClassNode *class_node = nullptr; + const GDScriptParser::FunctionNode *function_node = nullptr; StringName function_name; - bool debug_stack; - - List<Map<StringName, int>> stack_id_stack; - Map<StringName, int> stack_identifiers; - - List<GDScriptFunction::StackDebug> stack_debug; - List<Map<StringName, int>> block_identifier_stack; - Map<StringName, int> block_identifiers; - Map<StringName, int> local_named_constants; - - void add_stack_identifier(const StringName &p_id, int p_stackpos) { - stack_identifiers[p_id] = p_stackpos; - if (debug_stack) { - block_identifiers[p_id] = p_stackpos; - GDScriptFunction::StackDebug sd; - sd.added = true; - sd.line = current_line; - sd.identifier = p_id; - sd.pos = p_stackpos; - stack_debug.push_back(sd); - } + GDScriptCodeGenerator *generator = nullptr; + Map<StringName, GDScriptCodeGenerator::Address> parameters; + Map<StringName, GDScriptCodeGenerator::Address> locals; + List<Set<StringName>> locals_in_scope; + + GDScriptCodeGenerator::Address add_local(const StringName &p_name, const GDScriptDataType &p_type) { + uint32_t addr = generator->add_local(p_name, p_type); + locals[p_name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::LOCAL_VARIABLE, addr, p_type); + locals_in_scope.back()->get().insert(p_name); + return locals[p_name]; } - void push_stack_identifiers() { - stack_id_stack.push_back(stack_identifiers); - if (debug_stack) { - block_identifier_stack.push_back(block_identifiers); - block_identifiers.clear(); - } + GDScriptCodeGenerator::Address add_local_constant(const StringName &p_name, const Variant &p_value) { + uint32_t addr = generator->add_local_constant(p_name, p_value); + locals[p_name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::LOCAL_CONSTANT, addr); + return locals[p_name]; } - void pop_stack_identifiers() { - stack_identifiers = stack_id_stack.back()->get(); - stack_id_stack.pop_back(); - - if (debug_stack) { - for (Map<StringName, int>::Element *E = block_identifiers.front(); E; E = E->next()) { - GDScriptFunction::StackDebug sd; - sd.added = false; - sd.identifier = E->key(); - sd.line = current_line; - sd.pos = E->get(); - stack_debug.push_back(sd); - } - block_identifiers = block_identifier_stack.back()->get(); - block_identifier_stack.pop_back(); - } + GDScriptCodeGenerator::Address add_temporary(const GDScriptDataType &p_type = GDScriptDataType()) { + uint32_t addr = generator->add_temporary(); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::TEMPORARY, addr, p_type); } - HashMap<Variant, int, VariantHasher, VariantComparator> constant_map; - Map<StringName, int> name_map; -#ifdef TOOLS_ENABLED - Vector<StringName> named_globals; -#endif - - int get_name_map_pos(const StringName &p_identifier) { - int ret; - if (!name_map.has(p_identifier)) { - ret = name_map.size(); - name_map[p_identifier] = ret; - } else { - ret = name_map[p_identifier]; + GDScriptCodeGenerator::Address add_constant(const Variant &p_constant) { + GDScriptDataType type; + type.has_type = true; + type.kind = GDScriptDataType::BUILTIN; + type.builtin_type = p_constant.get_type(); + if (type.builtin_type == Variant::OBJECT) { + Object *obj = p_constant; + if (obj) { + type.kind = GDScriptDataType::NATIVE; + type.native_type = obj->get_class_name(); + + Ref<Script> script = obj->get_script(); + if (script.is_valid()) { + type.script_type = script.ptr(); + Ref<GDScript> gdscript = script; + if (gdscript.is_valid()) { + type.kind = GDScriptDataType::GDSCRIPT; + } else { + type.kind = GDScriptDataType::SCRIPT; + } + } + } else { + type.builtin_type = Variant::NIL; + } } - return ret; - } - int get_constant_pos(const Variant &p_constant) { - if (constant_map.has(p_constant)) { - return constant_map[p_constant] | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); - } - int pos = constant_map.size(); - constant_map[p_constant] = pos; - return pos | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); + uint32_t addr = generator->add_or_get_constant(p_constant); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CONSTANT, addr, type); } - Vector<int> opcodes; - void alloc_stack(int p_level) { - if (p_level >= stack_max) { - stack_max = p_level + 1; - } + void start_block() { + Set<StringName> scope; + locals_in_scope.push_back(scope); + generator->start_block(); } - void alloc_call(int p_params) { - if (p_params >= call_max) { - call_max = p_params; + + void end_block() { + Set<StringName> &scope = locals_in_scope.back()->get(); + for (Set<StringName>::Element *E = scope.front(); E; E = E->next()) { + locals.erase(E->get()); } + locals_in_scope.pop_back(); + generator->end_block(); } - - int current_line; - int stack_max; - int call_max; }; bool _is_class_member_property(CodeGen &codegen, const StringName &p_name); @@ -143,17 +122,16 @@ class GDScriptCompiler { void _set_error(const String &p_error, const GDScriptParser::Node *p_node); - bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level); - bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0); - bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0); - bool _generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type); + Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); + Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const; + GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner = nullptr) const; - int _parse_assign_right_expression(CodeGen &codegen, const GDScriptParser::AssignmentNode *p_assignment, int p_stack_level, int p_index_addr = 0); - int _parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root = false, bool p_initializer = false, int p_index_addr = 0); - Error _parse_match_pattern(CodeGen &codegen, const GDScriptParser::PatternNode *p_pattern, int p_stack_level, int p_value_addr, int p_type_addr, int &r_bound_variables, Vector<int> &r_patch_addresses, Vector<int> &r_block_patch_address); - Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, int p_stack_level = 0, int p_break_addr = -1, int p_continue_addr = -1); + GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); + GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); + GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested); + void _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block); + Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true); Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false); Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index a4e37a79f8..e59f99fc56 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -34,6 +34,10 @@ #include "gdscript.h" #include "gdscript_functions.h" +#ifdef DEBUG_ENABLED +#include "core/string_builder.h" +#endif + Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const { int address = p_address & ADDR_MASK; @@ -105,9 +109,9 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta #ifdef TOOLS_ENABLED case ADDR_TYPE_NAMED_GLOBAL: { #ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _named_globals_count, nullptr); + ERR_FAIL_INDEX_V(address, _global_names_count, nullptr); #endif - StringName id = _named_globals_ptr[address]; + StringName id = _global_names_ptr[address]; if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) { return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id]; @@ -212,7 +216,6 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_CALL_RETURN, \ &&OPCODE_CALL_ASYNC, \ &&OPCODE_CALL_BUILT_IN, \ - &&OPCODE_CALL_SELF, \ &&OPCODE_CALL_SELF_BASE, \ &&OPCODE_AWAIT, \ &&OPCODE_AWAIT_RESUME, \ @@ -1055,7 +1058,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a err_text = "Got a freed object as a result of the call."; OPCODE_BREAK; } - if (obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) { + if (obj && obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) { err_text = R"(Trying to call an async function without "await".)"; OPCODE_BREAK; } @@ -1139,10 +1142,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; - OPCODE(OPCODE_CALL_SELF) { - OPCODE_BREAK; - } - OPCODE(OPCODE_CALL_SELF_BASE) { CHECK_SPACE(2); int self_fun = _code_ptr[ip + 1]; @@ -1214,8 +1213,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_AWAIT) { - int ipofs = 2; - CHECK_SPACE(3); + CHECK_SPACE(2); //do the oneshot connect GET_VARIANT_PTR(argobj, 1); @@ -1265,7 +1263,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a gdfs->state.stack_size = _stack_size; gdfs->state.self = self; gdfs->state.alloca_size = alloca_size; - gdfs->state.ip = ip + ipofs; + gdfs->state.ip = ip + 2; gdfs->state.line = line; gdfs->state.script = _script; { @@ -1884,3 +1882,506 @@ GDScriptFunctionState::~GDScriptFunctionState() { instances_list.remove_from_list(); } } + +#ifdef DEBUG_ENABLED +static String _get_variant_string(const Variant &p_variant) { + String txt; + if (p_variant.get_type() == Variant::STRING) { + txt = "\"" + String(p_variant) + "\""; + } else if (p_variant.get_type() == Variant::STRING_NAME) { + txt = "&\"" + String(p_variant) + "\""; + } else if (p_variant.get_type() == Variant::NODE_PATH) { + txt = "^\"" + String(p_variant) + "\""; + } else if (p_variant.get_type() == Variant::OBJECT) { + Object *obj = p_variant; + if (!obj) { + txt = "null"; + } else { + GDScriptNativeClass *cls = Object::cast_to<GDScriptNativeClass>(obj); + if (cls) { + txt += cls->get_name(); + txt += " (class)"; + } else { + txt = obj->get_class(); + if (obj->get_script_instance()) { + txt += "(" + obj->get_script_instance()->get_script()->get_path() + ")"; + } + } + } + } else { + txt = p_variant; + } + return txt; +} + +static String _disassemble_address(const GDScript *p_script, const GDScriptFunction &p_function, int p_address) { + int addr = p_address & GDScriptFunction::ADDR_MASK; + + switch (p_address >> GDScriptFunction::ADDR_BITS) { + case GDScriptFunction::ADDR_TYPE_SELF: { + return "self"; + } break; + case GDScriptFunction::ADDR_TYPE_CLASS: { + return "class"; + } break; + case GDScriptFunction::ADDR_TYPE_MEMBER: { + return "member(" + p_script->debug_get_member_by_index(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT: { + return "class_const(" + p_function.get_global_name(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT: { + return "const(" + _get_variant_string(p_function.get_constant(addr)) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_STACK: { + return "stack(" + itos(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_STACK_VARIABLE: { + return "var_stack(" + itos(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_GLOBAL: { + return "global(" + _get_variant_string(GDScriptLanguage::get_singleton()->get_global_array()[addr]) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL: { + return "named_global(" + p_function.get_global_name(addr) + ")"; + } break; + case GDScriptFunction::ADDR_TYPE_NIL: { + return "nil"; + } break; + } + + return "<err>"; +} + +void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { +#define DADDR(m_ip) (_disassemble_address(_script, *this, _code_ptr[ip + m_ip])) + + for (int ip = 0; ip < _code_size;) { + StringBuilder text; + int incr = 0; + + text += " "; + text += itos(ip); + text += ": "; + + // This makes the compiler complain if some opcode is unchecked in the switch. + Opcode code = Opcode(_code_ptr[ip]); + + switch (code) { + case OPCODE_OPERATOR: { + int operation = _code_ptr[ip + 1]; + + text += "operator "; + + text += DADDR(4); + text += " = "; + text += DADDR(2); + text += " "; + text += Variant::get_operator_name(Variant::Operator(operation)); + text += " "; + text += DADDR(3); + + incr += 5; + } break; + case OPCODE_EXTENDS_TEST: { + text += "is object "; + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += " is "; + text += DADDR(2); + + incr += 4; + } break; + case OPCODE_IS_BUILTIN: { + text += "is builtin "; + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += " is "; + text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 2])); + + incr += 4; + } break; + case OPCODE_SET: { + text += "set "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "] = "; + text += DADDR(3); + + incr += 4; + } break; + case OPCODE_GET: { + text += "get "; + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += "["; + text += DADDR(2); + text += "]"; + + incr += 4; + } break; + case OPCODE_SET_NAMED: { + text += "set_named "; + text += DADDR(1); + text += "[\""; + text += _global_names_ptr[_code_ptr[ip + 2]]; + text += "\"] = "; + text += DADDR(3); + + incr += 4; + } break; + case OPCODE_GET_NAMED: { + text += "get_named "; + text += DADDR(3); + text += " = "; + text += DADDR(1); + text += "[\""; + text += _global_names_ptr[_code_ptr[ip + 2]]; + text += "\"]"; + + incr += 4; + } break; + case OPCODE_SET_MEMBER: { + text += "set_member "; + text += "[\""; + text += _global_names_ptr[_code_ptr[ip + 1]]; + text += "\"] = "; + text += DADDR(2); + + incr += 3; + } break; + case OPCODE_GET_MEMBER: { + text += "get_member "; + text += DADDR(2); + text += " = "; + text += "[\""; + text += _global_names_ptr[_code_ptr[ip + 1]]; + text += "\"]"; + + incr += 3; + } break; + case OPCODE_ASSIGN: { + text += "assign "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 3; + } break; + case OPCODE_ASSIGN_TRUE: { + text += "assign "; + text += DADDR(1); + text += " = true"; + + incr += 2; + } break; + case OPCODE_ASSIGN_FALSE: { + text += "assign "; + text += DADDR(1); + text += " = false"; + + incr += 2; + } break; + case OPCODE_ASSIGN_TYPED_BUILTIN: { + text += "assign typed builtin ("; + text += Variant::get_type_name((Variant::Type)_code_ptr[ip + 1]); + text += ") "; + text += DADDR(2); + text += " = "; + text += DADDR(3); + + incr += 4; + } break; + case OPCODE_ASSIGN_TYPED_NATIVE: { + Variant class_name = _constants_ptr[_code_ptr[ip + 1]]; + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(class_name.operator Object *()); + + text += "assign typed native ("; + text += nc->get_name().operator String(); + text += ") "; + text += DADDR(2); + text += " = "; + text += DADDR(3); + + incr += 4; + } break; + case OPCODE_ASSIGN_TYPED_SCRIPT: { + Variant script = _constants_ptr[_code_ptr[ip + 1]]; + Script *sc = Object::cast_to<Script>(script.operator Object *()); + + text += "assign typed script ("; + text += sc->get_path(); + text += ") "; + text += DADDR(2); + text += " = "; + text += DADDR(3); + + incr += 4; + } break; + case OPCODE_CAST_TO_BUILTIN: { + text += "cast builtin "; + text += DADDR(3); + text += " = "; + text += DADDR(2); + text += " as "; + text += Variant::get_type_name(Variant::Type(_code_ptr[ip + 1])); + + incr += 4; + } break; + case OPCODE_CAST_TO_NATIVE: { + Variant class_name = _constants_ptr[_code_ptr[ip + 1]]; + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(class_name.operator Object *()); + + text += "cast native "; + text += DADDR(3); + text += " = "; + text += DADDR(2); + text += " as "; + text += nc->get_name(); + + incr += 4; + } break; + case OPCODE_CAST_TO_SCRIPT: { + text += "cast "; + text += DADDR(3); + text += " = "; + text += DADDR(2); + text += " as "; + text += DADDR(1); + + incr += 4; + } break; + case OPCODE_CONSTRUCT: { + Variant::Type t = Variant::Type(_code_ptr[ip + 1]); + int argc = _code_ptr[ip + 2]; + + text += "construct "; + text += DADDR(3 + argc); + text += " = "; + + text += Variant::get_type_name(t) + "("; + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(i + 3); + } + text += ")"; + + incr = 4 + argc; + } break; + case OPCODE_CONSTRUCT_ARRAY: { + int argc = _code_ptr[ip + 1]; + text += " make_array "; + text += DADDR(2 + argc); + text += " = ["; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(2 + i); + } + + text += "]"; + + incr += 3 + argc; + } break; + case OPCODE_CONSTRUCT_DICTIONARY: { + int argc = _code_ptr[ip + 1]; + text += "make_dict "; + text += DADDR(2 + argc * 2); + text += " = {"; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(2 + i * 2 + 0); + text += ": "; + text += DADDR(2 + i * 2 + 1); + } + + text += "}"; + + incr += 3 + argc * 2; + } break; + case OPCODE_CALL: + case OPCODE_CALL_RETURN: + case OPCODE_CALL_ASYNC: { + bool ret = _code_ptr[ip] == OPCODE_CALL_RETURN; + bool async = _code_ptr[ip] == OPCODE_CALL_ASYNC; + + if (ret) { + text += "call-ret "; + } else if (async) { + text += "call-async "; + } else { + text += "call "; + } + + int argc = _code_ptr[ip + 1]; + if (ret || async) { + text += DADDR(4 + argc) + " = "; + } + + text += DADDR(2) + "."; + text += String(_global_names_ptr[_code_ptr[ip + 3]]); + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(4 + i); + } + text += ")"; + + incr = 5 + argc; + } break; + case OPCODE_CALL_BUILT_IN: { + text += "call-built-in "; + + int argc = _code_ptr[ip + 2]; + text += DADDR(3 + argc) + " = "; + + text += GDScriptFunctions::get_func_name(GDScriptFunctions::Function(_code_ptr[ip + 1])); + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(3 + i); + } + text += ")"; + + incr = 4 + argc; + } break; + case OPCODE_CALL_SELF_BASE: { + text += "call-self-base "; + + int argc = _code_ptr[ip + 2]; + text += DADDR(3 + argc) + " = "; + + text += _global_names_ptr[_code_ptr[ip + 1]]; + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(3 + i); + } + text += ")"; + + incr = 4 + argc; + } break; + case OPCODE_AWAIT: { + text += "await "; + text += DADDR(1); + + incr += 2; + } break; + case OPCODE_AWAIT_RESUME: { + text += "await resume "; + text += DADDR(1); + + incr = 2; + } break; + case OPCODE_JUMP: { + text += "jump "; + text += itos(_code_ptr[ip + 1]); + + incr = 2; + } break; + case OPCODE_JUMP_IF: { + text += "jump-if "; + text += DADDR(1); + text += " to "; + text += itos(_code_ptr[ip + 2]); + + incr = 3; + } break; + case OPCODE_JUMP_IF_NOT: { + text += "jump-if-not "; + text += DADDR(1); + text += " to "; + text += itos(_code_ptr[ip + 2]); + + incr = 3; + } break; + case OPCODE_JUMP_TO_DEF_ARGUMENT: { + text += "jump-to-default-argument "; + + incr = 1; + } break; + case OPCODE_RETURN: { + text += "return "; + text += DADDR(1); + + incr = 2; + } break; + case OPCODE_ITERATE_BEGIN: { + text += "for-init "; + text += DADDR(4); + text += " in "; + text += DADDR(2); + text += " counter "; + text += DADDR(1); + text += " end "; + text += itos(_code_ptr[ip + 3]); + + incr += 5; + } break; + case OPCODE_ITERATE: { + text += "for-loop "; + text += DADDR(4); + text += " in "; + text += DADDR(2); + text += " counter "; + text += DADDR(1); + text += " end "; + text += itos(_code_ptr[ip + 3]); + + incr += 5; + } break; + case OPCODE_LINE: { + int line = _code_ptr[ip + 1] - 1; + if (line >= 0 && line < p_code_lines.size()) { + text += "line "; + text += itos(line + 1); + text += ": "; + text += p_code_lines[line]; + } else { + text += ""; + } + + incr += 2; + } break; + case OPCODE_ASSERT: { + text += "assert ("; + text += DADDR(1); + text += ", "; + text += DADDR(2); + text += ")"; + + incr += 3; + } break; + case OPCODE_BREAKPOINT: { + text += "breakpoint"; + + incr += 1; + } break; + case OPCODE_END: { + text += "== END =="; + + incr += 1; + } break; + } + + ip += incr; + if (text.get_string_length() > 0) { + print_line(text.as_string()); + } + } +} +#endif diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 771baf6a08..c98ac09310 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -56,7 +56,8 @@ struct GDScriptDataType { bool has_type = false; Variant::Type builtin_type = Variant::NIL; StringName native_type; - Ref<Script> script_type; + Script *script_type = nullptr; + Ref<Script> script_type_ref; bool is_type(const Variant &p_variant, bool p_allow_implicit_conversion = false) const { if (!has_type) { @@ -182,7 +183,6 @@ public: OPCODE_CALL_RETURN, OPCODE_CALL_ASYNC, OPCODE_CALL_BUILT_IN, - OPCODE_CALL_SELF, OPCODE_CALL_SELF_BASE, OPCODE_AWAIT, OPCODE_AWAIT_RESUME, @@ -224,6 +224,7 @@ public: private: friend class GDScriptCompiler; + friend class GDScriptByteCodeGenerator; StringName source; @@ -232,10 +233,6 @@ private: int _constant_count; const StringName *_global_names_ptr; int _global_names_count; -#ifdef TOOLS_ENABLED - const StringName *_named_globals_ptr; - int _named_globals_count; -#endif const int *_default_arg_ptr; int _default_arg_count; const int *_code_ptr; @@ -252,9 +249,6 @@ private: StringName name; Vector<Variant> constants; Vector<StringName> global_names; -#ifdef TOOLS_ENABLED - Vector<StringName> named_globals; -#endif Vector<int> default_arguments; Vector<int> code; Vector<GDScriptDataType> argument_types; @@ -344,6 +338,10 @@ public: Variant call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state = nullptr); +#ifdef DEBUG_ENABLED + void disassemble(const Vector<String> &p_code_lines) const; +#endif + _FORCE_INLINE_ MultiplayerAPI::RPCMode get_rpc_mode() const { return rpc_mode; } GDScriptFunction(); ~GDScriptFunction(); diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp index fefbf906f0..31ce63bc6e 100644 --- a/modules/gdscript/gdscript_functions.cpp +++ b/modules/gdscript/gdscript_functions.cpp @@ -635,7 +635,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ case TEXT_CHAR: { VALIDATE_ARG_COUNT(1); VALIDATE_ARG_NUM(0); - CharType result[2] = { *p_args[0], 0 }; + char32_t result[2] = { *p_args[0], 0 }; r_ret = String(result); } break; case TEXT_ORD: { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 4761506381..2a69db130b 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -327,7 +327,7 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ bool found = false; const String &line = lines[i]; for (int j = 0; j < line.size(); j++) { - if (line[j] == CharType(0xFFFF)) { + if (line[j] == char32_t(0xFFFF)) { found = true; break; } else if (line[j] == '\t') { @@ -1042,7 +1042,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) { break; // Allow trailing comma. } - if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) { + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for enum key.)")) { EnumNode::Value item; item.identifier = parse_identifier(); item.parent_enum = enum_node; @@ -1476,7 +1476,9 @@ GDScriptParser::ContinueNode *GDScriptParser::parse_continue() { } current_suite->has_continue = true; end_statement(R"("continue")"); - return alloc_node<ContinueNode>(); + ContinueNode *cont = alloc_node<ContinueNode>(); + cont->is_for_match = is_continue_match; + return cont; } GDScriptParser::ForNode *GDScriptParser::parse_for() { @@ -1495,10 +1497,12 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { // Save break/continue state. bool could_break = can_break; bool could_continue = can_continue; + bool was_continue_match = is_continue_match; // Allow break/continue. can_break = true; can_continue = true; + is_continue_match = false; SuiteNode *suite = alloc_node<SuiteNode>(); if (n_for->variable) { @@ -1511,6 +1515,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { // Reset break/continue state. can_break = could_break; can_continue = could_continue; + is_continue_match = was_continue_match; return n_for; } @@ -1645,8 +1650,10 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { // Save continue state. bool could_continue = can_continue; + bool was_continue_match = is_continue_match; // Allow continue for match. can_continue = true; + is_continue_match = true; SuiteNode *suite = alloc_node<SuiteNode>(); if (branch->patterns.size() > 0) { @@ -1663,6 +1670,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { // Restore continue state. can_continue = could_continue; + is_continue_match = was_continue_match; return branch; } @@ -1820,16 +1828,19 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() { // Save break/continue state. bool could_break = can_break; bool could_continue = can_continue; + bool was_continue_match = is_continue_match; // Allow break/continue. can_break = true; can_continue = true; + is_continue_match = false; n_while->loop = parse_suite(R"("while" block)"); // Reset break/continue state. can_break = could_break; can_continue = could_continue; + is_continue_match = was_continue_match; return n_while; } @@ -2505,7 +2516,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) { if (match(GDScriptTokenizer::Token::LITERAL)) { if (previous.literal.get_type() != Variant::STRING) { - push_error(R"(Expect node path as string or identifer after "$".)"); + push_error(R"(Expect node path as string or identifier after "$".)"); return nullptr; } GetNodeNode *get_node = alloc_node<GetNodeNode>(); @@ -2528,7 +2539,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p } while (match(GDScriptTokenizer::Token::SLASH)); return get_node; } else { - push_error(R"(Expect node path as string or identifer after "$".)"); + push_error(R"(Expect node path as string or identifier after "$".)"); return nullptr; } } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 7d8ae7fc55..4c9473c7bd 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -609,6 +609,7 @@ public: }; struct ContinueNode : public Node { + bool is_for_match = false; ContinueNode() { type = CONTINUE; } @@ -1079,6 +1080,7 @@ private: bool panic_mode = false; bool can_break = false; bool can_continue = false; + bool is_continue_match = false; // Whether a `continue` will act on a `match`. bool is_ignoring_warnings = false; List<bool> multiline_stack; diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 0145ac39ff..9a40aa50ac 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -222,7 +222,7 @@ String GDScriptTokenizer::get_token_name(Token::Type p_token_type) { void GDScriptTokenizer::set_source_code(const String &p_source_code) { source = p_source_code; if (source.empty()) { - _source = L""; + _source = U""; } else { _source = source.ptr(); } @@ -263,7 +263,7 @@ bool GDScriptTokenizer::is_past_cursor() const { return true; } -CharType GDScriptTokenizer::_advance() { +char32_t GDScriptTokenizer::_advance() { if (unlikely(_is_at_end())) { return '\0'; } @@ -282,15 +282,15 @@ CharType GDScriptTokenizer::_advance() { return _peek(-1); } -void GDScriptTokenizer::push_paren(CharType p_char) { +void GDScriptTokenizer::push_paren(char32_t p_char) { paren_stack.push_back(p_char); } -bool GDScriptTokenizer::pop_paren(CharType p_expected) { +bool GDScriptTokenizer::pop_paren(char32_t p_expected) { if (paren_stack.empty()) { return false; } - CharType actual = paren_stack.back()->get(); + char32_t actual = paren_stack.back()->get(); paren_stack.pop_back(); return actual == p_expected; @@ -302,19 +302,19 @@ GDScriptTokenizer::Token GDScriptTokenizer::pop_error() { return error; } -static bool _is_alphanumeric(CharType c) { +static bool _is_alphanumeric(char32_t c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; } -static bool _is_digit(CharType c) { +static bool _is_digit(char32_t c) { return (c >= '0' && c <= '9'); } -static bool _is_hex_digit(CharType c) { +static bool _is_hex_digit(char32_t c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } -static bool _is_binary_digit(CharType c) { +static bool _is_binary_digit(char32_t c) { return (c == '0' || c == '1'); } @@ -404,7 +404,7 @@ void GDScriptTokenizer::push_error(const Token &p_error) { error_stack.push_back(p_error); } -GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(CharType p_paren) { +GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(char32_t p_paren) { if (paren_stack.empty()) { return make_error(vformat("Closing \"%c\" doesn't have an opening counterpart.", p_paren)); } @@ -413,8 +413,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::make_paren_error(CharType p_paren) { return error; } -GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(CharType p_test, Token::Type p_double_type) { - const CharType *next = _current + 1; +GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, Token::Type p_double_type) { + const char32_t *next = _current + 1; int chars = 2; // Two already matched. // Test before consuming characters, since we don't want to consume more than needed. @@ -578,7 +578,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { } void GDScriptTokenizer::newline(bool p_make_token) { - // Don't overwrite previous newline, nor create if we want a line contination. + // Don't overwrite previous newline, nor create if we want a line continuation. if (p_make_token && !pending_newline && !line_continuation) { Token newline(Token::NEWLINE); newline.start_line = line; @@ -602,7 +602,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { bool has_decimal = false; bool has_exponent = false; bool has_error = false; - bool (*digit_check_func)(CharType) = _is_digit; + bool (*digit_check_func)(char32_t) = _is_digit; if (_peek(-1) == '.') { has_decimal = true; @@ -762,7 +762,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { _advance(); } - CharType quote_char = _peek(-1); + char32_t quote_char = _peek(-1); if (_peek() == quote_char && _peek(1) == quote_char) { is_multiline = true; @@ -779,7 +779,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { return make_error("Unterminated string."); } - CharType ch = _peek(); + char32_t ch = _peek(); if (ch == '\\') { // Escape pattern. @@ -789,13 +789,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { } // Grab escape character. - CharType code = _peek(); + char32_t code = _peek(); _advance(); if (_is_at_end()) { return make_error("Unterminated string."); } - CharType escaped = 0; + char32_t escaped = 0; bool valid_escape = true; switch (code) { @@ -836,8 +836,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { return make_error("Unterminated string."); } - CharType digit = _peek(); - CharType value = 0; + char32_t digit = _peek(); + char32_t value = 0; if (digit >= '0' && digit <= '9') { value = digit - '0'; } else if (digit >= 'a' && digit <= 'f') { @@ -940,7 +940,7 @@ void GDScriptTokenizer::check_indent() { } for (;;) { - CharType current_indent_char = _peek(); + char32_t current_indent_char = _peek(); int indent_count = 0; if (current_indent_char != ' ' && current_indent_char != '\t' && current_indent_char != '\r' && current_indent_char != '\n' && current_indent_char != '#') { @@ -970,7 +970,7 @@ void GDScriptTokenizer::check_indent() { // Check indent level. bool mixed = false; while (!_is_at_end()) { - CharType space = _peek(); + char32_t space = _peek(); if (space == '\t') { // Consider individual tab columns. column += tab_size - 1; @@ -1039,7 +1039,7 @@ void GDScriptTokenizer::check_indent() { // First time indenting, choose character now. indent_char = current_indent_char; } else if (current_indent_char != indent_char) { - Token error = make_error(vformat("Used \"%c\" for indentation instead \"%c\" as used before in the file.", String(¤t_indent_char, 1).c_escape(), String(&indent_char, 1).c_escape())); + Token error = make_error(vformat("Used \"%s\" for indentation instead \"%s\" as used before in the file.", String(¤t_indent_char, 1).c_escape(), String(&indent_char, 1).c_escape())); error.start_line = line; error.start_column = 1; error.leftmost_column = 1; @@ -1103,7 +1103,7 @@ void GDScriptTokenizer::_skip_whitespace() { } for (;;) { - CharType c = _peek(); + char32_t c = _peek(); switch (c) { case ' ': _advance(); @@ -1192,7 +1192,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { return make_token(Token::TK_EOF); } - const CharType c = _advance(); + const char32_t c = _advance(); if (c == '\\') { // Line continuation with backslash. diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 100ed3f132..4453982d08 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -183,14 +183,14 @@ public: private: String source; - const CharType *_source = nullptr; - const CharType *_current = nullptr; + const char32_t *_source = nullptr; + const char32_t *_current = nullptr; int line = -1, column = -1; int cursor_line = -1, cursor_column = -1; int tab_size = 4; // Keep track of multichar tokens. - const CharType *_start = nullptr; + const char32_t *_start = nullptr; int start_line = 0, start_column = 0; int leftmost_column = 0, rightmost_column = 0; @@ -202,30 +202,30 @@ private: Token last_newline; int pending_indents = 0; List<int> indent_stack; - List<CharType> paren_stack; - CharType indent_char = '\0'; + List<char32_t> paren_stack; + char32_t indent_char = '\0'; int position = 0; int length = 0; _FORCE_INLINE_ bool _is_at_end() { return position >= length; } - _FORCE_INLINE_ CharType _peek(int p_offset = 0) { return position + p_offset >= 0 && position + p_offset < length ? _current[p_offset] : '\0'; } + _FORCE_INLINE_ char32_t _peek(int p_offset = 0) { return position + p_offset >= 0 && position + p_offset < length ? _current[p_offset] : '\0'; } int indent_level() const { return indent_stack.size(); } bool has_error() const { return !error_stack.empty(); } Token pop_error(); - CharType _advance(); + char32_t _advance(); void _skip_whitespace(); void check_indent(); Token make_error(const String &p_message); void push_error(const String &p_message); void push_error(const Token &p_error); - Token make_paren_error(CharType p_paren); + Token make_paren_error(char32_t p_paren); Token make_token(Token::Type p_type); Token make_literal(const Variant &p_literal); Token make_identifier(const StringName &p_identifier); - Token check_vcs_marker(CharType p_test, Token::Type p_double_type); - void push_paren(CharType p_char); - bool pop_paren(CharType p_expected); + Token check_vcs_marker(char32_t p_test, Token::Type p_double_type); + void push_paren(char32_t p_char); + bool pop_paren(char32_t p_expected); void newline(bool p_make_token); Token number(); diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 4d79d9d395..668dfd4835 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -491,7 +491,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & int start_pos = p_position.character; for (int c = p_position.character; c >= 0; c--) { start_pos = c; - CharType ch = line[c]; + char32_t ch = line[c]; bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; if (!valid_char) { break; @@ -500,7 +500,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & int end_pos = p_position.character; for (int c = p_position.character; c < line.length(); c++) { - CharType ch = line[c]; + char32_t ch = line[c]; bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; if (!valid_char) { break; @@ -552,7 +552,7 @@ Error ExtendGDScriptParser::get_left_function_call(const lsp::Position &p_positi } while (c >= 0) { - const CharType &character = line[c]; + const char32_t &character = line[c]; if (character == ')') { ++bracket_stack; } else if (character == '(') { diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index c554cbac05..da4cbe34c7 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -39,6 +39,11 @@ #include "gdscript_cache.h" #include "gdscript_tokenizer.h" +#ifdef TESTS_ENABLED +#include "tests/test_gdscript.h" +#include "tests/test_macros.h" +#endif + GDScriptLanguage *script_language_gd = nullptr; Ref<ResourceFormatLoaderGDScript> resource_loader_gd; Ref<ResourceFormatSaverGDScript> resource_saver_gd; @@ -79,7 +84,7 @@ public: return; } - // TODO: Readd compiled/encrypted GDScript on export. + // TODO: Readd compiled GDScript on export. return; } }; @@ -153,3 +158,26 @@ void unregister_gdscript_types() { GDScriptParser::cleanup(); GDScriptAnalyzer::cleanup(); } + +#ifdef TESTS_ENABLED +void test_tokenizer() { + TestGDScript::test(TestGDScript::TestType::TEST_TOKENIZER); +} + +void test_parser() { + TestGDScript::test(TestGDScript::TestType::TEST_PARSER); +} + +void test_compiler() { + TestGDScript::test(TestGDScript::TestType::TEST_COMPILER); +} + +void test_bytecode() { + TestGDScript::test(TestGDScript::TestType::TEST_BYTECODE); +} + +REGISTER_TEST_COMMAND("gdscript-tokenizer", &test_tokenizer); +REGISTER_TEST_COMMAND("gdscript-parser", &test_parser); +REGISTER_TEST_COMMAND("gdscript-compiler", &test_compiler); +REGISTER_TEST_COMMAND("gdscript-bytecode", &test_bytecode); +#endif diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp new file mode 100644 index 0000000000..68d9984b43 --- /dev/null +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -0,0 +1,231 @@ +/*************************************************************************/ +/* test_gdscript.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "test_gdscript.h" + +#include "core/os/file_access.h" +#include "core/os/main_loop.h" +#include "core/os/os.h" +#include "core/string_builder.h" + +#include "modules/gdscript/gdscript_analyzer.h" +#include "modules/gdscript/gdscript_compiler.h" +#include "modules/gdscript/gdscript_parser.h" +#include "modules/gdscript/gdscript_tokenizer.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif + +namespace TestGDScript { + +static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) { + GDScriptTokenizer tokenizer; + tokenizer.set_source_code(p_code); + + int tab_size = 4; +#ifdef TOOLS_ENABLED + if (EditorSettings::get_singleton()) { + tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size"); + } +#endif // TOOLS_ENABLED + String tab = String(" ").repeat(tab_size); + + GDScriptTokenizer::Token current = tokenizer.scan(); + while (current.type != GDScriptTokenizer::Token::TK_EOF) { + StringBuilder token; + token += " --> "; // Padding for line number. + + for (int l = current.start_line; l <= current.end_line; l++) { + print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab)); + } + + { + // Print carets to point at the token. + StringBuilder pointer; + pointer += " "; // Padding for line number. + int rightmost_column = current.rightmost_column; + if (current.end_line > current.start_line) { + rightmost_column--; // Don't point to the newline as a column. + } + for (int col = 1; col < rightmost_column; col++) { + if (col < current.leftmost_column) { + pointer += " "; + } else { + pointer += "^"; + } + } + print_line(pointer.as_string()); + } + + token += current.get_name(); + + if (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::LITERAL || current.type == GDScriptTokenizer::Token::IDENTIFIER || current.type == GDScriptTokenizer::Token::ANNOTATION) { + token += "("; + token += Variant::get_type_name(current.literal.get_type()); + token += ") "; + token += current.literal; + } + + print_line(token.as_string()); + + print_line("-------------------------------------------------------"); + + current = tokenizer.scan(); + } + + print_line(current.get_name()); // Should be EOF +} + +static void test_parser(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) { + GDScriptParser parser; + Error err = parser.parse(p_code, p_script_path, false); + + if (err != OK) { + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { + const GDScriptParser::ParserError &error = E->get(); + print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message)); + } + } + + GDScriptParser::TreePrinter printer; + + printer.print_tree(parser); +} + +static void test_compiler(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) { + GDScriptParser parser; + Error err = parser.parse(p_code, p_script_path, false); + + if (err != OK) { + print_line("Error in parser:"); + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { + const GDScriptParser::ParserError &error = E->get(); + print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message)); + } + return; + } + + GDScriptAnalyzer analyzer(&parser); + err = analyzer.analyze(); + + if (err != OK) { + print_line("Error in analyzer:"); + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { + const GDScriptParser::ParserError &error = E->get(); + print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message)); + } + return; + } + + GDScriptCompiler compiler; + Ref<GDScript> script; + script.instance(); + script->set_path(p_script_path); + + err = compiler.compile(&parser, script.ptr(), false); + + if (err) { + print_line("Error in compiler:"); + print_line(vformat("%02d:%02d: %s", compiler.get_error_line(), compiler.get_error_column(), compiler.get_error())); + return; + } + + for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { + const GDScriptFunction *func = E->value(); + + String signature = "Disassembling " + func->get_name().operator String() + "("; + for (int i = 0; i < func->get_argument_count(); i++) { + if (i > 0) { + signature += ", "; + } + signature += func->get_argument_name(i); + } + print_line(signature + ")"); + + func->disassemble(p_lines); + print_line(""); + print_line(""); + } +} + +void test(TestType p_type) { + List<String> cmdlargs = OS::get_singleton()->get_cmdline_args(); + + if (cmdlargs.empty()) { + return; + } + + String test = cmdlargs.back()->get(); + if (!test.ends_with(".gd")) { + print_line("This test expects a path to a GDScript file as its last parameter. Got: " + test); + return; + } + + FileAccessRef fa = FileAccess::open(test, FileAccess::READ); + ERR_FAIL_COND_MSG(!fa, "Could not open file: " + test); + + Vector<uint8_t> buf; + int flen = fa->get_len(); + buf.resize(fa->get_len() + 1); + fa->get_buffer(buf.ptrw(), flen); + buf.write[flen] = 0; + + String code; + code.parse_utf8((const char *)&buf[0]); + + Vector<String> lines; + int last = 0; + for (int i = 0; i <= code.length(); i++) { + if (code[i] == '\n' || code[i] == 0) { + lines.push_back(code.substr(last, i - last)); + last = i + 1; + } + } + + switch (p_type) { + case TEST_TOKENIZER: + test_tokenizer(code, lines); + break; + case TEST_PARSER: + test_parser(code, test, lines); + break; + case TEST_COMPILER: + test_compiler(code, test, lines); + break; + case TEST_BYTECODE: + print_line("Not implemented."); + } +} + +} // namespace TestGDScript diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h new file mode 100644 index 0000000000..5aa962dcf8 --- /dev/null +++ b/modules/gdscript/tests/test_gdscript.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* test_gdscript.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_GDSCRIPT_H +#define TEST_GDSCRIPT_H + +namespace TestGDScript { + +enum TestType { + TEST_TOKENIZER, + TEST_PARSER, + TEST_COMPILER, + TEST_BYTECODE, +}; + +void test(TestType p_type); + +} // namespace TestGDScript + +#endif // TEST_GDSCRIPT_H diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp index 7bb70b098f..320da182f8 100644 --- a/modules/jsonrpc/jsonrpc.cpp +++ b/modules/jsonrpc/jsonrpc.cpp @@ -98,6 +98,10 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem if (p_action.get_type() == Variant::DICTIONARY) { Dictionary dict = p_action; String method = dict.get("method", ""); + if (method.begins_with("$/")) { + return ret; + } + Array args; if (dict.has("params")) { Variant params = dict.get("params", Variant()); diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp index 501bfff075..12a982df6e 100644 --- a/modules/mbedtls/crypto_mbedtls.cpp +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -43,8 +43,8 @@ #define PEM_BEGIN_CRT "-----BEGIN CERTIFICATE-----\n" #define PEM_END_CRT "-----END CERTIFICATE-----\n" -#include "mbedtls/pem.h" #include <mbedtls/debug.h> +#include <mbedtls/pem.h> CryptoKey *CryptoKeyMbedTLS::create() { return memnew(CryptoKeyMbedTLS); @@ -294,20 +294,15 @@ Ref<X509Certificate> CryptoMbedTLS::generate_self_signed_certificate(Ref<CryptoK unsigned char buf[4096]; memset(buf, 0, 4096); - Ref<X509CertificateMbedTLS> out; - out.instance(); - mbedtls_x509write_crt_pem(&crt, buf, 4096, mbedtls_ctr_drbg_random, &ctr_drbg); - - int err = mbedtls_x509_crt_parse(&(out->cert), buf, 4096); - if (err != 0) { - mbedtls_mpi_free(&serial); - mbedtls_x509write_crt_free(&crt); - ERR_PRINT("Generated invalid certificate: " + itos(err)); - return nullptr; - } - + int ret = mbedtls_x509write_crt_pem(&crt, buf, 4096, mbedtls_ctr_drbg_random, &ctr_drbg); mbedtls_mpi_free(&serial); mbedtls_x509write_crt_free(&crt); + ERR_FAIL_COND_V_MSG(ret != 0, nullptr, "Failed to generate certificate: " + itos(ret)); + buf[4095] = '\0'; // Make sure strlen can't fail. + + Ref<X509CertificateMbedTLS> out; + out.instance(); + out->load_from_memory(buf, strlen((char *)buf) + 1); // Use strlen to find correct output size. return out; } diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 3e771e06f0..e959312393 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -1,6 +1,5 @@ import os import os.path -import sys import subprocess from SCons.Script import Dir, Environment diff --git a/modules/mono/build_scripts/mono_reg_utils.py b/modules/mono/build_scripts/mono_reg_utils.py index 3090a4759a..0ec7e2f433 100644 --- a/modules/mono/build_scripts/mono_reg_utils.py +++ b/modules/mono/build_scripts/mono_reg_utils.py @@ -9,7 +9,7 @@ if os.name == "nt": def _reg_open_key(key, subkey): try: return winreg.OpenKey(key, subkey) - except (WindowsError, OSError): + except OSError: if platform.architecture()[0] == "32bit": bitness_sam = winreg.KEY_WOW64_64KEY else: @@ -37,7 +37,7 @@ def _find_mono_in_reg(subkey, bits): with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey: value = winreg.QueryValueEx(hKey, "SdkInstallRoot")[0] return value - except (WindowsError, OSError): + except OSError: return None @@ -48,7 +48,7 @@ def _find_mono_in_reg_old(subkey, bits): if default_clr: return _find_mono_in_reg(subkey + "\\" + default_clr, bits) return None - except (WindowsError, EnvironmentError): + except OSError: return None @@ -97,7 +97,7 @@ def find_msbuild_tools_path_reg(): raise ValueError("Cannot find `installationPath` entry") except ValueError as e: print("Error reading output from vswhere: " + e.message) - except WindowsError: + except OSError: pass # Fine, vswhere not found except (subprocess.CalledProcessError, OSError): pass @@ -109,5 +109,5 @@ def find_msbuild_tools_path_reg(): with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, subkey) as hKey: value = winreg.QueryValueEx(hKey, "MSBuildToolsPath")[0] return value - except (WindowsError, OSError): + except OSError: return "" diff --git a/modules/mono/build_scripts/solution_builder.py b/modules/mono/build_scripts/solution_builder.py index 03f4e57f02..6a621c3c8b 100644 --- a/modules/mono/build_scripts/solution_builder.py +++ b/modules/mono/build_scripts/solution_builder.py @@ -8,9 +8,6 @@ def find_dotnet_cli(): import os.path if os.name == "nt": - windows_exts = os.environ["PATHEXT"] - windows_exts = windows_exts.split(os.pathsep) if windows_exts else [] - for hint_dir in os.environ["PATH"].split(os.pathsep): hint_dir = hint_dir.strip('"') hint_path = os.path.join(hint_dir, "dotnet") diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index dfc59e6ccb..4f8faffde2 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -88,7 +88,7 @@ <PropertyGroup> <!-- ExportDebug also defines DEBUG like Debug does. --> <DefineConstants Condition=" '$(Configuration)' == 'ExportDebug' ">$(DefineConstants);DEBUG</DefineConstants> - <!-- Debug defines TOOLS to differenciate between Debug and ExportDebug configurations. --> + <!-- Debug defines TOOLS to differentiate between Debug and ExportDebug configurations. --> <DefineConstants Condition=" '$(Configuration)' == 'Debug' ">$(DefineConstants);TOOLS</DefineConstants> <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants> diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index e6b0e8f1df..b217ae4bf7 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; namespace GodotTools.Core { @@ -14,14 +15,18 @@ namespace GodotTools.Core if (Path.DirectorySeparatorChar == '\\') dir = dir.Replace("/", "\\") + "\\"; - Uri fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute); - Uri relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute); + var fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute); + var relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute); - return relRoot.MakeRelativeUri(fullPath).ToString(); + // MakeRelativeUri converts spaces to %20, hence why we need UnescapeDataString + return Uri.UnescapeDataString(relRoot.MakeRelativeUri(fullPath).ToString()); } public static string NormalizePath(this string path) { + if (string.IsNullOrEmpty(path)) + return path; + bool rooted = path.IsAbsolutePath(); path = path.Replace('\\', '/'); @@ -31,7 +36,17 @@ namespace GodotTools.Core path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim(); - return rooted ? Path.DirectorySeparatorChar + path : path; + if (!rooted) + return path; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + string maybeDrive = parts[0]; + if (maybeDrive.Length == 2 && maybeDrive[1] == ':') + return path; // Already has drive letter + } + + return Path.DirectorySeparatorChar + path; } private static readonly string DriveRoot = Path.GetPathRoot(Environment.CurrentDirectory); @@ -45,7 +60,7 @@ namespace GodotTools.Core public static string ToSafeDirName(this string dirName, bool allowDirSeparator = false) { - var invalidChars = new List<string> { ":", "*", "?", "\"", "<", ">", "|" }; + var invalidChars = new List<string> {":", "*", "?", "\"", "<", ">", "|"}; if (allowDirSeparator) { diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs index 6f318aab4a..cc0da44a13 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs @@ -2,6 +2,7 @@ using GodotTools.Core; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Text.RegularExpressions; namespace GodotTools.ProjectEditor @@ -88,7 +89,7 @@ namespace GodotTools.ProjectEditor string solutionPath = Path.Combine(DirectoryPath, Name + ".sln"); string content = string.Format(SolutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg); - File.WriteAllText(solutionPath, content); + File.WriteAllText(solutionPath, content, Encoding.UTF8); // UTF-8 with BOM } public DotNetSolution(string name) diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index 41c94f19c8..e4d6b2e010 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -20,7 +20,7 @@ <ItemGroup> <None Include="MSBuild.exe" CopyToOutputDirectory="Always" /> </ItemGroup> - <Target Name="CopyMSBuildStubWindows" AfterTargets="Build" Condition="$([MSBuild]::IsOsPlatform(Windows))"> + <Target Name="CopyMSBuildStubWindows" AfterTargets="Build" Condition=" '$(GodotPlatform)' == 'windows' Or ( '$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT' ) "> <PropertyGroup> <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir> diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 4041c56597..4e2c0f17cc 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -61,10 +61,9 @@ namespace GodotTools.ProjectEditor if (item.ItemType != itemType) continue; - string normalizedExclude = item.Exclude.NormalizePath(); - - var glob = MSBuildGlob.Parse(normalizedExclude); + string normalizedRemove = item.Remove.NormalizePath(); + var glob = MSBuildGlob.Parse(normalizedRemove); excluded.AddRange(includedFiles.Where(includedFile => glob.IsMatch(includedFile))); } diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs index a8afb38728..1d800b8151 100644 --- a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs +++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs @@ -48,7 +48,7 @@ namespace GodotTools var firstMatch = classes.FirstOrDefault(classDecl => classDecl.BaseCount != 0 && // If it doesn't inherit anything, it can't be a Godot.Object. - classDecl.SearchName != searchName // Filter by the name we're looking for + classDecl.SearchName == searchName // Filter by the name we're looking for ); if (firstMatch == null) diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs index f60e469503..42ede3f3f3 100755 --- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs @@ -328,7 +328,7 @@ MONO_AOT_MODE_LAST = 1000, if (lipoExitCode != 0) throw new Exception($"Command 'lipo' exited with code: {lipoExitCode}"); - // TODO: Add the AOT lib and interpreter libs as device only to supress warnings when targeting the simulator + // TODO: Add the AOT lib and interpreter libs as device only to suppress warnings when targeting the simulator // Add the fat AOT static library to the Xcode project exporter.AddIosProjectStaticLib(fatOutputFilePath); diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 3148458d7e..2a450c5b87 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -31,6 +31,7 @@ namespace GodotTools private CheckBox aboutDialogCheckBox; private Button bottomPanelBtn; + private Button toolBarBuildButton; public GodotIdeManager GodotIdeManager { get; private set; } @@ -127,6 +128,7 @@ namespace GodotTools { menuPopup.RemoveItem(menuPopup.GetItemIndex((int)MenuOptions.CreateSln)); bottomPanelBtn.Show(); + toolBarBuildButton.Show(); } private void _ShowAboutDialog() @@ -468,6 +470,15 @@ namespace GodotTools aboutVBox.AddChild(aboutDialogCheckBox); } + toolBarBuildButton = new Button + { + Text = "Build", + HintTooltip = "Build solution", + FocusMode = Control.FocusModeEnum.None + }; + toolBarBuildButton.PressedSignal += _BuildSolutionPressed; + AddControlToContainer(CustomControlContainer.Toolbar, toolBarBuildButton); + if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) { ApplyNecessaryChangesToSolution(); @@ -475,20 +486,12 @@ namespace GodotTools else { bottomPanelBtn.Hide(); + toolBarBuildButton.Hide(); menuPopup.AddItem("Create C# solution".TTR(), (int)MenuOptions.CreateSln); } menuPopup.IdPressed += _MenuOptionPressed; - var buildButton = new Button - { - Text = "Build", - HintTooltip = "Build solution", - FocusMode = Control.FocusModeEnum.None - }; - buildButton.PressedSignal += _BuildSolutionPressed; - AddControlToContainer(CustomControlContainer.Toolbar, buildButton); - // External editor settings EditorDef("mono/editor/external_editor", ExternalEditorId.None); diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp index 430c82953e..f7d6e7e302 100644 --- a/modules/mono/editor/script_class_parser.cpp +++ b/modules/mono/editor/script_class_parser.cpp @@ -151,7 +151,7 @@ ScriptClassParser::Token ScriptClassParser::get_token() { case '"': { bool verbatim = idx != 0 && code[idx - 1] == '@'; - CharType begin_str = code[idx]; + char32_t begin_str = code[idx]; idx++; String tk_string = String(); while (true) { @@ -170,13 +170,13 @@ ScriptClassParser::Token ScriptClassParser::get_token() { } else if (code[idx] == '\\' && !verbatim) { //escaped characters... idx++; - CharType next = code[idx]; + char32_t next = code[idx]; if (next == 0) { error_str = "Unterminated String"; error = true; return TK_ERROR; } - CharType res = 0; + char32_t res = 0; switch (next) { case 'b': @@ -234,7 +234,7 @@ ScriptClassParser::Token ScriptClassParser::get_token() { if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) { //a number - const CharType *rptr; + const char32_t *rptr; double number = String::to_float(&code[idx], &rptr); idx += (rptr - &code[idx]); value = number; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index a963810d63..f77d3052f4 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -44,6 +44,15 @@ namespace Godot.Collections Add(element); } + public Array(params object[] array) : this() + { + if (array == null) + { + throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'"); + } + safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor_MonoArray(array)); + } + internal Array(ArraySafeHandle handle) { safeHandle = handle; @@ -72,6 +81,11 @@ namespace Godot.Collections return godot_icall_Array_Resize(GetPtr(), newSize); } + public static Array operator +(Array left, Array right) + { + return new Array(godot_icall_Array_Concatenate(left.GetPtr(), right.GetPtr())); + } + // IDisposable public void Dispose() @@ -155,6 +169,9 @@ namespace Godot.Collections internal extern static IntPtr godot_icall_Array_Ctor(); [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Array_Ctor_MonoArray(System.Array array); + + [MethodImpl(MethodImplOptions.InternalCall)] internal extern static void godot_icall_Array_Dtor(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] @@ -176,6 +193,9 @@ namespace Godot.Collections internal extern static void godot_icall_Array_Clear(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static IntPtr godot_icall_Array_Concatenate(IntPtr left, IntPtr right); + + [MethodImpl(MethodImplOptions.InternalCall)] internal extern static bool godot_icall_Array_Contains(IntPtr ptr, object item); [MethodImpl(MethodImplOptions.InternalCall)] @@ -231,6 +251,15 @@ namespace Godot.Collections objectArray = new Array(collection); } + public Array(params T[] array) : this() + { + if (array == null) + { + throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'"); + } + objectArray = new Array(array); + } + public Array(Array array) { objectArray = array; @@ -266,6 +295,11 @@ namespace Godot.Collections return objectArray.Resize(newSize); } + public static Array<T> operator +(Array<T> left, Array<T> right) + { + return new Array<T>(left.objectArray + right.objectArray); + } + // IList<T> public T this[int index] diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index 5aba31c622..3f1120575f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -207,7 +207,7 @@ namespace Godot } } - internal Quat RotationQuat() + public Quat RotationQuat() { Basis orthonormalizedBasis = Orthonormalized(); real_t det = orthonormalizedBasis.Determinant(); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index 41b4e9367f..bd1dbc1229 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -440,7 +440,12 @@ namespace Godot // </summary> public static bool IsAbsPath(this string instance) { - return System.IO.Path.IsPathRooted(instance); + if (string.IsNullOrEmpty(instance)) + return false; + else if (instance.Length > 1) + return instance[0] == '/' || instance[0] == '\\' || instance.Contains(":/") || instance.Contains(":\\"); + else + return instance[0] == '/' || instance[0] == '\\'; } // <summary> @@ -448,7 +453,7 @@ namespace Godot // </summary> public static bool IsRelPath(this string instance) { - return !System.IO.Path.IsPathRooted(instance); + return !IsAbsPath(instance); } // <summary> @@ -624,41 +629,46 @@ namespace Godot return instance.Length; } - // <summary> - // Do a simple expression match, where '*' matches zero or more arbitrary characters and '?' matches any single character except '.'. - // </summary> - public static bool ExprMatch(this string instance, string expr, bool caseSensitive) + /// <summary> + /// Do a simple expression match, where '*' matches zero or more arbitrary characters and '?' matches any single character except '.'. + /// </summary> + private static bool ExprMatch(this string instance, string expr, bool caseSensitive) { - if (expr.Length == 0 || instance.Length == 0) - return false; + // case '\0': + if (expr.Length == 0) + return instance.Length == 0; switch (expr[0]) { - case '\0': - return instance[0] == 0; case '*': - return ExprMatch(expr + 1, instance, caseSensitive) || instance[0] != 0 && ExprMatch(expr, instance + 1, caseSensitive); + return ExprMatch(instance, expr.Substring(1), caseSensitive) || (instance.Length > 0 && ExprMatch(instance.Substring(1), expr, caseSensitive)); case '?': - return instance[0] != 0 && instance[0] != '.' && ExprMatch(expr + 1, instance + 1, caseSensitive); + return instance.Length > 0 && instance[0] != '.' && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); default: - return (caseSensitive ? instance[0] == expr[0] : char.ToUpper(instance[0]) == char.ToUpper(expr[0])) && - ExprMatch(expr + 1, instance + 1, caseSensitive); + if (instance.Length == 0) return false; + return (caseSensitive ? instance[0] == expr[0] : char.ToUpper(instance[0]) == char.ToUpper(expr[0])) && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); } } - // <summary> - // Do a simple case sensitive expression match, using ? and * wildcards (see [method expr_match]). - // </summary> + /// <summary> + /// Do a simple case sensitive expression match, using ? and * wildcards (see [method expr_match]). + /// </summary> public static bool Match(this string instance, string expr, bool caseSensitive = true) { + if (instance.Length == 0 || expr.Length == 0) + return false; + return instance.ExprMatch(expr, caseSensitive); } - // <summary> - // Do a simple case insensitive expression match, using ? and * wildcards (see [method expr_match]). - // </summary> + /// <summary> + /// Do a simple case insensitive expression match, using ? and * wildcards (see [method expr_match]). + /// </summary> public static bool MatchN(this string instance, string expr) { + if (instance.Length == 0 || expr.Length == 0) + return false; + return instance.ExprMatch(expr, caseSensitive: false); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 26bd828a5b..3dff37279b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -670,41 +670,37 @@ namespace Godot public static bool operator <(Vector2 left, Vector2 right) { - if (Mathf.IsEqualApprox(left.x, right.x)) + if (left.x == right.x) { return left.y < right.y; } - return left.x < right.x; } public static bool operator >(Vector2 left, Vector2 right) { - if (Mathf.IsEqualApprox(left.x, right.x)) + if (left.x == right.x) { return left.y > right.y; } - return left.x > right.x; } public static bool operator <=(Vector2 left, Vector2 right) { - if (Mathf.IsEqualApprox(left.x, right.x)) + if (left.x == right.x) { return left.y <= right.y; } - return left.x <= right.x; } public static bool operator >=(Vector2 left, Vector2 right) { - if (Mathf.IsEqualApprox(left.x, right.x)) + if (left.x == right.x) { return left.y >= right.y; } - return left.x >= right.x; } @@ -714,7 +710,6 @@ namespace Godot { return Equals((Vector2)obj); } - return false; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index d9b16a6afd..4a4a2a43cd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -713,49 +713,53 @@ namespace Godot public static bool operator <(Vector3 left, Vector3 right) { - if (Mathf.IsEqualApprox(left.x, right.x)) + if (left.x == right.x) { - if (Mathf.IsEqualApprox(left.y, right.y)) + if (left.y == right.y) + { return left.z < right.z; + } return left.y < right.y; } - return left.x < right.x; } public static bool operator >(Vector3 left, Vector3 right) { - if (Mathf.IsEqualApprox(left.x, right.x)) + if (left.x == right.x) { - if (Mathf.IsEqualApprox(left.y, right.y)) + if (left.y == right.y) + { return left.z > right.z; + } return left.y > right.y; } - return left.x > right.x; } public static bool operator <=(Vector3 left, Vector3 right) { - if (Mathf.IsEqualApprox(left.x, right.x)) + if (left.x == right.x) { - if (Mathf.IsEqualApprox(left.y, right.y)) + if (left.y == right.y) + { return left.z <= right.z; + } return left.y < right.y; } - return left.x < right.x; } public static bool operator >=(Vector3 left, Vector3 right) { - if (Mathf.IsEqualApprox(left.x, right.x)) + if (left.x == right.x) { - if (Mathf.IsEqualApprox(left.y, right.y)) + if (left.y == right.y) + { return left.z >= right.z; + } return left.y > right.y; } - return left.x > right.x; } diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp index 766b00d612..3313e8cb77 100644 --- a/modules/mono/glue/collections_glue.cpp +++ b/modules/mono/glue/collections_glue.cpp @@ -104,10 +104,31 @@ void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index) { } } +Array *godot_icall_Array_Ctor_MonoArray(MonoArray *mono_array) { + Array *godot_array = memnew(Array); + unsigned int count = mono_array_length(mono_array); + godot_array->resize(count); + for (unsigned int i = 0; i < count; i++) { + MonoObject *item = mono_array_get(mono_array, MonoObject *, i); + godot_icall_Array_SetAt(godot_array, i, item); + } + return godot_array; +} + Array *godot_icall_Array_Duplicate(Array *ptr, MonoBoolean deep) { return memnew(Array(ptr->duplicate(deep))); } +Array *godot_icall_Array_Concatenate(Array *left, Array *right) { + int count = left->size() + right->size(); + Array *new_array = memnew(Array(left->duplicate(false))); + new_array->resize(count); + for (unsigned int i = 0; i < (unsigned int)right->size(); i++) { + new_array->operator[](i + left->size()) = right->operator[](i); + } + return new_array; +} + int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) { return ptr->find(GDMonoMarshal::mono_object_to_variant(item)); } @@ -284,6 +305,7 @@ MonoString *godot_icall_Dictionary_ToString(Dictionary *ptr) { void godot_register_collections_icalls() { mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor", (void *)godot_icall_Array_Ctor); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor_MonoArray", (void *)godot_icall_Array_Ctor_MonoArray); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Dtor", (void *)godot_icall_Array_Dtor); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_At", (void *)godot_icall_Array_At); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_At_Generic", (void *)godot_icall_Array_At_Generic); @@ -291,6 +313,7 @@ void godot_register_collections_icalls() { mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Count", (void *)godot_icall_Array_Count); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Add", (void *)godot_icall_Array_Add); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Clear", (void *)godot_icall_Array_Clear); + mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Concatenate", (void *)godot_icall_Array_Concatenate); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Contains", (void *)godot_icall_Array_Contains); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_CopyTo", (void *)godot_icall_Array_CopyTo); mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Duplicate", (void *)godot_icall_Array_Duplicate); diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 9dbeee57ce..6e351001d4 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -107,7 +107,7 @@ void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, [[maybe_unused]] GDMonoAssembly *gdassembly = memnew(GDMonoAssembly(name, image, assembly)); #ifdef GD_MONO_HOT_RELOAD - const char *path = mono_image_get_filename(image); + String path = String::utf8(mono_image_get_filename(image)); if (FileAccess::exists(path)) { gdassembly->modified_time = FileAccess::get_modified_time(path); } @@ -464,7 +464,9 @@ GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_a if (!assembly) { assembly = _load_assembly_search(p_name, p_aname, p_refonly, p_search_dirs); - ERR_FAIL_NULL_V(assembly, nullptr); + if (!assembly) { + return nullptr; + } } GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name); @@ -487,7 +489,9 @@ GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_ if (!assembly) { assembly = _real_load_assembly_from(p_path, p_refonly); - ERR_FAIL_NULL_V(assembly, nullptr); + if (!assembly) { + return nullptr; + } } GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name); diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp index c5a988b8c3..b8ee0204c4 100644 --- a/modules/mono/mono_gd/gd_mono_log.cpp +++ b/modules/mono/mono_gd/gd_mono_log.cpp @@ -64,25 +64,32 @@ static int get_log_level_id(const char *p_log_level) { return -1; } +static String make_text(const char *log_domain, const char *log_level, const char *message) { + String text(message); + text += " (in domain "; + text += log_domain; + if (log_level) { + text += ", "; + text += log_level; + } + text += ")"; + return text; +} + void GDMonoLog::mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *) { FileAccess *f = GDMonoLog::get_singleton()->log_file; if (GDMonoLog::get_singleton()->log_level_id >= get_log_level_id(log_level)) { - String text(message); - text += " (in domain "; - text += log_domain; - if (log_level) { - text += ", "; - text += log_level; - } - text += ")\n"; + String text = make_text(log_domain, log_level, message); + text += "\n"; f->seek_end(); f->store_string(text); } if (fatal) { - ERR_PRINT("Mono: FATAL ERROR, ABORTING! Logfile: '" + GDMonoLog::get_singleton()->log_file_path + "'."); + String text = make_text(log_domain, log_level, message); + ERR_PRINT("Mono: FATAL ERROR '" + text + "', ABORTING! Logfile: '" + GDMonoLog::get_singleton()->log_file_path + "'."); // Make sure to flush before aborting f->flush(); f->close(); diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp index 6d7d5f76cd..c460e283ea 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -311,44 +311,6 @@ bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_ return false; } -String mono_to_utf8_string(MonoString *p_mono_string) { - MonoError error; - char *utf8 = mono_string_to_utf8_checked(p_mono_string, &error); - - if (!mono_error_ok(&error)) { - ERR_PRINT(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&error) + "'."); - mono_error_cleanup(&error); - return String(); - } - - String ret = String::utf8(utf8); - - mono_free(utf8); - - return ret; -} - -String mono_to_utf16_string(MonoString *p_mono_string) { - int len = mono_string_length(p_mono_string); - String ret; - - if (len == 0) { - return ret; - } - - ret.resize(len + 1); - ret.set(len, 0); - - CharType *src = (CharType *)mono_string_chars(p_mono_string); - CharType *dst = ret.ptrw(); - - for (int i = 0; i < len; i++) { - dst[i] = src[i]; - } - - return ret; -} - MonoObject *variant_to_mono_object(const Variant *p_var) { ManagedType type; diff --git a/modules/mono/mono_gd/gd_mono_marshal.h b/modules/mono/mono_gd/gd_mono_marshal.h index 4ff330fd43..a1fd975916 100644 --- a/modules/mono/mono_gd/gd_mono_marshal.h +++ b/modules/mono/mono_gd/gd_mono_marshal.h @@ -69,15 +69,11 @@ bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_ // String -String mono_to_utf8_string(MonoString *p_mono_string); -String mono_to_utf16_string(MonoString *p_mono_string); - _FORCE_INLINE_ String mono_string_to_godot_not_null(MonoString *p_mono_string) { - if constexpr (sizeof(CharType) == 2) { - return mono_to_utf16_string(p_mono_string); - } - - return mono_to_utf8_string(p_mono_string); + char32_t *utf32 = (char32_t *)mono_string_to_utf32(p_mono_string); + String ret = String(utf32); + mono_free(utf32); + return ret; } _FORCE_INLINE_ String mono_string_to_godot(MonoString *p_mono_string) { @@ -88,20 +84,8 @@ _FORCE_INLINE_ String mono_string_to_godot(MonoString *p_mono_string) { return mono_string_to_godot_not_null(p_mono_string); } -_FORCE_INLINE_ MonoString *mono_from_utf8_string(const String &p_string) { - return mono_string_new(mono_domain_get(), p_string.utf8().get_data()); -} - -_FORCE_INLINE_ MonoString *mono_from_utf16_string(const String &p_string) { - return mono_string_from_utf16((mono_unichar2 *)p_string.c_str()); -} - _FORCE_INLINE_ MonoString *mono_string_from_godot(const String &p_string) { - if constexpr (sizeof(CharType) == 2) { - return mono_from_utf16_string(p_string); - } - - return mono_from_utf8_string(p_string); + return mono_string_from_utf32((mono_unichar4 *)(p_string.get_data())); } // Variant diff --git a/modules/mono/register_types.h b/modules/mono/register_types.h index 7fd0d24eb0..e30d9a8abd 100644 --- a/modules/mono/register_types.h +++ b/modules/mono/register_types.h @@ -28,5 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#ifndef MONO_REGISTER_TYPES_H +#define MONO_REGISTER_TYPES_H + void register_mono_types(); void unregister_mono_types(); + +#endif // MONO_REGISTER_TYPES_H diff --git a/modules/mono/utils/mono_reg_utils.cpp b/modules/mono/utils/mono_reg_utils.cpp index e0cf916a01..a619f0b975 100644 --- a/modules/mono/utils/mono_reg_utils.cpp +++ b/modules/mono/utils/mono_reg_utils.cpp @@ -71,12 +71,12 @@ LONG _RegKeyQueryString(HKEY hKey, const String &p_value_name, String &r_value) buffer.resize(512); DWORD dwBufferSize = buffer.size(); - LONG res = RegQueryValueExW(hKey, p_value_name.c_str(), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize); + LONG res = RegQueryValueExW(hKey, (LPCWSTR)(p_value_name.utf16().get_data()), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize); if (res == ERROR_MORE_DATA) { // dwBufferSize now contains the actual size buffer.resize(dwBufferSize); - res = RegQueryValueExW(hKey, p_value_name.c_str(), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize); + res = RegQueryValueExW(hKey, (LPCWSTR)(p_value_name.utf16().get_data()), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize); } if (res == ERROR_SUCCESS) { @@ -90,7 +90,7 @@ LONG _RegKeyQueryString(HKEY hKey, const String &p_value_name, String &r_value) LONG _find_mono_in_reg(const String &p_subkey, MonoRegInfo &r_info, bool p_old_reg = false) { HKEY hKey; - LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, p_subkey.c_str(), &hKey); + LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, (LPCWSTR)(p_subkey.utf16().get_data()), &hKey); if (res != ERROR_SUCCESS) goto cleanup; @@ -127,7 +127,7 @@ LONG _find_mono_in_reg_old(const String &p_subkey, MonoRegInfo &r_info) { String default_clr; HKEY hKey; - LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, p_subkey.c_str(), &hKey); + LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, (LPCWSTR)(p_subkey.utf16().get_data()), &hKey); if (res != ERROR_SUCCESS) goto cleanup; diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index ccfaf5aba7..5d1abd0c09 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -54,12 +54,16 @@ String cwd() { #ifdef WINDOWS_ENABLED const DWORD expected_size = ::GetCurrentDirectoryW(0, nullptr); - String buffer; + Char16String buffer; buffer.resize((int)expected_size); - if (::GetCurrentDirectoryW(expected_size, buffer.ptrw()) == 0) + if (::GetCurrentDirectoryW(expected_size, (wchar_t *)buffer.ptrw()) == 0) return "."; - return buffer.simplify_path(); + String result; + if (result.parse_utf16(buffer.ptr())) { + return "."; + } + return result.simplify_path(); #else char buffer[PATH_MAX]; if (::getcwd(buffer, sizeof(buffer)) == nullptr) { @@ -86,7 +90,7 @@ String abspath(const String &p_path) { String realpath(const String &p_path) { #ifdef WINDOWS_ENABLED // Open file without read/write access - HANDLE hFile = ::CreateFileW(p_path.c_str(), 0, + HANDLE hFile = ::CreateFileW((LPCWSTR)(p_path.utf16().get_data()), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); @@ -100,12 +104,18 @@ String realpath(const String &p_path) { return p_path; } - String buffer; + Char16String buffer; buffer.resize((int)expected_size); - ::GetFinalPathNameByHandleW(hFile, buffer.ptrw(), expected_size, FILE_NAME_NORMALIZED); + ::GetFinalPathNameByHandleW(hFile, (wchar_t *)buffer.ptrw(), expected_size, FILE_NAME_NORMALIZED); ::CloseHandle(hFile); - return buffer.simplify_path(); + + String result; + if (result.parse_utf16(buffer.ptr())) { + return p_path; + } + + return result.simplify_path(); #elif UNIX_ENABLED char *resolved_path = ::realpath(p_path.utf8().get_data(), nullptr); @@ -130,7 +140,7 @@ String join(const String &p_a, const String &p_b) { return p_b; } - const CharType a_last = p_a[p_a.length() - 1]; + const char32_t a_last = p_a[p_a.length() - 1]; if ((a_last == '/' || a_last == '\\') || (p_b.size() > 0 && (p_b[0] == '/' || p_b[0] == '\\'))) { return p_a + p_b; diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index f8d9804de4..65da4328f6 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -49,7 +49,7 @@ int sfind(const String &p_text, int p_from) { return -1; } - const CharType *src = p_text.c_str(); + const char32_t *src = p_text.get_data(); for (int i = p_from; i <= (len - src_len); i++) { bool found = true; @@ -64,7 +64,7 @@ int sfind(const String &p_text, int p_from) { found = src[read_pos] == '%'; break; case 1: { - CharType c = src[read_pos]; + char32_t c = src[read_pos]; found = src[read_pos] == 's' || (c >= '0' && c <= '4'); break; } @@ -121,7 +121,7 @@ String sformat(const String &p_text, const Variant &p1, const Variant &p2, const int result = 0; while ((result = sfind(p_text, search_from)) >= 0) { - CharType c = p_text[result + 1]; + char32_t c = p_text[result + 1]; int req_index = (c == 's' ? findex++ : c - '0'); diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp index 50ca01067b..c10a276eae 100644 --- a/modules/regex/regex.cpp +++ b/modules/regex/regex.cpp @@ -156,26 +156,13 @@ void RegExMatch::_bind_methods() { } void RegEx::_pattern_info(uint32_t what, void *where) const { - if (sizeof(CharType) == 2) { - pcre2_pattern_info_16((pcre2_code_16 *)code, what, where); - - } else { - pcre2_pattern_info_32((pcre2_code_32 *)code, what, where); - } + pcre2_pattern_info_32((pcre2_code_32 *)code, what, where); } void RegEx::clear() { - if (sizeof(CharType) == 2) { - if (code) { - pcre2_code_free_16((pcre2_code_16 *)code); - code = nullptr; - } - - } else { - if (code) { - pcre2_code_free_32((pcre2_code_32 *)code); - code = nullptr; - } + if (code) { + pcre2_code_free_32((pcre2_code_32 *)code); + code = nullptr; } } @@ -187,39 +174,20 @@ Error RegEx::compile(const String &p_pattern) { PCRE2_SIZE offset; uint32_t flags = PCRE2_DUPNAMES; - if (sizeof(CharType) == 2) { - pcre2_general_context_16 *gctx = (pcre2_general_context_16 *)general_ctx; - pcre2_compile_context_16 *cctx = pcre2_compile_context_create_16(gctx); - PCRE2_SPTR16 p = (PCRE2_SPTR16)pattern.c_str(); - - code = pcre2_compile_16(p, pattern.length(), flags, &err, &offset, cctx); + pcre2_general_context_32 *gctx = (pcre2_general_context_32 *)general_ctx; + pcre2_compile_context_32 *cctx = pcre2_compile_context_create_32(gctx); + PCRE2_SPTR32 p = (PCRE2_SPTR32)pattern.get_data(); - pcre2_compile_context_free_16(cctx); + code = pcre2_compile_32(p, pattern.length(), flags, &err, &offset, cctx); - if (!code) { - PCRE2_UCHAR16 buf[256]; - pcre2_get_error_message_16(err, buf, 256); - String message = String::num(offset) + ": " + String((const CharType *)buf); - ERR_PRINT(message.utf8()); - return FAILED; - } + pcre2_compile_context_free_32(cctx); - } else { - pcre2_general_context_32 *gctx = (pcre2_general_context_32 *)general_ctx; - pcre2_compile_context_32 *cctx = pcre2_compile_context_create_32(gctx); - PCRE2_SPTR32 p = (PCRE2_SPTR32)pattern.c_str(); - - code = pcre2_compile_32(p, pattern.length(), flags, &err, &offset, cctx); - - pcre2_compile_context_free_32(cctx); - - if (!code) { - PCRE2_UCHAR32 buf[256]; - pcre2_get_error_message_32(err, buf, 256); - String message = String::num(offset) + ": " + String((const CharType *)buf); - ERR_PRINT(message.utf8()); - return FAILED; - } + if (!code) { + PCRE2_UCHAR32 buf[256]; + pcre2_get_error_message_32(err, buf, 256); + String message = String::num(offset) + ": " + String((const char32_t *)buf); + ERR_PRINT(message.utf8()); + return FAILED; } return OK; } @@ -234,69 +202,39 @@ Ref<RegExMatch> RegEx::search(const String &p_subject, int p_offset, int p_end) length = p_end; } - if (sizeof(CharType) == 2) { - pcre2_code_16 *c = (pcre2_code_16 *)code; - pcre2_general_context_16 *gctx = (pcre2_general_context_16 *)general_ctx; - pcre2_match_context_16 *mctx = pcre2_match_context_create_16(gctx); - PCRE2_SPTR16 s = (PCRE2_SPTR16)p_subject.c_str(); - - pcre2_match_data_16 *match = pcre2_match_data_create_from_pattern_16(c, gctx); - - int res = pcre2_match_16(c, s, length, p_offset, 0, match, mctx); + pcre2_code_32 *c = (pcre2_code_32 *)code; + pcre2_general_context_32 *gctx = (pcre2_general_context_32 *)general_ctx; + pcre2_match_context_32 *mctx = pcre2_match_context_create_32(gctx); + PCRE2_SPTR32 s = (PCRE2_SPTR32)p_subject.get_data(); - if (res < 0) { - pcre2_match_data_free_16(match); - return nullptr; - } - - uint32_t size = pcre2_get_ovector_count_16(match); - PCRE2_SIZE *ovector = pcre2_get_ovector_pointer_16(match); - - result->data.resize(size); - - for (uint32_t i = 0; i < size; i++) { - result->data.write[i].start = ovector[i * 2]; - result->data.write[i].end = ovector[i * 2 + 1]; - } - - pcre2_match_data_free_16(match); - pcre2_match_context_free_16(mctx); - - } else { - pcre2_code_32 *c = (pcre2_code_32 *)code; - pcre2_general_context_32 *gctx = (pcre2_general_context_32 *)general_ctx; - pcre2_match_context_32 *mctx = pcre2_match_context_create_32(gctx); - PCRE2_SPTR32 s = (PCRE2_SPTR32)p_subject.c_str(); + pcre2_match_data_32 *match = pcre2_match_data_create_from_pattern_32(c, gctx); - pcre2_match_data_32 *match = pcre2_match_data_create_from_pattern_32(c, gctx); + int res = pcre2_match_32(c, s, length, p_offset, 0, match, mctx); - int res = pcre2_match_32(c, s, length, p_offset, 0, match, mctx); - - if (res < 0) { - pcre2_match_data_free_32(match); - pcre2_match_context_free_32(mctx); + if (res < 0) { + pcre2_match_data_free_32(match); + pcre2_match_context_free_32(mctx); - return nullptr; - } + return nullptr; + } - uint32_t size = pcre2_get_ovector_count_32(match); - PCRE2_SIZE *ovector = pcre2_get_ovector_pointer_32(match); + uint32_t size = pcre2_get_ovector_count_32(match); + PCRE2_SIZE *ovector = pcre2_get_ovector_pointer_32(match); - result->data.resize(size); + result->data.resize(size); - for (uint32_t i = 0; i < size; i++) { - result->data.write[i].start = ovector[i * 2]; - result->data.write[i].end = ovector[i * 2 + 1]; - } - - pcre2_match_data_free_32(match); - pcre2_match_context_free_32(mctx); + for (uint32_t i = 0; i < size; i++) { + result->data.write[i].start = ovector[i * 2]; + result->data.write[i].end = ovector[i * 2 + 1]; } + pcre2_match_data_free_32(match); + pcre2_match_context_free_32(mctx); + result->subject = p_subject; uint32_t count; - const CharType *table; + const char32_t *table; uint32_t entry_size; _pattern_info(PCRE2_INFO_NAMECOUNT, &count); @@ -304,7 +242,7 @@ Ref<RegExMatch> RegEx::search(const String &p_subject, int p_offset, int p_end) _pattern_info(PCRE2_INFO_NAMEENTRYSIZE, &entry_size); for (uint32_t i = 0; i < count; i++) { - CharType id = table[i * entry_size]; + char32_t id = table[i * entry_size]; if (result->data[id].start == -1) { continue; } @@ -344,7 +282,7 @@ String RegEx::sub(const String &p_subject, const String &p_replacement, bool p_a const int safety_zone = 1; PCRE2_SIZE olength = p_subject.length() + 1; // space for output string and one terminating \0 character - Vector<CharType> output; + Vector<char32_t> output; output.resize(olength + safety_zone); uint32_t flags = PCRE2_SUBSTITUTE_OVERFLOW_LENGTH; @@ -357,55 +295,28 @@ String RegEx::sub(const String &p_subject, const String &p_replacement, bool p_a length = p_end; } - if (sizeof(CharType) == 2) { - pcre2_code_16 *c = (pcre2_code_16 *)code; - pcre2_general_context_16 *gctx = (pcre2_general_context_16 *)general_ctx; - pcre2_match_context_16 *mctx = pcre2_match_context_create_16(gctx); - PCRE2_SPTR16 s = (PCRE2_SPTR16)p_subject.c_str(); - PCRE2_SPTR16 r = (PCRE2_SPTR16)p_replacement.c_str(); - PCRE2_UCHAR16 *o = (PCRE2_UCHAR16 *)output.ptrw(); + pcre2_code_32 *c = (pcre2_code_32 *)code; + pcre2_general_context_32 *gctx = (pcre2_general_context_32 *)general_ctx; + pcre2_match_context_32 *mctx = pcre2_match_context_create_32(gctx); + PCRE2_SPTR32 s = (PCRE2_SPTR32)p_subject.get_data(); + PCRE2_SPTR32 r = (PCRE2_SPTR32)p_replacement.get_data(); + PCRE2_UCHAR32 *o = (PCRE2_UCHAR32 *)output.ptrw(); - pcre2_match_data_16 *match = pcre2_match_data_create_from_pattern_16(c, gctx); - - int res = pcre2_substitute_16(c, s, length, p_offset, flags, match, mctx, r, p_replacement.length(), o, &olength); - - if (res == PCRE2_ERROR_NOMEMORY) { - output.resize(olength + safety_zone); - o = (PCRE2_UCHAR16 *)output.ptrw(); - res = pcre2_substitute_16(c, s, length, p_offset, flags, match, mctx, r, p_replacement.length(), o, &olength); - } + pcre2_match_data_32 *match = pcre2_match_data_create_from_pattern_32(c, gctx); - pcre2_match_data_free_16(match); - pcre2_match_context_free_16(mctx); + int res = pcre2_substitute_32(c, s, length, p_offset, flags, match, mctx, r, p_replacement.length(), o, &olength); - if (res < 0) { - return String(); - } - - } else { - pcre2_code_32 *c = (pcre2_code_32 *)code; - pcre2_general_context_32 *gctx = (pcre2_general_context_32 *)general_ctx; - pcre2_match_context_32 *mctx = pcre2_match_context_create_32(gctx); - PCRE2_SPTR32 s = (PCRE2_SPTR32)p_subject.c_str(); - PCRE2_SPTR32 r = (PCRE2_SPTR32)p_replacement.c_str(); - PCRE2_UCHAR32 *o = (PCRE2_UCHAR32 *)output.ptrw(); - - pcre2_match_data_32 *match = pcre2_match_data_create_from_pattern_32(c, gctx); - - int res = pcre2_substitute_32(c, s, length, p_offset, flags, match, mctx, r, p_replacement.length(), o, &olength); - - if (res == PCRE2_ERROR_NOMEMORY) { - output.resize(olength + safety_zone); - o = (PCRE2_UCHAR32 *)output.ptrw(); - res = pcre2_substitute_32(c, s, length, p_offset, flags, match, mctx, r, p_replacement.length(), o, &olength); - } + if (res == PCRE2_ERROR_NOMEMORY) { + output.resize(olength + safety_zone); + o = (PCRE2_UCHAR32 *)output.ptrw(); + res = pcre2_substitute_32(c, s, length, p_offset, flags, match, mctx, r, p_replacement.length(), o, &olength); + } - pcre2_match_data_free_32(match); - pcre2_match_context_free_32(mctx); + pcre2_match_data_free_32(match); + pcre2_match_context_free_32(mctx); - if (res < 0) { - return String(); - } + if (res < 0) { + return String(); } return String(output.ptr(), olength); @@ -435,7 +346,7 @@ Array RegEx::get_names() const { ERR_FAIL_COND_V(!is_valid(), result); uint32_t count; - const CharType *table; + const char32_t *table; uint32_t entry_size; _pattern_info(PCRE2_INFO_NAMECOUNT, &count); @@ -453,39 +364,21 @@ Array RegEx::get_names() const { } RegEx::RegEx() { - if (sizeof(CharType) == 2) { - general_ctx = pcre2_general_context_create_16(&_regex_malloc, &_regex_free, nullptr); - - } else { - general_ctx = pcre2_general_context_create_32(&_regex_malloc, &_regex_free, nullptr); - } + general_ctx = pcre2_general_context_create_32(&_regex_malloc, &_regex_free, nullptr); code = nullptr; } RegEx::RegEx(const String &p_pattern) { - if (sizeof(CharType) == 2) { - general_ctx = pcre2_general_context_create_16(&_regex_malloc, &_regex_free, nullptr); - - } else { - general_ctx = pcre2_general_context_create_32(&_regex_malloc, &_regex_free, nullptr); - } + general_ctx = pcre2_general_context_create_32(&_regex_malloc, &_regex_free, nullptr); code = nullptr; compile(p_pattern); } RegEx::~RegEx() { - if (sizeof(CharType) == 2) { - if (code) { - pcre2_code_free_16((pcre2_code_16 *)code); - } - pcre2_general_context_free_16((pcre2_general_context_16 *)general_ctx); - - } else { - if (code) { - pcre2_code_free_32((pcre2_code_32 *)code); - } - pcre2_general_context_free_32((pcre2_general_context_32 *)general_ctx); + if (code) { + pcre2_code_free_32((pcre2_code_32 *)code); } + pcre2_general_context_free_32((pcre2_general_context_32 *)general_ctx); } void RegEx::_bind_methods() { diff --git a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp index 3aceaf11c5..346833ab9c 100644 --- a/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/stb_vorbis/audio_stream_ogg_vorbis.cpp @@ -158,14 +158,16 @@ void AudioStreamOGGVorbis::clear_data() { void AudioStreamOGGVorbis::set_data(const Vector<uint8_t> &p_data) { int src_data_len = p_data.size(); -#define MAX_TEST_MEM (1 << 20) - uint32_t alloc_try = 1024; Vector<char> alloc_mem; char *w; stb_vorbis *ogg_stream = nullptr; stb_vorbis_alloc ogg_alloc; + // Vorbis comments may be up to UINT32_MAX, but that's arguably pretty rare. + // Let's go with 2^30 so we don't risk going out of bounds. + const uint32_t MAX_TEST_MEM = 1 << 30; + while (alloc_try < MAX_TEST_MEM) { alloc_mem.resize(alloc_try); w = alloc_mem.ptrw(); @@ -205,6 +207,8 @@ void AudioStreamOGGVorbis::set_data(const Vector<uint8_t> &p_data) { break; } } + + ERR_FAIL_COND_MSG(alloc_try == MAX_TEST_MEM, vformat("Couldn't set vorbis data even with an alloc buffer of %d bytes, report bug.", MAX_TEST_MEM)); } Vector<uint8_t> AudioStreamOGGVorbis::get_data() const { diff --git a/modules/theora/doc_classes/VideoStreamTheora.xml b/modules/theora/doc_classes/VideoStreamTheora.xml index 92244a4d28..cb8852d5ef 100644 --- a/modules/theora/doc_classes/VideoStreamTheora.xml +++ b/modules/theora/doc_classes/VideoStreamTheora.xml @@ -4,7 +4,8 @@ [VideoStream] resource for Ogg Theora videos. </brief_description> <description> - [VideoStream] resource handling the [url=https://www.theora.org/]Ogg Theora[/url] video format with [code].ogv[/code] extension. + [VideoStream] resource handling the [url=https://www.theora.org/]Ogg Theora[/url] video format with [code].ogv[/code] extension. The Theora codec is less efficient than [VideoStreamWebm]'s VP8 and VP9, but it requires less CPU resources to decode. The Theora codec is decoded on the CPU. + [b]Note:[/b] While Ogg Theora videos can also have an [code].ogg[/code] extension, you will have to rename the extension to [code].ogv[/code] to use those videos within Godot. </description> <tutorials> </tutorials> @@ -22,7 +23,7 @@ <argument index="0" name="file" type="String"> </argument> <description> - Sets the Ogg Theora video file that this [VideoStreamTheora] resource handles. The [code]file[/code] name should have the [code].o[/code] extension. + Sets the Ogg Theora video file that this [VideoStreamTheora] resource handles. The [code]file[/code] name should have the [code].ogv[/code] extension. </description> </method> </methods> diff --git a/modules/tinyexr/image_loader_tinyexr.cpp b/modules/tinyexr/image_loader_tinyexr.cpp index 9e7266b95a..5bdcb84244 100644 --- a/modules/tinyexr/image_loader_tinyexr.cpp +++ b/modules/tinyexr/image_loader_tinyexr.cpp @@ -73,8 +73,10 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f } // Read HALF channel as FLOAT. (GH-13490) + bool use_float16 = false; for (int i = 0; i < exr_header.num_channels; i++) { if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) { + use_float16 = true; exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; } } @@ -102,33 +104,10 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f idxB = c; } else if (strcmp(exr_header.channels[c].name, "A") == 0) { idxA = c; - } - } - - if (exr_header.num_channels == 1) { - // Grayscale channel only. - idxR = 0; - idxG = 0; - idxB = 0; - idxA = 0; - } else { - // Assume RGB(A) - if (idxR == -1) { - ERR_PRINT("TinyEXR: R channel not found."); - // @todo { free exr_image } - return ERR_FILE_CORRUPT; - } - - if (idxG == -1) { - ERR_PRINT("TinyEXR: G channel not found."); - // @todo { free exr_image } - return ERR_FILE_CORRUPT; - } - - if (idxB == -1) { - ERR_PRINT("TinyEXR: B channel not found."); - // @todo { free exr_image } - return ERR_FILE_CORRUPT; + } else if (strcmp(exr_header.channels[c].name, "Y") == 0) { + idxR = c; + idxG = c; + idxB = c; } } @@ -138,14 +117,27 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f Image::Format format; int output_channels = 0; + int channel_size = use_float16 ? 2 : 4; if (idxA != -1) { - imgdata.resize(exr_image.width * exr_image.height * 8); //RGBA16 - format = Image::FORMAT_RGBAH; + imgdata.resize(exr_image.width * exr_image.height * 4 * channel_size); //RGBA + format = use_float16 ? Image::FORMAT_RGBAH : Image::FORMAT_RGBAF; output_channels = 4; - } else { - imgdata.resize(exr_image.width * exr_image.height * 6); //RGB16 - format = Image::FORMAT_RGBH; + } else if (idxB != -1) { + ERR_FAIL_COND_V(idxG == -1, ERR_FILE_CORRUPT); + ERR_FAIL_COND_V(idxR == -1, ERR_FILE_CORRUPT); + imgdata.resize(exr_image.width * exr_image.height * 3 * channel_size); //RGB + format = use_float16 ? Image::FORMAT_RGBH : Image::FORMAT_RGBF; output_channels = 3; + } else if (idxG != -1) { + ERR_FAIL_COND_V(idxR == -1, ERR_FILE_CORRUPT); + imgdata.resize(exr_image.width * exr_image.height * 2 * channel_size); //RG + format = use_float16 ? Image::FORMAT_RGH : Image::FORMAT_RGF; + output_channels = 2; + } else { + ERR_FAIL_COND_V(idxR == -1, ERR_FILE_CORRUPT); + imgdata.resize(exr_image.width * exr_image.height * 1 * channel_size); //R + format = use_float16 ? Image::FORMAT_RH : Image::FORMAT_RF; + output_channels = 1; } EXRTile single_image_tile; @@ -175,9 +167,11 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f exr_tiles = exr_image.tiles; } + //print_line("reading format: " + Image::get_format_name(format)); { uint8_t *wd = imgdata.ptrw(); - uint16_t *iw = (uint16_t *)wd; + uint16_t *iw16 = (uint16_t *)wd; + float *iw32 = (float *)wd; // Assume `out_rgba` have enough memory allocated. for (int tile_index = 0; tile_index < num_tiles; tile_index++) { @@ -187,41 +181,99 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f int th = tile.height; const float *r_channel_start = reinterpret_cast<const float *>(tile.images[idxR]); - const float *g_channel_start = reinterpret_cast<const float *>(tile.images[idxG]); - const float *b_channel_start = reinterpret_cast<const float *>(tile.images[idxB]); + const float *g_channel_start = nullptr; + const float *b_channel_start = nullptr; const float *a_channel_start = nullptr; + if (idxG != -1) { + g_channel_start = reinterpret_cast<const float *>(tile.images[idxG]); + } + if (idxB != -1) { + b_channel_start = reinterpret_cast<const float *>(tile.images[idxB]); + } if (idxA != -1) { a_channel_start = reinterpret_cast<const float *>(tile.images[idxA]); } - uint16_t *first_row_w = iw + (tile.offset_y * tile_height * exr_image.width + tile.offset_x * tile_width) * output_channels; + uint16_t *first_row_w16 = iw16 + (tile.offset_y * tile_height * exr_image.width + tile.offset_x * tile_width) * output_channels; + float *first_row_w32 = iw32 + (tile.offset_y * tile_height * exr_image.width + tile.offset_x * tile_width) * output_channels; for (int y = 0; y < th; y++) { const float *r_channel = r_channel_start + y * tile_width; - const float *g_channel = g_channel_start + y * tile_width; - const float *b_channel = b_channel_start + y * tile_width; + const float *g_channel = nullptr; + const float *b_channel = nullptr; const float *a_channel = nullptr; - + if (g_channel_start) { + g_channel = g_channel_start + y * tile_width; + } + if (b_channel_start) { + b_channel = b_channel_start + y * tile_width; + } if (a_channel_start) { a_channel = a_channel_start + y * tile_width; } - uint16_t *row_w = first_row_w + (y * exr_image.width * output_channels); - - for (int x = 0; x < tw; x++) { - Color color(*r_channel++, *g_channel++, *b_channel++); - - if (p_force_linear) { - color = color.to_linear(); + if (use_float16) { + uint16_t *row_w = first_row_w16 + (y * exr_image.width * output_channels); + + for (int x = 0; x < tw; x++) { + Color color; + color.r = *r_channel++; + if (g_channel) { + color.g = *g_channel++; + } + if (b_channel) { + color.b = *b_channel++; + } + if (a_channel) { + color.a = *a_channel++; + } + + if (p_force_linear) { + color = color.to_linear(); + } + + *row_w++ = Math::make_half_float(color.r); + if (g_channel) { + *row_w++ = Math::make_half_float(color.g); + } + if (b_channel) { + *row_w++ = Math::make_half_float(color.b); + } + if (a_channel) { + *row_w++ = Math::make_half_float(color.a); + } } - - *row_w++ = Math::make_half_float(color.r); - *row_w++ = Math::make_half_float(color.g); - *row_w++ = Math::make_half_float(color.b); - - if (idxA != -1) { - *row_w++ = Math::make_half_float(*a_channel++); + } else { + float *row_w = first_row_w32 + (y * exr_image.width * output_channels); + + for (int x = 0; x < tw; x++) { + Color color; + color.r = *r_channel++; + if (g_channel) { + color.g = *g_channel++; + } + if (b_channel) { + color.b = *b_channel++; + } + if (a_channel) { + color.a = *a_channel++; + } + + if (p_force_linear) { + color = color.to_linear(); + } + + *row_w++ = color.r; + if (g_channel) { + *row_w++ = color.g; + } + if (b_channel) { + *row_w++ = color.b; + } + if (a_channel) { + *row_w++ = color.a; + } } } } diff --git a/modules/visual_script/visual_script_builtin_funcs.cpp b/modules/visual_script/visual_script_builtin_funcs.cpp index a0dcd76d10..177f9192b8 100644 --- a/modules/visual_script/visual_script_builtin_funcs.cpp +++ b/modules/visual_script/visual_script_builtin_funcs.cpp @@ -1060,7 +1060,7 @@ void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func, const Variant **p_in } break; case VisualScriptBuiltinFunc::TEXT_CHAR: { - CharType result[2] = { *p_inputs[0], 0 }; + char32_t result[2] = { *p_inputs[0], 0 }; *r_return = String(result); diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 5581ea9318..b1d8c05d87 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -1124,8 +1124,8 @@ void VisualScriptEditor::_update_members() { TreeItem *ti = members->create_item(variables); ti->set_text(0, E->get()); - Variant var = script->get_variable_default_value(E->get()); - ti->set_suffix(0, "= " + String(var)); + + ti->set_suffix(0, "= " + _sanitized_variant_text(E->get())); ti->set_icon(0, type_icons[script->get_variable_info(E->get()).type]); ti->set_selectable(0, true); @@ -1167,6 +1167,18 @@ void VisualScriptEditor::_update_members() { updating_members = false; } +String VisualScriptEditor::_sanitized_variant_text(const StringName &property_name) { + Variant var = script->get_variable_default_value(property_name); + + if (script->get_variable_info(property_name).type != Variant::NIL) { + Callable::CallError ce; + const Variant *converted = &var; + var = Variant::construct(script->get_variable_info(property_name).type, &converted, 1, ce, false); + } + + return String(var); +} + void VisualScriptEditor::_member_selected() { if (updating_members) { return; @@ -2682,7 +2694,8 @@ void VisualScriptEditor::reload(bool p_soft) { _update_graph(); } -void VisualScriptEditor::get_breakpoints(List<int> *p_breakpoints) { +Array VisualScriptEditor::get_breakpoints() { + Array breakpoints; List<StringName> functions; script->get_function_list(&functions); for (List<StringName>::Element *E = functions.front(); E; E = E->next()) { @@ -2691,10 +2704,11 @@ void VisualScriptEditor::get_breakpoints(List<int> *p_breakpoints) { for (List<int>::Element *F = nodes.front(); F; F = F->next()) { Ref<VisualScriptNode> vsn = script->get_node(E->get(), F->get()); if (vsn->is_breakpoint()) { - p_breakpoints->push_back(F->get() - 1); //subtract 1 because breakpoints in text start from zero + breakpoints.push_back(F->get() - 1); //subtract 1 because breakpoints in text start from zero } } } + return breakpoints; } void VisualScriptEditor::add_callback(const String &p_function, PackedStringArray p_args) { diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 0c5665cee8..66e435741f 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -146,6 +146,7 @@ class VisualScriptEditor : public ScriptEditorBase { bool updating_members; void _update_members(); + String _sanitized_variant_text(const StringName &property_name); StringName selected; @@ -312,7 +313,7 @@ public: virtual void ensure_focus() override; virtual void tag_saved_version() override; virtual void reload(bool p_soft) override; - virtual void get_breakpoints(List<int> *p_breakpoints) override; + virtual Array get_breakpoints() override; virtual void add_callback(const String &p_function, PackedStringArray p_args) override; virtual void update_settings() override; virtual bool show_members_overview() override; diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp index 2ac7793b8c..60a439b291 100644 --- a/modules/visual_script/visual_script_expression.cpp +++ b/modules/visual_script/visual_script_expression.cpp @@ -187,7 +187,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { while (true) { #define GET_CHAR() (str_ofs >= expression.length() ? 0 : expression[str_ofs++]) - CharType cchar = GET_CHAR(); + char32_t cchar = GET_CHAR(); if (cchar == 0) { r_token.type = TK_EOF; return OK; @@ -329,7 +329,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { case '"': { String str; while (true) { - CharType ch = GET_CHAR(); + char32_t ch = GET_CHAR(); if (ch == 0) { _set_error("Unterminated String"); @@ -340,13 +340,13 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } else if (ch == '\\') { //escaped characters... - CharType next = GET_CHAR(); + char32_t next = GET_CHAR(); if (next == 0) { _set_error("Unterminated String"); r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } - CharType res = 0; + char32_t res = 0; switch (next) { case 'b': @@ -367,7 +367,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { case 'u': { // hex number for (int j = 0; j < 4; j++) { - CharType c = GET_CHAR(); + char32_t c = GET_CHAR(); if (c == 0) { _set_error("Unterminated String"); @@ -379,7 +379,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } - CharType v; + char32_t v; if (c >= '0' && c <= '9') { v = c - '0'; } else if (c >= 'a' && c <= 'f') { @@ -431,7 +431,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { #define READING_DONE 4 int reading = READING_INT; - CharType c = cchar; + char32_t c = cchar; bool exp_sign = false; bool exp_beg = false; bool is_float = false; diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 87aa64211e..1b77ed3168 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -933,36 +933,36 @@ static const char *op_names[] = { }; String VisualScriptOperator::get_caption() const { - static const wchar_t *op_names[] = { + static const char32_t *op_names[] = { //comparison - L"A = B", //OP_EQUAL, - L"A \u2260 B", //OP_NOT_EQUAL, - L"A < B", //OP_LESS, - L"A \u2264 B", //OP_LESS_EQUAL, - L"A > B", //OP_GREATER, - L"A \u2265 B", //OP_GREATER_EQUAL, + U"A = B", //OP_EQUAL, + U"A \u2260 B", //OP_NOT_EQUAL, + U"A < B", //OP_LESS, + U"A \u2264 B", //OP_LESS_EQUAL, + U"A > B", //OP_GREATER, + U"A \u2265 B", //OP_GREATER_EQUAL, //mathematic - L"A + B", //OP_ADD, - L"A - B", //OP_SUBTRACT, - L"A \u00D7 B", //OP_MULTIPLY, - L"A \u00F7 B", //OP_DIVIDE, - L"\u00AC A", //OP_NEGATE, - L"+ A", //OP_POSITIVE, - L"A mod B", //OP_MODULE, - L"A .. B", //OP_STRING_CONCAT, + U"A + B", //OP_ADD, + U"A - B", //OP_SUBTRACT, + U"A \u00D7 B", //OP_MULTIPLY, + U"A \u00F7 B", //OP_DIVIDE, + U"\u00AC A", //OP_NEGATE, + U"+ A", //OP_POSITIVE, + U"A mod B", //OP_MODULE, + U"A .. B", //OP_STRING_CONCAT, //bitwise - L"A << B", //OP_SHIFT_LEFT, - L"A >> B", //OP_SHIFT_RIGHT, - L"A & B", //OP_BIT_AND, - L"A | B", //OP_BIT_OR, - L"A ^ B", //OP_BIT_XOR, - L"~A", //OP_BIT_NEGATE, + U"A << B", //OP_SHIFT_LEFT, + U"A >> B", //OP_SHIFT_RIGHT, + U"A & B", //OP_BIT_AND, + U"A | B", //OP_BIT_OR, + U"A ^ B", //OP_BIT_XOR, + U"~A", //OP_BIT_NEGATE, //logic - L"A and B", //OP_AND, - L"A or B", //OP_OR, - L"A xor B", //OP_XOR, - L"not A", //OP_NOT, - L"A in B", //OP_IN, + U"A and B", //OP_AND, + U"A or B", //OP_OR, + U"A xor B", //OP_XOR, + U"not A", //OP_NOT, + U"A in B", //OP_IN, }; return op_names[op]; diff --git a/modules/webm/doc_classes/VideoStreamWebm.xml b/modules/webm/doc_classes/VideoStreamWebm.xml index dfa04720cf..2edbc08cc8 100644 --- a/modules/webm/doc_classes/VideoStreamWebm.xml +++ b/modules/webm/doc_classes/VideoStreamWebm.xml @@ -4,7 +4,8 @@ [VideoStream] resource for WebM videos. </brief_description> <description> - [VideoStream] resource handling the [url=https://www.webmproject.org/]WebM[/url] video format with [code].webm[/code] extension. + [VideoStream] resource handling the [url=https://www.webmproject.org/]WebM[/url] video format with [code].webm[/code] extension. Both the VP8 and VP9 codecs are supported. The VP8 and VP9 codecs are more efficient than [VideoStreamTheora], but they require more CPU resources to decode (especially VP9). Both the VP8 and VP9 codecs are decoded on the CPU. + [b]Note:[/b] There are known bugs and performance issues with WebM video playback in Godot. If you run into problems, try using the Ogg Theora format instead: [VideoStreamTheora] </description> <tutorials> </tutorials> diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml index 2054276655..c80b903e39 100644 --- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml +++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml @@ -97,7 +97,7 @@ { "urls": [ "turn:turn.example.com:3478" ], # One or more TURN servers. "username": "a_username", # Optional username for the TURN server. - "credentials": "a_password", # Optional password for the TURN server. + "credential": "a_password", # Optional password for the TURN server. } ] } diff --git a/modules/webrtc/webrtc_data_channel_gdnative.h b/modules/webrtc/webrtc_data_channel_gdnative.h index b578802250..03396d207d 100644 --- a/modules/webrtc/webrtc_data_channel_gdnative.h +++ b/modules/webrtc/webrtc_data_channel_gdnative.h @@ -28,11 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifdef WEBRTC_GDNATIVE_ENABLED - #ifndef WEBRTC_DATA_CHANNEL_GDNATIVE_H #define WEBRTC_DATA_CHANNEL_GDNATIVE_H +#ifdef WEBRTC_GDNATIVE_ENABLED + #include "modules/gdnative/include/net/godot_net.h" #include "webrtc_data_channel.h" @@ -75,6 +75,6 @@ public: ~WebRTCDataChannelGDNative(); }; -#endif // WEBRTC_DATA_CHANNEL_GDNATIVE_H - #endif // WEBRTC_GDNATIVE_ENABLED + +#endif // WEBRTC_DATA_CHANNEL_GDNATIVE_H diff --git a/modules/webrtc/webrtc_data_channel_js.h b/modules/webrtc/webrtc_data_channel_js.h index 455866cbf1..7545910e66 100644 --- a/modules/webrtc/webrtc_data_channel_js.h +++ b/modules/webrtc/webrtc_data_channel_js.h @@ -28,11 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifdef JAVASCRIPT_ENABLED - #ifndef WEBRTC_DATA_CHANNEL_JS_H #define WEBRTC_DATA_CHANNEL_JS_H +#ifdef JAVASCRIPT_ENABLED + #include "webrtc_data_channel.h" class WebRTCDataChannelJS : public WebRTCDataChannel { @@ -88,6 +88,6 @@ public: ~WebRTCDataChannelJS(); }; -#endif // WEBRTC_DATA_CHANNEL_JS_H - #endif // JAVASCRIPT_ENABLED + +#endif // WEBRTC_DATA_CHANNEL_JS_H diff --git a/modules/webrtc/webrtc_multiplayer.h b/modules/webrtc/webrtc_multiplayer.h index bfdcf6daa1..fb37bd7722 100644 --- a/modules/webrtc/webrtc_multiplayer.h +++ b/modules/webrtc/webrtc_multiplayer.h @@ -112,4 +112,4 @@ public: ConnectionStatus get_connection_status() const override; }; -#endif +#endif // WEBRTC_MULTIPLAYER_H diff --git a/modules/webrtc/webrtc_peer_connection_gdnative.h b/modules/webrtc/webrtc_peer_connection_gdnative.h index 74b7db1307..846b65c466 100644 --- a/modules/webrtc/webrtc_peer_connection_gdnative.h +++ b/modules/webrtc/webrtc_peer_connection_gdnative.h @@ -28,11 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifdef WEBRTC_GDNATIVE_ENABLED - #ifndef WEBRTC_PEER_CONNECTION_GDNATIVE_H #define WEBRTC_PEER_CONNECTION_GDNATIVE_H +#ifdef WEBRTC_GDNATIVE_ENABLED + #include "modules/gdnative/include/net/godot_net.h" #include "webrtc_peer_connection.h" @@ -68,6 +68,6 @@ public: ~WebRTCPeerConnectionGDNative(); }; -#endif // WEBRTC_PEER_CONNECTION_GDNATIVE_H - #endif // WEBRTC_GDNATIVE_ENABLED + +#endif // WEBRTC_PEER_CONNECTION_GDNATIVE_H |