summaryrefslogtreecommitdiff
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.cpp353
1 files changed, 287 insertions, 66 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 19584ce194..5e210074ed 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -39,6 +39,7 @@
#ifdef DEBUG_ENABLED
#include "core/os/os.h"
#include "core/string/string_builder.h"
+#include "gdscript_warning.h"
#endif // DEBUG_ENABLED
#ifdef TOOLS_ENABLED
@@ -132,9 +133,9 @@ 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("@warning_ignore", { Variant::STRING, "warning" }), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, 0, 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);
- // TODO: Warning annotations.
}
GDScriptParser::~GDScriptParser() {
@@ -196,6 +197,10 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
return;
}
+ if (ignored_warning_codes.has(p_code)) {
+ return;
+ }
+
String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower();
if (ignored_warnings.has(warn_name)) {
return;
@@ -579,12 +584,12 @@ void GDScriptParser::parse_program() {
}
}
- parse_class_body();
+ parse_class_body(true);
#ifdef TOOLS_ENABLED
- for (Map<int, GDScriptTokenizer::CommentData>::Element *E = tokenizer.get_comments().front(); E; E = E->next()) {
- if (E->get().new_line && E->get().comment.begins_with("##")) {
- class_doc_line = MIN(class_doc_line, E->key());
+ for (const KeyValue<int, GDScriptTokenizer::CommentData> &E : tokenizer.get_comments()) {
+ if (E.value.new_line && E.value.comment.begins_with("##")) {
+ class_doc_line = MIN(class_doc_line, E.key);
}
}
if (has_comment(class_doc_line)) {
@@ -615,9 +620,10 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() {
}
consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after class declaration.)");
- consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected newline after class declaration.)");
- if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) {
+ bool multiline = match(GDScriptTokenizer::Token::NEWLINE);
+
+ if (multiline && !consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) {
current_class = previous_class;
return n_class;
}
@@ -630,9 +636,11 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() {
end_statement("superclass");
}
- parse_class_body();
+ parse_class_body(multiline);
- consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)");
+ if (multiline) {
+ consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)");
+ }
current_class = previous_class;
return n_class;
@@ -698,24 +706,21 @@ void GDScriptParser::parse_extends() {
template <class T>
void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind) {
advance();
- T *member = (this->*p_parse_function)();
- if (member == nullptr) {
- return;
- }
+
#ifdef TOOLS_ENABLED
- int doc_comment_line = member->start_line - 1;
+ int doc_comment_line = previous.start_line - 1;
#endif // TOOLS_ENABLED
// Consume annotations.
+ List<AnnotationNode *> annotations;
while (!annotation_stack.is_empty()) {
AnnotationNode *last_annotation = annotation_stack.back()->get();
if (last_annotation->applies_to(p_target)) {
- member->annotations.push_front(last_annotation);
+ annotations.push_front(last_annotation);
annotation_stack.pop_back();
} else {
push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind));
clear_unused_annotations();
- return;
}
#ifdef TOOLS_ENABLED
if (last_annotation->start_line == doc_comment_line) {
@@ -724,6 +729,16 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
#endif // TOOLS_ENABLED
}
+ T *member = (this->*p_parse_function)();
+ if (member == nullptr) {
+ return;
+ }
+
+ // Apply annotations.
+ for (AnnotationNode *&annotation : annotations) {
+ member->annotations.push_back(annotation);
+ }
+
#ifdef TOOLS_ENABLED
// Consume doc comments.
class_doc_line = MIN(class_doc_line, doc_comment_line - 1);
@@ -737,17 +752,33 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
#endif // TOOLS_ENABLED
if (member->identifier != nullptr) {
- // Enums may be unnamed.
- // TODO: Consider names in outer scope too, for constants and classes (and static functions?)
- if (current_class->members_indices.has(member->identifier->name)) {
- push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier);
+ if (!((String)member->identifier->name).is_empty()) { // Enums may be unnamed.
+
+#ifdef DEBUG_ENABLED
+ List<MethodInfo> gdscript_funcs;
+ GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs);
+ for (MethodInfo &info : gdscript_funcs) {
+ if (info.name == member->identifier->name) {
+ push_warning(member->identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_member_kind, member->identifier->name, "built-in function");
+ }
+ }
+ if (Variant::has_utility_function(member->identifier->name)) {
+ push_warning(member->identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_member_kind, member->identifier->name, "built-in function");
+ }
+#endif
+
+ if (current_class->members_indices.has(member->identifier->name)) {
+ push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier);
+ } else {
+ current_class->add_member(member);
+ }
} else {
current_class->add_member(member);
}
}
}
-void GDScriptParser::parse_class_body() {
+void GDScriptParser::parse_class_body(bool p_is_multiline) {
bool class_end = false;
while (!class_end && !is_at_end()) {
switch (current.type) {
@@ -786,6 +817,8 @@ void GDScriptParser::parse_class_body() {
class_end = true;
break;
default:
+ // Display a completion with identifiers.
+ make_completion_context(COMPLETION_IDENTIFIER, nullptr);
push_error(vformat(R"(Unexpected "%s" in class body.)", current.get_name()));
advance();
break;
@@ -793,6 +826,9 @@ void GDScriptParser::parse_class_body() {
if (panic_mode) {
synchronize();
}
+ if (!p_is_multiline) {
+ class_end = true;
+ }
}
}
@@ -941,7 +977,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var
void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
switch (p_variable->property) {
- case VariableNode::PROP_INLINE:
+ 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();
@@ -949,9 +985,32 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*");
consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*");
- p_variable->setter = parse_suite("setter definition");
- break;
+ IdentifierNode *identifier = alloc_node<IdentifierNode>();
+ identifier->name = "@" + p_variable->identifier->name + "_setter";
+
+ FunctionNode *function = alloc_node<FunctionNode>();
+ function->identifier = identifier;
+
+ FunctionNode *previous_function = current_function;
+ current_function = function;
+ ParameterNode *parameter = alloc_node<ParameterNode>();
+ parameter->identifier = p_variable->setter_parameter;
+
+ if (parameter->identifier != nullptr) {
+ function->parameters_indices[parameter->identifier->name] = 0;
+ function->parameters.push_back(parameter);
+
+ 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;
+ break;
+ }
case VariableNode::PROP_SETGET:
consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "set")");
make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable);
@@ -966,11 +1025,25 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) {
void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
switch (p_variable->property) {
- case VariableNode::PROP_INLINE:
+ case VariableNode::PROP_INLINE: {
consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)");
- p_variable->getter = parse_suite("getter definition");
+ IdentifierNode *identifier = alloc_node<IdentifierNode>();
+ identifier->name = "@" + p_variable->identifier->name + "_getter";
+
+ FunctionNode *function = alloc_node<FunctionNode>();
+ function->identifier = identifier;
+
+ FunctionNode *previous_function = current_function;
+ current_function = function;
+
+ SuiteNode *body = alloc_node<SuiteNode>();
+ function->body = parse_suite("getter declaration", body);
+
+ p_variable->getter = function;
+ current_function = previous_function;
break;
+ }
case VariableNode::PROP_SETGET:
consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")");
make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable);
@@ -1053,7 +1126,9 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
SignalNode *signal = alloc_node<SignalNode>();
signal->identifier = parse_identifier();
- if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ push_multiline(true);
+ advance();
do {
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Allow for trailing comma.
@@ -1076,6 +1151,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
}
} while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
+ pop_multiline();
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*");
}
@@ -1099,13 +1175,31 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
HashMap<StringName, int> elements;
+#ifdef DEBUG_ENABLED
+ List<MethodInfo> gdscript_funcs;
+ GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs);
+#endif
+
do {
if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
break; // Allow trailing comma.
}
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for enum key.)")) {
EnumNode::Value item;
- item.identifier = parse_identifier();
+ GDScriptParser::IdentifierNode *identifier = parse_identifier();
+#ifdef DEBUG_ENABLED
+ for (MethodInfo &info : gdscript_funcs) {
+ if (info.name == identifier->name) {
+ push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function");
+ }
+ }
+ if (Variant::has_utility_function(identifier->name)) {
+ push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function");
+ } else if (ClassDB::class_exists(identifier->name)) {
+ push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "global class");
+ }
+#endif
+ item.identifier = identifier;
item.parent_enum = enum_node;
item.line = previous.start_line;
item.leftmost_column = previous.leftmost_column;
@@ -1358,6 +1452,9 @@ 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)) {
+ break;
+ }
Node *statement = parse_statement();
if (statement == nullptr) {
if (error_count++ > 100) {
@@ -1398,7 +1495,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
break;
}
- } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end());
+ } while ((multiline || previous.type == GDScriptTokenizer::Token::SEMICOLON) && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end());
if (multiline) {
if (!lambda_ended) {
@@ -1424,6 +1521,8 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code;
#endif
+ bool is_annotation = false;
+
switch (current.type) {
case GDScriptTokenizer::Token::PASS:
advance();
@@ -1493,6 +1592,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
break;
case GDScriptTokenizer::Token::ANNOTATION: {
advance();
+ is_annotation = true;
AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT);
if (annotation != nullptr) {
annotation_stack.push_back(annotation);
@@ -1533,6 +1633,18 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
}
}
+ // Apply annotations to statement.
+ while (!is_annotation && result != nullptr && !annotation_stack.is_empty()) {
+ AnnotationNode *last_annotation = annotation_stack.back()->get();
+ if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) {
+ result->annotations.push_front(last_annotation);
+ annotation_stack.pop_back();
+ } else {
+ push_error(vformat(R"(Annotation "%s" cannot be applied to a statement.)", last_annotation->name));
+ clear_unused_annotations();
+ }
+ }
+
#ifdef DEBUG_ENABLED
if (unreachable && result != nullptr) {
current_suite->has_unreachable_code = true;
@@ -1608,6 +1720,10 @@ 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".)");
+ }
+
consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)");
// Save break/continue state.
@@ -2123,22 +2239,34 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionN
operation->operation = UnaryOpNode::OP_NEGATIVE;
operation->variant_op = Variant::OP_NEGATE;
operation->operand = parse_precedence(PREC_SIGN, false);
+ if (operation->operand == nullptr) {
+ push_error(R"(Expected expression after "-" operator.)");
+ }
break;
case GDScriptTokenizer::Token::PLUS:
operation->operation = UnaryOpNode::OP_POSITIVE;
operation->variant_op = Variant::OP_POSITIVE;
operation->operand = parse_precedence(PREC_SIGN, false);
+ if (operation->operand == nullptr) {
+ push_error(R"(Expected expression after "+" operator.)");
+ }
break;
case GDScriptTokenizer::Token::TILDE:
operation->operation = UnaryOpNode::OP_COMPLEMENT;
operation->variant_op = Variant::OP_BIT_NEGATE;
operation->operand = parse_precedence(PREC_BIT_NOT, false);
+ if (operation->operand == nullptr) {
+ push_error(R"(Expected expression after "~" operator.)");
+ }
break;
case GDScriptTokenizer::Token::NOT:
case GDScriptTokenizer::Token::BANG:
operation->operation = UnaryOpNode::OP_LOGIC_NOT;
operation->variant_op = Variant::OP_NOT;
operation->operand = parse_precedence(PREC_LOGIC_NOT, false);
+ if (operation->operand == nullptr) {
+ push_error(vformat(R"(Expected expression after "%s" operator.)", op_type == GDScriptTokenizer::Token::NOT ? "not" : "!"));
+ }
break;
default:
return nullptr; // Unreachable.
@@ -2275,6 +2403,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(Expressio
operation->false_expr = parse_precedence(PREC_TERNARY, false);
+ if (operation->false_expr == nullptr) {
+ push_error(R"(Expected expression after "else".)");
+ }
+
return operation;
}
@@ -2472,8 +2604,13 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
switch (dictionary->style) {
case DictionaryNode::LUA_TABLE:
- if (key != nullptr && key->type != Node::IDENTIFIER) {
- push_error("Expected identifier as LUA-style dictionary key.");
+ if (key != nullptr && key->type != Node::IDENTIFIER && key->type != Node::LITERAL) {
+ push_error("Expected identifier or string as LUA-style dictionary key.");
+ 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.");
advance();
break;
}
@@ -2487,7 +2624,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode
}
if (key != nullptr) {
key->is_constant = true;
- key->reduced_value = static_cast<IdentifierNode *>(key)->name;
+ if (key->type == Node::IDENTIFIER) {
+ key->reduced_value = static_cast<IdentifierNode *>(key)->name;
+ } else if (key->type == Node::LITERAL) {
+ key->reduced_value = StringName(static_cast<LiteralNode *>(key)->value.operator String());
+ }
}
break;
case DictionaryNode::PYTHON_DICT:
@@ -2567,6 +2708,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *
subscript->base = p_previous_operand;
subscript->index = parse_expression(false);
+ if (subscript->index == nullptr) {
+ push_error(R"(Expected expression after "[".)");
+ }
+
pop_multiline();
consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)");
@@ -2692,6 +2837,23 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
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 "/".)");
+ 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 {
push_error(R"(Expect node path as string or identifier after "$".)");
return nullptr;
@@ -2781,6 +2943,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
return lambda;
}
+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.)");
+ return nullptr;
+}
+
GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign) {
// Just for better error messages.
GDScriptTokenizer::Token::Type invalid = previous.type;
@@ -2931,7 +3098,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
if (!comments.has(p_line)) {
return;
}
- ERR_FAIL_COND(p_brief != "" || p_desc != "" || p_tutorials.size() != 0);
+ ERR_FAIL_COND(!p_brief.is_empty() || !p_desc.is_empty() || p_tutorials.size() != 0);
int line = p_line;
bool in_codeblock = false;
@@ -2963,7 +3130,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
String striped_line = doc_line.strip_edges();
// Set the read mode.
- if (striped_line.begins_with("@desc:") && p_desc == "") {
+ if (striped_line.begins_with("@desc:") && p_desc.is_empty()) {
mode = DESC;
striped_line = striped_line.trim_prefix("@desc:");
in_codeblock = _in_codeblock(doc_line, in_codeblock);
@@ -2981,9 +3148,9 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
} else {
/* Syntax:
- @tutorial ( The Title Here ) : https://the.url/
- ^ open ^ close ^ colon ^ url
- */
+ * @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 < striped_line.length() && (striped_line[open_bracket_pos] == ' ' || striped_line[open_bracket_pos] == '\t')) {
open_bracket_pos++;
@@ -3137,7 +3304,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
{ nullptr, nullptr, PREC_NONE }, // TRAIT,
{ nullptr, nullptr, PREC_NONE }, // VAR,
{ nullptr, nullptr, PREC_NONE }, // VOID,
- { nullptr, nullptr, PREC_NONE }, // YIELD,
+ { &GDScriptParser::parse_yield, nullptr, PREC_NONE }, // YIELD,
// Punctuation
{ &GDScriptParser::parse_array, &GDScriptParser::parse_subscript, PREC_SUBSCRIPT }, // BRACKET_OPEN,
{ nullptr, nullptr, PREC_NONE }, // BRACKET_CLOSE,
@@ -3244,8 +3411,8 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
Variant::construct(parameter.type, r, &(name), 1, error);
p_annotation->resolved_arguments.push_back(r);
if (error.error != Callable::CallError::CALL_OK) {
- push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
- p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1);
+ push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
+ p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1);
return false;
}
break;
@@ -3253,13 +3420,13 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
[[fallthrough]];
default: {
if (argument->type != Node::LITERAL) {
- push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
+ push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
return false;
}
Variant value = static_cast<LiteralNode *>(argument)->value;
if (!Variant::can_convert_strict(value.get_type(), parameter.type)) {
- push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
+ push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
return false;
}
Callable::CallError error;
@@ -3268,8 +3435,8 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
Variant::construct(parameter.type, r, &(args), 1, error);
p_annotation->resolved_arguments.push_back(r);
if (error.error != Callable::CallError::CALL_OK) {
- push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
- p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1);
+ push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
+ p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1);
return false;
}
break;
@@ -3330,7 +3497,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint_string = hint_string;
- // This is called after tne analyzer is done finding the type, so this should be set here.
+ // This is called after the analyzer is done finding the type, so this should be set here.
DataType export_type = variable->get_datatype();
if (p_annotation->name == "@export") {
@@ -3372,12 +3539,12 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint = PROPERTY_HINT_ENUM;
String enum_hint_string;
- for (const Map<StringName, int>::Element *E = export_type.enum_values.front(); E; E = E->next()) {
- enum_hint_string += E->key().operator String().camelcase_to_underscore(true).capitalize().xml_escape();
+ for (OrderedHashMap<StringName, int>::Element E = export_type.enum_values.front(); E; E = E.next()) {
+ enum_hint_string += E.key().operator String().capitalize().xml_escape();
enum_hint_string += ":";
- enum_hint_string += String::num_int64(E->get()).xml_escape();
+ enum_hint_string += String::num_int64(E.value()).xml_escape();
- if (E->next()) {
+ if (E.next()) {
enum_hint_string += ",";
}
}
@@ -3414,7 +3581,24 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) {
- ERR_FAIL_V_MSG(false, "Not implemented.");
+#ifdef DEBUG_ENABLED
+ bool has_error = false;
+ for (const Variant &warning_name : p_annotation->resolved_arguments) {
+ GDScriptWarning::Code warning = GDScriptWarning::get_code_from_name(String(warning_name).to_upper());
+ if (warning == GDScriptWarning::WARNING_MAX) {
+ push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation);
+ has_error = true;
+ } else {
+ p_node->ignored_warnings.push_back(warning);
+ }
+ }
+
+ return !has_error;
+
+#else // ! DEBUG_ENABLED
+ // Only available in debug builds.
+ return true;
+#endif // DEBUG_ENABLED
}
template <Multiplayer::RPCMode t_mode>
@@ -3435,22 +3619,22 @@ 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") {
- rpc_config.rpc_mode = Multiplayer::RPC_MODE_ANY;
- } else if (mode == "auth") {
+ if (mode == "any_peer") {
+ rpc_config.rpc_mode = Multiplayer::RPC_MODE_ANY_PEER;
+ } else if (mode == "authority") {
rpc_config.rpc_mode = Multiplayer::RPC_MODE_AUTHORITY;
- } else if (mode == "sync") {
- rpc_config.sync = true;
- } else if (mode == "nosync") {
- rpc_config.sync = false;
+ } else if (mode == "call_local") {
+ rpc_config.call_local = true;
+ } else if (mode == "call_remote") {
+ rpc_config.call_local = false;
} else if (mode == "reliable") {
rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE;
} else if (mode == "unreliable") {
rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE;
- } else if (mode == "ordered") {
- rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_ORDERED;
+ } else if (mode == "unreliable_ordered") {
+ rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE_ORDERED;
} else {
- push_error(R"(Invalid RPC argument. Must be one of: 'sync'/'nosync' (local calls), 'any'/'auth' (permission), 'reliable'/'unreliable'/'ordered' (transfer mode).)", p_annotation);
+ 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);
}
}
}
@@ -3562,6 +3746,39 @@ String GDScriptParser::DataType::to_string() const {
ERR_FAIL_V_MSG("<unresolved type", "Kind set outside the enum range.");
}
+static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) {
+ switch (p_type) {
+ case Variant::PACKED_BYTE_ARRAY:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY:
+ return Variant::INT;
+ case Variant::PACKED_FLOAT32_ARRAY:
+ case Variant::PACKED_FLOAT64_ARRAY:
+ return Variant::FLOAT;
+ case Variant::PACKED_STRING_ARRAY:
+ return Variant::STRING;
+ case Variant::PACKED_VECTOR2_ARRAY:
+ return Variant::VECTOR2;
+ case Variant::PACKED_VECTOR3_ARRAY:
+ return Variant::VECTOR3;
+ case Variant::PACKED_COLOR_ARRAY:
+ return Variant::COLOR;
+ default:
+ return Variant::NIL;
+ }
+}
+
+bool GDScriptParser::DataType::is_typed_container_type() const {
+ return kind == GDScriptParser::DataType::BUILTIN && _variant_type_to_typed_array_element_type(builtin_type) != Variant::NIL;
+}
+
+GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() const {
+ GDScriptParser::DataType type;
+ type.kind = GDScriptParser::DataType::BUILTIN;
+ type.builtin_type = _variant_type_to_typed_array_element_type(builtin_type);
+ return type;
+}
+
/*---------- PRETTY PRINT FOR DEBUG ----------*/
#ifdef DEBUG_ENABLED
@@ -4019,7 +4236,11 @@ void GDScriptParser::TreePrinter::print_get_node(GetNodeNode *p_get_node) {
}
void GDScriptParser::TreePrinter::print_identifier(IdentifierNode *p_identifier) {
- push_text(p_identifier->name);
+ if (p_identifier != nullptr) {
+ push_text(p_identifier->name);
+ } else {
+ push_text("<invalid identifier>");
+ }
}
void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) {
@@ -4354,7 +4575,7 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) {
if (p_variable->property == VariableNode::PROP_INLINE) {
push_line(":");
increase_indent();
- print_suite(p_variable->getter);
+ print_suite(p_variable->getter->body);
decrease_indent();
} else {
push_line(" =");
@@ -4374,7 +4595,7 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) {
}
push_line("):");
increase_indent();
- print_suite(p_variable->setter);
+ print_suite(p_variable->setter->body);
decrease_indent();
} else {
push_line(" =");
@@ -4413,7 +4634,7 @@ void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) {
}
print_class(p_parser.get_tree());
- print_line(printed);
+ print_line(String(printed));
}
#endif // DEBUG_ENABLED