diff options
Diffstat (limited to 'modules/gdscript')
22 files changed, 663 insertions, 190 deletions
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..e170667a30 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -7,6 +7,7 @@ List of core built-in GDScript functions. Math functions and other utilities. Everything else is provided by objects. (Keywords: builtin, built in, global functions.) </description> <tutorials> + <link title="Random number generation">https://docs.godotengine.org/en/latest/tutorials/math/random_number_generation.html</link> </tutorials> <methods> <method name="Color8"> @@ -369,24 +370,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> @@ -632,6 +628,7 @@ var main = load("res://main.tscn") # main will contain a PackedScene resource. [/codeblock] [b]Important:[/b] The path must be absolute, a local path will just return [code]null[/code]. + This method is a simplified version of [method ResourceLoader.load], which can be used for more advanced scenarios. </description> </method> <method name="log"> @@ -771,24 +768,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("%2d %2d %2d" % [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 +820,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 +894,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 +984,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..7f7410a92c 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'); } @@ -73,6 +73,13 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) color_region_cache[p_line] = -1; int in_region = -1; if (p_line != 0) { + int prev_region_line = p_line - 1; + while (prev_region_line > 0 && !color_region_cache.has(prev_region_line)) { + prev_region_line--; + } + for (int i = prev_region_line; i < p_line - 1; i++) { + get_line_syntax_highlighting(i); + } if (!color_region_cache.has(p_line - 1)) { get_line_syntax_highlighting(p_line - 1); } @@ -119,7 +126,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 +160,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 +178,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 +198,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 +578,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 +591,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..e70e3f7272 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1044,8 +1044,10 @@ GDScript::~GDScript() { MutexLock lock(GDScriptLanguage::get_singleton()->lock); while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { - E->self()->_clear_stack(); + // Order matters since clearing the stack may already cause + // the GDSCriptFunctionState to be destroyed and thus removed from the list. pending_func_states.remove(E); + E->self()->_clear_stack(); } } @@ -1053,7 +1055,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(); @@ -1449,8 +1453,10 @@ GDScriptInstance::~GDScriptInstance() { MutexLock lock(GDScriptLanguage::get_singleton()->lock); while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { - E->self()->_clear_stack(); + // Order matters since clearing the stack may already cause + // the GDSCriptFunctionState to be destroyed and thus removed from the list. pending_func_states.remove(E); + E->self()->_clear_stack(); } if (script.is_valid() && owner) { @@ -2035,7 +2041,33 @@ 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). + SelfList<GDScript> *s = script_list.first(); + while (s) { + GDScript *script = s->self(); + // This ensures the current script is not released before we can check what's the next one + // in the list (we can't get the next upfront because we don't know if the reference breaking + // will cause it -or any other after it, for that matter- to be released so the next one + // is not the same as before). + script->reference(); + + 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>(); + } + + s = s->next(); + script->unreference(); + } + + 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 9c148e2148..ab4edb04b9 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -577,6 +577,12 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas GDScriptParser::DataType datatype = member.constant->get_datatype(); if (member.constant->initializer) { + if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) { + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer)); + } else if (member.constant->initializer->type == GDScriptParser::Node::DICTIONARY) { + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(member.constant->initializer)); + } + if (!member.constant->initializer->is_constant) { push_error(R"(Initializer for a constant must be a constant expression.)", member.constant->initializer); } @@ -794,7 +800,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); @@ -848,7 +854,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); @@ -1113,6 +1119,11 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant GDScriptParser::DataType type; reduce_expression(p_constant->initializer); + if (p_constant->initializer->type == GDScriptParser::Node::ARRAY) { + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_constant->initializer)); + } else if (p_constant->initializer->type == GDScriptParser::Node::DICTIONARY) { + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_constant->initializer)); + } if (!p_constant->initializer->is_constant) { push_error(vformat(R"(Assigned value for constant "%s" isn't a constant expression.)", p_constant->identifier->name), p_constant->initializer); @@ -1264,14 +1275,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; } @@ -1282,7 +1297,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); } @@ -1418,22 +1433,9 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre } void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) { - bool all_is_constant = true; - for (int i = 0; i < p_array->elements.size(); i++) { GDScriptParser::ExpressionNode *element = p_array->elements[i]; reduce_expression(element); - all_is_constant = all_is_constant && element->is_constant; - } - - if (all_is_constant) { - Array array; - array.resize(p_array->elements.size()); - for (int i = 0; i < p_array->elements.size(); i++) { - array[i] = p_array->elements[i]->reduced_value; - } - p_array->is_constant = true; - p_array->reduced_value = array; } // It's array in any case. @@ -1980,8 +1982,6 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { } void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { - bool all_is_constant = true; - HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements; for (int i = 0; i < p_dictionary->elements.size(); i++) { @@ -1990,7 +1990,6 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti reduce_expression(element.key); } reduce_expression(element.value); - all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; if (element.key->is_constant) { if (elements.has(element.key->reduced_value)) { @@ -2001,16 +2000,6 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti } } - if (all_is_constant) { - Dictionary dict; - for (int i = 0; i < p_dictionary->elements.size(); i++) { - const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; - dict[element.key->reduced_value] = element.value->reduced_value; - } - p_dictionary->is_constant = true; - p_dictionary->reduced_value = dict; - } - // It's dictionary in any case. GDScriptParser::DataType dict_type; dict_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -2073,18 +2062,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; } @@ -2721,6 +2724,46 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) p_unary_op->set_datatype(result); } +void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array) { + bool all_is_constant = true; + + for (int i = 0; i < p_array->elements.size(); i++) { + GDScriptParser::ExpressionNode *element = p_array->elements[i]; + all_is_constant = all_is_constant && element->is_constant; + if (!all_is_constant) { + return; + } + } + + Array array; + array.resize(p_array->elements.size()); + for (int i = 0; i < p_array->elements.size(); i++) { + array[i] = p_array->elements[i]->reduced_value; + } + p_array->is_constant = true; + p_array->reduced_value = array; +} + +void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { + bool all_is_constant = true; + + for (int i = 0; i < p_dictionary->elements.size(); i++) { + const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; + all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; + if (!all_is_constant) { + return; + } + } + + Dictionary dict; + for (int i = 0; i < p_dictionary->elements.size(); i++) { + const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; + dict[element.key->reduced_value] = element.value->reduced_value; + } + p_dictionary->is_constant = true; + p_dictionary->reduced_value = dict; +} + GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source) { GDScriptParser::DataType result; result.is_constant = true; @@ -2971,7 +3014,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..f3cbb320b7 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. @@ -89,6 +89,9 @@ class GDScriptAnalyzer { void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op); void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op); + void const_fold_array(GDScriptParser::ArrayNode *p_array); + void const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary); + // Helpers. GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source); GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const; diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 8f0ce99de6..cc9e87b882 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -529,6 +529,7 @@ void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant append(p_arguments[i]); } append(p_target); + alloc_call(p_arguments.size()); } void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) { @@ -579,8 +580,8 @@ void GDScriptByteCodeGenerator::write_endif() { } 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); + int counter_pos = add_temporary() | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + int container_pos = add_temporary() | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); current_breaks_to_patch.push_back(List<int>()); @@ -628,7 +629,9 @@ void GDScriptByteCodeGenerator::write_endfor() { } current_breaks_to_patch.pop_back(); - current_stack_size -= 2; // Remove loop temporaries. + // Remove loop temporaries. + pop_temporary(); + pop_temporary(); } void GDScriptByteCodeGenerator::start_while_condition() { 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_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 7a6a7bcf48..bad450c9f9 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -76,7 +76,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N } } -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(); } @@ -98,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: { @@ -124,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; } } @@ -149,6 +149,12 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } + // 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); + } + return result; } @@ -1668,7 +1674,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser 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()); + return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script); } else { if (p_for_ready) { func_name = "_ready"; @@ -1685,7 +1691,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser if (p_func) { for (int i = 0; i < p_func->parameters.size(); i++) { const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; - GDScriptDataType par_type = _gdtype_from_datatype(parameter->get_datatype()); + 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); @@ -1718,6 +1724,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, field->initializer, false, true); if (error) { + memdelete(codegen.generator); return error; } GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype())); @@ -1738,6 +1745,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser 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; } GDScriptCodeGenerator::Address dst_addr = codegen.parameters[parameter->identifier->name]; @@ -1751,6 +1759,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser Error err = _parse_block(codegen, p_func->body); if (err) { + memdelete(codegen.generator); return err; } } @@ -1800,6 +1809,8 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser p_script->member_functions[func_name] = gd_function; + memdelete(codegen.generator); + return OK; } @@ -1825,7 +1836,7 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP return_type.kind = GDScriptDataType::BUILTIN; return_type.builtin_type = Variant::NIL; } else { - return_type = _gdtype_from_datatype(p_variable->get_datatype()); + return_type = _gdtype_from_datatype(p_variable->get_datatype(), p_script); } codegen.generator->write_start(p_script, func_name, false, p_variable->rpc_mode, return_type); @@ -1837,6 +1848,7 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP error = _parse_block(codegen, p_is_setter ? p_variable->setter : p_variable->getter); if (error) { + memdelete(codegen.generator); return error; } @@ -1870,6 +1882,7 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP #ifdef TOOLS_ENABLED p_script->member_lines[func_name] = p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line; #endif + memdelete(codegen.generator); return OK; } @@ -1920,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(); @@ -1987,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 80fba6a934..fe34d6cba3 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -51,12 +51,11 @@ class GDScriptCompiler { GDScriptCodeGenerator *generator = nullptr; Map<StringName, GDScriptCodeGenerator::Address> parameters; Map<StringName, GDScriptCodeGenerator::Address> locals; - List<Set<StringName>> locals_in_scope; + List<Map<StringName, GDScriptCodeGenerator::Address>> locals_stack; 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]; } @@ -84,7 +83,7 @@ class GDScriptCompiler { Ref<Script> script = obj->get_script(); if (script.is_valid()) { - type.script_type = script; + type.script_type = script.ptr(); Ref<GDScript> gdscript = script; if (gdscript.is_valid()) { type.kind = GDScriptDataType::GDSCRIPT; @@ -102,17 +101,14 @@ class GDScriptCompiler { } void start_block() { - Set<StringName> scope; - locals_in_scope.push_back(scope); + Map<StringName, GDScriptCodeGenerator::Address> old_locals = locals; + locals_stack.push_back(old_locals); generator->start_block(); } 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(); + locals = locals_stack.back()->get(); + locals_stack.pop_back(); generator->end_block(); } }; @@ -125,7 +121,7 @@ class GDScriptCompiler { 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; 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()); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 2e372575da..aec05b6771 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -792,6 +792,9 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, continue; } option = ScriptCodeCompletionOption(member.constant->identifier->name, ScriptCodeCompletionOption::KIND_CONSTANT); + if (member.constant->initializer) { + option.default_value = member.constant->initializer->reduced_value; + } break; case GDScriptParser::ClassNode::Member::CLASS: if (p_only_functions) { @@ -2404,6 +2407,11 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path Variant::get_constants_for_type(completion_context.builtin_type, &constants); for (const List<StringName>::Element *E = constants.front(); E != nullptr; E = E->next()) { ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT); + bool valid = false; + Variant default_value = Variant::get_constant_value(completion_context.builtin_type, E->get(), &valid); + if (valid) { + option.default_value = default_value; + } options.insert(option.display, option); } } break; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index aa48a7cdb4..e59f99fc56 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -1058,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; } diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index d1c98a5456..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) { 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 d890103f15..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; @@ -2516,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>(); @@ -2539,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_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index f5cf1e29f0..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; @@ -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/icons/GDScript.svg b/modules/gdscript/icons/GDScript.svg index 953bb9ae9e..aa59125ea9 100644 --- a/modules/gdscript/icons/GDScript.svg +++ b/modules/gdscript/icons/GDScript.svg @@ -1,5 +1 @@ -<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> -<g transform="translate(0 -1036.4)"> -<path transform="translate(0 1036.4)" d="m7 1l-0.56445 2.2578a5 5 0 0 0 -0.68945 0.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -0.28516 0.68555l-2.2539 0.5625v2l2.2578 0.56445a5 5 0 0 0 0.2793 0.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953a5 5 0 0 0 0.68555 0.28516l0.5625 2.2539h2l0.56445-2.2578a5 5 0 0 0 0.6875 -0.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941a5 5 0 0 0 0.28516 -0.68555l2.2539-0.5625v-2l-2.2578-0.56445a5 5 0 0 0 -0.2793 -0.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -0.68555 -0.28516l-0.5625-2.2539h-2zm1 5a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2 -2 2 2 0 0 1 2 -2z" fill="#e0e0e0"/> -</g> -</svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2l2.2578.56445a5 5 0 0 0 .2793.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953a5 5 0 0 0 .68555.28516l.5625 2.2539h2l.56445-2.2578a5 5 0 0 0 .6875-.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941a5 5 0 0 0 .28516-.68555l2.2539-.5625v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2zm1 5a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2z" fill="#e0e0e0"/></svg> 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..931b683a44 --- /dev/null +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -0,0 +1,307 @@ +/*************************************************************************/ +/* 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/io/file_access_pack.h" +#include "core/os/file_access.h" +#include "core/os/main_loop.h" +#include "core/os/os.h" +#include "core/project_settings.h" +#include "core/string_builder.h" +#include "scene/resources/packed_scene.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 init_autoloads() { + Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + + // First pass, add the constants so they exist before any script is loaded. + for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { + const ProjectSettings::AutoloadInfo &info = E->get(); + + if (info.is_singleton) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_global_constant(info.name, Variant()); + } + } + } + + // Second pass, load into global constants. + for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { + const ProjectSettings::AutoloadInfo &info = E->get(); + + if (!info.is_singleton) { + // Skip non-singletons since we don't have a scene tree here anyway. + continue; + } + + RES res = ResourceLoader::load(info.path); + ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); + Node *n = nullptr; + if (res->is_class("PackedScene")) { + Ref<PackedScene> ps = res; + n = ps->instance(); + } else if (res->is_class("Script")) { + Ref<Script> script_res = res; + StringName ibt = script_res->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path); + + Object *obj = ClassDB::instance(ibt); + + ERR_CONTINUE_MSG(obj == nullptr, + "Cannot instance script for autoload, expected 'Node' inheritance, got: " + + String(ibt)); + + n = Object::cast_to<Node>(obj); + n->set_script(script_res); + } + + ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); + n->set_name(info.name); + + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_global_constant(info.name, n); + } + } +} + +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); + + // Init PackedData since it's used by ProjectSettings. + PackedData *packed_data = memnew(PackedData); + + // Setup project settings since it's needed by the languages to get the global scripts. + // This also sets up the base resource path. + Error err = ProjectSettings::get_singleton()->setup(fa->get_path_absolute().get_base_dir(), String(), true); + if (err) { + print_line("Could not load project settings."); + // Keep going since some scripts still work without this. + } + + // Initialize the language for the test routine. + ScriptServer::init_languages(); + init_autoloads(); + + 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."); + } + + // Destroy stuff we set up earlier. + ScriptServer::finish_languages(); + memdelete(packed_data); +} + +} // 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 |