diff options
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 333 |
1 files changed, 224 insertions, 109 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index db7b3e7ace..4d4eadf0fa 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -31,6 +31,7 @@ #include "gdscript_parser.h" #include "gdscript.h" +#include "gdscript_tokenizer_buffer.h" #include "core/config/project_settings.h" #include "core/io/file_access.h" @@ -73,8 +74,11 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { HashMap<String, String> GDScriptParser::theme_color_names; #endif +HashMap<StringName, GDScriptParser::AnnotationInfo> GDScriptParser::valid_annotations; + void GDScriptParser::cleanup() { builtin_types.clear(); + valid_annotations.clear(); } void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { @@ -89,41 +93,43 @@ bool GDScriptParser::annotation_exists(const String &p_annotation_name) const { GDScriptParser::GDScriptParser() { // Register valid annotations. - // TODO: Should this be static? - register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); - register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); - register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation); - - register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); - // Export annotations. - register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); - register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true); - register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true); - register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); - register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true); - register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>); - register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>); - register_annotation(MethodInfo("@export_placeholder", PropertyInfo(Variant::STRING, "placeholder")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); - register_annotation(MethodInfo("@export_range", PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max"), PropertyInfo(Variant::FLOAT, "step"), PropertyInfo(Variant::STRING, "extra_hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, varray(1.0, ""), true); - register_annotation(MethodInfo("@export_exp_easing", PropertyInfo(Variant::STRING, "hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, varray(""), true); - register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>); - register_annotation(MethodInfo("@export_node_path", PropertyInfo(Variant::STRING, "type")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, varray(""), true); - register_annotation(MethodInfo("@export_flags", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, varray(), true); - register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>); - register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>); - register_annotation(MethodInfo("@export_flags_2d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, Variant::INT>); - register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); - register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); - register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); - register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>); - // Export grouping annotations. - register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>); - register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray("")); - register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray("")); - // Warning annotations. - register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); - // Networking. - register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0)); + if (unlikely(valid_annotations.is_empty())) { + register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); + register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); + register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation); + + register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); + // Export annotations. + register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); + register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); + register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true); + register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true); + register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); + register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true); + register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>); + register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>); + register_annotation(MethodInfo("@export_placeholder", PropertyInfo(Variant::STRING, "placeholder")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); + register_annotation(MethodInfo("@export_range", PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max"), PropertyInfo(Variant::FLOAT, "step"), PropertyInfo(Variant::STRING, "extra_hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, varray(1.0, ""), true); + register_annotation(MethodInfo("@export_exp_easing", PropertyInfo(Variant::STRING, "hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, varray(""), true); + register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>); + register_annotation(MethodInfo("@export_node_path", PropertyInfo(Variant::STRING, "type")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, varray(""), true); + register_annotation(MethodInfo("@export_flags", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, varray(), true); + register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>); + register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>); + register_annotation(MethodInfo("@export_flags_2d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, Variant::INT>); + register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); + register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); + register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); + register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>); + // Export grouping annotations. + register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>); + register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray("")); + register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray("")); + // Warning annotations. + register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); + // Networking. + register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0)); + } #ifdef DEBUG_ENABLED is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); @@ -222,7 +228,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { return; } - if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) { + if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) { return; } CompletionContext context; @@ -230,7 +236,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node context.current_class = current_class; context.current_function = current_function; context.current_suite = current_suite; - context.current_line = tokenizer.get_cursor_line(); + context.current_line = tokenizer->get_cursor_line(); context.current_argument = p_argument; context.node = p_node; completion_context = context; @@ -240,7 +246,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { return; } - if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) { + if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) { return; } CompletionContext context; @@ -248,7 +254,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ context.current_class = current_class; context.current_function = current_function; context.current_suite = current_suite; - context.current_line = tokenizer.get_cursor_line(); + context.current_line = tokenizer->get_cursor_line(); context.builtin_type = p_builtin_type; completion_context = context; } @@ -261,7 +267,7 @@ void GDScriptParser::push_completion_call(Node *p_call) { call.call = p_call; call.argument = 0; completion_call_stack.push_back(call); - if (previous.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_BEGINNING) { + if (previous.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_BEGINNING) { completion_call = call; } } @@ -324,17 +330,21 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ source = source.replace_first(String::chr(0xFFFF), String()); } - tokenizer.set_source_code(source); - tokenizer.set_cursor_position(cursor_line, cursor_column); - script_path = p_script_path; - current = tokenizer.scan(); + GDScriptTokenizerText *text_tokenizer = memnew(GDScriptTokenizerText); + text_tokenizer->set_source_code(source); + + tokenizer = text_tokenizer; + + tokenizer->set_cursor_position(cursor_line, cursor_column); + script_path = p_script_path.simplify_path(); + current = tokenizer->scan(); // Avoid error or newline as the first token. // The latter can mess with the parser when opening files filled exclusively with comments and newlines. while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) { if (current.type == GDScriptTokenizer::Token::ERROR) { push_error(current.literal); } - current = tokenizer.scan(); + current = tokenizer->scan(); } #ifdef DEBUG_ENABLED @@ -355,6 +365,9 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ parse_program(); pop_multiline(); + memdelete(text_tokenizer); + tokenizer = nullptr; + #ifdef DEBUG_ENABLED if (multiline_stack.size() > 0) { ERR_PRINT("Parser bug: Imbalanced multiline stack."); @@ -368,6 +381,41 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ } } +Error GDScriptParser::parse_binary(const Vector<uint8_t> &p_binary, const String &p_script_path) { + GDScriptTokenizerBuffer *buffer_tokenizer = memnew(GDScriptTokenizerBuffer); + Error err = buffer_tokenizer->set_code_buffer(p_binary); + + if (err) { + memdelete(buffer_tokenizer); + return err; + } + + tokenizer = buffer_tokenizer; + script_path = p_script_path; + current = tokenizer->scan(); + // Avoid error or newline as the first token. + // The latter can mess with the parser when opening files filled exclusively with comments and newlines. + while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) { + if (current.type == GDScriptTokenizer::Token::ERROR) { + push_error(current.literal); + } + current = tokenizer->scan(); + } + + push_multiline(false); // Keep one for the whole parsing. + parse_program(); + pop_multiline(); + + memdelete(buffer_tokenizer); + tokenizer = nullptr; + + if (errors.is_empty()) { + return OK; + } else { + return ERR_PARSE_ERROR; + } +} + GDScriptTokenizer::Token GDScriptParser::advance() { lambda_ended = false; // Empty marker since we're past the end in any case. @@ -375,16 +423,16 @@ GDScriptTokenizer::Token GDScriptParser::advance() { ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream."); } if (for_completion && !completion_call_stack.is_empty()) { - if (completion_call.call == nullptr && tokenizer.is_past_cursor()) { + if (completion_call.call == nullptr && tokenizer->is_past_cursor()) { completion_call = completion_call_stack.back()->get(); passed_cursor = true; } } previous = current; - current = tokenizer.scan(); + current = tokenizer->scan(); while (current.type == GDScriptTokenizer::Token::ERROR) { push_error(current.literal); - current = tokenizer.scan(); + current = tokenizer->scan(); } if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line. for (Node *n : nodes_in_progress) { @@ -453,19 +501,19 @@ void GDScriptParser::synchronize() { void GDScriptParser::push_multiline(bool p_state) { multiline_stack.push_back(p_state); - tokenizer.set_multiline_mode(p_state); + tokenizer->set_multiline_mode(p_state); if (p_state) { // Consume potential whitespace tokens already waiting in line. while (current.type == GDScriptTokenizer::Token::NEWLINE || current.type == GDScriptTokenizer::Token::INDENT || current.type == GDScriptTokenizer::Token::DEDENT) { - current = tokenizer.scan(); // Don't call advance() here, as we don't want to change the previous token. + current = tokenizer->scan(); // Don't call advance() here, as we don't want to change the previous token. } } } void GDScriptParser::pop_multiline() { - ERR_FAIL_COND_MSG(multiline_stack.size() == 0, "Parser bug: trying to pop from multiline stack without available value."); + ERR_FAIL_COND_MSG(multiline_stack.is_empty(), "Parser bug: trying to pop from multiline stack without available value."); multiline_stack.pop_back(); - tokenizer.set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false); + tokenizer->set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false); } bool GDScriptParser::is_statement_end_token() const { @@ -504,7 +552,7 @@ void GDScriptParser::end_statement(const String &p_context) { void GDScriptParser::parse_program() { head = alloc_node<ClassNode>(); - head->fqcn = script_path; + head->fqcn = GDScript::canonicalize_path(script_path); current_class = head; bool can_have_class_or_extends = true; @@ -584,7 +632,7 @@ void GDScriptParser::parse_program() { complete_extents(head); #ifdef TOOLS_ENABLED - const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments(); int line = MIN(max_script_doc_line, head->end_line); while (line > 0) { if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) { @@ -593,6 +641,7 @@ void GDScriptParser::parse_program() { } line--; } + #endif // TOOLS_ENABLED if (!check(GDScriptTokenizer::Token::TK_EOF)) { @@ -625,7 +674,7 @@ GDScriptParser::ClassNode *GDScriptParser::find_class(const String &p_qualified_ // Starts at index 1 because index 0 was handled above. for (int i = 1; result != nullptr && i < class_names.size(); i++) { - String current_name = class_names[i]; + const String ¤t_name = class_names[i]; GDScriptParser::ClassNode *next = nullptr; if (result->has_member(current_name)) { GDScriptParser::ClassNode::Member member = result->get_member(current_name); @@ -661,7 +710,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) { if (n_class->outer) { String fqcn = n_class->outer->fqcn; if (fqcn.is_empty()) { - fqcn = script_path; + fqcn = GDScript::canonicalize_path(script_path); } n_class->fqcn = fqcn + "::" + n_class->identifier->name; } else { @@ -789,7 +838,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b if (has_comment(member->start_line, true)) { // Inline doc comment. member->doc_data = parse_class_doc_comment(member->start_line, true); - } else if (has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + } else if (has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) { // Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members. // This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice. member->doc_data = parse_class_doc_comment(doc_comment_line); @@ -798,7 +847,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b if (has_comment(member->start_line, true)) { // Inline doc comment. member->doc_data = parse_doc_comment(member->start_line, true); - } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) { // Normal doc comment. member->doc_data = parse_doc_comment(doc_comment_line); } @@ -1116,7 +1165,12 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) { case VariableNode::PROP_INLINE: { FunctionNode *function = alloc_node<FunctionNode>(); - consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)"); + if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after "get(".)*"); + consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after "get()".)*"); + } else { + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" or "(" after "get".)"); + } IdentifierNode *identifier = alloc_node<IdentifierNode>(); complete_extents(identifier); @@ -1264,8 +1318,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { EnumNode *enum_node = alloc_node<EnumNode>(); bool named = false; - if (check(GDScriptTokenizer::Token::IDENTIFIER)) { - advance(); + if (match(GDScriptTokenizer::Token::IDENTIFIER)) { enum_node->identifier = parse_identifier(); named = true; } @@ -1349,7 +1402,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { if (i == enum_node->values.size() - 1 || enum_node->values[i + 1].line > enum_value_line) { doc_data = parse_doc_comment(enum_value_line, true); } - } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) { // Normal doc comment. doc_data = parse_doc_comment(doc_comment_line); } @@ -2338,6 +2391,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode IdentifierNode *identifier = alloc_node<IdentifierNode>(); complete_extents(identifier); identifier->name = previous.get_identifier(); + if (identifier->name.operator String().is_empty()) { + print_line("Empty identifier found."); + } identifier->suite = current_suite; if (current_suite != nullptr && current_suite->has_local(identifier->name)) { @@ -3042,7 +3098,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre // Allow for trailing comma. break; } - bool use_identifier_completion = current.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE; + bool use_identifier_completion = current.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE; ExpressionNode *argument = parse_expression(false); if (argument == nullptr) { push_error(R"(Expected expression as the function argument.)"); @@ -3212,7 +3268,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p // Reset the multiline stack since we don't want the multiline mode one in the lambda body. push_multiline(false); if (multiline_context) { - tokenizer.push_expression_indented_block(); + tokenizer->push_expression_indented_block(); } push_multiline(true); // For the parameters. @@ -3259,9 +3315,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p if (multiline_context) { // If we're in multiline mode, we want to skip the spurious DEDENT and NEWLINE tokens. while (check(GDScriptTokenizer::Token::DEDENT) || check(GDScriptTokenizer::Token::INDENT) || check(GDScriptTokenizer::Token::NEWLINE)) { - current = tokenizer.scan(); // Not advance() since we don't want to change the previous token. + current = tokenizer->scan(); // Not advance() since we don't want to change the previous token. } - tokenizer.pop_expression_indented_block(); + tokenizer->pop_expression_indented_block(); } current_function = previous_function; @@ -3277,6 +3333,19 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p } GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign) { + // x is not int + // ^ ^^^ ExpressionNode, TypeNode + // ^^^^^^^^^^^^ TypeTestNode + // ^^^^^^^^^^^^ UnaryOpNode + UnaryOpNode *not_node = nullptr; + if (match(GDScriptTokenizer::Token::NOT)) { + not_node = alloc_node<UnaryOpNode>(); + not_node->operation = UnaryOpNode::OP_LOGIC_NOT; + not_node->variant_op = Variant::OP_NOT; + reset_extents(not_node, p_previous_operand); + update_extents(not_node); + } + TypeTestNode *type_test = alloc_node<TypeTestNode>(); reset_extents(type_test, p_previous_operand); update_extents(type_test); @@ -3285,8 +3354,21 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode * type_test->test_type = parse_type(); complete_extents(type_test); + if (not_node != nullptr) { + not_node->operand = type_test; + complete_extents(not_node); + } + if (type_test->test_type == nullptr) { - push_error(R"(Expected type specifier after "is".)"); + if (not_node == nullptr) { + push_error(R"(Expected type specifier after "is".)"); + } else { + push_error(R"(Expected type specifier after "is not".)"); + } + } + + if (not_node != nullptr) { + return not_node; } return type_test; @@ -3337,14 +3419,21 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) { // Typed collection (like Array[int]). - type->container_type = parse_type(false); // Don't allow void for array element type. - if (type->container_type == nullptr) { - push_error(R"(Expected type for collection after "[".)"); - complete_extents(type); - type = nullptr; - } else if (type->container_type->container_type != nullptr) { - push_error("Nested typed collections are not supported."); - } + bool first_pass = true; + do { + TypeNode *container_type = parse_type(false); // Don't allow void for element type. + if (container_type == nullptr) { + push_error(vformat(R"(Expected type for collection after "%s".)", first_pass ? "[" : ",")); + complete_extents(type); + type = nullptr; + break; + } else if (container_type->container_types.size() > 0) { + push_error("Nested typed collections are not supported."); + } else { + type->container_types.append(container_type); + } + first_pass = false; + } while (match(GDScriptTokenizer::Token::COMMA)); consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after collection type.)"); if (type != nullptr) { complete_extents(type); @@ -3477,20 +3566,20 @@ static String _process_doc_line(const String &p_line, const String &p_text, cons } bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) { - bool has_comment = tokenizer.get_comments().has(p_line); + bool has_comment = tokenizer->get_comments().has(p_line); // If there are no comments or if we don't care whether the comment // is a docstring, we have our result. if (!p_must_be_doc || !has_comment) { return has_comment; } - return tokenizer.get_comments()[p_line].comment.begins_with("##"); + return tokenizer->get_comments()[p_line].comment.begins_with("##"); } GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) { ERR_FAIL_COND_V(!has_comment(p_line, true), MemberDocData()); - const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments(); int line = p_line; if (!p_single_line) { @@ -3521,11 +3610,17 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool if (state == DOC_LINE_NORMAL) { String stripped_line = doc_line.strip_edges(); - if (stripped_line.begins_with("@deprecated")) { + if (stripped_line == "@deprecated" || stripped_line.begins_with("@deprecated:")) { result.is_deprecated = true; + if (stripped_line.begins_with("@deprecated:")) { + result.deprecated_message = stripped_line.trim_prefix("@deprecated:").strip_edges(); + } continue; - } else if (stripped_line.begins_with("@experimental")) { + } else if (stripped_line == "@experimental" || stripped_line.begins_with("@experimental:")) { result.is_experimental = true; + if (stripped_line.begins_with("@experimental:")) { + result.experimental_message = stripped_line.trim_prefix("@experimental:").strip_edges(); + } continue; } } @@ -3539,7 +3634,7 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_single_line) { ERR_FAIL_COND_V(!has_comment(p_line, true), ClassDocData()); - const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments(); int line = p_line; if (!p_single_line) { @@ -3624,11 +3719,17 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, result.tutorials.append(Pair<String, String>(title, link)); continue; - } else if (stripped_line.begins_with("@deprecated")) { + } else if (stripped_line == "@deprecated" || stripped_line.begins_with("@deprecated:")) { result.is_deprecated = true; + if (stripped_line.begins_with("@deprecated:")) { + result.deprecated_message = stripped_line.trim_prefix("@deprecated:").strip_edges(); + } continue; - } else if (stripped_line.begins_with("@experimental")) { + } else if (stripped_line == "@experimental" || stripped_line.begins_with("@experimental:")) { result.is_experimental = true; + if (stripped_line.begins_with("@experimental:")) { + result.experimental_message = stripped_line.trim_prefix("@experimental:").strip_edges(); + } continue; } } @@ -3844,12 +3945,12 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifdef DEBUG_ENABLED - if (this->_is_tool) { + if (_is_tool) { push_error(R"("@tool" annotation can only be used once.)", p_annotation); return false; } #endif // DEBUG_ENABLED - this->_is_tool = true; + _is_tool = true; return true; } @@ -3985,19 +4086,19 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } hint_string += arg_string; } - variable->export_info.hint_string = hint_string; // This is called after the analyzer is done finding the type, so this should be set here. DataType export_type = variable->get_datatype(); + bool use_default_variable_type_check = true; if (p_annotation->name == SNAME("@export_range")) { if (export_type.builtin_type == Variant::INT) { variable->export_info.type = Variant::INT; } } else if (p_annotation->name == SNAME("@export_multiline")) { - if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) { - DataType inner_type = export_type.get_container_element_type(); + if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type(0)) { + DataType inner_type = export_type.get_container_element_type(0); if (inner_type.builtin_type != Variant::STRING) { push_error(vformat(R"("%s" annotation on arrays requires a string type but type "%s" was given instead.)", p_annotation->name.operator String(), inner_type.to_string()), variable); return false; @@ -4021,11 +4122,9 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node return true; } - } + } else if (p_annotation->name == SNAME("@export")) { + use_default_variable_type_check = false; - // WARNING: Do not merge with the previous `else if`! Otherwise `else` (default variable type check) - // will not work for the above annotations. `@export` and `@export_enum` validate the type separately. - if (p_annotation->name == SNAME("@export")) { if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); return false; @@ -4033,8 +4132,8 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node bool is_array = false; - if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) { - export_type = export_type.get_container_element_type(); // Use inner type for. + if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type(0)) { + export_type = export_type.get_container_element_type(0); // Use inner type for. is_array = true; } @@ -4143,6 +4242,8 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.type = Variant::ARRAY; } } else if (p_annotation->name == SNAME("@export_enum")) { + use_default_variable_type_check = false; + Variant::Type enum_type = Variant::INT; if (export_type.kind == DataType::BUILTIN && export_type.builtin_type == Variant::STRING) { @@ -4160,7 +4261,15 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node push_error(vformat(R"("@export_enum" annotation requires a variable of type "int" or "String" but type "%s" was given instead.)", export_type.to_string()), variable); return false; } - } else { + } else if (p_annotation->name == SNAME("@export_storage")) { + use_default_variable_type_check = false; // Can be applied to a variable of any type. + + // Save the info because the compiler uses export info for overwriting member info. + variable->export_info = export_type.to_property_info(variable->identifier->name); + variable->export_info.usage |= PROPERTY_USAGE_STORAGE; + } + + if (use_default_variable_type_check) { // Validate variable type with export. if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != t_type)) { // Allow float/int conversion. @@ -4344,8 +4453,8 @@ String GDScriptParser::DataType::to_string() const { if (builtin_type == Variant::NIL) { return "null"; } - if (builtin_type == Variant::ARRAY && has_container_element_type()) { - return vformat("Array[%s]", container_element_type->to_string()); + if (builtin_type == Variant::ARRAY && has_container_element_type(0)) { + return vformat("Array[%s]", container_element_types[0].to_string()); } return Variant::get_type_name(builtin_type); case NATIVE: @@ -4398,36 +4507,36 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co switch (kind) { case BUILTIN: result.type = builtin_type; - if (builtin_type == Variant::ARRAY && has_container_element_type()) { - const DataType *elem_type = container_element_type; - switch (elem_type->kind) { + if (builtin_type == Variant::ARRAY && has_container_element_type(0)) { + const DataType elem_type = get_container_element_type(0); + switch (elem_type.kind) { case BUILTIN: result.hint = PROPERTY_HINT_ARRAY_TYPE; - result.hint_string = Variant::get_type_name(elem_type->builtin_type); + result.hint_string = Variant::get_type_name(elem_type.builtin_type); break; case NATIVE: result.hint = PROPERTY_HINT_ARRAY_TYPE; - result.hint_string = elem_type->native_type; + result.hint_string = elem_type.native_type; break; case SCRIPT: result.hint = PROPERTY_HINT_ARRAY_TYPE; - if (elem_type->script_type.is_valid() && elem_type->script_type->get_global_name() != StringName()) { - result.hint_string = elem_type->script_type->get_global_name(); + if (elem_type.script_type.is_valid() && elem_type.script_type->get_global_name() != StringName()) { + result.hint_string = elem_type.script_type->get_global_name(); } else { - result.hint_string = elem_type->native_type; + result.hint_string = elem_type.native_type; } break; case CLASS: result.hint = PROPERTY_HINT_ARRAY_TYPE; - if (elem_type->class_type != nullptr && elem_type->class_type->get_global_name() != StringName()) { - result.hint_string = elem_type->class_type->get_global_name(); + if (elem_type.class_type != nullptr && elem_type.class_type->get_global_name() != StringName()) { + result.hint_string = elem_type.class_type->get_global_name(); } else { - result.hint_string = elem_type->native_type; + result.hint_string = elem_type.native_type; } break; case ENUM: result.hint = PROPERTY_HINT_ARRAY_TYPE; - result.hint_string = String(elem_type->native_type).replace("::", "."); + result.hint_string = String(elem_type.native_type).replace("::", "."); break; case VARIANT: case RESOLVING: @@ -4986,6 +5095,9 @@ void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const for (const AnnotationNode *E : p_function->annotations) { print_annotation(E); } + if (p_function->is_static) { + push_text("Static "); + } push_text(p_context); push_text(" "); if (p_function->identifier) { @@ -5330,6 +5442,9 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) { print_annotation(E); } + if (p_variable->is_static) { + push_text("Static "); + } push_text("Variable "); print_identifier(p_variable->identifier); |