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.cpp549
1 files changed, 353 insertions, 196 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index b32313dad4..13838ee0a1 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -30,23 +30,27 @@
#include "gdscript_parser.h"
+#include "gdscript.h"
+
+#ifdef DEBUG_ENABLED
+#include "gdscript_warning.h"
+#endif
+
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
#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"
#include "core/string/string_builder.h"
-#include "gdscript_warning.h"
#include "servers/text_server.h"
-#endif // DEBUG_ENABLED
+#endif
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
-#endif // TOOLS_ENABLED
+#endif
static HashMap<StringName, Variant::Type> builtin_types;
Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
@@ -81,6 +85,8 @@ GDScriptParser::GDScriptParser() {
// 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>);
@@ -102,6 +108,7 @@ GDScriptParser::GDScriptParser() {
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(""));
@@ -109,7 +116,7 @@ GDScriptParser::GDScriptParser() {
// 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), 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("authority", "call_remote", "unreliable", 0));
#ifdef DEBUG_ENABLED
is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable");
@@ -623,7 +630,7 @@ bool GDScriptParser::has_class(const GDScriptParser::ClassNode *p_class) const {
return false;
}
-GDScriptParser::ClassNode *GDScriptParser::parse_class() {
+GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) {
ClassNode *n_class = alloc_node<ClassNode>();
ClassNode *previous_class = current_class;
@@ -712,19 +719,19 @@ void GDScriptParser::parse_extends() {
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) {
return;
}
- current_class->extends.push_back(previous.literal);
+ current_class->extends.push_back(parse_identifier());
while (match(GDScriptTokenizer::Token::PERIOD)) {
make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)")) {
return;
}
- current_class->extends.push_back(previous.literal);
+ current_class->extends.push_back(parse_identifier());
}
}
template <class T>
-void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind) {
+void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) {
advance();
#ifdef TOOLS_ENABLED
@@ -749,7 +756,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
#endif // TOOLS_ENABLED
}
- T *member = (this->*p_parse_function)();
+ T *member = (this->*p_parse_function)(p_is_static);
if (member == nullptr) {
return;
}
@@ -761,7 +768,11 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
#ifdef TOOLS_ENABLED
// Consume doc comments.
class_doc_line = MIN(class_doc_line, doc_comment_line - 1);
- if (has_comment(doc_comment_line)) {
+
+ // Check whether current line has a doc comment
+ if (has_comment(previous.start_line, true)) {
+ member->doc_description = get_doc_comment(previous.start_line, true);
+ } else if (has_comment(doc_comment_line, true)) {
if constexpr (std::is_same_v<T, ClassNode>) {
get_class_doc_comment(doc_comment_line, member->doc_brief_description, member->doc_description, member->doc_tutorials, true);
} else {
@@ -799,10 +810,15 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
void GDScriptParser::parse_class_body(bool p_is_multiline) {
bool class_end = false;
+ bool next_is_static = false;
while (!class_end && !is_at_end()) {
- switch (current.type) {
+ GDScriptTokenizer::Token token = current;
+ switch (token.type) {
case GDScriptTokenizer::Token::VAR:
- parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable");
+ parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable", next_is_static);
+ if (next_is_static) {
+ current_class->has_static_data = true;
+ }
break;
case GDScriptTokenizer::Token::CONST:
parse_class_member(&GDScriptParser::parse_constant, AnnotationInfo::CONSTANT, "constant");
@@ -810,9 +826,8 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
case GDScriptTokenizer::Token::SIGNAL:
parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");
break;
- case GDScriptTokenizer::Token::STATIC:
case GDScriptTokenizer::Token::FUNC:
- parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function");
+ parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function", next_is_static);
break;
case GDScriptTokenizer::Token::CLASS:
parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class");
@@ -820,6 +835,13 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
case GDScriptTokenizer::Token::ENUM:
parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");
break;
+ case GDScriptTokenizer::Token::STATIC: {
+ advance();
+ next_is_static = true;
+ if (!check(GDScriptTokenizer::Token::FUNC) && !check(GDScriptTokenizer::Token::VAR)) {
+ push_error(R"(Expected "func" or "var" after "static".)");
+ }
+ } break;
case GDScriptTokenizer::Token::ANNOTATION: {
advance();
@@ -866,6 +888,9 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
advance();
break;
}
+ if (token.type != GDScriptTokenizer::Token::STATIC) {
+ next_is_static = false;
+ }
if (panic_mode) {
synchronize();
}
@@ -875,11 +900,11 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
}
}
-GDScriptParser::VariableNode *GDScriptParser::parse_variable() {
- return parse_variable(true);
+GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) {
+ return parse_variable(p_is_static, true);
}
-GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_property) {
+GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) {
VariableNode *variable = alloc_node<VariableNode>();
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
@@ -889,6 +914,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper
variable->identifier = parse_identifier();
variable->export_info.name = variable->identifier->name;
+ variable->is_static = p_is_static;
if (match(GDScriptTokenizer::Token::COLON)) {
if (check(GDScriptTokenizer::Token::NEWLINE)) {
@@ -1032,6 +1058,7 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
complete_extents(identifier);
identifier->name = "@" + p_variable->identifier->name + "_setter";
function->identifier = identifier;
+ function->is_static = p_variable->is_static;
consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)");
@@ -1083,6 +1110,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
complete_extents(identifier);
identifier->name = "@" + p_variable->identifier->name + "_getter";
function->identifier = identifier;
+ function->is_static = p_variable->is_static;
FunctionNode *previous_function = current_function;
current_function = function;
@@ -1107,7 +1135,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
}
}
-GDScriptParser::ConstantNode *GDScriptParser::parse_constant() {
+GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_static) {
ConstantNode *constant = alloc_node<ConstantNode>();
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
@@ -1174,7 +1202,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
return parameter;
}
-GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
+GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_static) {
SignalNode *signal = alloc_node<SignalNode>();
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
@@ -1219,7 +1247,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
return signal;
}
-GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
+GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
EnumNode *enum_node = alloc_node<EnumNode>();
bool named = false;
@@ -1343,7 +1371,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
default_used = true;
} else {
if (default_used) {
- push_error("Cannot have a mandatory parameters after optional parameters.");
+ push_error("Cannot have mandatory parameters after optional parameters.");
continue;
}
}
@@ -1368,23 +1396,23 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
}
}
+ if (!p_function->source_lambda && p_function->identifier && p_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init) {
+ if (!p_function->is_static) {
+ push_error(R"(Static constructor must be declared static.)");
+ }
+ if (p_function->parameters.size() != 0) {
+ push_error(R"(Static constructor cannot have parameters.)");
+ }
+ current_class->has_static_data = true;
+ }
+
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type));
}
-GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
+GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) {
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;
- }
-
make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) {
@@ -1396,7 +1424,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
current_function = function;
function->identifier = parse_identifier();
- function->is_static = _static;
+ function->is_static = p_is_static;
SuiteNode *body = alloc_node<SuiteNode>();
SuiteNode *previous_suite = current_suite;
@@ -1439,27 +1467,32 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
valid = false;
}
- if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ push_multiline(true);
+ advance();
// Arguments.
push_completion_call(annotation);
make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true);
- if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
- push_multiline(true);
- int argument_index = 0;
- do {
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true);
- set_last_completion_call_arg(argument_index++);
- ExpressionNode *argument = parse_expression(false);
- if (argument == nullptr) {
- valid = false;
- continue;
- }
- annotation->arguments.push_back(argument);
- } while (match(GDScriptTokenizer::Token::COMMA));
- pop_multiline();
+ int argument_index = 0;
+ do {
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
+ // Allow for trailing comma.
+ break;
+ }
- consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*");
- }
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true);
+ set_last_completion_call_arg(argument_index++);
+ ExpressionNode *argument = parse_expression(false);
+ if (argument == nullptr) {
+ push_error("Expected expression as the annotation argument.");
+ valid = false;
+ continue;
+ }
+ annotation->arguments.push_back(argument);
+ } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
+
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*");
pop_completion_call();
}
complete_extents(annotation);
@@ -1475,7 +1508,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
void GDScriptParser::clear_unused_annotations() {
for (const AnnotationNode *annotation : annotation_stack) {
- push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name), annotation);
+ push_error(vformat(R"(Annotation "%s" does not precede a valid target, so it will have no effect.)", annotation->name), annotation);
}
annotation_stack.clear();
@@ -1503,6 +1536,11 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
suite->parent_function = current_function;
current_suite = suite;
+ if (!p_for_lambda && suite->parent_block != nullptr && suite->parent_block->is_in_loop) {
+ // Do not reset to false if true is set before calling parse_suite().
+ suite->is_in_loop = true;
+ }
+
bool multiline = false;
if (match(GDScriptTokenizer::Token::NEWLINE)) {
@@ -1521,7 +1559,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
int error_count = 0;
do {
- if (!multiline && previous.type == GDScriptTokenizer::Token::SEMICOLON && check(GDScriptTokenizer::Token::NEWLINE)) {
+ if (is_at_end() || (!multiline && previous.type == GDScriptTokenizer::Token::SEMICOLON && check(GDScriptTokenizer::Token::NEWLINE))) {
break;
}
Node *statement = parse_statement();
@@ -1603,11 +1641,11 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
break;
case GDScriptTokenizer::Token::VAR:
advance();
- result = parse_variable();
+ result = parse_variable(false, false);
break;
case GDScriptTokenizer::Token::CONST:
advance();
- result = parse_constant();
+ result = parse_constant(false);
break;
case GDScriptTokenizer::Token::IF:
advance();
@@ -1637,7 +1675,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
advance();
ReturnNode *n_return = alloc_node<ReturnNode>();
if (!is_statement_end()) {
- if (current_function && current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
+ if (current_function && (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init || current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init)) {
push_error(R"(Constructor cannot return a value.)");
}
n_return->return_value = parse_expression(false);
@@ -1817,7 +1855,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
n_for->list = parse_expression(false);
if (!n_for->list) {
- push_error(R"(Expected a list or range after "in".)");
+ push_error(R"(Expected iterable after "in".)");
}
consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)");
@@ -1838,9 +1876,8 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
}
suite->add_local(SuiteNode::Local(n_for->variable, current_function));
}
-
+ suite->is_in_loop = true;
n_for->loop = parse_suite(R"("for" block)", suite);
- n_for->loop->is_loop = true;
complete_extents(n_for);
// Reset break/continue state.
@@ -2153,8 +2190,9 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() {
can_break = true;
can_continue = true;
- n_while->loop = parse_suite(R"("while" block)");
- n_while->loop->is_loop = true;
+ SuiteNode *suite = alloc_node<SuiteNode>();
+ suite->is_in_loop = true;
+ n_while->loop = parse_suite(R"("while" block)", suite);
complete_extents(n_while);
// Reset break/continue state.
@@ -2198,7 +2236,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) || (previous_operand->type == Node::LAMBDA && lambda_ended)) {
+ if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) || lambda_ended) {
return previous_operand;
}
// Also switch multiline mode on here for infix operators.
@@ -2731,12 +2769,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
switch (dictionary->style) {
case DictionaryNode::LUA_TABLE:
if (key != nullptr && key->type != Node::IDENTIFIER && key->type != Node::LITERAL) {
- push_error("Expected identifier or string as LUA-style dictionary key.");
+ push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)");
advance();
break;
}
if (key != nullptr && key->type == Node::LITERAL && static_cast<LiteralNode *>(key)->value.get_type() != Variant::STRING) {
- push_error("Expected identifier or string as LUA-style dictionary key.");
+ push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)");
advance();
break;
}
@@ -2820,6 +2858,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *
attribute->base = p_previous_operand;
+ if (current.is_node_name()) {
+ current.type = GDScriptTokenizer::Token::IDENTIFIER;
+ }
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) {
complete_extents(attribute);
return attribute;
@@ -3138,6 +3179,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
bool previous_in_lambda = in_lambda;
in_lambda = true;
+ // Save break/continue state.
+ bool could_break = can_break;
+ bool could_continue = can_continue;
+
+ // Disallow break/continue.
+ can_break = false;
+ can_continue = false;
+
function->body = parse_suite("lambda declaration", body, true);
complete_extents(function);
complete_extents(lambda);
@@ -3155,6 +3204,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
current_function = previous_function;
in_lambda = previous_in_lambda;
lambda->function = function;
+
+ // Reset break/continue state.
+ can_break = could_break;
+ can_continue = could_continue;
+
return lambda;
}
@@ -3175,7 +3229,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *
}
GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) {
- push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)");
+ push_error(R"("yield" was removed in Godot 4. Use "await" instead.)");
return nullptr;
}
@@ -3248,35 +3302,125 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
}
#ifdef TOOLS_ENABLED
-static bool _in_codeblock(String p_line, bool p_already_in, int *r_block_begins = nullptr) {
- int start_block = p_line.rfind("[codeblock]");
- int end_block = p_line.rfind("[/codeblock]");
-
- if (start_block != -1 && r_block_begins) {
- *r_block_begins = start_block;
+enum DocLineState {
+ DOC_LINE_NORMAL,
+ DOC_LINE_IN_CODE,
+ DOC_LINE_IN_CODEBLOCK,
+};
+
+static String _process_doc_line(const String &p_line, const String &p_text, const String &p_space_prefix, DocLineState &r_state) {
+ String line = p_line;
+ if (r_state == DOC_LINE_NORMAL) {
+ line = line.strip_edges(true, false);
+ } else {
+ line = line.trim_prefix(p_space_prefix);
}
- if (p_already_in) {
- if (end_block == -1) {
- return true;
- } else if (start_block == -1) {
- return false;
+ String line_join;
+ if (!p_text.is_empty()) {
+ if (r_state == DOC_LINE_NORMAL) {
+ if (p_text.ends_with("[/codeblock]")) {
+ line_join = "\n";
+ } else if (!p_text.ends_with("[br]")) {
+ line_join = " ";
+ }
} else {
- return start_block > end_block;
+ line_join = "\n";
}
- } else {
- if (start_block == -1) {
- return false;
- } else if (end_block == -1) {
- return true;
- } else {
- return start_block > end_block;
+ }
+
+ String result;
+ int from = 0;
+ int buffer_start = 0;
+ const int len = line.length();
+ bool process = true;
+ while (process) {
+ switch (r_state) {
+ case DOC_LINE_NORMAL: {
+ int lb_pos = line.find_char('[', from);
+ if (lb_pos < 0) {
+ process = false;
+ break;
+ }
+ int rb_pos = line.find_char(']', lb_pos + 1);
+ if (rb_pos < 0) {
+ process = false;
+ break;
+ }
+
+ from = rb_pos + 1;
+
+ String tag = line.substr(lb_pos + 1, rb_pos - lb_pos - 1);
+ if (tag == "code") {
+ r_state = DOC_LINE_IN_CODE;
+ } else if (tag == "codeblock") {
+ if (lb_pos == 0) {
+ line_join = "\n";
+ } else {
+ result += line.substr(buffer_start, lb_pos - buffer_start) + '\n';
+ }
+ result += "[codeblock]";
+ if (from < len) {
+ result += '\n';
+ }
+
+ r_state = DOC_LINE_IN_CODEBLOCK;
+ buffer_start = from;
+ }
+ } break;
+ case DOC_LINE_IN_CODE: {
+ int pos = line.find("[/code]", from);
+ if (pos < 0) {
+ process = false;
+ break;
+ }
+
+ from = pos + 7;
+
+ r_state = DOC_LINE_NORMAL;
+ } break;
+ case DOC_LINE_IN_CODEBLOCK: {
+ int pos = line.find("[/codeblock]", from);
+ if (pos < 0) {
+ process = false;
+ break;
+ }
+
+ from = pos + 12;
+
+ if (pos == 0) {
+ line_join = "\n";
+ } else {
+ result += line.substr(buffer_start, pos - buffer_start) + '\n';
+ }
+ result += "[/codeblock]";
+ if (from < len) {
+ result += '\n';
+ }
+
+ r_state = DOC_LINE_NORMAL;
+ buffer_start = from;
+ } break;
}
}
+
+ result += line.substr(buffer_start);
+ if (r_state == DOC_LINE_NORMAL) {
+ result = result.strip_edges(false, true);
+ }
+
+ return line_join + result;
}
-bool GDScriptParser::has_comment(int p_line) {
- return tokenizer.get_comments().has(p_line);
+bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {
+ 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("##");
}
String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) {
@@ -3293,7 +3437,7 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) {
String doc;
int line = p_line;
- bool in_codeblock = false;
+ DocLineState state = DOC_LINE_NORMAL;
while (comments.has(line - 1)) {
if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) {
@@ -3302,29 +3446,24 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) {
line--;
}
- int codeblock_begins = 0;
+ String space_prefix;
+ if (comments.has(line) && comments[line].comment.begins_with("##")) {
+ int i = 2;
+ for (; i < comments[line].comment.length(); i++) {
+ if (comments[line].comment[i] != ' ') {
+ break;
+ }
+ }
+ space_prefix = String(" ").repeat(i - 2);
+ }
+
while (comments.has(line)) {
if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
break;
}
- String doc_line = comments[line].comment.trim_prefix("##");
-
- in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins);
-
- if (in_codeblock) {
- int i = 0;
- for (; i < codeblock_begins; i++) {
- if (doc_line[i] != ' ') {
- break;
- }
- }
- doc_line = doc_line.substr(i);
- } else {
- doc_line = doc_line.strip_edges();
- }
- String line_join = (in_codeblock) ? "\n" : " ";
- doc = (doc.is_empty()) ? doc_line : doc + line_join + doc_line;
+ String doc_line = comments[line].comment.trim_prefix("##");
+ doc += _process_doc_line(doc_line, doc, space_prefix, state);
line++;
}
@@ -3339,7 +3478,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
ERR_FAIL_COND(!p_brief.is_empty() || !p_desc.is_empty() || p_tutorials.size() != 0);
int line = p_line;
- bool in_codeblock = false;
+ DocLineState state = DOC_LINE_NORMAL;
enum Mode {
BRIEF,
DESC,
@@ -3357,96 +3496,87 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
}
}
- int codeblock_begins = 0;
+ String space_prefix;
+ if (comments.has(line) && comments[line].comment.begins_with("##")) {
+ int i = 2;
+ for (; i < comments[line].comment.length(); i++) {
+ if (comments[line].comment[i] != ' ') {
+ break;
+ }
+ }
+ space_prefix = String(" ").repeat(i - 2);
+ }
+
while (comments.has(line)) {
if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
break;
}
- String title, link; // For tutorials.
String doc_line = comments[line++].comment.trim_prefix("##");
- String stripped_line = doc_line.strip_edges();
-
- // Set the read mode.
- if (stripped_line.is_empty() && mode == BRIEF && !p_brief.is_empty()) {
- mode = DESC;
- continue;
-
- } else if (stripped_line.begins_with("@tutorial")) {
- int begin_scan = String("@tutorial").length();
- if (begin_scan >= stripped_line.length()) {
- continue; // invalid syntax.
- }
-
- if (stripped_line[begin_scan] == ':') { // No title.
- // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional.
- title = "";
- link = stripped_line.trim_prefix("@tutorial:").strip_edges();
-
- } else {
- /* Syntax:
- * @tutorial ( The Title Here ) : https://the.url/
- * ^ open ^ close ^ colon ^ url
- */
- int open_bracket_pos = begin_scan, close_bracket_pos = 0;
- 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 == stripped_line.length() || stripped_line[open_bracket_pos++] != '(') {
- continue; // invalid syntax.
- }
- close_bracket_pos = open_bracket_pos;
- while (close_bracket_pos < stripped_line.length() && stripped_line[close_bracket_pos] != ')') {
- close_bracket_pos++;
- }
- if (close_bracket_pos == stripped_line.length()) {
- continue; // invalid syntax.
- }
+ String title, link; // For tutorials.
- int colon_pos = close_bracket_pos + 1;
- while (colon_pos < stripped_line.length() && (stripped_line[colon_pos] == ' ' || stripped_line[colon_pos] == '\t')) {
- colon_pos++;
+ if (state == DOC_LINE_NORMAL) {
+ // Set the read mode.
+ String stripped_line = doc_line.strip_edges();
+ if (stripped_line.is_empty()) {
+ if (mode == BRIEF && !p_brief.is_empty()) {
+ mode = DESC;
}
- if (colon_pos == stripped_line.length() || stripped_line[colon_pos++] != ':') {
- continue; // invalid syntax.
+ continue;
+ } else if (stripped_line.begins_with("@tutorial")) {
+ int begin_scan = String("@tutorial").length();
+ if (begin_scan >= stripped_line.length()) {
+ continue; // Invalid syntax.
}
- 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 (stripped_line.is_empty()) {
- continue;
- } else {
- // Tutorial docs are single line, we need a @tag after it.
- if (mode == TUTORIALS) {
- mode = DONE;
- }
+ if (stripped_line[begin_scan] == ':') { // No title.
+ // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional.
+ title = "";
+ link = stripped_line.trim_prefix("@tutorial:").strip_edges();
+ } else {
+ /* Syntax:
+ * @tutorial ( The Title Here ) : https://the.url/
+ * ^ open ^ close ^ colon ^ url
+ */
+ int open_bracket_pos = begin_scan, close_bracket_pos = 0;
+ 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 == stripped_line.length() || stripped_line[open_bracket_pos++] != '(') {
+ continue; // Invalid syntax.
+ }
+ close_bracket_pos = open_bracket_pos;
+ while (close_bracket_pos < stripped_line.length() && stripped_line[close_bracket_pos] != ')') {
+ close_bracket_pos++;
+ }
+ if (close_bracket_pos == stripped_line.length()) {
+ continue; // Invalid syntax.
+ }
- in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins);
- }
+ int colon_pos = close_bracket_pos + 1;
+ while (colon_pos < stripped_line.length() && (stripped_line[colon_pos] == ' ' || stripped_line[colon_pos] == '\t')) {
+ colon_pos++;
+ }
+ if (colon_pos == stripped_line.length() || stripped_line[colon_pos++] != ':') {
+ continue; // Invalid syntax.
+ }
- if (in_codeblock) {
- int i = 0;
- for (; i < codeblock_begins; i++) {
- if (doc_line[i] != ' ') {
- break;
+ 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;
+ } else if (mode == TUTORIALS) { // Tutorial docs are single line, we need a @tag after it.
+ mode = DONE;
}
- doc_line = doc_line.substr(i);
- } else {
- doc_line = stripped_line;
}
- String line_join = (in_codeblock) ? "\n" : " ";
switch (mode) {
case BRIEF:
- p_brief = (p_brief.length() == 0) ? doc_line : p_brief + line_join + doc_line;
+ p_brief += _process_doc_line(doc_line, p_brief, space_prefix, state);
break;
case DESC:
- p_desc = (p_desc.length() == 0) ? doc_line : p_desc + line_join + doc_line;
+ p_desc += _process_doc_line(doc_line, p_desc, space_prefix, state);
break;
case TUTORIALS:
p_tutorials.append(Pair<String, String>(title, link));
@@ -3455,6 +3585,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
break;
}
}
+
if (current_class->members.size() > 0) {
const ClassNode::Member &m = current_class->members[0];
int first_member_line = m.get_line();
@@ -3665,6 +3796,12 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
}
bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) {
+#ifdef DEBUG_ENABLED
+ if (this->_is_tool) {
+ push_error(R"("@tool" annotation can only be used once.)", p_annotation);
+ return false;
+ }
+#endif // DEBUG_ENABLED
this->_is_tool = true;
return true;
}
@@ -3673,6 +3810,16 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p
ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)");
ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
ClassNode *p_class = static_cast<ClassNode *>(p_node);
+#ifdef DEBUG_ENABLED
+ if (!p_class->icon_path.is_empty()) {
+ push_error(R"("@icon" annotation can only be used once.)", p_annotation);
+ return false;
+ }
+ if (String(p_annotation->resolved_arguments[0]).is_empty()) {
+ push_error(R"("@icon" annotation argument must contain the path to the icon.)", p_annotation->arguments[0]);
+ return false;
+ }
+#endif // DEBUG_ENABLED
p_class->icon_path = p_annotation->resolved_arguments[0];
return true;
}
@@ -3685,8 +3832,12 @@ bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node
}
VariableNode *variable = static_cast<VariableNode *>(p_node);
+ if (variable->is_static) {
+ push_error(R"("@onready" annotation cannot be applied to a static variable.)", p_annotation);
+ return false;
+ }
if (variable->onready) {
- push_error(R"("@onready" annotation can only be used once per variable.)");
+ push_error(R"("@onready" annotation can only be used once per variable.)", p_annotation);
return false;
}
variable->onready = true;
@@ -3699,6 +3850,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
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);
+ if (variable->is_static) {
+ push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
+ return false;
+ }
if (variable->exported) {
push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);
return false;
@@ -3843,7 +3998,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
variable->export_info.hint_string = export_type.to_string();
} else {
- push_error(R"(Export type can only be built-in, a resource, a node or an enum.)", variable);
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable);
return false;
}
@@ -3888,8 +4043,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint_string = enum_hint_string;
} break;
default:
- // TODO: Allow custom user resources.
- 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);
break;
}
@@ -4000,21 +4154,16 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
Dictionary rpc_config;
rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
if (!p_annotation->resolved_arguments.is_empty()) {
- 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();
- last -= 1;
- }
- if (last > 3) {
- push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation);
- return false;
- }
-
unsigned char locality_args = 0;
unsigned char permission_args = 0;
unsigned char transfer_mode_args = 0;
- for (int i = last; i >= 0; i--) {
+ for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
+ if (i == 3) {
+ rpc_config["channel"] = p_annotation->resolved_arguments[i].operator int();
+ continue;
+ }
+
String arg = p_annotation->resolved_arguments[i].operator String();
if (arg == "call_local") {
locality_args++;
@@ -4054,6 +4203,17 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
return true;
}
+bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target) {
+ 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 *p_class = static_cast<ClassNode *>(p_target);
+ if (p_class->annotated_static_unload) {
+ push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation);
+ return false;
+ }
+ p_class->annotated_static_unload = true;
+ return true;
+}
+
GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const {
switch (type) {
case CONSTANT:
@@ -4108,16 +4268,13 @@ String GDScriptParser::DataType::to_string() const {
}
return native_type.operator String();
case CLASS:
- if (is_meta_type) {
- return GDScript::get_class_static();
- }
if (class_type->identifier != nullptr) {
return class_type->identifier->name.operator String();
}
return class_type->fqcn;
case SCRIPT: {
if (is_meta_type) {
- return script_type->get_class_name().operator String();
+ return script_type != nullptr ? script_type->get_class_name().operator String() : "";
}
String name = script_type != nullptr ? script_type->get_name() : "";
if (!name.is_empty()) {
@@ -4466,7 +4623,7 @@ void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) {
} else {
first = false;
}
- push_text(p_class->extends[i]);
+ push_text(p_class->extends[i]->name);
}
}