diff options
Diffstat (limited to 'modules/gdscript')
29 files changed, 4073 insertions, 2163 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 9e40a69712..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"> @@ -167,6 +168,7 @@ i = ceil(1.45) # i is 2 i = ceil(1.001) # i is 2 [/codeblock] + See also [method floor], [method round], and [method stepify]. </description> </method> <method name="char"> @@ -338,6 +340,7 @@ # a is -3.0 a = floor(-2.99) [/codeblock] + See also [method ceil], [method round], and [method stepify]. [b]Note:[/b] This method returns a float. If you need an integer, you can use [code]int(s)[/code] directly. </description> </method> @@ -367,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> @@ -630,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"> @@ -769,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> @@ -827,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"> @@ -900,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"> @@ -989,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> @@ -1043,6 +1026,7 @@ [codeblock] round(2.6) # Returns 3 [/codeblock] + See also [method floor], [method ceil], and [method stepify]. </description> </method> <method name="seed"> @@ -1161,6 +1145,7 @@ stepify(100, 32) # Returns 96 stepify(3.14159, 0.01) # Returns 3.14 [/codeblock] + See also [method ceil], [method floor], and [method round]. </description> </method> <method name="str" qualifiers="vararg"> diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 62ccb93901..631a102130 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -8,7 +8,7 @@ [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. </description> <tutorials> - <link>https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link> + <link title="GDScript tutorial index">https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link> </tutorials> <methods> <method name="get_as_byte_code" qualifiers="const"> 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/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index 6d454e43f2..944ed859f5 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -37,9 +37,11 @@ void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<Strin GDScriptLanguage::get_singleton()->get_recognized_extensions(r_extensions); } -Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_extracted_strings) { - // Parse and match all GDScript function API that involves translation string. - // E.g get_node("Label").text = "something", var test = tr("something"), "something" will be matched and collected. +Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) { + // Extract all translatable strings using the parsed tree from GDSriptParser. + // The strategy is to find all ExpressionNode and AssignmentNode from the tree and extract strings if relevant, i.e + // Search strings in ExpressionNode -> CallNode -> tr(), set_text(), set_placeholder() etc. + // Search strings in AssignmentNode -> text = "__", hint_tooltip = "__" etc. Error err; RES loaded_res = ResourceLoader::load(p_path, "", false, &err); @@ -48,108 +50,302 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve return err; } + ids = r_ids; + ids_ctx_plural = r_ids_ctx_plural; Ref<GDScript> gdscript = loaded_res; String source_code = gdscript->get_source_code(); - Vector<String> parsed_strings; - - // Search translation strings with RegEx. - regex.clear(); - regex.compile(String("|").join(patterns)); - Array results = regex.search_all(source_code); - _get_captured_strings(results, &parsed_strings); - - // Special handling for FileDialog. - Vector<String> temp; - _parse_file_dialog(source_code, &temp); - parsed_strings.append_array(temp); - - // Filter out / and + - String filter = "(?:\\\\\\n|\"[\\s\\\\]*\\+\\s*\")"; - regex.clear(); - regex.compile(filter); - for (int i = 0; i < parsed_strings.size(); i++) { - parsed_strings.set(i, regex.sub(parsed_strings[i], "", true)); + + GDScriptParser parser; + err = parser.parse(source_code, p_path, false); + if (err != OK) { + ERR_PRINT("Failed to parse with GDScript with GDScriptParser."); + return err; } - r_extracted_strings->append_array(parsed_strings); + // Traverse through the parsed tree from GDScriptParser. + GDScriptParser::ClassNode *c = parser.get_tree(); + _traverse_class(c); return OK; } -void GDScriptEditorTranslationParserPlugin::_parse_file_dialog(const String &p_source_code, Vector<String> *r_output) { - // FileDialog API has the form .filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]). - // First filter: Get "*.png ; PNG Images", "*.gd ; GDScript Files" from PackedStringArray. - regex.clear(); - regex.compile(String("|").join(file_dialog_patterns)); - Array results = regex.search_all(p_source_code); - - Vector<String> temp; - _get_captured_strings(results, &temp); - String captured_strings = String(",").join(temp); - - // Second filter: Get the texts after semicolon from "*.png ; PNG Images","*.gd ; GDScript Files". - String second_filter = "\"[^;]+;" + text + "\""; - regex.clear(); - regex.compile(second_filter); - results = regex.search_all(captured_strings); - _get_captured_strings(results, r_output); - for (int i = 0; i < r_output->size(); i++) { - r_output->set(i, r_output->get(i).strip_edges()); +void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) { + for (int i = 0; i < p_class->members.size(); i++) { + const GDScriptParser::ClassNode::Member &m = p_class->members[i]; + // There are 7 types of Member, but only class, function and variable can contain translatable strings. + switch (m.type) { + case GDScriptParser::ClassNode::Member::CLASS: + _traverse_class(m.m_class); + break; + case GDScriptParser::ClassNode::Member::FUNCTION: + _traverse_function(m.function); + break; + case GDScriptParser::ClassNode::Member::VARIABLE: + _read_variable(m.variable); + break; + default: + break; + } + } +} + +void GDScriptEditorTranslationParserPlugin::_traverse_function(const GDScriptParser::FunctionNode *p_func) { + _traverse_block(p_func->body); +} + +void GDScriptEditorTranslationParserPlugin::_read_variable(const GDScriptParser::VariableNode *p_var) { + _assess_expression(p_var->initializer); +} + +void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser::SuiteNode *p_suite) { + if (!p_suite) { + return; + } + + const Vector<GDScriptParser::Node *> &statements = p_suite->statements; + for (int i = 0; i < statements.size(); i++) { + GDScriptParser::Node *statement = statements[i]; + + // Statements with Node type constant, break, continue, pass, breakpoint are skipped because they can't contain translatable strings. + switch (statement->type) { + case GDScriptParser::Node::VARIABLE: + _assess_expression(static_cast<GDScriptParser::VariableNode *>(statement)->initializer); + break; + case GDScriptParser::Node::IF: { + GDScriptParser::IfNode *if_node = static_cast<GDScriptParser::IfNode *>(statement); + _assess_expression(if_node->condition); + //FIXME : if the elif logic is changed in GDScriptParser, then this probably will have to change as well. See GDScriptParser::TreePrinter::print_if(). + _traverse_block(if_node->true_block); + _traverse_block(if_node->false_block); + break; + } + case GDScriptParser::Node::FOR: { + GDScriptParser::ForNode *for_node = static_cast<GDScriptParser::ForNode *>(statement); + _assess_expression(for_node->list); + _traverse_block(for_node->loop); + break; + } + case GDScriptParser::Node::WHILE: { + GDScriptParser::WhileNode *while_node = static_cast<GDScriptParser::WhileNode *>(statement); + _assess_expression(while_node->condition); + _traverse_block(while_node->loop); + break; + } + case GDScriptParser::Node::MATCH: { + GDScriptParser::MatchNode *match_node = static_cast<GDScriptParser::MatchNode *>(statement); + _assess_expression(match_node->test); + for (int j = 0; j < match_node->branches.size(); j++) { + _traverse_block(match_node->branches[j]->block); + } + break; + } + case GDScriptParser::Node::RETURN: + _assess_expression(static_cast<GDScriptParser::ReturnNode *>(statement)->return_value); + break; + case GDScriptParser::Node::ASSERT: + _assess_expression((static_cast<GDScriptParser::AssertNode *>(statement))->condition); + break; + case GDScriptParser::Node::ASSIGNMENT: + _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(statement)); + break; + default: + if (statement->is_expression()) { + _assess_expression(static_cast<GDScriptParser::ExpressionNode *>(statement)); + } + break; + } + } +} + +void GDScriptEditorTranslationParserPlugin::_assess_expression(GDScriptParser::ExpressionNode *p_expression) { + // Explore all ExpressionNodes to find CallNodes which contain translation strings, such as tr(), set_text() etc. + // tr() can be embedded quite deep within multiple ExpressionNodes so need to dig down to search through all ExpressionNodes. + if (!p_expression) { + return; + } + + // ExpressionNode of type await, cast, get_node, identifier, literal, preload, self, subscript, unary are ignored as they can't be CallNode + // containing translation strings. + switch (p_expression->type) { + case GDScriptParser::Node::ARRAY: { + GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(p_expression); + for (int i = 0; i < array_node->elements.size(); i++) { + _assess_expression(array_node->elements[i]); + } + break; + } + case GDScriptParser::Node::ASSIGNMENT: + _assess_assignment(static_cast<GDScriptParser::AssignmentNode *>(p_expression)); + break; + case GDScriptParser::Node::BINARY_OPERATOR: { + GDScriptParser::BinaryOpNode *binary_op_node = static_cast<GDScriptParser::BinaryOpNode *>(p_expression); + _assess_expression(binary_op_node->left_operand); + _assess_expression(binary_op_node->right_operand); + break; + } + case GDScriptParser::Node::CALL: { + GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_expression); + _extract_from_call(call_node); + for (int i = 0; i < call_node->arguments.size(); i++) { + _assess_expression(call_node->arguments[i]); + } + } break; + case GDScriptParser::Node::DICTIONARY: { + GDScriptParser::DictionaryNode *dict_node = static_cast<GDScriptParser::DictionaryNode *>(p_expression); + for (int i = 0; i < dict_node->elements.size(); i++) { + _assess_expression(dict_node->elements[i].key); + _assess_expression(dict_node->elements[i].value); + } + break; + } + case GDScriptParser::Node::TERNARY_OPERATOR: { + GDScriptParser::TernaryOpNode *ternary_op_node = static_cast<GDScriptParser::TernaryOpNode *>(p_expression); + _assess_expression(ternary_op_node->condition); + _assess_expression(ternary_op_node->true_expr); + _assess_expression(ternary_op_node->false_expr); + break; + } + default: + break; + } +} + +void GDScriptEditorTranslationParserPlugin::_assess_assignment(GDScriptParser::AssignmentNode *p_assignment) { + // Extract the translatable strings coming from assignments. For example, get_node("Label").text = "____" + + StringName assignee_name; + if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { + assignee_name = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee)->name; + } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) { + assignee_name = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->attribute->name; + } + + if (assignment_patterns.has(assignee_name) && p_assignment->assigned_value->type == GDScriptParser::Node::LITERAL) { + // If the assignment is towards one of the extract patterns (text, hint_tooltip etc.), and the value is a string literal, we collect the string. + ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_assignment->assigned_value)->value); + } else if (assignee_name == fd_filters && p_assignment->assigned_value->type == GDScriptParser::Node::CALL) { + // FileDialog.filters accepts assignment in the form of PackedStringArray. For example, + // get_node("FileDialog").filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]). + + GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value); + if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) { + GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]); + + // Extract the name in "extension ; name" of PackedStringArray. + for (int i = 0; i < array_node->elements.size(); i++) { + _extract_fd_literals(array_node->elements[i]); + } + } + } else { + // If the assignee is not in extract patterns or the assigned_value is not Literal type, try to see if the assigned_value contains tr(). + _assess_expression(p_assignment->assigned_value); } } -void GDScriptEditorTranslationParserPlugin::_get_captured_strings(const Array &p_results, Vector<String> *r_output) { - Ref<RegExMatch> result; - for (int i = 0; i < p_results.size(); i++) { - result = p_results[i]; - for (int j = 0; j < result->get_group_count(); j++) { - String s = result->get_string(j + 1); - // Prevent reading text with only spaces. - if (!s.strip_edges().empty()) { - r_output->push_back(s); +void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::CallNode *p_call) { + // Extract the translatable strings coming from function calls. For example: + // tr("___"), get_node("Label").set_text("____"), get_node("LineEdit").set_placeholder("____"). + + StringName function_name = p_call->function_name; + + // Variables for extracting tr() and tr_n(). + Vector<String> id_ctx_plural; + id_ctx_plural.resize(3); + bool extract_id_ctx_plural = true; + + if (function_name == tr_func) { + // Extract from tr(id, ctx). + for (int i = 0; i < p_call->arguments.size(); i++) { + if (p_call->arguments[i]->type == GDScriptParser::Node::LITERAL) { + id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[i])->value; + } else { + // Avoid adding something like tr("Flying dragon", var_context_level_1). We want to extract both id and context together. + extract_id_ctx_plural = false; + } + } + if (extract_id_ctx_plural) { + ids_ctx_plural->push_back(id_ctx_plural); + } + } else if (function_name == trn_func) { + // Extract from tr_n(id, plural, n, ctx). + Vector<int> indices; + indices.push_back(0); + indices.push_back(3); + indices.push_back(1); + for (int i = 0; i < indices.size(); i++) { + if (indices[i] >= p_call->arguments.size()) { + continue; + } + + if (p_call->arguments[indices[i]]->type == GDScriptParser::Node::LITERAL) { + id_ctx_plural.write[i] = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[indices[i]])->value; + } else { + extract_id_ctx_plural = false; + } + } + if (extract_id_ctx_plural) { + ids_ctx_plural->push_back(id_ctx_plural); + } + } else if (first_arg_patterns.has(function_name)) { + // Extracting argument with only string literals. In other words, not extracting something like set_text("hello " + some_var). + if (p_call->arguments[0]->type == GDScriptParser::Node::LITERAL) { + ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[0])->value); + } + } else if (second_arg_patterns.has(function_name)) { + if (p_call->arguments[1]->type == GDScriptParser::Node::LITERAL) { + ids->push_back(static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[1])->value); + } + } else if (function_name == fd_add_filter) { + // Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images"). + _extract_fd_literals(p_call->arguments[0]); + + } else if (function_name == fd_set_filter && p_call->arguments[0]->type == GDScriptParser::Node::CALL) { + // FileDialog.set_filters() accepts assignment in the form of PackedStringArray. For example, + // get_node("FileDialog").set_filters( PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"])). + + GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(p_call->arguments[0]); + if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) { + GDScriptParser::ArrayNode *array_node = static_cast<GDScriptParser::ArrayNode *>(call_node->arguments[0]); + for (int i = 0; i < array_node->elements.size(); i++) { + _extract_fd_literals(array_node->elements[i]); } } } } +void GDScriptEditorTranslationParserPlugin::_extract_fd_literals(GDScriptParser::ExpressionNode *p_expression) { + // Extract the name in "extension ; name". + + if (p_expression->type == GDScriptParser::Node::LITERAL) { + String arg_val = String(static_cast<GDScriptParser::LiteralNode *>(p_expression)->value); + PackedStringArray arr = arg_val.split(";", true); + if (arr.size() != 2) { + ERR_PRINT("Argument for setting FileDialog has bad format."); + return; + } + ids->push_back(arr[1].strip_edges()); + } +} + GDScriptEditorTranslationParserPlugin::GDScriptEditorTranslationParserPlugin() { - // Regex search pattern templates. - // The extra complication in the regex pattern is to ensure that the matching works when users write over multiple lines, use tabs etc. - const String dot = "\\.[\\s\\\\]*"; - const String str_assign_template = "[\\s\\\\]*=[\\s\\\\]*\"" + text + "\""; - const String first_arg_template = "[\\s\\\\]*\\([\\s\\\\]*\"" + text + "\"[\\s\\S]*?\\)"; - const String second_arg_template = "[\\s\\\\]*\\([\\s\\S]+?,[\\s\\\\]*\"" + text + "\"[\\s\\S]*?\\)"; - - // Common patterns. - patterns.push_back("tr" + first_arg_template); - patterns.push_back(dot + "text" + str_assign_template); - patterns.push_back(dot + "placeholder_text" + str_assign_template); - patterns.push_back(dot + "hint_tooltip" + str_assign_template); - patterns.push_back(dot + "set_text" + first_arg_template); - patterns.push_back(dot + "set_tooltip" + first_arg_template); - patterns.push_back(dot + "set_placeholder" + first_arg_template); - - // Tabs and TabContainer API. - patterns.push_back(dot + "set_tab_title" + second_arg_template); - patterns.push_back(dot + "add_tab" + first_arg_template); - - // PopupMenu API. - patterns.push_back(dot + "add_check_item" + first_arg_template); - patterns.push_back(dot + "add_icon_check_item" + second_arg_template); - patterns.push_back(dot + "add_icon_item" + second_arg_template); - patterns.push_back(dot + "add_icon_radio_check_item" + second_arg_template); - patterns.push_back(dot + "add_item" + first_arg_template); - patterns.push_back(dot + "add_multistate_item" + first_arg_template); - patterns.push_back(dot + "add_radio_check_item" + first_arg_template); - patterns.push_back(dot + "add_separator" + first_arg_template); - patterns.push_back(dot + "add_submenu_item" + first_arg_template); - patterns.push_back(dot + "set_item_text" + second_arg_template); - //patterns.push_back(dot + "set_item_tooltip" + second_arg_template); //no tr() behind this function. might be bug. - - // FileDialog API - special case. - const String fd_text = "((?:[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*(?:\"[\\s\\\\]*\\+[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*)*\"[\\s\\\\]*,?)*)"; - const String packed_string_array = "[\\s\\\\]*PackedStringArray[\\s\\\\]*\\([\\s\\\\]*\\[" + fd_text + "\\][\\s\\\\]*\\)"; - file_dialog_patterns.push_back(dot + "add_filter[\\s\\\\]*\\(" + fd_text + "[\\s\\\\]*\\)"); - file_dialog_patterns.push_back(dot + "filters[\\s\\\\]*=" + packed_string_array); - file_dialog_patterns.push_back(dot + "set_filters[\\s\\\\]*\\(" + packed_string_array + "[\\s\\\\]*\\)"); + assignment_patterns.insert("text"); + assignment_patterns.insert("placeholder_text"); + assignment_patterns.insert("hint_tooltip"); + + first_arg_patterns.insert("set_text"); + first_arg_patterns.insert("set_tooltip"); + first_arg_patterns.insert("set_placeholder"); + first_arg_patterns.insert("add_tab"); + first_arg_patterns.insert("add_check_item"); + first_arg_patterns.insert("add_item"); + first_arg_patterns.insert("add_multistate_item"); + first_arg_patterns.insert("add_radio_check_item"); + first_arg_patterns.insert("add_separator"); + first_arg_patterns.insert("add_submenu_item"); + + second_arg_patterns.insert("set_tab_title"); + second_arg_patterns.insert("add_icon_check_item"); + second_arg_patterns.insert("add_icon_item"); + second_arg_patterns.insert("add_icon_radio_check_item"); + second_arg_patterns.insert("set_item_text"); } diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h index 9fa4b69f01..5ea416d4cc 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h @@ -31,23 +31,40 @@ #ifndef GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H #define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H +#include "core/set.h" #include "editor/editor_translation_parser.h" +#include "modules/gdscript/gdscript_parser.h" #include "modules/regex/regex.h" class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin { GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin); - // Regex and search patterns that are used to match translation strings. - const String text = "((?:[^\"\\\\]|\\\\[\\s\\S])*(?:\"[\\s\\\\]*\\+[\\s\\\\]*\"(?:[^\"\\\\]|\\\\[\\s\\S])*)*)"; - RegEx regex; - Vector<String> patterns; - Vector<String> file_dialog_patterns; + Vector<String> *ids; + Vector<Vector<String>> *ids_ctx_plural; - void _parse_file_dialog(const String &p_source_code, Vector<String> *r_output); - void _get_captured_strings(const Array &p_results, Vector<String> *r_output); + // List of patterns used for extracting translation strings. + StringName tr_func = "tr"; + StringName trn_func = "tr_n"; + Set<StringName> assignment_patterns; + Set<StringName> first_arg_patterns; + Set<StringName> second_arg_patterns; + // FileDialog patterns. + StringName fd_add_filter = "add_filter"; + StringName fd_set_filter = "set_filters"; + StringName fd_filters = "filters"; + + void _traverse_class(const GDScriptParser::ClassNode *p_class); + void _traverse_function(const GDScriptParser::FunctionNode *p_func); + void _traverse_block(const GDScriptParser::SuiteNode *p_suite); + + void _read_variable(const GDScriptParser::VariableNode *p_var); + void _assess_expression(GDScriptParser::ExpressionNode *p_expression); + void _assess_assignment(GDScriptParser::AssignmentNode *p_assignment); + void _extract_from_call(GDScriptParser::CallNode *p_call); + void _extract_fd_literals(GDScriptParser::ExpressionNode *p_expression); public: - virtual Error parse_file(const String &p_path, Vector<String> *r_extracted_strings) override; + virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override; virtual void get_recognized_extensions(List<String> *r_extensions) const override; GDScriptEditorTranslationParserPlugin(); diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 9170255c02..e70e3f7272 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -596,6 +596,19 @@ Error GDScript::reload(bool p_keep_state) { return OK; } + { + String source_path = path; + if (source_path.empty()) { + source_path = get_path(); + } + if (!source_path.empty()) { + MutexLock lock(GDScriptCache::singleton->lock); + if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) { + GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this; + } + } + } + valid = false; GDScriptParser parser; Error err = parser.parse(source, path, false); @@ -1031,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(); } } @@ -1040,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(); @@ -1436,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) { @@ -2022,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.h b/modules/gdscript/gdscript.h index 9906b4014d..79317ff846 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -69,6 +69,7 @@ class GDScript : public Script { friend class GDScriptInstance; friend class GDScriptFunction; + friend class GDScriptAnalyzer; friend class GDScriptCompiler; friend class GDScriptFunctions; friend class GDScriptLanguage; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 561cdbbda4..943a49060f 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -33,6 +33,7 @@ #include "core/class_db.h" #include "core/hash_map.h" #include "core/io/resource_loader.h" +#include "core/os/file_access.h" #include "core/project_settings.h" #include "core/script_language.h" #include "gdscript.h" @@ -181,7 +182,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class); return err; @@ -207,11 +208,12 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } + base = parser->get_parser()->head->get_datatype(); } } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) { const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name); @@ -226,7 +228,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; @@ -301,6 +303,16 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } + // Check for cyclic inheritance. + const GDScriptParser::ClassNode *base_class = result.class_type; + while (base_class) { + if (base_class->fqcn == p_class->fqcn) { + push_error("Cyclic inheritance.", p_class); + return ERR_PARSE_ERROR; + } + base_class = base_class->base_type.class_type; + } + p_class->base_type = result; class_type.native_type = result.native_type; p_class->set_datatype(class_type); @@ -308,7 +320,10 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, if (p_recursive) { for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { - resolve_inheritance(p_class->members[i].m_class, true); + Error err = resolve_inheritance(p_class->members[i].m_class, true); + if (err) { + return err; + } } } } @@ -490,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) { @@ -498,7 +516,13 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas if (member.variable->initializer != nullptr) { if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) { - push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) { + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); + } else { + // TODO: Add warning. + mark_node_unsafe(member.variable->initializer); + } } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { #ifdef DEBUG_ENABLED parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION); @@ -519,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; @@ -533,6 +558,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas break; case GDScriptParser::DataType::NATIVE: if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) { + member.variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; member.variable->export_info.hint_string = get_real_class_name(datatype.native_type); } else { push_error(R"(Export type can only be built-in or a resource.)", member.variable); @@ -596,17 +622,67 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas enum_type.is_meta_type = true; enum_type.is_constant = true; + // Enums can't be nested, so we can safely override this. + current_enum = member.m_enum; + for (int j = 0; j < member.m_enum->values.size(); j++) { - enum_type.enum_values[member.m_enum->values[j].identifier->name] = member.m_enum->values[j].value; + GDScriptParser::EnumNode::Value &element = member.m_enum->values.write[j]; + + if (element.custom_value) { + reduce_expression(element.custom_value); + if (!element.custom_value->is_constant) { + push_error(R"(Enum values must be constant.)", element.custom_value); + } else if (element.custom_value->reduced_value.get_type() != Variant::INT) { + push_error(R"(Enum values must be integers.)", element.custom_value); + } else { + element.value = element.custom_value->reduced_value; + element.resolved = true; + } + } else { + if (element.index > 0) { + element.value = element.parent_enum->values[element.index - 1].value + 1; + } else { + element.value = 0; + } + element.resolved = true; + } + + enum_type.enum_values[element.identifier->name] = element.value; } + current_enum = nullptr; + member.m_enum->set_datatype(enum_type); } break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); break; - case GDScriptParser::ClassNode::Member::ENUM_VALUE: - break; // Nothing to do, type and value set in parser. + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + if (member.enum_value.custom_value) { + current_enum = member.enum_value.parent_enum; + reduce_expression(member.enum_value.custom_value); + current_enum = nullptr; + + if (!member.enum_value.custom_value->is_constant) { + push_error(R"(Enum values must be constant.)", member.enum_value.custom_value); + } else if (member.enum_value.custom_value->reduced_value.get_type() != Variant::INT) { + push_error(R"(Enum values must be integers.)", member.enum_value.custom_value); + } else { + member.enum_value.value = member.enum_value.custom_value->reduced_value; + member.enum_value.resolved = true; + } + } else { + if (member.enum_value.index > 0) { + member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1; + } else { + member.enum_value.value = 0; + } + member.enum_value.resolved = true; + } + // Also update the original references. + member.enum_value.parent_enum->values.write[member.enum_value.index] = member.enum_value; + p_class->members.write[i].enum_value = member.enum_value; + } break; case GDScriptParser::ClassNode::Member::CLASS: break; // Done later. case GDScriptParser::ClassNode::Member::UNDEFINED: @@ -718,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); @@ -772,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); @@ -842,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: @@ -994,7 +1071,13 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable if (p_variable->initializer != nullptr) { if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) { - push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) { + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); + } else { + // TODO: Add warning. + mark_node_unsafe(p_variable->initializer); + } #ifdef DEBUG_ENABLED } else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION); @@ -1181,14 +1264,19 @@ 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; } if (p_parameter->datatype_specifier != nullptr) { @@ -1198,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); } @@ -1378,16 +1466,23 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig bool compatible = true; GDScriptParser::DataType op_type = p_assignment->assigned_value->get_datatype(); if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { - op_type = get_operation_type(p_assignment->variant_op, p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), compatible); + op_type = get_operation_type(p_assignment->variant_op, p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), compatible, p_assignment->assigned_value); } if (compatible) { compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true); if (!compatible) { if (p_assignment->assignee->get_datatype().is_hard_type()) { - push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value); + // Try reverse test since it can be a masked subtype. + if (!is_type_compatible(op_type, p_assignment->assignee->get_datatype(), true)) { + push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value); + } else { + // TODO: Add warning. + mark_node_unsafe(p_assignment); + } } else { // TODO: Warning in this case. + mark_node_unsafe(p_assignment); } } } else { @@ -1534,7 +1629,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o ERR_PRINT("Parser bug: unknown binary operation."); } } - p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value)); + p_binary_op->set_datatype(type_from_variant(p_binary_op->reduced_value, p_binary_op)); return; } @@ -1548,7 +1643,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o } else { if (p_binary_op->variant_op < Variant::OP_MAX) { bool valid = false; - result = get_operation_type(p_binary_op->variant_op, p_binary_op->left_operand->get_datatype(), right_type, valid); + result = get_operation_type(p_binary_op->variant_op, p_binary_op->left_operand->get_datatype(), right_type, valid, p_binary_op); if (!valid) { push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", p_binary_op->left_operand->get_datatype().to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op); @@ -1634,6 +1729,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa } signature += p_call->arguments[i]->get_datatype().to_string(); } + signature += ")"; push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee); } break; case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: @@ -1684,7 +1780,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa for (int i = 0; i < p_call->arguments.size(); i++) { GDScriptParser::DataType par_type = type_from_property(info.arguments[i]); - if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype())) { + if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) { types_match = false; break; #ifdef DEBUG_ENABLED @@ -1711,6 +1807,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa } signature += p_call->arguments[i]->get_datatype().to_string(); } + signature += ")"; push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call); } } @@ -1889,6 +1986,8 @@ 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++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; if (p_dictionary->style == GDScriptParser::DictionaryNode::PYTHON_DICT) { @@ -1896,6 +1995,14 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti } 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)) { + push_error(vformat(R"(Key "%s" was already used in this dictionary (at line %d).)", element.key->reduced_value, elements[element.key->reduced_value]->start_line), element.key); + } else { + elements[element.key->reduced_value] = element.value; + } + } } if (all_is_constant) { @@ -1965,23 +2072,37 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod if (valid) { p_identifier->is_constant = true; p_identifier->reduced_value = result; - p_identifier->set_datatype(type_from_variant(result)); + p_identifier->set_datatype(type_from_variant(result, p_identifier)); } else { 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; } @@ -2096,7 +2217,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod if (valid) { p_identifier->is_constant = true; p_identifier->reduced_value = int_constant; - p_identifier->set_datatype(type_from_variant(int_constant)); + p_identifier->set_datatype(type_from_variant(int_constant, p_identifier)); return; } } @@ -2104,6 +2225,33 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) { // TODO: This is opportunity to further infer types. + + // Check if we are inside and enum. This allows enum values to access other elements of the same enum. + if (current_enum) { + for (int i = 0; i < current_enum->values.size(); i++) { + const GDScriptParser::EnumNode::Value &element = current_enum->values[i]; + if (element.identifier->name == p_identifier->name) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN; + type.builtin_type = Variant::INT; + type.is_constant = true; + if (element.parent_enum->identifier) { + type.enum_type = element.parent_enum->identifier->name; + } + p_identifier->set_datatype(type); + + if (element.resolved) { + p_identifier->is_constant = true; + p_identifier->reduced_value = element.value; + } else { + push_error(R"(Cannot use another enum element before it was declared.)", p_identifier); + } + return; // Found anyway. + } + } + } + // Check if identifier is local. // If that's the case, the declaration already was solved before. switch (p_identifier->source) { @@ -2166,10 +2314,34 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident return; } + // Try singletons. + // Do this before globals because this might be a singleton loading another one before it's compiled. + if (ProjectSettings::get_singleton()->has_autoload(name)) { + const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(name); + if (autoload.is_singleton) { + // Singleton exists, so it's at least a Node. + GDScriptParser::DataType result; + result.kind = GDScriptParser::DataType::NATIVE; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + if (autoload.path.to_lower().ends_with(GDScriptLanguage::get_singleton()->get_extension())) { + Ref<GDScriptParserRef> parser = get_parser_for(autoload.path); + if (parser.is_valid()) { + Error err = parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + if (err == OK) { + result = type_from_metatype(parser->get_parser()->head->get_datatype()); + } + } + } + result.is_constant = true; + p_identifier->set_datatype(result); + return; + } + } + if (GDScriptLanguage::get_singleton()->get_global_map().has(name)) { int idx = GDScriptLanguage::get_singleton()->get_global_map()[name]; Variant constant = GDScriptLanguage::get_singleton()->get_global_array()[idx]; - p_identifier->set_datatype(type_from_variant(constant)); + p_identifier->set_datatype(type_from_variant(constant, p_identifier)); p_identifier->is_constant = true; p_identifier->reduced_value = constant; return; @@ -2177,7 +2349,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(name)) { Variant constant = GDScriptLanguage::get_singleton()->get_named_globals_map()[name]; - p_identifier->set_datatype(type_from_variant(constant)); + p_identifier->set_datatype(type_from_variant(constant, p_identifier)); p_identifier->is_constant = true; p_identifier->reduced_value = constant; return; @@ -2199,13 +2371,44 @@ void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) { p_literal->reduced_value = p_literal->value; p_literal->is_constant = true; - p_literal->set_datatype(type_from_variant(p_literal->reduced_value)); + p_literal->set_datatype(type_from_variant(p_literal->reduced_value, p_literal)); } void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { + if (!p_preload->path) { + return; + } + + reduce_expression(p_preload->path); + + if (!p_preload->path->is_constant) { + push_error("Preloaded path must be a constant string.", p_preload->path); + return; + } + + if (p_preload->path->reduced_value.get_type() != Variant::STRING) { + push_error("Preloaded path must be a constant string.", p_preload->path); + } else { + p_preload->resolved_path = p_preload->path->reduced_value; + // TODO: Save this as script dependency. + if (p_preload->resolved_path.is_rel_path()) { + p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path); + } + p_preload->resolved_path = p_preload->resolved_path.simplify_path(); + if (!FileAccess::exists(p_preload->resolved_path)) { + push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path); + } else { + // TODO: Don't load if validating: use completion cache. + p_preload->resource = ResourceLoader::load(p_preload->resolved_path); + if (p_preload->resource.is_null()) { + push_error(vformat(R"(Could not p_preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + } + } + } + p_preload->is_constant = true; p_preload->reduced_value = p_preload->resource; - p_preload->set_datatype(type_from_variant(p_preload->reduced_value)); + p_preload->set_datatype(type_from_variant(p_preload->reduced_value, p_preload)); } void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { @@ -2254,7 +2457,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } else { p_subscript->is_constant = true; p_subscript->reduced_value = value; - result_type = type_from_variant(value); + result_type = type_from_variant(value, p_subscript); } result_type.kind = GDScriptParser::DataType::VARIANT; } else { @@ -2294,7 +2497,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } else { p_subscript->is_constant = true; p_subscript->reduced_value = value; - result_type = type_from_variant(value); + result_type = type_from_variant(value, p_subscript); } result_type.kind = GDScriptParser::DataType::VARIANT; } else { @@ -2378,7 +2581,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri // Check resulting type if possible. result_type.builtin_type = Variant::NIL; result_type.kind = GDScriptParser::DataType::BUILTIN; - result_type.type_source = GDScriptParser::DataType::INFERRED; + result_type.type_source = base_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED; switch (base_type.builtin_type) { // Can't index at all. @@ -2509,16 +2712,22 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) GDScriptParser::DataType result; + if (p_unary_op->operand == nullptr) { + result.kind = GDScriptParser::DataType::VARIANT; + p_unary_op->set_datatype(result); + return; + } + if (p_unary_op->operand->is_constant) { p_unary_op->is_constant = true; p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant()); - result = type_from_variant(p_unary_op->reduced_value); + result = type_from_variant(p_unary_op->reduced_value, p_unary_op); } else if (p_unary_op->operand->get_datatype().is_variant()) { result.kind = GDScriptParser::DataType::VARIANT; mark_node_unsafe(p_unary_op); } else { bool valid = false; - result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), p_unary_op->operand->get_datatype(), valid); + result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), p_unary_op->operand->get_datatype(), valid, p_unary_op); if (!valid) { push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", p_unary_op->operand->get_datatype().to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op->operand); @@ -2528,7 +2737,7 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) p_unary_op->set_datatype(result); } -GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value) { +GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source) { GDScriptParser::DataType result; result.is_constant = true; result.kind = GDScriptParser::DataType::BUILTIN; @@ -2550,19 +2759,44 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va scr = obj->get_script(); } if (scr.is_valid()) { - result.script_type = scr; - result.script_path = scr->get_path(); - Ref<GDScript> gds = scr; - if (gds.is_valid()) { - result.kind = GDScriptParser::DataType::CLASS; - Ref<GDScriptParserRef> ref = get_parser_for(gds->get_path()); - ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); - result.class_type = ref->get_parser()->head; - result.script_path = ref->get_parser()->script_path; + if (scr->is_valid()) { + result.script_type = scr; + result.script_path = scr->get_path(); + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + result.kind = GDScriptParser::DataType::CLASS; + // This might be an inner class, so we want to get the parser for the root. + // But still get the inner class from that tree. + GDScript *current = gds.ptr(); + List<StringName> class_chain; + while (current->_owner) { + // Push to front so it's in reverse. + class_chain.push_front(current->name); + current = current->_owner; + } + + Ref<GDScriptParserRef> ref = get_parser_for(current->path); + ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + + GDScriptParser::ClassNode *found = ref->get_parser()->head; + + // It should be okay to assume this exists, since we have a complete script already. + for (const List<StringName>::Element *E = class_chain.front(); E; E = E->next()) { + found = found->get_member(E->get()).m_class; + } + + result.class_type = found; + result.script_path = ref->get_parser()->script_path; + } else { + result.kind = GDScriptParser::DataType::SCRIPT; + } + result.native_type = scr->get_instance_base_type(); } else { - result.kind = GDScriptParser::DataType::SCRIPT; + push_error(vformat(R"(Constant value uses script from "%s" which is loaded but not compiled.)", scr->get_path()), p_source); + result.kind = GDScriptParser::DataType::VARIANT; + result.type_source = GDScriptParser::DataType::UNDETECTED; + result.is_meta_type = false; } - result.native_type = scr->get_instance_base_type(); } else { result.kind = GDScriptParser::DataType::NATIVE; if (result.native_type == GDScriptNativeClass::get_class_static()) { @@ -2753,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)) { @@ -2818,7 +3052,7 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con } #endif -GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid) { +GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source) { // This function creates dummy variant values and apply the operation to those. Less error-prone than keeping a table of valid operations. GDScriptParser::DataType result; @@ -2883,7 +3117,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } // Avoid error in formatting operator (%) where it doesn't find a placeholder. - if (a_type == Variant::STRING) { + if (a_type == Variant::STRING && b_type != Variant::ARRAY) { a = String("%s"); } @@ -2891,7 +3125,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator Variant::evaluate(p_operation, a, b, ret, r_valid); if (r_valid) { - return type_from_variant(ret); + return type_from_variant(ret, p_source); } return result; @@ -3089,6 +3323,9 @@ Error GDScriptAnalyzer::resolve_program() { List<String> parser_keys; depended_parsers.get_key_list(&parser_keys); for (const List<String>::Element *E = parser_keys.front(); E != nullptr; E = E->next()) { + if (depended_parsers[E->get()].is_null()) { + return ERR_PARSE_ERROR; + } depended_parsers[E->get()]->raise_status(GDScriptParserRef::FULLY_SOLVED); } depended_parsers.clear(); diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 06d3530cb6..c3911cce76 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -41,6 +41,8 @@ class GDScriptAnalyzer { GDScriptParser *parser = nullptr; HashMap<String, Ref<GDScriptParserRef>> depended_parsers; + const GDScriptParser::EnumNode *current_enum = nullptr; + Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); @@ -65,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. @@ -88,7 +90,7 @@ class GDScriptAnalyzer { void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op); // Helpers. - GDScriptParser::DataType type_from_variant(const Variant &p_value); + 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; GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const; GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name); @@ -96,7 +98,7 @@ class GDScriptAnalyzer { bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); - GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid); + GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source); bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const; void push_error(const String &p_message, const GDScriptParser::Node *p_origin); void mark_node_unsafe(const GDScriptParser::Node *p_node); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp new file mode 100644 index 0000000000..eabf53581d --- /dev/null +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -0,0 +1,737 @@ +/*************************************************************************/ +/* 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); + alloc_call(p_arguments.size()); +} + +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 583283ff46..57b95f5b21 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -85,6 +85,17 @@ Error GDScriptParserRef::raise_status(Status p_new_status) { return result; } } + if (result != OK) { + if (parser != nullptr) { + memdelete(parser); + parser = nullptr; + } + if (analyzer != nullptr) { + memdelete(analyzer); + analyzer = nullptr; + } + return result; + } } return result; @@ -97,33 +108,36 @@ 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); } if (singleton->parser_map.has(p_path)) { - ref = singleton->parser_map[p_path]; + ref = Ref<GDScriptParserRef>(singleton->parser_map[p_path]); } else { + if (!FileAccess::exists(p_path)) { + r_error = ERR_FILE_NOT_FOUND; + return ref; + } GDScriptParser *parser = memnew(GDScriptParser); ref.instance(); ref->parser = parser; ref->path = p_path; - singleton->parser_map[p_path] = ref; - ref->unreference(); + singleton->parser_map[p_path] = ref.ptr(); } r_error = ref->raise_status(p_status); @@ -154,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); } @@ -171,15 +185,12 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri script->set_script_path(p_path); script->load_source_code(p_path); - singleton->shallow_gdscript_cache[p_path] = script; - // The one in cache is not a hard reference: if the script dies somewhere else it's fine. - // Scripts remove themselves from cache when they die. - script->unreference(); + singleton->shallow_gdscript_cache[p_path] = script.ptr(); return script; } 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); @@ -202,7 +213,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro return script; } - singleton->full_gdscript_cache[p_path] = script; + singleton->full_gdscript_cache[p_path] = script.ptr(); singleton->shallow_gdscript_cache.erase(p_path); return script; @@ -211,7 +222,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro Error GDScriptCache::finish_compiling(const String &p_owner) { // Mark this as compiled. Ref<GDScript> script = get_shallow_script(p_owner); - singleton->full_gdscript_cache[p_owner] = script; + singleton->full_gdscript_cache[p_owner] = script.ptr(); singleton->shallow_gdscript_cache.erase(p_owner); Set<String> depends = singleton->dependencies[p_owner]; diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 770704d6eb..865df34051 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -70,9 +70,9 @@ public: class GDScriptCache { // String key is full path. - HashMap<String, Ref<GDScriptParserRef>> parser_map; - HashMap<String, Ref<GDScript>> shallow_gdscript_cache; - HashMap<String, Ref<GDScript>> full_gdscript_cache; + HashMap<String, GDScriptParserRef *> parser_map; + HashMap<String, GDScript *> shallow_gdscript_cache; + HashMap<String, GDScript *> full_gdscript_cache; HashMap<String, Set<String>> dependencies; friend class GDScript; 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 e34d87f5cc..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); - } - } - - // 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); + // 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); } - 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; - } - if ((ret >> GDScriptFunction::ADDR_BITS & GDScriptFunction::ADDR_TYPE_STACK) == GDScriptFunction::ADDR_TYPE_STACK) { - slevel++; - codegen.alloc_stack(slevel); + GDScriptCodeGenerator::Address val = _parse_expression(codegen, r_error, an->elements[i]); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - - 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 + GDScriptDataType type = _gdtype_from_datatype(call->get_datatype()); + GDScriptCodeGenerator::Address result = codegen.add_temporary(type); - Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); - - 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))); + + 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); - 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; + 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); - int res = _parse_expression(codegen, binary->left_operand, p_stack_level); - if (res < 0) { - return res; + gen->write_end_or(result); + + 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; - - int src_address_a = _parse_expression(codegen, binary->left_operand, slevel); - if (src_address_a < 0) { - return -1; - } + GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, binary->left_operand); - 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; - } - } 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; + if (right_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - } break; - case GDScriptParser::BinaryOpNode::OP_MODULO: { - if (!_create_binary_operator(codegen, binary, Variant::OP_MODULE, p_stack_level)) { - return -1; + if (left_operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - } 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; - } - } 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; + } + ERR_FAIL_V_MSG(p_previous_test, "Reaching the end of pattern compilation without matching a pattern."); +} + +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())); } - return OK; } -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; +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); - // Parse the branch block. - _parse_block(codegen, branch->block, slevel, p_break_addr, p_continue_addr); + // Check if pattern did match. + gen->write_if(pattern_result); - // 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. + // Remove the result from stack. + gen->pop_temporary(); - // 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); + 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(); } } } } - 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); - - 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; - } - } - } - } - - /* 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); + codegen.generator->write_start(p_script, func_name, false, p_variable->rpc_mode, return_type); - 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 - - // 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; + codegen.generator->set_initial_line(p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line); -#ifdef DEBUG_ENABLED - - { - gdfunc->func_cname = (String(source) + " - " + String(func_name)).utf8(); - gdfunc->_func_cname = gdfunc->func_cname.get_data(); - } - -#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,21 +1933,32 @@ 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(); if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) { - if (!parsed_classes.has(p_script->_base)) { - if (parsing_classes.has(p_script->_base)) { - String class_name = p_class->identifier ? p_class->identifier->name : "<main>"; - _set_error("Cyclic class reference for '" + class_name + "'.", p_class); - return ERR_PARSE_ERROR; + if (p_class->base_type.script_path == main_script->path) { + if (!parsed_classes.has(p_script->_base)) { + if (parsing_classes.has(p_script->_base)) { + String class_name = p_class->identifier ? p_class->identifier->name : "<main>"; + _set_error("Cyclic class reference for '" + class_name + "'.", p_class); + return ERR_PARSE_ERROR; + } + Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); + if (err) { + return err; + } } - Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); + } else { + Error err = OK; + base = GDScriptCache::get_full_script(p_class->base_type.script_path, err, main_script->path); if (err) { return err; } + if (base.is_null() && !base->is_valid()) { + return ERR_COMPILATION_FAILED; + } } } @@ -2660,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; @@ -2696,11 +2036,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar const GDScriptParser::ConstantNode *constant = member.constant; StringName name = constant->identifier->name; - ERR_CONTINUE(constant->initializer->type != GDScriptParser::Node::LITERAL); - - const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(constant->initializer); - - p_script->constants.insert(name, literal->value); + p_script->constants.insert(name, constant->initializer->reduced_value); #ifdef TOOLS_ENABLED p_script->member_lines[name] = constant->start_line; 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_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 88bf8cde0f..2e372575da 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -483,6 +483,8 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na #ifdef TOOLS_ENABLED +#define COMPLETION_RECURSION_LIMIT 200 + struct GDScriptCompletionIdentifier { GDScriptParser::DataType type; String enumeration; @@ -766,9 +768,11 @@ static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, } } -static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result); +static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth); + +static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) { + ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT); -static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, bool p_only_functions, bool p_static, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) { if (!p_parent_only) { bool outer = false; const GDScriptParser::ClassNode *clss = p_class; @@ -820,7 +824,6 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, break; case GDScriptParser::ClassNode::Member::SIGNAL: if (p_only_functions || outer) { - clss = clss->outer; continue; } option = ScriptCodeCompletionOption(member.signal->identifier->name, ScriptCodeCompletionOption::KIND_SIGNAL); @@ -840,10 +843,12 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, base_type.type = p_class->base_type; base_type.type.is_meta_type = p_static; - _find_identifiers_in_base(base_type, p_only_functions, r_result); + _find_identifiers_in_base(base_type, p_only_functions, r_result, p_recursion_depth + 1); } -static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { +static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) { + ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT); + GDScriptParser::DataType base_type = p_base.type; bool _static = base_type.is_meta_type; @@ -856,7 +861,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base while (!base_type.has_no_type()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { - _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result); + _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result, p_recursion_depth + 1); // This already finds all parent identifiers, so we are done. base_type = GDScriptParser::DataType(); } break; @@ -1007,14 +1012,14 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } } -static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { +static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result, int p_recursion_depth) { if (!p_only_functions && p_context.current_suite) { // This includes function parameters, since they are also locals. _find_identifiers_in_suite(p_context.current_suite, r_result); } if (p_context.current_class) { - _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result); + _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1); } for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { @@ -2453,7 +2458,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path break; } if (!_guess_expression_type(completion_context, static_cast<const GDScriptParser::AssignmentNode *>(completion_context.node)->assignee, type)) { - _find_identifiers(completion_context, false, options); + _find_identifiers(completion_context, false, options, 0); r_forced = true; break; } @@ -2462,7 +2467,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path _find_enumeration_candidates(completion_context, type.enumeration, options); r_forced = options.size() > 0; } else { - _find_identifiers(completion_context, false, options); + _find_identifiers(completion_context, false, options, 0); r_forced = true; } } break; @@ -2470,7 +2475,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path is_function = true; [[fallthrough]]; case GDScriptParser::COMPLETION_IDENTIFIER: { - _find_identifiers(completion_context, is_function, options); + _find_identifiers(completion_context, is_function, options, 0); } break; case GDScriptParser::COMPLETION_ATTRIBUTE_METHOD: is_function = true; @@ -2484,7 +2489,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path break; } - _find_identifiers_in_base(base, is_function, options); + _find_identifiers_in_base(base, is_function, options, 0); } } break; case GDScriptParser::COMPLETION_SUBSCRIPT: { @@ -2504,7 +2509,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path c.current_class = nullptr; } - _find_identifiers_in_base(base, false, options); + _find_identifiers_in_base(base, false, options, 0); } break; case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: { if (!completion_context.current_class) { @@ -2530,7 +2535,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path // TODO: Improve this to only list types. if (found) { - _find_identifiers_in_base(base, false, options); + _find_identifiers_in_base(base, false, options, 0); } r_forced = true; } break; @@ -2651,7 +2656,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path if (!completion_context.current_class) { break; } - _find_identifiers_in_class(completion_context.current_class, true, false, true, options); + _find_identifiers_in_class(completion_context.current_class, true, false, true, options, 0); } break; } 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 0967f74285..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') { @@ -466,44 +466,40 @@ void GDScriptParser::pop_multiline() { } bool GDScriptParser::is_statement_end() { - return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON); + return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON) || check(GDScriptTokenizer::Token::TK_EOF); } void GDScriptParser::end_statement(const String &p_context) { bool found = false; - while (is_statement_end()) { + while (is_statement_end() && !is_at_end()) { // Remove sequential newlines/semicolons. found = true; advance(); } - if (!found) { + if (!found && !is_at_end()) { push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)", p_context, current.get_name())); } } void GDScriptParser::parse_program() { - if (current.type == GDScriptTokenizer::Token::TK_EOF) { - // Empty file. - push_error("Source file is empty."); - return; - } - head = alloc_node<ClassNode>(); current_class = head; if (match(GDScriptTokenizer::Token::ANNOTATION)) { // Check for @tool annotation. AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); - if (annotation->name == "@tool") { - // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). - _is_tool = true; - if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after "@tool" annotation.)"); - } - // @tool annotation has no specific target. - annotation->apply(this, nullptr); - } else { - annotation_stack.push_back(annotation); + if (annotation != nullptr) { + if (annotation->name == "@tool") { + // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). + _is_tool = true; + if (previous.type != GDScriptTokenizer::Token::NEWLINE) { + push_error(R"(Expected newline after "@tool" annotation.)"); + } + // @tool annotation has no specific target. + annotation->apply(this, nullptr); + } else { + annotation_stack.push_back(annotation); + } } } @@ -590,6 +586,14 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { return n_class; } + if (match(GDScriptTokenizer::Token::EXTENDS)) { + if (n_class->extends_used) { + push_error(R"(Cannot use "extends" more than once in the same class.)"); + } + parse_extends(); + end_statement("superclass"); + } + parse_class_body(); consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)"); @@ -635,7 +639,6 @@ void GDScriptParser::parse_extends() { current_class->extends_path = previous.literal; if (!match(GDScriptTokenizer::Token::PERIOD)) { - end_statement("superclass path"); return; } } @@ -990,9 +993,15 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { signal->identifier = parse_identifier(); if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { - while (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { + do { + if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { + // Allow for trailing comma. + break; + } + ParameterNode *parameter = parse_parameter(); if (parameter == nullptr) { + push_error("Expected signal parameter name."); break; } if (parameter->default_value != nullptr) { @@ -1004,7 +1013,8 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { signal->parameters_indices[parameter->identifier->name] = signal->parameters.size(); signal->parameters.push_back(parameter); } - } + } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*"); } @@ -1026,17 +1036,22 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { push_multiline(true); consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")")); - int current_value = 0; + HashMap<StringName, int> elements; do { 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; + item.line = previous.start_line; + item.leftmost_column = previous.leftmost_column; - if (!named) { + if (elements.has(item.identifier->name)) { + push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)", item.identifier->name, elements[item.identifier->name]), item.identifier); + } else if (!named) { // TODO: Abstract this recursive member check. ClassNode *parent = current_class; while (parent != nullptr) { @@ -1048,19 +1063,18 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { } } - if (match(GDScriptTokenizer::Token::EQUAL)) { - if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected integer value after "=".)")) { - item.custom_value = parse_literal(); + elements[item.identifier->name] = item.line; - if (item.custom_value->value.get_type() != Variant::INT) { - push_error(R"(Expected integer value after "=".)"); - item.custom_value = nullptr; - } else { - current_value = item.custom_value->value; - } + if (match(GDScriptTokenizer::Token::EQUAL)) { + ExpressionNode *value = parse_expression(false); + if (value == nullptr) { + push_error(R"(Expected expression value after "=".)"); } + item.custom_value = value; } - item.value = current_value++; + item.rightmost_column = previous.rightmost_column; + + item.index = enum_node->values.size(); enum_node->values.push_back(item); if (!named) { // Add as member of current class. @@ -1142,6 +1156,9 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() { if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) { make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function); function->return_type = parse_type(true); + if (function->return_type == nullptr) { + push_error(R"(Expected return type or "void" after "->".)"); + } } // TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens. @@ -1346,7 +1363,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { advance(); ReturnNode *n_return = alloc_node<ReturnNode>(); if (!is_statement_end()) { - if (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) { + if (current_function && current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) { push_error(R"(Constructor cannot return a value.)"); } n_return->return_value = parse_expression(false); @@ -1459,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() { @@ -1478,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) { @@ -1494,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; } @@ -1628,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) { @@ -1646,6 +1670,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { // Restore continue state. can_continue = could_continue; + is_continue_match = was_continue_match; return branch; } @@ -1803,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; } @@ -1932,8 +1960,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_ } GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_previous_operand, bool p_can_assign) { - if (!current_function || current_function->is_static) { - push_error(R"(Cannot use "self" outside a non-static function.)"); + if (current_function && current_function->is_static) { + push_error(R"(Cannot use "self" inside a static function.)"); } SelfNode *self = alloc_node<SelfNode>(); self->current_class = current_class; @@ -2488,27 +2516,30 @@ 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>(); make_completion_context(COMPLETION_GET_NODE, get_node); get_node->string = parse_literal(); return get_node; - } else if (check(GDScriptTokenizer::Token::IDENTIFIER)) { + } else if (current.is_node_name()) { GetNodeNode *get_node = alloc_node<GetNodeNode>(); int chain_position = 0; do { make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++); - if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expect node identifer after "/".)")) { + if (!current.is_node_name()) { + push_error(R"(Expect node path after "/".)"); return nullptr; } - IdentifierNode *identifier = parse_identifier(); + advance(); + IdentifierNode *identifier = alloc_node<IdentifierNode>(); + identifier->name = previous.get_identifier(); get_node->chain.push_back(identifier); } 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; } } @@ -2527,29 +2558,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_ if (preload->path == nullptr) { push_error(R"(Expected resource path after "(".)"); - } else if (preload->path->type != Node::LITERAL) { - push_error("Preloaded path must be a constant string."); - } else { - LiteralNode *path = static_cast<LiteralNode *>(preload->path); - if (path->value.get_type() != Variant::STRING) { - push_error("Preloaded path must be a constant string."); - } else { - preload->resolved_path = path->value; - // TODO: Save this as script dependency. - if (preload->resolved_path.is_rel_path()) { - preload->resolved_path = script_path.get_base_dir().plus_file(preload->resolved_path); - } - preload->resolved_path = preload->resolved_path.simplify_path(); - if (!FileAccess::exists(preload->resolved_path)) { - push_error(vformat(R"(Preload file "%s" does not exist.)", preload->resolved_path)); - } else { - // TODO: Don't load if validating: use completion cache. - preload->resource = ResourceLoader::load(preload->resolved_path); - if (preload->resource.is_null()) { - push_error(vformat(R"(Could not preload resource file "%s".)", preload->resolved_path)); - } - } - } } pop_completion_call(); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index edfe330c0c..4c9473c7bd 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -405,7 +405,10 @@ public: struct EnumNode : public Node { struct Value { IdentifierNode *identifier = nullptr; - LiteralNode *custom_value = nullptr; + ExpressionNode *custom_value = nullptr; + EnumNode *parent_enum = nullptr; + int index = -1; + bool resolved = false; int value = 0; int line = 0; int leftmost_column = 0; @@ -606,6 +609,7 @@ public: }; struct ContinueNode : public Node { + bool is_for_match = false; ContinueNode() { type = CONTINUE; } @@ -1076,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 7a4bdd88ba..9a40aa50ac 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -156,6 +156,64 @@ const char *GDScriptTokenizer::Token::get_name() const { return token_names[type]; } +bool GDScriptTokenizer::Token::is_identifier() const { + // Note: Most keywords should not be recognized as identifiers. + // These are only exceptions for stuff that already is on the engine's API. + switch (type) { + case IDENTIFIER: + case MATCH: // Used in String.match(). + return true; + default: + return false; + } +} + +bool GDScriptTokenizer::Token::is_node_name() const { + // This is meant to allow keywords with the $ notation, but not as general identifiers. + switch (type) { + case IDENTIFIER: + case AND: + case AS: + case ASSERT: + case AWAIT: + case BREAK: + case BREAKPOINT: + case CLASS_NAME: + case CLASS: + case CONST: + case CONTINUE: + case ELIF: + case ELSE: + case ENUM: + case EXTENDS: + case FOR: + case FUNC: + case IF: + case IN: + case IS: + case MATCH: + case NAMESPACE: + case NOT: + case OR: + case PASS: + case PRELOAD: + case RETURN: + case SELF: + case SIGNAL: + case STATIC: + case SUPER: + case TRAIT: + case UNDERSCORE: + case VAR: + case VOID: + case WHILE: + case YIELD: + return true; + default: + return false; + } +} + String GDScriptTokenizer::get_token_name(Token::Type p_token_type) { ERR_FAIL_INDEX_V_MSG(p_token_type, Token::TK_MAX, "<error>", "Using token type out of the enum."); return token_names[p_token_type]; @@ -164,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(); } @@ -205,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'; } @@ -224,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; @@ -244,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'); } @@ -346,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)); } @@ -355,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. @@ -520,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; @@ -544,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; @@ -563,7 +621,19 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { } // Allow '_' to be used in a number, for readability. + bool previous_was_underscore = false; while (digit_check_func(_peek()) || _peek() == '_') { + if (_peek() == '_') { + if (previous_was_underscore) { + Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + } + previous_was_underscore = true; + } _advance(); } @@ -614,7 +684,27 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { _advance(); } // Consume exponent digits. + if (!_is_digit(_peek())) { + Token error = make_error(R"(Expected exponent value after "e".)"); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + } + previous_was_underscore = false; while (_is_digit(_peek()) || _peek() == '_') { + if (_peek() == '_') { + if (previous_was_underscore) { + Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + } + previous_was_underscore = true; + } _advance(); } } @@ -672,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; @@ -689,7 +779,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { return make_error("Unterminated string."); } - CharType ch = _peek(); + char32_t ch = _peek(); if (ch == '\\') { // Escape pattern. @@ -699,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) { @@ -746,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') { @@ -850,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 != '#') { @@ -880,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; @@ -949,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; @@ -1013,7 +1103,7 @@ void GDScriptTokenizer::_skip_whitespace() { } for (;;) { - CharType c = _peek(); + char32_t c = _peek(); switch (c) { case ' ': _advance(); @@ -1102,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 059a226924..4453982d08 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -168,9 +168,9 @@ public: String source; const char *get_name() const; - // TODO: Allow some keywords as identifiers? - bool is_identifier() const { return type == IDENTIFIER; } - StringName get_identifier() const { return literal; } + bool is_identifier() const; + bool is_node_name() const; + StringName get_identifier() const { return source; } Token(Type p_type) { type = p_type; @@ -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 ae7898fdf2..668dfd4835 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -237,7 +237,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p case ClassNode::Member::ENUM_VALUE: { lsp::DocumentSymbol symbol; - symbol.name = m.constant->identifier->name; + symbol.name = m.enum_value.identifier->name; symbol.kind = lsp::SymbolKind::EnumMember; symbol.deprecated = false; symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); @@ -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 |