diff options
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 631 |
1 files changed, 459 insertions, 172 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 8563f2b432..b4da94e448 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -35,6 +35,7 @@ #include "core/io/resource_loader.h" #include "core/math/math_defs.h" #include "gdscript.h" +#include "scene/main/multiplayer_api.h" #ifdef DEBUG_ENABLED #include "core/os/os.h" @@ -60,11 +61,14 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { builtin_types["Transform2D"] = Variant::TRANSFORM2D; builtin_types["Vector3"] = Variant::VECTOR3; builtin_types["Vector3i"] = Variant::VECTOR3I; + builtin_types["Vector4"] = Variant::VECTOR4; + builtin_types["Vector4i"] = Variant::VECTOR4I; builtin_types["AABB"] = Variant::AABB; builtin_types["Plane"] = Variant::PLANE; builtin_types["Quaternion"] = Variant::QUATERNION; builtin_types["Basis"] = Variant::BASIS; builtin_types["Transform3D"] = Variant::TRANSFORM3D; + builtin_types["Projection"] = Variant::PROJECTION; builtin_types["Color"] = Variant::COLOR; builtin_types["RID"] = Variant::RID; builtin_types["Object"] = Variant::OBJECT; @@ -105,35 +109,44 @@ void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const } } +bool GDScriptParser::annotation_exists(const String &p_annotation_name) const { + return valid_annotations.has(p_annotation_name); +} + GDScriptParser::GDScriptParser() { // Register valid annotations. // TODO: Should this be static? register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); - register_annotation(MethodInfo("@icon", { Variant::STRING, "icon_path" }), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); + register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_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", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, 0, true); - register_annotation(MethodInfo("@export_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, 1, true); + register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, 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", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, 1, true); + 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"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); - register_annotation(MethodInfo("@export_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, 3); - register_annotation(MethodInfo("@export_exp_easing", { Variant::STRING, "hint1" }, { Variant::STRING, "hint2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, 2); + 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", { Variant::STRING, "type" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, 1, true); - register_annotation(MethodInfo("@export_flags", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, 0, true); + 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("@warning_ignore", { Variant::STRING, "warning" }), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, 0, true); + // 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", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, 4, true); + 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("", "", "", 0), true); } GDScriptParser::~GDScriptParser() { @@ -153,6 +166,7 @@ void GDScriptParser::clear() { for_completion = false; errors.clear(); multiline_stack.clear(); + nodes_in_progress.clear(); } void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { @@ -203,7 +217,8 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ if (ignored_warnings.has(warn_name)) { return; } - if (!GLOBAL_GET("debug/gdscript/warnings/" + warn_name)) { + int warn_level = (int)GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(p_code)); + if (!warn_level) { return; } @@ -215,6 +230,11 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ warning.leftmost_column = p_source->leftmost_column; warning.rightmost_column = p_source->rightmost_column; + if (warn_level == GDScriptWarning::WarnLevel::ERROR) { + push_error(warning.get_message(), p_source); + return; + } + List<GDScriptWarning>::Element *before = nullptr; for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) { if (E->get().start_line > warning.start_line) { @@ -398,6 +418,9 @@ GDScriptTokenizer::Token GDScriptParser::advance() { push_error(current.literal); current = tokenizer.scan(); } + for (Node *n : nodes_in_progress) { + update_extents(n); + } return previous; } @@ -513,9 +536,13 @@ void GDScriptParser::parse_program() { head = alloc_node<ClassNode>(); current_class = head; + // If we happen to parse an annotation before extends or class_name keywords, track it. + // @tool is allowed, but others should fail. + AnnotationNode *premature_annotation = nullptr; + if (match(GDScriptTokenizer::Token::ANNOTATION)) { - // Check for @tool annotation. - AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); + // Check for @tool, script-level, or standalone annotation. + AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { if (annotation->name == SNAME("@tool")) { // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). @@ -525,7 +552,14 @@ void GDScriptParser::parse_program() { } // @tool annotation has no specific target. annotation->apply(this, nullptr); + } else if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) { + premature_annotation = annotation; + if (previous.type != GDScriptTokenizer::Token::NEWLINE) { + push_error(R"(Expected newline after a standalone annotation.)"); + } + annotation->apply(this, head); } else { + premature_annotation = annotation; annotation_stack.push_back(annotation); } } @@ -535,8 +569,8 @@ void GDScriptParser::parse_program() { // Order here doesn't matter, but there should be only one of each at most. switch (current.type) { case GDScriptTokenizer::Token::CLASS_NAME: - if (!annotation_stack.is_empty()) { - push_error(R"("class_name" should be used before annotations.)"); + if (premature_annotation != nullptr) { + push_error(R"("class_name" should be used before annotations (except @tool).)"); } advance(); if (head->identifier != nullptr) { @@ -546,8 +580,8 @@ void GDScriptParser::parse_program() { } break; case GDScriptTokenizer::Token::EXTENDS: - if (!annotation_stack.is_empty()) { - push_error(R"("extends" should be used before annotations.)"); + if (premature_annotation != nullptr) { + push_error(R"("extends" should be used before annotations (except @tool).)"); } advance(); if (head->extends_used) { @@ -568,12 +602,12 @@ void GDScriptParser::parse_program() { } if (match(GDScriptTokenizer::Token::ANNOTATION)) { - // Check for @icon annotation. - AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); + // Check for a script-level, or standalone annotation. + AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - if (annotation->name == SNAME("@icon")) { + if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) { if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after "@icon" annotation.)"); + push_error(R"(Expected newline after a standalone annotation.)"); } annotation->apply(this, head); } else { @@ -583,6 +617,7 @@ void GDScriptParser::parse_program() { } parse_class_body(true); + complete_extents(head); #ifdef TOOLS_ENABLED for (const KeyValue<int, GDScriptTokenizer::CommentData> &E : tokenizer.get_comments()) { @@ -623,6 +658,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { if (multiline && !consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) { current_class = previous_class; + complete_extents(n_class); return n_class; } @@ -635,6 +671,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { } parse_class_body(multiline); + complete_extents(n_class); if (multiline) { consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)"); @@ -649,17 +686,6 @@ void GDScriptParser::parse_class_name() { current_class->identifier = parse_identifier(); } - // TODO: Move this to annotation - if (match(GDScriptTokenizer::Token::COMMA)) { - // Icon path. - if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected class icon path string after ",".)")) { - if (previous.literal.get_type() != Variant::STRING) { - push_error(vformat(R"(Only strings can be used for the class icon path, found "%s" instead.)", Variant::get_type_name(previous.literal.get_type()))); - } - current_class->icon_path = previous.literal; - } - } - if (match(GDScriptTokenizer::Token::EXTENDS)) { // Allow extends on the same line. parse_extends(); @@ -801,9 +827,18 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { break; case GDScriptTokenizer::Token::ANNOTATION: { advance(); - AnnotationNode *annotation = parse_annotation(AnnotationInfo::CLASS_LEVEL); + + // Check for class-level annotations. + AnnotationNode *annotation = parse_annotation(AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - annotation_stack.push_back(annotation); + if (annotation->applies_to(AnnotationInfo::STANDALONE)) { + if (previous.type != GDScriptTokenizer::Token::NEWLINE) { + push_error(R"(Expected newline after a standalone annotation.)"); + } + annotation->apply(this, head); + } else { + annotation_stack.push_back(annotation); + } } break; } @@ -835,11 +870,13 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable() { } GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_property) { + VariableNode *variable = alloc_node<VariableNode>(); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) { + complete_extents(variable); return nullptr; } - VariableNode *variable = alloc_node<VariableNode>(); variable->identifier = parse_identifier(); variable->export_info.name = variable->identifier->name; @@ -847,10 +884,10 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper if (check(GDScriptTokenizer::Token::NEWLINE)) { if (p_allow_property) { advance(); - return parse_property(variable, true); } else { push_error(R"(Expected type after ":")"); + complete_extents(variable); return nullptr; } } else if (check((GDScriptTokenizer::Token::EQUAL))) { @@ -889,6 +926,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper } } + complete_extents(variable); end_statement("variable declaration"); return variable; @@ -897,6 +935,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_variable, bool p_need_indent) { if (p_need_indent) { if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block for property after ":".)")) { + complete_extents(p_variable); return nullptr; } } @@ -906,6 +945,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var make_completion_context(COMPLETION_PROPERTY_DECLARATION, property); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected "get" or "set" for property declaration.)")) { + complete_extents(p_variable); return nullptr; } @@ -962,6 +1002,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var } function = parse_identifier(); } + complete_extents(p_variable); if (p_variable->property == VariableNode::PROP_SETGET) { end_statement("property declaration"); @@ -976,37 +1017,37 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var void GDScriptParser::parse_property_setter(VariableNode *p_variable) { switch (p_variable->property) { case VariableNode::PROP_INLINE: { - consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)"); - if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name after "(".)")) { - p_variable->setter_parameter = parse_identifier(); - } - consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*"); - consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*"); - + FunctionNode *function = alloc_node<FunctionNode>(); IdentifierNode *identifier = alloc_node<IdentifierNode>(); + complete_extents(identifier); identifier->name = "@" + p_variable->identifier->name + "_setter"; - - FunctionNode *function = alloc_node<FunctionNode>(); function->identifier = identifier; - FunctionNode *previous_function = current_function; - current_function = function; + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)"); ParameterNode *parameter = alloc_node<ParameterNode>(); - parameter->identifier = p_variable->setter_parameter; - - if (parameter->identifier != nullptr) { + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name after "(".)")) { + reset_extents(parameter, previous); + p_variable->setter_parameter = parse_identifier(); + parameter->identifier = p_variable->setter_parameter; function->parameters_indices[parameter->identifier->name] = 0; function->parameters.push_back(parameter); + } + complete_extents(parameter); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*"); + consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*"); + + FunctionNode *previous_function = current_function; + current_function = function; + if (p_variable->setter_parameter != nullptr) { SuiteNode *body = alloc_node<SuiteNode>(); body->add_local(parameter, function); - function->body = parse_suite("setter declaration", body); p_variable->setter = function; } - current_function = previous_function; + complete_extents(function); break; } case VariableNode::PROP_SETGET: @@ -1024,12 +1065,13 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) { void GDScriptParser::parse_property_getter(VariableNode *p_variable) { switch (p_variable->property) { case VariableNode::PROP_INLINE: { + FunctionNode *function = alloc_node<FunctionNode>(); + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)"); IdentifierNode *identifier = alloc_node<IdentifierNode>(); + complete_extents(identifier); identifier->name = "@" + p_variable->identifier->name + "_getter"; - - FunctionNode *function = alloc_node<FunctionNode>(); function->identifier = identifier; FunctionNode *previous_function = current_function; @@ -1037,9 +1079,10 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) { SuiteNode *body = alloc_node<SuiteNode>(); function->body = parse_suite("getter declaration", body); - p_variable->getter = function; + current_function = previous_function; + complete_extents(function); break; } case VariableNode::PROP_SETGET: @@ -1055,11 +1098,12 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) { } GDScriptParser::ConstantNode *GDScriptParser::parse_constant() { + ConstantNode *constant = alloc_node<ConstantNode>(); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) { return nullptr; } - ConstantNode *constant = alloc_node<ConstantNode>(); constant->identifier = parse_identifier(); if (match(GDScriptTokenizer::Token::COLON)) { @@ -1078,12 +1122,15 @@ GDScriptParser::ConstantNode *GDScriptParser::parse_constant() { if (constant->initializer == nullptr) { push_error(R"(Expected initializer expression for constant.)"); + complete_extents(constant); return nullptr; } } else { + complete_extents(constant); return nullptr; } + complete_extents(constant); end_statement("constant declaration"); return constant; @@ -1113,15 +1160,18 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() { parameter->default_value = parse_expression(false); } + complete_extents(parameter); return parameter; } GDScriptParser::SignalNode *GDScriptParser::parse_signal() { + SignalNode *signal = alloc_node<SignalNode>(); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) { + complete_extents(signal); return nullptr; } - SignalNode *signal = alloc_node<SignalNode>(); signal->identifier = parse_identifier(); if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { @@ -1153,6 +1203,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*"); } + complete_extents(signal); end_statement("signal declaration"); return signal; @@ -1264,6 +1315,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { } #endif // TOOLS_ENABLED + complete_extents(enum_node); end_statement("enum"); return enum_node; @@ -1315,19 +1367,22 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod } GDScriptParser::FunctionNode *GDScriptParser::parse_function() { + FunctionNode *function = alloc_node<FunctionNode>(); + bool _static = false; if (previous.type == GDScriptTokenizer::Token::STATIC) { // TODO: Improve message if user uses "static" with "var" or "const" if (!consume(GDScriptTokenizer::Token::FUNC, R"(Expected "func" after "static".)")) { + complete_extents(function); return nullptr; } _static = true; } - FunctionNode *function = alloc_node<FunctionNode>(); make_completion_context(COMPLETION_OVERRIDE_METHOD, function); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) { + complete_extents(function); return nullptr; } @@ -1349,6 +1404,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() { function->body = parse_suite("function declaration", body); current_function = previous_function; + complete_extents(function); return function; } @@ -1396,6 +1452,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali } pop_completion_call(); } + complete_extents(annotation); match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional. @@ -1414,12 +1471,12 @@ void GDScriptParser::clear_unused_annotations() { annotation_stack.clear(); } -bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, int p_optional_arguments, bool p_is_vararg) { +bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments, bool p_is_vararg) { ERR_FAIL_COND_V_MSG(valid_annotations.has(p_info.name), false, vformat(R"(Annotation "%s" already registered.)", p_info.name)); AnnotationInfo new_annotation; new_annotation.info = p_info; - new_annotation.info.default_arguments.resize(p_optional_arguments); + new_annotation.info.default_arguments = p_default_arguments; if (p_is_vararg) { new_annotation.info.flags |= METHOD_FLAG_VARARG; } @@ -1445,9 +1502,11 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, if (multiline) { if (!consume(GDScriptTokenizer::Token::INDENT, vformat(R"(Expected indented block after %s.)", p_context))) { current_suite = suite->parent_block; + complete_extents(suite); return suite; } } + reset_extents(suite, current); int error_count = 0; @@ -1497,6 +1556,8 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, } while ((multiline || previous.type == GDScriptTokenizer::Token::SEMICOLON) && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end()); + complete_extents(suite); + if (multiline) { if (!lambda_ended) { consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)", p_context)); @@ -1527,6 +1588,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { case GDScriptTokenizer::Token::PASS: advance(); result = alloc_node<PassNode>(); + complete_extents(result); end_statement(R"("pass")"); break; case GDScriptTokenizer::Token::VAR: @@ -1574,6 +1636,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { // If this fails the expression will be nullptr, but that's the same as no return, so it's fine. n_return->return_value = parse_expression(false); } + complete_extents(n_return); result = n_return; current_suite->has_return = true; @@ -1584,6 +1647,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { case GDScriptTokenizer::Token::BREAKPOINT: advance(); result = alloc_node<BreakpointNode>(); + complete_extents(result); end_statement(R"("breakpoint")"); break; case GDScriptTokenizer::Token::ASSERT: @@ -1609,10 +1673,12 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { lambda_ended = true; has_ended_lambda = true; } else { + advance(); push_error(vformat(R"(Expected statement, found "%s" instead.)", previous.get_name())); } + } else { + end_statement("expression"); } - end_statement("expression"); lambda_ended = lambda_ended || has_ended_lambda; result = expression; @@ -1624,6 +1690,10 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { case Node::AWAIT: // Fine. break; + case Node::LAMBDA: + // Standalone lambdas can't be used, so make this an error. + push_error("Standalone lambdas cannot be accessed. Consider assigning it to a variable.", expression); + break; default: push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION); } @@ -1671,6 +1741,7 @@ GDScriptParser::AssertNode *GDScriptParser::parse_assert() { assert->condition = parse_expression(false); if (assert->condition == nullptr) { push_error("Expected expression to assert."); + complete_extents(assert); return nullptr; } @@ -1679,12 +1750,14 @@ GDScriptParser::AssertNode *GDScriptParser::parse_assert() { assert->message = parse_expression(false); if (assert->message == nullptr) { push_error(R"(Expected error message for assert after ",".)"); + complete_extents(assert); return nullptr; } } consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after assert expression.)*"); + complete_extents(assert); end_statement(R"("assert")"); return assert; @@ -1694,8 +1767,10 @@ GDScriptParser::BreakNode *GDScriptParser::parse_break() { if (!can_break) { push_error(R"(Cannot use "break" outside of a loop.)"); } + BreakNode *break_node = alloc_node<BreakNode>(); + complete_extents(break_node); end_statement(R"("break")"); - return alloc_node<BreakNode>(); + return break_node; } GDScriptParser::ContinueNode *GDScriptParser::parse_continue() { @@ -1703,9 +1778,10 @@ GDScriptParser::ContinueNode *GDScriptParser::parse_continue() { push_error(R"(Cannot use "continue" outside of a loop or pattern matching block.)"); } current_suite->has_continue = true; - end_statement(R"("continue")"); ContinueNode *cont = alloc_node<ContinueNode>(); cont->is_for_match = is_continue_match; + complete_extents(cont); + end_statement(R"("continue")"); return cont; } @@ -1738,11 +1814,16 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { SuiteNode *suite = alloc_node<SuiteNode>(); if (n_for->variable) { + const SuiteNode::Local &local = current_suite->get_local(n_for->variable->name); + if (local.type != SuiteNode::Local::UNDEFINED) { + push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), n_for->variable->name), n_for->variable); + } suite->add_local(SuiteNode::Local(n_for->variable, current_function)); } suite->parent_for = n_for; n_for->loop = parse_suite(R"("for" block)", suite); + complete_extents(n_for); // Reset break/continue state. can_break = could_break; @@ -1770,15 +1851,16 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) { } if (match(GDScriptTokenizer::Token::ELIF)) { - IfNode *elif = parse_if("elif"); - SuiteNode *else_block = alloc_node<SuiteNode>(); + IfNode *elif = parse_if("elif"); else_block->statements.push_back(elif); + complete_extents(else_block); n_if->false_block = else_block; } else if (match(GDScriptTokenizer::Token::ELSE)) { consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "else".)"); n_if->false_block = parse_suite(R"("else" block)"); } + complete_extents(n_if); if (n_if->false_block != nullptr && n_if->false_block->has_return && n_if->true_block->has_return) { current_suite->has_return = true; @@ -1802,13 +1884,13 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected a newline after "match" statement.)"); if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected an indented block after "match" statement.)")) { + complete_extents(match); return match; } #ifdef DEBUG_ENABLED bool all_have_return = true; bool have_wildcard = false; - bool wildcard_has_return = false; bool have_wildcard_without_continue = false; #endif @@ -1820,15 +1902,12 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { } #ifdef DEBUG_ENABLED - if (have_wildcard_without_continue) { + if (have_wildcard_without_continue && !branch->patterns.is_empty()) { push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN); } if (branch->has_wildcard) { have_wildcard = true; - if (branch->block->has_return) { - wildcard_has_return = true; - } if (!branch->block->has_continue) { have_wildcard_without_continue = true; } @@ -1839,11 +1918,12 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { #endif match->branches.push_back(branch); } + complete_extents(match); consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)"); #ifdef DEBUG_ENABLED - if (wildcard_has_return || (all_have_return && have_wildcard)) { + if (all_have_return && have_wildcard) { current_suite->has_return = true; } #endif @@ -1853,6 +1933,7 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { MatchBranchNode *branch = alloc_node<MatchBranchNode>(); + reset_extents(branch, current); bool has_bind = false; @@ -1880,6 +1961,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { } if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) { + complete_extents(branch); return nullptr; } @@ -1900,6 +1982,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { } branch->block = parse_suite("match pattern block", suite); + complete_extents(branch); // Restore continue state. can_continue = could_continue; @@ -1910,12 +1993,14 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_root_pattern) { PatternNode *pattern = alloc_node<PatternNode>(); + reset_extents(pattern, current); switch (current.type) { case GDScriptTokenizer::Token::VAR: { // Bind. advance(); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected bind name after "var".)")) { + complete_extents(pattern); return nullptr; } pattern->pattern_type = PatternNode::PT_BIND; @@ -1926,12 +2011,14 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ if (p_root_pattern != nullptr) { if (p_root_pattern->has_bind(pattern->bind->name)) { push_error(vformat(R"(Bind variable name "%s" was already used in this pattern.)", pattern->bind->name)); + complete_extents(pattern); return nullptr; } } if (current_suite->has_local(pattern->bind->name)) { push_error(vformat(R"(There's already a %s named "%s" in this scope.)", current_suite->get_local(pattern->bind->name).get_name(), pattern->bind->name)); + complete_extents(pattern); return nullptr; } @@ -1984,6 +2071,7 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); } else { PatternNode *sub_pattern = alloc_node<PatternNode>(); + complete_extents(sub_pattern); sub_pattern->pattern_type = PatternNode::PT_REST; pattern->dictionary.push_back({ nullptr, sub_pattern }); pattern->rest_used = true; @@ -2032,6 +2120,7 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ break; } } + complete_extents(pattern); return pattern; } @@ -2065,6 +2154,7 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() { is_continue_match = false; n_while->loop = parse_suite(R"("while" block)"); + complete_extents(n_while); // Reset break/continue state. can_break = could_break; @@ -2103,7 +2193,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign); while (p_precedence <= get_rule(current.type)->precedence) { - if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL)) { + if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) || (previous_operand->type == Node::LAMBDA && lambda_ended)) { return previous_operand; } // Also switch multiline mode on here for infix operators. @@ -2137,6 +2227,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token."); } IdentifierNode *identifier = alloc_node<IdentifierNode>(); + complete_extents(identifier); identifier->name = previous.get_identifier(); if (current_suite != nullptr && current_suite->has_local(identifier->name)) { @@ -2188,6 +2279,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_ } LiteralNode *literal = alloc_node<LiteralNode>(); + complete_extents(literal); literal->value = previous.literal; return literal; } @@ -2197,6 +2289,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_pre push_error(R"(Cannot use "self" inside a static function.)"); } SelfNode *self = alloc_node<SelfNode>(); + complete_extents(self); self->current_class = current_class; return self; } @@ -2204,6 +2297,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_pre GDScriptParser::ExpressionNode *GDScriptParser::parse_builtin_constant(ExpressionNode *p_previous_operand, bool p_can_assign) { GDScriptTokenizer::Token::Type op_type = previous.type; LiteralNode *constant = alloc_node<LiteralNode>(); + complete_extents(constant); switch (op_type) { case GDScriptTokenizer::Token::CONST_PI: @@ -2264,30 +2358,38 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionN } break; default: + complete_extents(operation); return nullptr; // Unreachable. } + complete_extents(operation); return operation; } GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_not_in_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { // check that NOT is followed by IN by consuming it before calling parse_binary_operator which will only receive a plain IN + UnaryOpNode *operation = alloc_node<UnaryOpNode>(); + reset_extents(operation, p_previous_operand); + update_extents(operation); consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "not" in content-test operator.)"); ExpressionNode *in_operation = parse_binary_operator(p_previous_operand, p_can_assign); - UnaryOpNode *operation = alloc_node<UnaryOpNode>(); operation->operation = UnaryOpNode::OP_LOGIC_NOT; operation->variant_op = Variant::OP_NOT; operation->operand = in_operation; + complete_extents(operation); return operation; } GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { GDScriptTokenizer::Token op = previous; BinaryOpNode *operation = alloc_node<BinaryOpNode>(); + reset_extents(operation, p_previous_operand); + update_extents(operation); Precedence precedence = (Precedence)(get_rule(op.type)->precedence + 1); operation->left_operand = p_previous_operand; operation->right_operand = parse_precedence(precedence, false); + complete_extents(operation); if (operation->right_operand == nullptr) { push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name())); @@ -2390,8 +2492,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { // Only one ternary operation exists, so no abstraction here. TernaryOpNode *operation = alloc_node<TernaryOpNode>(); - operation->true_expr = p_previous_operand; + reset_extents(operation, p_previous_operand); + update_extents(operation); + operation->true_expr = p_previous_operand; operation->condition = parse_precedence(PREC_TERNARY, false); if (operation->condition == nullptr) { @@ -2406,6 +2510,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(Expressio push_error(R"(Expected expression after "else".)"); } + complete_extents(operation); return operation; } @@ -2458,6 +2563,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } AssignmentNode *assignment = alloc_node<AssignmentNode>(); + reset_extents(assignment, p_previous_operand); + update_extents(assignment); + make_completion_context(COMPLETION_ASSIGN, assignment); #ifdef DEBUG_ENABLED bool has_operator = true; @@ -2522,6 +2630,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode if (assignment->assigned_value == nullptr) { push_error(R"(Expected an expression after "=".)"); } + complete_extents(assignment); #ifdef DEBUG_ENABLED if (source_variable != nullptr) { @@ -2543,6 +2652,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_pr push_error(R"(Expected signal or coroutine after "await".)"); } await->to_await = element; + complete_extents(await); if (current_function) { // Might be null in a getter or setter. current_function->is_coroutine = true; @@ -2571,6 +2681,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_array(ExpressionNode *p_pr } pop_multiline(); consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after array elements.)"); + complete_extents(array); return array; } @@ -2662,6 +2773,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode } pop_multiline(); consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" after dictionary elements.)"); + complete_extents(dictionary); return dictionary; } @@ -2679,6 +2791,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_grouping(ExpressionNode *p GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign) { SubscriptNode *attribute = alloc_node<SubscriptNode>(); + reset_extents(attribute, p_previous_operand); + update_extents(attribute); if (for_completion) { bool is_builtin = false; @@ -2698,17 +2812,21 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * attribute->base = p_previous_operand; if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) { + complete_extents(attribute); return attribute; } attribute->is_attribute = true; attribute->attribute = parse_identifier(); + complete_extents(attribute); return attribute; } GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign) { SubscriptNode *subscript = alloc_node<SubscriptNode>(); + reset_extents(subscript, p_previous_operand); + update_extents(subscript); make_completion_context(COMPLETION_SUBSCRIPT, subscript); @@ -2721,15 +2839,19 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode * pop_multiline(); consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)"); + complete_extents(subscript); return subscript; } GDScriptParser::ExpressionNode *GDScriptParser::parse_cast(ExpressionNode *p_previous_operand, bool p_can_assign) { CastNode *cast = alloc_node<CastNode>(); + reset_extents(cast, p_previous_operand); + update_extents(cast); cast->operand = p_previous_operand; cast->cast_type = parse_type(); + complete_extents(cast); if (cast->cast_type == nullptr) { push_error(R"(Expected type specifier after "as".)"); @@ -2741,6 +2863,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_cast(ExpressionNode *p_pre GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_previous_operand, bool p_can_assign) { CallNode *call = alloc_node<CallNode>(); + reset_extents(call, p_previous_operand); if (previous.type == GDScriptTokenizer::Token::SUPER) { // Super call. @@ -2751,6 +2874,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre if (current_function == nullptr) { push_error(R"(Cannot use implicit "super" call outside of a function.)"); pop_multiline(); + complete_extents(call); return nullptr; } if (current_function->identifier) { @@ -2763,6 +2887,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre make_completion_context(COMPLETION_SUPER_METHOD, call, true); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) { pop_multiline(); + complete_extents(call); return nullptr; } IdentifierNode *identifier = parse_identifier(); @@ -2819,56 +2944,108 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre pop_multiline(); consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after call arguments.)*"); + complete_extents(call); return call; } 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 identifier after "$".)"); + if (!current.is_node_name() && !check(GDScriptTokenizer::Token::LITERAL) && !check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) { + push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous.get_name())); + return nullptr; + } + + if (check(GDScriptTokenizer::Token::LITERAL)) { + if (current.literal.get_type() != Variant::STRING) { + push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous.get_name())); 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 (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 (!current.is_node_name()) { - push_error(R"(Expect node path after "/".)"); + } + + GetNodeNode *get_node = alloc_node<GetNodeNode>(); + + // Store the last item in the path so the parser knows what to expect. + // Allow allows more specific error messages. + enum PathState { + PATH_STATE_START, + PATH_STATE_SLASH, + PATH_STATE_PERCENT, + PATH_STATE_NODE_NAME, + } path_state = PATH_STATE_START; + + if (previous.type == GDScriptTokenizer::Token::DOLLAR) { + // Detect initial slash, which will be handled in the loop if it matches. + match(GDScriptTokenizer::Token::SLASH); +#ifdef DEBUG_ENABLED + } else { + get_node->use_dollar = false; +#endif + } + + int context_argument = 0; + + do { + if (previous.type == GDScriptTokenizer::Token::PERCENT) { + if (path_state != PATH_STATE_START && path_state != PATH_STATE_SLASH) { + push_error(R"("%" is only valid in the beginning of a node name (either after "$" or after "/"))"); + complete_extents(get_node); return nullptr; } - 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 if (match(GDScriptTokenizer::Token::SLASH)) { - GetNodeNode *get_node = alloc_node<GetNodeNode>(); - IdentifierNode *identifier_root = alloc_node<IdentifierNode>(); - get_node->chain.push_back(identifier_root); - int chain_position = 0; - do { - make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++); - if (!current.is_node_name()) { - push_error(R"(Expect node path after "/".)"); + get_node->full_path += "%"; + + path_state = PATH_STATE_PERCENT; + } else if (previous.type == GDScriptTokenizer::Token::SLASH) { + if (path_state != PATH_STATE_START && path_state != PATH_STATE_NODE_NAME) { + push_error(R"("/" is only valid at the beginning of the path or after a node name.)"); + complete_extents(get_node); return nullptr; } + + get_node->full_path += "/"; + + path_state = PATH_STATE_SLASH; + } + + make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++); + + if (match(GDScriptTokenizer::Token::LITERAL)) { + if (previous.literal.get_type() != Variant::STRING) { + String previous_token; + switch (path_state) { + case PATH_STATE_START: + previous_token = "$"; + break; + case PATH_STATE_PERCENT: + previous_token = "%"; + break; + case PATH_STATE_SLASH: + previous_token = "/"; + break; + default: + break; + } + push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous_token)); + complete_extents(get_node); + return nullptr; + } + + get_node->full_path += previous.literal.operator String(); + + path_state = PATH_STATE_NODE_NAME; + } else if (current.is_node_name()) { 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 identifier after "$".)"); - return nullptr; - } + get_node->full_path += previous.get_identifier(); + + path_state = PATH_STATE_NODE_NAME; + } else if (!check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) { + push_error(vformat(R"(Unexpected "%s" in node path.)", current.get_name())); + complete_extents(get_node); + return nullptr; + } + } while (match(GDScriptTokenizer::Token::SLASH) || match(GDScriptTokenizer::Token::PERCENT)); + + complete_extents(get_node); + return get_node; } GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_previous_operand, bool p_can_assign) { @@ -2891,6 +3068,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_ pop_multiline(); consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after preload path.)*"); + complete_extents(preload); return preload; } @@ -2926,6 +3104,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p current_function = function; SuiteNode *body = alloc_node<SuiteNode>(); + body->parent_function = current_function; + body->parent_block = current_suite; + SuiteNode *previous_suite = current_suite; current_suite = body; @@ -2937,6 +3118,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p in_lambda = true; function->body = parse_suite("lambda declaration", body, true); + complete_extents(function); + complete_extents(lambda); pop_multiline(); @@ -2981,13 +3164,15 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { if (!match(GDScriptTokenizer::Token::IDENTIFIER)) { if (match(GDScriptTokenizer::Token::VOID)) { if (p_allow_void) { - TypeNode *void_type = alloc_node<TypeNode>(); + complete_extents(type); + TypeNode *void_type = type; return void_type; } else { push_error(R"("void" is only allowed for a function return type.)"); } } // Leave error message to the caller who knows the context. + complete_extents(type); return nullptr; } @@ -3000,11 +3185,15 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { 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."); } consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after collection type.)"); + if (type != nullptr) { + complete_extents(type); + } return type; } @@ -3017,6 +3206,7 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { } } + complete_extents(type); return type; } @@ -3138,24 +3328,23 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & String title, link; // For tutorials. String doc_line = comments[line++].comment.trim_prefix("##"); - String striped_line = doc_line.strip_edges(); + String stripped_line = doc_line.strip_edges(); // Set the read mode. - if (striped_line.begins_with("@desc:") && p_desc.is_empty()) { + if (stripped_line.is_empty() && mode == BRIEF && !p_brief.is_empty()) { mode = DESC; - striped_line = striped_line.trim_prefix("@desc:"); - in_codeblock = _in_codeblock(doc_line, in_codeblock); + continue; - } else if (striped_line.begins_with("@tutorial")) { + } else if (stripped_line.begins_with("@tutorial")) { int begin_scan = String("@tutorial").length(); - if (begin_scan >= striped_line.length()) { + if (begin_scan >= stripped_line.length()) { continue; // invalid syntax. } - if (striped_line[begin_scan] == ':') { // No title. + if (stripped_line[begin_scan] == ':') { // No title. // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional. title = ""; - link = striped_line.trim_prefix("@tutorial:").strip_edges(); + link = stripped_line.trim_prefix("@tutorial:").strip_edges(); } else { /* Syntax: @@ -3163,35 +3352,35 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & * ^ open ^ close ^ colon ^ url */ int open_bracket_pos = begin_scan, close_bracket_pos = 0; - while (open_bracket_pos < striped_line.length() && (striped_line[open_bracket_pos] == ' ' || striped_line[open_bracket_pos] == '\t')) { + while (open_bracket_pos < stripped_line.length() && (stripped_line[open_bracket_pos] == ' ' || stripped_line[open_bracket_pos] == '\t')) { open_bracket_pos++; } - if (open_bracket_pos == striped_line.length() || striped_line[open_bracket_pos++] != '(') { + if (open_bracket_pos == stripped_line.length() || stripped_line[open_bracket_pos++] != '(') { continue; // invalid syntax. } close_bracket_pos = open_bracket_pos; - while (close_bracket_pos < striped_line.length() && striped_line[close_bracket_pos] != ')') { + while (close_bracket_pos < stripped_line.length() && stripped_line[close_bracket_pos] != ')') { close_bracket_pos++; } - if (close_bracket_pos == striped_line.length()) { + if (close_bracket_pos == stripped_line.length()) { continue; // invalid syntax. } int colon_pos = close_bracket_pos + 1; - while (colon_pos < striped_line.length() && (striped_line[colon_pos] == ' ' || striped_line[colon_pos] == '\t')) { + while (colon_pos < stripped_line.length() && (stripped_line[colon_pos] == ' ' || stripped_line[colon_pos] == '\t')) { colon_pos++; } - if (colon_pos == striped_line.length() || striped_line[colon_pos++] != ':') { + if (colon_pos == stripped_line.length() || stripped_line[colon_pos++] != ':') { continue; // invalid syntax. } - title = striped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges(); - link = striped_line.substr(colon_pos).strip_edges(); + title = stripped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges(); + link = stripped_line.substr(colon_pos).strip_edges(); } mode = TUTORIALS; in_codeblock = false; - } else if (striped_line.is_empty()) { + } else if (stripped_line.is_empty()) { continue; } else { // Tutorial docs are single line, we need a @tag after it. @@ -3211,7 +3400,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & } doc_line = doc_line.substr(i); } else { - doc_line = striped_line; + doc_line = stripped_line; } String line_join = (in_codeblock) ? "\n" : " "; @@ -3226,7 +3415,16 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & p_tutorials.append(Pair<String, String>(title, link)); break; case DONE: - return; + break; + } + } + if (current_class->members.size() > 0) { + const ClassNode::Member &m = current_class->members[0]; + int first_member_line = m.get_line(); + if (first_member_line == line) { + p_brief = ""; + p_desc = ""; + p_tutorials.clear(); } } } @@ -3270,7 +3468,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // STAR, { nullptr, &GDScriptParser::parse_binary_operator, PREC_POWER }, // STAR_STAR, { nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // SLASH, - { nullptr, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // PERCENT, + { &GDScriptParser::parse_get_node, &GDScriptParser::parse_binary_operator, PREC_FACTOR }, // PERCENT, // Assignment { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // EQUAL, { nullptr, &GDScriptParser::parse_assignment, PREC_ASSIGNMENT }, // PLUS_EQUAL, @@ -3551,11 +3749,42 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; variable->export_info.hint_string = export_type.native_type; + } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; + variable->export_info.hint_string = export_type.native_type; } else { - push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); + push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable); return false; } break; + case GDScriptParser::DataType::CLASS: + // Can assume type is a global GDScript class. + if (!ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { + push_error(R"(Exported script type must extend Resource.)"); + return false; + } + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; + variable->export_info.hint_string = export_type.class_type->identifier->name; + break; + case GDScriptParser::DataType::SCRIPT: { + StringName class_name; + if (export_type.script_type != nullptr && export_type.script_type.is_valid()) { + class_name = export_type.script_type->get_language()->get_global_class_name(export_type.script_type->get_path()); + } + if (class_name == StringName()) { + Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script")); + if (script.is_valid()) { + class_name = script->get_language()->get_global_class_name(export_type.script_path); + } + } + if (class_name != StringName() && ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), SNAME("Resource"))) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; + variable->export_info.hint_string = class_name; + } + } break; case GDScriptParser::DataType::ENUM: { variable->export_info.type = Variant::INT; variable->export_info.hint = PROPERTY_HINT_ENUM; @@ -3604,6 +3833,36 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node return true; } +template <PropertyUsageFlags t_usage> +bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_node) { + AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation); + + annotation->export_info.name = annotation->resolved_arguments[0]; + + switch (t_usage) { + case PROPERTY_USAGE_CATEGORY: { + annotation->export_info.usage = t_usage; + } break; + + case PROPERTY_USAGE_GROUP: { + annotation->export_info.usage = t_usage; + if (annotation->resolved_arguments.size() == 2) { + annotation->export_info.hint_string = annotation->resolved_arguments[1]; + } + } break; + + case PROPERTY_USAGE_SUBGROUP: { + annotation->export_info.usage = t_usage; + if (annotation->resolved_arguments.size() == 2) { + annotation->export_info.hint_string = annotation->resolved_arguments[1]; + } + } break; + } + + current_class->add_member_group(annotation); + return true; +} + bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) { #ifdef DEBUG_ENABLED bool has_error = false; @@ -3625,16 +3884,21 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod #endif // DEBUG_ENABLED } -template <Multiplayer::RPCMode t_mode> -bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name)); +bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_node) { + ERR_FAIL_COND_V_MSG(p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name)); - Multiplayer::RPCConfig rpc_config; - rpc_config.rpc_mode = t_mode; + FunctionNode *function = static_cast<FunctionNode *>(p_node); + if (function->rpc_config.get_type() != Variant::NIL) { + push_error(R"(RPC annotations can only be used once per function.)", p_annotation); + return false; + } + + Dictionary rpc_config; + rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY; if (p_annotation->resolved_arguments.size()) { int last = p_annotation->resolved_arguments.size() - 1; if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) { - rpc_config.channel = p_annotation->resolved_arguments[last].operator int(); + rpc_config["channel"] = p_annotation->resolved_arguments[last].operator int(); last -= 1; } if (last > 3) { @@ -3644,37 +3908,25 @@ bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Nod for (int i = last; i >= 0; i--) { String mode = p_annotation->resolved_arguments[i].operator String(); if (mode == "any_peer") { - rpc_config.rpc_mode = Multiplayer::RPC_MODE_ANY_PEER; + rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER; } else if (mode == "authority") { - rpc_config.rpc_mode = Multiplayer::RPC_MODE_AUTHORITY; + rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY; } else if (mode == "call_local") { - rpc_config.call_local = true; + rpc_config["call_local"] = true; } else if (mode == "call_remote") { - rpc_config.call_local = false; + rpc_config["call_local"] = false; } else if (mode == "reliable") { - rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; + rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_RELIABLE; } else if (mode == "unreliable") { - rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE; + rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE; } else if (mode == "unreliable_ordered") { - rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE_ORDERED; + rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED; } else { push_error(R"(Invalid RPC argument. Must be one of: 'call_local'/'call_remote' (local calls), 'any_peer'/'authority' (permission), 'reliable'/'unreliable'/'unreliable_ordered' (transfer mode).)", p_annotation); } } } - switch (p_node->type) { - case Node::FUNCTION: { - FunctionNode *function = static_cast<FunctionNode *>(p_node); - if (function->rpc_config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) { - push_error(R"(RPC annotations can only be used once per function.)", p_annotation); - return false; - } - function->rpc_config = rpc_config; - break; - } - default: - return false; // Unreachable. - } + function->rpc_config = rpc_config; return true; } @@ -3749,7 +4001,7 @@ String GDScriptParser::DataType::to_string() const { if (is_meta_type) { return script_type->get_class_name().operator String(); } - String name = script_type->get_name(); + String name = script_type != nullptr ? script_type->get_name() : ""; if (!name.is_empty()) { return name; } @@ -3801,6 +4053,46 @@ GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() co return type; } +void GDScriptParser::complete_extents(Node *p_node) { + while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) { + ERR_PRINT("Parser bug: Mismatch in extents tracking stack."); + nodes_in_progress.pop_back(); + } + if (nodes_in_progress.is_empty()) { + ERR_PRINT("Parser bug: Extents tracking stack is empty."); + } else { + nodes_in_progress.pop_back(); + } +} + +void GDScriptParser::update_extents(Node *p_node) { + p_node->end_line = previous.end_line; + p_node->end_column = previous.end_column; + p_node->leftmost_column = MIN(p_node->leftmost_column, previous.leftmost_column); + p_node->rightmost_column = MAX(p_node->rightmost_column, previous.rightmost_column); +} + +void GDScriptParser::reset_extents(Node *p_node, GDScriptTokenizer::Token p_token) { + p_node->start_line = p_token.start_line; + p_node->end_line = p_token.end_line; + p_node->start_column = p_token.start_column; + p_node->end_column = p_token.end_column; + p_node->leftmost_column = p_token.leftmost_column; + p_node->rightmost_column = p_token.rightmost_column; +} + +void GDScriptParser::reset_extents(Node *p_node, Node *p_from) { + if (p_from == nullptr) { + return; + } + p_node->start_line = p_from->start_line; + p_node->end_line = p_from->end_line; + p_node->start_column = p_from->start_column; + p_node->end_column = p_from->end_column; + p_node->leftmost_column = p_from->leftmost_column; + p_node->rightmost_column = p_from->rightmost_column; +} + /*---------- PRETTY PRINT FOR DEBUG ----------*/ #ifdef DEBUG_ENABLED @@ -4087,6 +4379,8 @@ void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) { break; case ClassNode::Member::ENUM_VALUE: break; // Nothing. Will be printed by enum. + case ClassNode::Member::GROUP: + break; // Nothing. Groups are only used by inspector. case ClassNode::Member::UNDEFINED: push_line("<unknown member>"); break; @@ -4250,17 +4544,10 @@ void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const } void GDScriptParser::TreePrinter::print_get_node(GetNodeNode *p_get_node) { - push_text("$"); - if (p_get_node->string != nullptr) { - print_literal(p_get_node->string); - } else { - for (int i = 0; i < p_get_node->chain.size(); i++) { - if (i > 0) { - push_text("/"); - } - print_identifier(p_get_node->chain[i]); - } + if (p_get_node->use_dollar) { + push_text("$"); } + push_text(p_get_node->full_path); } void GDScriptParser::TreePrinter::print_identifier(IdentifierNode *p_identifier) { |