summaryrefslogtreecommitdiffstats
path: root/modules/gdscript/gdscript_parser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r--modules/gdscript/gdscript_parser.cpp231
1 files changed, 152 insertions, 79 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 9a4c92f601..b6db6a940b 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -137,11 +137,22 @@ GDScriptParser::GDScriptParser() {
#endif
#ifdef TOOLS_ENABLED
- if (theme_color_names.is_empty()) {
+ if (unlikely(theme_color_names.is_empty())) {
+ // Vectors.
theme_color_names.insert("x", "axis_x_color");
theme_color_names.insert("y", "axis_y_color");
theme_color_names.insert("z", "axis_z_color");
theme_color_names.insert("w", "axis_w_color");
+
+ // Color.
+ theme_color_names.insert("r", "axis_x_color");
+ theme_color_names.insert("r8", "axis_x_color");
+ theme_color_names.insert("g", "axis_y_color");
+ theme_color_names.insert("g8", "axis_y_color");
+ theme_color_names.insert("b", "axis_z_color");
+ theme_color_names.insert("b8", "axis_z_color");
+ theme_color_names.insert("a", "axis_w_color");
+ theme_color_names.insert("a8", "axis_w_color");
}
#endif
}
@@ -166,7 +177,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {
panic_mode = true;
// TODO: Improve positional information.
if (p_origin == nullptr) {
- errors.push_back({ p_message, current.start_line, current.start_column });
+ errors.push_back({ p_message, previous.start_line, previous.start_column });
} else {
errors.push_back({ p_message, p_origin->start_line, p_origin->leftmost_column });
}
@@ -234,8 +245,27 @@ void GDScriptParser::apply_pending_warnings() {
}
#endif
-void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) {
- if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
+void GDScriptParser::override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument) {
+ if (!for_completion) {
+ return;
+ }
+ if (completion_context.node != p_for_node) {
+ return;
+ }
+ CompletionContext context;
+ context.type = p_type;
+ context.current_class = current_class;
+ context.current_function = current_function;
+ context.current_suite = current_suite;
+ context.current_line = tokenizer->get_cursor_line();
+ context.current_argument = p_argument;
+ context.node = p_node;
+ context.parser = this;
+ completion_context = context;
+}
+
+void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument) {
+ if (!for_completion) {
return;
}
if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
@@ -249,11 +279,12 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node
context.current_line = tokenizer->get_cursor_line();
context.current_argument = p_argument;
context.node = p_node;
+ context.parser = this;
completion_context = context;
}
-void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) {
- if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
+void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type) {
+ if (!for_completion) {
return;
}
if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
@@ -266,6 +297,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ
context.current_suite = current_suite;
context.current_line = tokenizer->get_cursor_line();
context.builtin_type = p_builtin_type;
+ context.parser = this;
completion_context = context;
}
@@ -298,13 +330,14 @@ void GDScriptParser::set_last_completion_call_arg(int p_argument) {
completion_call_stack.back()->get().argument = p_argument;
}
-Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) {
+Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion, bool p_parse_body) {
clear();
String source = p_source_code;
int cursor_line = -1;
int cursor_column = -1;
for_completion = p_for_completion;
+ parse_body = p_parse_body;
int tab_size = 4;
#ifdef TOOLS_ENABLED
@@ -401,7 +434,7 @@ Error GDScriptParser::parse_binary(const Vector<uint8_t> &p_binary, const String
}
tokenizer = buffer_tokenizer;
- script_path = p_script_path;
+ 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.
@@ -678,6 +711,12 @@ void GDScriptParser::parse_program() {
}
}
+ // When the only thing needed is the class name and the icon, we don't need to parse the hole file.
+ // It really speed up the call to GDScriptLanguage::get_global_class_name especially for large script.
+ if (!parse_body) {
+ return;
+ }
+
#undef PUSH_PENDING_ANNOTATIONS_TO_HEAD
parse_class_body(true);
@@ -1600,7 +1639,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
advance();
// Arguments.
push_completion_call(annotation);
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true);
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0);
int argument_index = 0;
do {
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
@@ -1608,7 +1647,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
break;
}
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true);
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index);
set_last_completion_call_arg(argument_index++);
ExpressionNode *argument = parse_expression(false);
if (argument == nullptr) {
@@ -2549,8 +2588,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_
}
LiteralNode *literal = alloc_node<LiteralNode>();
- complete_extents(literal);
literal->value = previous.literal;
+ reset_extents(literal, p_previous_operand);
+ update_extents(literal);
+ make_completion_context(COMPLETION_NONE, literal, -1);
+ complete_extents(literal);
return literal;
}
@@ -3045,12 +3087,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *
const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand);
Variant::Type builtin_type = get_builtin_type(id->name);
if (builtin_type < Variant::VARIANT_MAX) {
- make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type, true);
+ make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type);
is_builtin = true;
}
}
if (!is_builtin) {
- make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true);
+ make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1);
}
}
@@ -3081,6 +3123,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *
subscript->base = p_previous_operand;
subscript->index = parse_expression(false);
+#ifdef TOOLS_ENABLED
+ if (subscript->index != nullptr && subscript->index->type == Node::LITERAL) {
+ override_completion_context(subscript->index, COMPLETION_SUBSCRIPT, subscript);
+ }
+#endif
+
if (subscript->index == nullptr) {
push_error(R"(Expected expression after "[".)");
}
@@ -3175,23 +3223,24 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
push_completion_call(call);
int argument_index = 0;
do {
- make_completion_context(ct, call, argument_index++, true);
+ make_completion_context(ct, call, argument_index);
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Allow for trailing comma.
break;
}
- 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.)");
} else {
call->arguments.push_back(argument);
- if (argument->type == Node::IDENTIFIER && use_identifier_completion) {
- completion_context.type = COMPLETION_IDENTIFIER;
+ if (argument->type == Node::LITERAL) {
+ override_completion_context(argument, ct, call, argument_index);
}
}
+
ct = COMPLETION_CALL_ARGUMENTS;
+ argument_index++;
} while (match(GDScriptTokenizer::Token::COMMA));
pop_completion_call();
@@ -3204,7 +3253,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) {
// We want code completion after a DOLLAR even if the current code is invalid.
- make_completion_context(COMPLETION_GET_NODE, nullptr, -1, true);
+ make_completion_context(COMPLETION_GET_NODE, nullptr, -1);
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()));
@@ -3261,7 +3310,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
path_state = PATH_STATE_SLASH;
}
- make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++, true);
+ make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++);
if (match(GDScriptTokenizer::Token::LITERAL)) {
if (previous.literal.get_type() != Variant::STRING) {
@@ -4044,7 +4093,7 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
return true;
}
-bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
#ifdef DEBUG_ENABLED
if (_is_tool) {
push_error(R"("@tool" annotation can only be used once.)", p_annotation);
@@ -4055,7 +4104,7 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p
return true;
}
-bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)");
ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
@@ -4086,7 +4135,7 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p
return true;
}
-bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node"))) {
@@ -4160,8 +4209,66 @@ static String _get_annotation_error_string(const StringName &p_annotation_name,
return vformat(R"("%s" annotation requires a variable of type %s, but type "%s" was given instead.)", p_annotation_name, string, p_provided_type.to_string());
}
+static StringName _find_narrowest_native_or_global_class(const GDScriptParser::DataType &p_type) {
+ switch (p_type.kind) {
+ case GDScriptParser::DataType::NATIVE: {
+ if (p_type.is_meta_type) {
+ return Object::get_class_static(); // `GDScriptNativeClass` is not an exposed class.
+ }
+ return p_type.native_type;
+ } break;
+ case GDScriptParser::DataType::SCRIPT: {
+ Ref<Script> script;
+ if (p_type.script_type.is_valid()) {
+ script = p_type.script_type;
+ } else {
+ script = ResourceLoader::load(p_type.script_path, SNAME("Script"));
+ }
+
+ if (p_type.is_meta_type) {
+ return script.is_valid() ? script->get_class() : Script::get_class_static();
+ }
+ if (script.is_null()) {
+ return p_type.native_type;
+ }
+ if (script->get_global_name() != StringName()) {
+ return script->get_global_name();
+ }
+
+ Ref<Script> base_script = script->get_base_script();
+ if (base_script.is_null()) {
+ return script->get_instance_base_type();
+ }
+
+ GDScriptParser::DataType base_type;
+ base_type.kind = GDScriptParser::DataType::SCRIPT;
+ base_type.builtin_type = Variant::OBJECT;
+ base_type.native_type = base_script->get_instance_base_type();
+ base_type.script_type = base_script;
+ base_type.script_path = base_script->get_path();
+
+ return _find_narrowest_native_or_global_class(base_type);
+ } break;
+ case GDScriptParser::DataType::CLASS: {
+ if (p_type.is_meta_type) {
+ return GDScript::get_class_static();
+ }
+ if (p_type.class_type == nullptr) {
+ return p_type.native_type;
+ }
+ if (p_type.class_type->get_global_name() != StringName()) {
+ return p_type.class_type->get_global_name();
+ }
+ return _find_narrowest_native_or_global_class(p_type.class_type->base_type);
+ } break;
+ default: {
+ ERR_FAIL_V(StringName());
+ } break;
+ }
+}
+
template <PropertyHint t_hint, Variant::Type t_type>
-bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
ERR_FAIL_NULL_V(p_class, false);
@@ -4302,53 +4409,21 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint_string = String();
break;
case GDScriptParser::DataType::NATIVE:
+ case GDScriptParser::DataType::SCRIPT:
+ case GDScriptParser::DataType::CLASS: {
+ const StringName class_name = _find_narrowest_native_or_global_class(export_type);
if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
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, a node, or an enum.)", p_annotation);
- return false;
- }
- break;
- case GDScriptParser::DataType::CLASS:
- if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
- variable->export_info.type = Variant::OBJECT;
- variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
- variable->export_info.hint_string = export_type.to_string();
+ variable->export_info.hint_string = class_name;
} 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.to_string();
+ variable->export_info.hint_string = class_name;
} else {
push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
return false;
}
-
- break;
- case GDScriptParser::DataType::SCRIPT: {
- StringName class_name;
- StringName native_base;
- if (export_type.script_type.is_valid()) {
- class_name = export_type.script_type->get_language()->get_global_class_name(export_type.script_type->get_path());
- native_base = export_type.script_type->get_instance_base_type();
- }
- 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);
- native_base = script->get_instance_base_type();
- }
- }
- if (class_name != StringName() && native_base != StringName() && ClassDB::is_parent_class(native_base, 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: {
if (export_type.is_meta_type) {
@@ -4432,7 +4507,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
// For `@export_storage` and `@export_custom`, there is no need to check the variable type, argument values,
// or handle array exports in a special way, so they are implemented as separate methods.
-bool GDScriptParser::export_storage_annotation(const AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
+bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
VariableNode *variable = static_cast<VariableNode *>(p_node);
@@ -4454,7 +4529,7 @@ bool GDScriptParser::export_storage_annotation(const AnnotationNode *p_annotatio
return true;
}
-bool GDScriptParser::export_custom_annotation(const AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
+bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
ERR_FAIL_COND_V_MSG(p_annotation->resolved_arguments.size() < 2, false, R"(Annotation "@export_custom" requires 2 arguments.)");
@@ -4483,31 +4558,29 @@ bool GDScriptParser::export_custom_annotation(const AnnotationNode *p_annotation
}
template <PropertyUsageFlags t_usage>
-bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
- AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation);
-
- if (annotation->resolved_arguments.is_empty()) {
+bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+ if (p_annotation->resolved_arguments.is_empty()) {
return false;
}
- annotation->export_info.name = annotation->resolved_arguments[0];
+ p_annotation->export_info.name = p_annotation->resolved_arguments[0];
switch (t_usage) {
case PROPERTY_USAGE_CATEGORY: {
- annotation->export_info.usage = t_usage;
+ p_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];
+ p_annotation->export_info.usage = t_usage;
+ if (p_annotation->resolved_arguments.size() == 2) {
+ p_annotation->export_info.hint_string = p_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];
+ p_annotation->export_info.usage = t_usage;
+ if (p_annotation->resolved_arguments.size() == 2) {
+ p_annotation->export_info.hint_string = p_annotation->resolved_arguments[1];
}
} break;
}
@@ -4515,7 +4588,7 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation
return true;
}
-bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
#ifndef DEBUG_ENABLED
// Only available in debug builds.
return true;
@@ -4590,7 +4663,7 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod
#endif // DEBUG_ENABLED
}
-bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name));
FunctionNode *function = static_cast<FunctionNode *>(p_target);
@@ -4651,7 +4724,7 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
return true;
}
-bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name));
ClassNode *class_node = static_cast<ClassNode *>(p_target);
if (class_node->annotated_static_unload) {
@@ -4722,9 +4795,9 @@ String GDScriptParser::DataType::to_string() const {
return class_type->fqcn;
case SCRIPT: {
if (is_meta_type) {
- return script_type != nullptr ? script_type->get_class_name().operator String() : "";
+ return script_type.is_valid() ? script_type->get_class_name().operator String() : "";
}
- String name = script_type != nullptr ? script_type->get_name() : "";
+ String name = script_type.is_valid() ? script_type->get_name() : "";
if (!name.is_empty()) {
return name;
}