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.cpp846
1 files changed, 567 insertions, 279 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 5c2d4a060c..4625855329 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -31,10 +31,7 @@
#include "gdscript_parser.h"
#include "gdscript.h"
-
-#ifdef DEBUG_ENABLED
-#include "gdscript_warning.h"
-#endif
+#include "gdscript_tokenizer_buffer.h"
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
@@ -52,11 +49,18 @@
#include "editor/editor_settings.h"
#endif
+// This function is used to determine that a type is "built-in" as opposed to native
+// and custom classes. So `Variant::NIL` and `Variant::OBJECT` are excluded:
+// `Variant::NIL` - `null` is literal, not a type.
+// `Variant::OBJECT` - `Object` should be treated as a class, not as a built-in type.
static HashMap<StringName, Variant::Type> builtin_types;
Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
- if (builtin_types.is_empty()) {
- for (int i = 1; i < Variant::VARIANT_MAX; i++) {
- builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i;
+ if (unlikely(builtin_types.is_empty())) {
+ for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+ Variant::Type type = (Variant::Type)i;
+ if (type != Variant::NIL && type != Variant::OBJECT) {
+ builtin_types[Variant::get_type_name(type)] = type;
+ }
}
}
@@ -66,8 +70,15 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
return Variant::VARIANT_MAX;
}
+#ifdef TOOLS_ENABLED
+HashMap<String, String> GDScriptParser::theme_color_names;
+#endif
+
+HashMap<StringName, GDScriptParser::AnnotationInfo> GDScriptParser::valid_annotations;
+
void GDScriptParser::cleanup() {
builtin_types.clear();
+ valid_annotations.clear();
}
void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const {
@@ -82,45 +93,55 @@ bool GDScriptParser::annotation_exists(const String &p_annotation_name) const {
GDScriptParser::GDScriptParser() {
// Register valid annotations.
- // TODO: Should this be static?
- register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
- register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
- register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
-
- register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
- // Export annotations.
- register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
- register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true);
- register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true);
- register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>);
- register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true);
- register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>);
- register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>);
- register_annotation(MethodInfo("@export_placeholder", PropertyInfo(Variant::STRING, "placeholder")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>);
- register_annotation(MethodInfo("@export_range", PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max"), PropertyInfo(Variant::FLOAT, "step"), PropertyInfo(Variant::STRING, "extra_hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, varray(1.0, ""), true);
- register_annotation(MethodInfo("@export_exp_easing", PropertyInfo(Variant::STRING, "hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, varray(""), true);
- register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>);
- register_annotation(MethodInfo("@export_node_path", PropertyInfo(Variant::STRING, "type")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, varray(""), true);
- register_annotation(MethodInfo("@export_flags", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, varray(), true);
- register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>);
- register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>);
- register_annotation(MethodInfo("@export_flags_2d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, Variant::INT>);
- register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>);
- register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>);
- register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>);
- register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>);
- // Export grouping annotations.
- register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>);
- register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));
- register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray(""));
- // Warning annotations.
- register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true);
- // Networking.
- register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0));
+ if (unlikely(valid_annotations.is_empty())) {
+ register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
+ register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
+ register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
+
+ register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
+ // Export annotations.
+ register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
+ register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true);
+ register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true);
+ register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>);
+ register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true);
+ register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>);
+ register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>);
+ register_annotation(MethodInfo("@export_placeholder", PropertyInfo(Variant::STRING, "placeholder")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>);
+ register_annotation(MethodInfo("@export_range", PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max"), PropertyInfo(Variant::FLOAT, "step"), PropertyInfo(Variant::STRING, "extra_hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, varray(1.0, ""), true);
+ register_annotation(MethodInfo("@export_exp_easing", PropertyInfo(Variant::STRING, "hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, varray(""), true);
+ register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>);
+ register_annotation(MethodInfo("@export_node_path", PropertyInfo(Variant::STRING, "type")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, varray(""), true);
+ register_annotation(MethodInfo("@export_flags", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, varray(), true);
+ register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_2d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>);
+ register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>);
+ // Export grouping annotations.
+ register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>);
+ register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));
+ register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray(""));
+ // Warning annotations.
+ register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true);
+ // Networking.
+ register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0));
+ }
#ifdef DEBUG_ENABLED
is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable");
#endif
+
+#ifdef TOOLS_ENABLED
+ if (theme_color_names.is_empty()) {
+ 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");
+ }
+#endif
}
GDScriptParser::~GDScriptParser() {
@@ -157,7 +178,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {
#ifdef DEBUG_ENABLED
void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) {
- ERR_FAIL_COND(p_source == nullptr);
+ ERR_FAIL_NULL(p_source);
if (is_ignoring_warnings) {
return;
}
@@ -206,7 +227,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node
if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
return;
}
- if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) {
+ if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
return;
}
CompletionContext context;
@@ -214,7 +235,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node
context.current_class = current_class;
context.current_function = current_function;
context.current_suite = current_suite;
- context.current_line = tokenizer.get_cursor_line();
+ context.current_line = tokenizer->get_cursor_line();
context.current_argument = p_argument;
context.node = p_node;
completion_context = context;
@@ -224,7 +245,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ
if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
return;
}
- if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) {
+ if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
return;
}
CompletionContext context;
@@ -232,7 +253,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ
context.current_class = current_class;
context.current_function = current_function;
context.current_suite = current_suite;
- context.current_line = tokenizer.get_cursor_line();
+ context.current_line = tokenizer->get_cursor_line();
context.builtin_type = p_builtin_type;
completion_context = context;
}
@@ -245,7 +266,7 @@ void GDScriptParser::push_completion_call(Node *p_call) {
call.call = p_call;
call.argument = 0;
completion_call_stack.push_back(call);
- if (previous.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_BEGINNING) {
+ if (previous.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE || previous.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_BEGINNING) {
completion_call = call;
}
}
@@ -308,17 +329,21 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
source = source.replace_first(String::chr(0xFFFF), String());
}
- tokenizer.set_source_code(source);
- tokenizer.set_cursor_position(cursor_line, cursor_column);
- script_path = p_script_path;
- current = tokenizer.scan();
+ GDScriptTokenizerText *text_tokenizer = memnew(GDScriptTokenizerText);
+ text_tokenizer->set_source_code(source);
+
+ tokenizer = text_tokenizer;
+
+ tokenizer->set_cursor_position(cursor_line, cursor_column);
+ script_path = p_script_path.simplify_path();
+ current = tokenizer->scan();
// Avoid error or newline as the first token.
// The latter can mess with the parser when opening files filled exclusively with comments and newlines.
while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) {
if (current.type == GDScriptTokenizer::Token::ERROR) {
push_error(current.literal);
}
- current = tokenizer.scan();
+ current = tokenizer->scan();
}
#ifdef DEBUG_ENABLED
@@ -339,6 +364,9 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
parse_program();
pop_multiline();
+ memdelete(text_tokenizer);
+ tokenizer = nullptr;
+
#ifdef DEBUG_ENABLED
if (multiline_stack.size() > 0) {
ERR_PRINT("Parser bug: Imbalanced multiline stack.");
@@ -352,6 +380,41 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_
}
}
+Error GDScriptParser::parse_binary(const Vector<uint8_t> &p_binary, const String &p_script_path) {
+ GDScriptTokenizerBuffer *buffer_tokenizer = memnew(GDScriptTokenizerBuffer);
+ Error err = buffer_tokenizer->set_code_buffer(p_binary);
+
+ if (err) {
+ memdelete(buffer_tokenizer);
+ return err;
+ }
+
+ tokenizer = buffer_tokenizer;
+ script_path = p_script_path;
+ current = tokenizer->scan();
+ // Avoid error or newline as the first token.
+ // The latter can mess with the parser when opening files filled exclusively with comments and newlines.
+ while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) {
+ if (current.type == GDScriptTokenizer::Token::ERROR) {
+ push_error(current.literal);
+ }
+ current = tokenizer->scan();
+ }
+
+ push_multiline(false); // Keep one for the whole parsing.
+ parse_program();
+ pop_multiline();
+
+ memdelete(buffer_tokenizer);
+ tokenizer = nullptr;
+
+ if (errors.is_empty()) {
+ return OK;
+ } else {
+ return ERR_PARSE_ERROR;
+ }
+}
+
GDScriptTokenizer::Token GDScriptParser::advance() {
lambda_ended = false; // Empty marker since we're past the end in any case.
@@ -359,19 +422,21 @@ GDScriptTokenizer::Token GDScriptParser::advance() {
ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream.");
}
if (for_completion && !completion_call_stack.is_empty()) {
- if (completion_call.call == nullptr && tokenizer.is_past_cursor()) {
+ if (completion_call.call == nullptr && tokenizer->is_past_cursor()) {
completion_call = completion_call_stack.back()->get();
passed_cursor = true;
}
}
previous = current;
- current = tokenizer.scan();
+ current = tokenizer->scan();
while (current.type == GDScriptTokenizer::Token::ERROR) {
push_error(current.literal);
- current = tokenizer.scan();
+ current = tokenizer->scan();
}
- for (Node *n : nodes_in_progress) {
- update_extents(n);
+ if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line.
+ for (Node *n : nodes_in_progress) {
+ update_extents(n);
+ }
}
return previous;
}
@@ -435,19 +500,19 @@ void GDScriptParser::synchronize() {
void GDScriptParser::push_multiline(bool p_state) {
multiline_stack.push_back(p_state);
- tokenizer.set_multiline_mode(p_state);
+ tokenizer->set_multiline_mode(p_state);
if (p_state) {
// Consume potential whitespace tokens already waiting in line.
while (current.type == GDScriptTokenizer::Token::NEWLINE || current.type == GDScriptTokenizer::Token::INDENT || current.type == GDScriptTokenizer::Token::DEDENT) {
- current = tokenizer.scan(); // Don't call advance() here, as we don't want to change the previous token.
+ current = tokenizer->scan(); // Don't call advance() here, as we don't want to change the previous token.
}
}
}
void GDScriptParser::pop_multiline() {
- ERR_FAIL_COND_MSG(multiline_stack.size() == 0, "Parser bug: trying to pop from multiline stack without available value.");
+ ERR_FAIL_COND_MSG(multiline_stack.is_empty(), "Parser bug: trying to pop from multiline stack without available value.");
multiline_stack.pop_back();
- tokenizer.set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false);
+ tokenizer->set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false);
}
bool GDScriptParser::is_statement_end_token() const {
@@ -497,7 +562,7 @@ void GDScriptParser::parse_program() {
if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
// `@icon` needs to be applied in the parser. See GH-72444.
if (annotation->name == SNAME("@icon")) {
- annotation->apply(this, head);
+ annotation->apply(this, head, nullptr);
} else {
head->annotations.push_back(annotation);
}
@@ -566,14 +631,16 @@ void GDScriptParser::parse_program() {
complete_extents(head);
#ifdef TOOLS_ENABLED
- 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);
+ const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();
+ int line = MIN(max_script_doc_line, head->end_line);
+ while (line > 0) {
+ if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) {
+ head->doc_data = parse_class_doc_comment(line);
+ break;
}
+ line--;
}
- if (has_comment(class_doc_line)) {
- get_class_doc_comment(class_doc_line, head->doc_brief_description, head->doc_description, head->doc_tutorials, false);
- }
+
#endif // TOOLS_ENABLED
if (!check(GDScriptTokenizer::Token::TK_EOF)) {
@@ -606,7 +673,7 @@ GDScriptParser::ClassNode *GDScriptParser::find_class(const String &p_qualified_
// Starts at index 1 because index 0 was handled above.
for (int i = 1; result != nullptr && i < class_names.size(); i++) {
- String current_name = class_names[i];
+ const String &current_name = class_names[i];
GDScriptParser::ClassNode *next = nullptr;
if (result->has_member(current_name)) {
GDScriptParser::ClassNode::Member member = result->get_member(current_name);
@@ -734,10 +801,6 @@ template <class T>
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
- int doc_comment_line = previous.start_line - 1;
-#endif // TOOLS_ENABLED
-
// Consume annotations.
List<AnnotationNode *> annotations;
while (!annotation_stack.is_empty()) {
@@ -749,11 +812,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind));
clear_unused_annotations();
}
-#ifdef TOOLS_ENABLED
- if (last_annotation->start_line == doc_comment_line) {
- doc_comment_line--;
- }
-#endif // TOOLS_ENABLED
}
T *member = (this->*p_parse_function)(p_is_static);
@@ -761,42 +819,44 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
return;
}
+#ifdef TOOLS_ENABLED
+ int doc_comment_line = member->start_line - 1;
+#endif // TOOLS_ENABLED
+
for (AnnotationNode *&annotation : annotations) {
member->annotations.push_back(annotation);
+#ifdef TOOLS_ENABLED
+ if (annotation->start_line <= doc_comment_line) {
+ doc_comment_line = annotation->start_line - 1;
+ }
+#endif // TOOLS_ENABLED
}
#ifdef TOOLS_ENABLED
- // Consume doc comments.
- class_doc_line = MIN(class_doc_line, doc_comment_line - 1);
-
- // 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 {
- member->doc_description = get_doc_comment(doc_comment_line);
+ if constexpr (std::is_same_v<T, ClassNode>) {
+ if (has_comment(member->start_line, true)) {
+ // Inline doc comment.
+ member->doc_data = parse_class_doc_comment(member->start_line, true);
+ } else if (has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {
+ // Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members.
+ // This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice.
+ member->doc_data = parse_class_doc_comment(doc_comment_line);
+ }
+ } else {
+ if (has_comment(member->start_line, true)) {
+ // Inline doc comment.
+ member->doc_data = parse_doc_comment(member->start_line, true);
+ } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {
+ // Normal doc comment.
+ member->doc_data = parse_doc_comment(doc_comment_line);
}
}
+
+ min_member_doc_line = member->end_line + 1; // Prevent multiple members from using the same doc comment.
#endif // TOOLS_ENABLED
if (member->identifier != nullptr) {
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 {
@@ -1104,7 +1164,12 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
case VariableNode::PROP_INLINE: {
FunctionNode *function = alloc_node<FunctionNode>();
- consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)");
+ if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after "get(".)*");
+ consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after "get()".)*");
+ } else {
+ consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" or "(" after "get".)");
+ }
IdentifierNode *identifier = alloc_node<IdentifierNode>();
complete_extents(identifier);
@@ -1252,14 +1317,16 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
EnumNode *enum_node = alloc_node<EnumNode>();
bool named = false;
- if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
- advance();
+ if (match(GDScriptTokenizer::Token::IDENTIFIER)) {
enum_node->identifier = parse_identifier();
named = true;
}
push_multiline(true);
consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
+#ifdef TOOLS_ENABLED
+ int min_enum_value_doc_line = previous.end_line + 1;
+#endif
HashMap<StringName, int> elements;
@@ -1322,34 +1389,35 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
}
} while (match(GDScriptTokenizer::Token::COMMA));
- pop_multiline();
- consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");
-
#ifdef TOOLS_ENABLED
// Enum values documentation.
for (int i = 0; i < enum_node->values.size(); i++) {
- if (i == enum_node->values.size() - 1) {
- // If close bracket is same line as last value.
- if (enum_node->values[i].line != previous.start_line && has_comment(enum_node->values[i].line)) {
- if (named) {
- enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true);
- } else {
- current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true));
- }
+ int enum_value_line = enum_node->values[i].line;
+ int doc_comment_line = enum_value_line - 1;
+
+ MemberDocData doc_data;
+ if (has_comment(enum_value_line, true)) {
+ // Inline doc comment.
+ if (i == enum_node->values.size() - 1 || enum_node->values[i + 1].line > enum_value_line) {
+ doc_data = parse_doc_comment(enum_value_line, true);
}
+ } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer->get_comments()[doc_comment_line].new_line) {
+ // Normal doc comment.
+ doc_data = parse_doc_comment(doc_comment_line);
+ }
+
+ if (named) {
+ enum_node->values.write[i].doc_data = doc_data;
} else {
- // If two values are same line.
- if (enum_node->values[i].line != enum_node->values[i + 1].line && has_comment(enum_node->values[i].line)) {
- if (named) {
- enum_node->values.write[i].doc_description = get_doc_comment(enum_node->values[i].line, true);
- } else {
- current_class->set_enum_value_doc(enum_node->values[i].identifier->name, get_doc_comment(enum_node->values[i].line, true));
- }
- }
+ current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, doc_data);
}
+
+ min_enum_value_doc_line = enum_value_line + 1; // Prevent multiple enum values from using the same doc comment.
}
#endif // TOOLS_ENABLED
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");
complete_extents(enum_node);
end_statement("enum");
@@ -1851,7 +1919,18 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
n_for->variable = parse_identifier();
}
- consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable name.)");
+ if (match(GDScriptTokenizer::Token::COLON)) {
+ n_for->datatype_specifier = parse_type();
+ if (n_for->datatype_specifier == nullptr) {
+ push_error(R"(Expected type specifier after ":".)");
+ }
+ }
+
+ if (n_for->datatype_specifier == nullptr) {
+ consume(GDScriptTokenizer::Token::IN, R"(Expected "in" or ":" after "for" variable name.)");
+ } else {
+ consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "for" variable type specifier.)");
+ }
n_for->list = parse_expression(false);
@@ -2011,7 +2090,37 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
push_error(R"(No pattern found for "match" branch.)");
}
- if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) {
+ bool has_guard = false;
+ if (match(GDScriptTokenizer::Token::WHEN)) {
+ // Pattern guard.
+ // Create block for guard because it also needs to access the bound variables from patterns, and we don't want to add them to the outer scope.
+ branch->guard_body = alloc_node<SuiteNode>();
+ if (branch->patterns.size() > 0) {
+ for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) {
+ SuiteNode::Local local(E.value, current_function);
+ local.type = SuiteNode::Local::PATTERN_BIND;
+ branch->guard_body->add_local(local);
+ }
+ }
+
+ SuiteNode *parent_block = current_suite;
+ branch->guard_body->parent_block = parent_block;
+ current_suite = branch->guard_body;
+
+ ExpressionNode *guard = parse_expression(false);
+ if (guard == nullptr) {
+ push_error(R"(Expected expression for pattern guard after "when".)");
+ } else {
+ branch->guard_body->statements.append(guard);
+ }
+ current_suite = parent_block;
+ complete_extents(branch->guard_body);
+
+ has_guard = true;
+ branch->has_wildcard = false; // If it has a guard, the wildcard might still not match.
+ }
+
+ if (!consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":"%s after "match" %s.)", has_guard ? "" : R"( or "when")", has_guard ? "pattern guard" : "patterns"))) {
complete_extents(branch);
return nullptr;
}
@@ -2281,6 +2390,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
IdentifierNode *identifier = alloc_node<IdentifierNode>();
complete_extents(identifier);
identifier->name = previous.get_identifier();
+ if (identifier->name.operator String().is_empty()) {
+ print_line("Empty identifier found.");
+ }
+ identifier->suite = current_suite;
if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
@@ -2444,7 +2557,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression
complete_extents(operation);
if (operation->right_operand == nullptr) {
- push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name()));
+ push_error(vformat(R"(Expected expression after "%s" operator.)", op.get_name()));
}
// TODO: Also for unary, ternary, and assignment.
@@ -2984,7 +3097,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
// Allow for trailing comma.
break;
}
- bool use_identifier_completion = current.cursor_place == GDScriptTokenizer::CURSOR_END || current.cursor_place == GDScriptTokenizer::CURSOR_MIDDLE;
+ bool use_identifier_completion = current.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE;
ExpressionNode *argument = parse_expression(false);
if (argument == nullptr) {
push_error(R"(Expected expression as the function argument.)");
@@ -3033,10 +3146,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
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;
@@ -3140,6 +3251,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign) {
LambdaNode *lambda = alloc_node<LambdaNode>();
lambda->parent_function = current_function;
+ lambda->parent_lambda = current_lambda;
+
FunctionNode *function = alloc_node<FunctionNode>();
function->source_lambda = lambda;
@@ -3154,7 +3267,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
// Reset the multiline stack since we don't want the multiline mode one in the lambda body.
push_multiline(false);
if (multiline_context) {
- tokenizer.push_expression_indented_block();
+ tokenizer->push_expression_indented_block();
}
push_multiline(true); // For the parameters.
@@ -3167,6 +3280,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
FunctionNode *previous_function = current_function;
current_function = function;
+ LambdaNode *previous_lambda = current_lambda;
+ current_lambda = lambda;
+
SuiteNode *body = alloc_node<SuiteNode>();
body->parent_function = current_function;
body->parent_block = current_suite;
@@ -3198,12 +3314,13 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
if (multiline_context) {
// If we're in multiline mode, we want to skip the spurious DEDENT and NEWLINE tokens.
while (check(GDScriptTokenizer::Token::DEDENT) || check(GDScriptTokenizer::Token::INDENT) || check(GDScriptTokenizer::Token::NEWLINE)) {
- current = tokenizer.scan(); // Not advance() since we don't want to change the previous token.
+ current = tokenizer->scan(); // Not advance() since we don't want to change the previous token.
}
- tokenizer.pop_expression_indented_block();
+ tokenizer->pop_expression_indented_block();
}
current_function = previous_function;
+ current_lambda = previous_lambda;
in_lambda = previous_in_lambda;
lambda->function = function;
@@ -3215,6 +3332,19 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
}
GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ // x is not int
+ // ^ ^^^ ExpressionNode, TypeNode
+ // ^^^^^^^^^^^^ TypeTestNode
+ // ^^^^^^^^^^^^ UnaryOpNode
+ UnaryOpNode *not_node = nullptr;
+ if (match(GDScriptTokenizer::Token::NOT)) {
+ not_node = alloc_node<UnaryOpNode>();
+ not_node->operation = UnaryOpNode::OP_LOGIC_NOT;
+ not_node->variant_op = Variant::OP_NOT;
+ reset_extents(not_node, p_previous_operand);
+ update_extents(not_node);
+ }
+
TypeTestNode *type_test = alloc_node<TypeTestNode>();
reset_extents(type_test, p_previous_operand);
update_extents(type_test);
@@ -3223,8 +3353,21 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *
type_test->test_type = parse_type();
complete_extents(type_test);
+ if (not_node != nullptr) {
+ not_node->operand = type_test;
+ complete_extents(not_node);
+ }
+
if (type_test->test_type == nullptr) {
- push_error(R"(Expected type specifier after "is".)");
+ if (not_node == nullptr) {
+ push_error(R"(Expected type specifier after "is".)");
+ } else {
+ push_error(R"(Expected type specifier after "is not".)");
+ }
+ }
+
+ if (not_node != nullptr) {
+ return not_node;
}
return type_test;
@@ -3275,14 +3418,21 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) {
// Typed collection (like Array[int]).
- type->container_type = parse_type(false); // Don't allow void for array element type.
- if (type->container_type == nullptr) {
- push_error(R"(Expected type for collection after "[".)");
- complete_extents(type);
- type = nullptr;
- } else if (type->container_type->container_type != nullptr) {
- push_error("Nested typed collections are not supported.");
- }
+ bool first_pass = true;
+ do {
+ TypeNode *container_type = parse_type(false); // Don't allow void for element type.
+ if (container_type == nullptr) {
+ push_error(vformat(R"(Expected type for collection after "%s".)", first_pass ? "[" : ","));
+ complete_extents(type);
+ type = nullptr;
+ break;
+ } else if (container_type->container_types.size() > 0) {
+ push_error("Nested typed collections are not supported.");
+ } else {
+ type->container_types.append(container_type);
+ }
+ first_pass = false;
+ } while (match(GDScriptTokenizer::Token::COMMA));
consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after collection type.)");
if (type != nullptr) {
complete_extents(type);
@@ -3415,41 +3565,32 @@ static String _process_doc_line(const String &p_line, const String &p_text, cons
}
bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {
- bool has_comment = tokenizer.get_comments().has(p_line);
+ bool has_comment = tokenizer->get_comments().has(p_line);
// If there are no comments or if we don't care whether the comment
// is a docstring, we have our result.
if (!p_must_be_doc || !has_comment) {
return has_comment;
}
- return tokenizer.get_comments()[p_line].comment.begins_with("##");
+ return tokenizer->get_comments()[p_line].comment.begins_with("##");
}
-String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) {
- const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
- ERR_FAIL_COND_V(!comments.has(p_line), String());
-
- if (p_single_line) {
- if (comments[p_line].comment.begins_with("##")) {
- return comments[p_line].comment.trim_prefix("##").strip_edges();
- }
- return "";
- }
-
- String doc;
+GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) {
+ ERR_FAIL_COND_V(!has_comment(p_line, true), MemberDocData());
+ const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();
int line = p_line;
- DocLineState state = DOC_LINE_NORMAL;
- while (comments.has(line - 1)) {
- if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) {
- break;
+ if (!p_single_line) {
+ while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) {
+ line--;
}
- line--;
}
+ max_script_doc_line = MIN(max_script_doc_line, line - 1);
+
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] != ' ') {
@@ -3459,47 +3600,52 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) {
space_prefix = String(" ").repeat(i - 2);
}
- while (comments.has(line)) {
- if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
- break;
- }
+ DocLineState state = DOC_LINE_NORMAL;
+ MemberDocData result;
+ while (line <= p_line) {
String doc_line = comments[line].comment.trim_prefix("##");
- doc += _process_doc_line(doc_line, doc, space_prefix, state);
line++;
+
+ if (state == DOC_LINE_NORMAL) {
+ String stripped_line = doc_line.strip_edges();
+ if (stripped_line == "@deprecated" || stripped_line.begins_with("@deprecated:")) {
+ result.is_deprecated = true;
+ if (stripped_line.begins_with("@deprecated:")) {
+ result.deprecated_message = stripped_line.trim_prefix("@deprecated:").strip_edges();
+ }
+ continue;
+ } else if (stripped_line == "@experimental" || stripped_line.begins_with("@experimental:")) {
+ result.is_experimental = true;
+ if (stripped_line.begins_with("@experimental:")) {
+ result.experimental_message = stripped_line.trim_prefix("@experimental:").strip_edges();
+ }
+ continue;
+ }
+ }
+
+ result.description += _process_doc_line(doc_line, result.description, space_prefix, state);
}
- return doc;
+ return result;
}
-void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class) {
- const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
- if (!comments.has(p_line)) {
- return;
- }
- ERR_FAIL_COND(!p_brief.is_empty() || !p_desc.is_empty() || p_tutorials.size() != 0);
+GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_single_line) {
+ ERR_FAIL_COND_V(!has_comment(p_line, true), ClassDocData());
+ const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer->get_comments();
int line = p_line;
- DocLineState state = DOC_LINE_NORMAL;
- enum Mode {
- BRIEF,
- DESC,
- TUTORIALS,
- DONE,
- };
- Mode mode = BRIEF;
- if (p_inner_class) {
- while (comments.has(line - 1)) {
- if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) {
- break;
- }
+ if (!p_single_line) {
+ while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) {
line--;
}
}
+ max_script_doc_line = MIN(max_script_doc_line, line - 1);
+
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] != ' ') {
@@ -3509,23 +3655,26 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
space_prefix = String(" ").repeat(i - 2);
}
- while (comments.has(line)) {
- if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
- break;
- }
+ DocLineState state = DOC_LINE_NORMAL;
+ bool is_in_brief = true;
+ ClassDocData result;
- String doc_line = comments[line++].comment.trim_prefix("##");
- String title, link; // For tutorials.
+ while (line <= p_line) {
+ String doc_line = comments[line].comment.trim_prefix("##");
+ line++;
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;
- }
+
+ // A blank line separates the description from the brief.
+ if (is_in_brief && !result.brief.is_empty() && stripped_line.is_empty()) {
+ is_in_brief = false;
continue;
- } else if (stripped_line.begins_with("@tutorial")) {
+ }
+
+ if (stripped_line.begins_with("@tutorial")) {
+ String title, link;
+
int begin_scan = String("@tutorial").length();
if (begin_scan >= stripped_line.length()) {
continue; // Invalid syntax.
@@ -3567,36 +3716,31 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String &
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;
+ result.tutorials.append(Pair<String, String>(title, link));
+ continue;
+ } else if (stripped_line == "@deprecated" || stripped_line.begins_with("@deprecated:")) {
+ result.is_deprecated = true;
+ if (stripped_line.begins_with("@deprecated:")) {
+ result.deprecated_message = stripped_line.trim_prefix("@deprecated:").strip_edges();
+ }
+ continue;
+ } else if (stripped_line == "@experimental" || stripped_line.begins_with("@experimental:")) {
+ result.is_experimental = true;
+ if (stripped_line.begins_with("@experimental:")) {
+ result.experimental_message = stripped_line.trim_prefix("@experimental:").strip_edges();
+ }
+ continue;
}
}
- switch (mode) {
- case BRIEF:
- p_brief += _process_doc_line(doc_line, p_brief, space_prefix, state);
- break;
- case DESC:
- p_desc += _process_doc_line(doc_line, p_desc, space_prefix, state);
- break;
- case TUTORIALS:
- p_tutorials.append(Pair<String, String>(title, link));
- break;
- case DONE:
- break;
+ if (is_in_brief) {
+ result.brief += _process_doc_line(doc_line, result.brief, space_prefix, state);
+ } else {
+ result.description += _process_doc_line(doc_line, result.description, space_prefix, state);
}
}
- 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();
- }
- }
+ return result;
}
#endif // TOOLS_ENABLED
@@ -3663,6 +3807,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
{ nullptr, nullptr, PREC_NONE }, // PASS,
{ nullptr, nullptr, PREC_NONE }, // RETURN,
{ nullptr, nullptr, PREC_NONE }, // MATCH,
+ { nullptr, nullptr, PREC_NONE }, // WHEN,
// Keywords
{ nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS,
{ nullptr, nullptr, PREC_NONE }, // ASSERT,
@@ -3746,12 +3891,12 @@ const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(con
return empty;
}
-bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) {
+bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target, ClassNode *p_class) {
if (is_applied) {
return true;
}
is_applied = true;
- return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target);
+ return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target, p_class);
}
bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const {
@@ -3797,43 +3942,56 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
return true;
}
-bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) {
+bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
#ifdef DEBUG_ENABLED
- if (this->_is_tool) {
+ if (_is_tool) {
push_error(R"("@tool" annotation can only be used once.)", p_annotation);
return false;
}
#endif // DEBUG_ENABLED
- this->_is_tool = true;
+ _is_tool = true;
return true;
}
-bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) {
- ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)");
+bool GDScriptParser::icon_annotation(const 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);
- ClassNode *p_class = static_cast<ClassNode *>(p_node);
+
+ ClassNode *class_node = static_cast<ClassNode *>(p_target);
+ String path = p_annotation->resolved_arguments[0];
+
#ifdef DEBUG_ENABLED
- if (!p_class->icon_path.is_empty()) {
+ if (!class_node->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()) {
+ if (path.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];
+
+ class_node->icon_path = path;
+
+ if (path.is_empty() || path.is_absolute_path()) {
+ class_node->simplified_icon_path = path.simplify_path();
+ } else if (path.is_relative_path()) {
+ class_node->simplified_icon_path = script_path.get_base_dir().path_join(path).simplify_path();
+ } else {
+ class_node->simplified_icon_path = path;
+ }
+
return true;
}
-bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_node) {
- ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
+bool GDScriptParser::onready_annotation(const 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"))) {
push_error(R"("@onready" can only be used in classes that inherit "Node".)", p_annotation);
}
- VariableNode *variable = static_cast<VariableNode *>(p_node);
+ VariableNode *variable = static_cast<VariableNode *>(p_target);
if (variable->is_static) {
push_error(R"("@onready" annotation cannot be applied to a static variable.)", p_annotation);
return false;
@@ -3848,10 +4006,11 @@ bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node
}
template <PropertyHint t_hint, Variant::Type t_type>
-bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_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));
+bool GDScriptParser::export_annotations(const 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);
- VariableNode *variable = static_cast<VariableNode *>(p_node);
+ VariableNode *variable = static_cast<VariableNode *>(p_target);
if (variable->is_static) {
push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation);
return false;
@@ -3881,6 +4040,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
}
+ // WARNING: Do not merge with the previous `if` because there `!=`, not `==`!
if (p_annotation->name == SNAME("@export_flags")) {
const int64_t max_flags = 32;
Vector<String> t = arg_string.split(":", true, 1);
@@ -3906,6 +4066,18 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Starting from argument %d, the flag value must be specified explicitly.)", i + 1, max_flags + 1), p_annotation->arguments[i]);
return false;
}
+ } else if (p_annotation->name == SNAME("@export_node_path")) {
+ String native_class = arg_string;
+ if (ScriptServer::is_global_class(arg_string)) {
+ native_class = ScriptServer::get_global_class_native_base(arg_string);
+ }
+ if (!ClassDB::class_exists(native_class)) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_node_path": The class "%s" was not found in the global scope.)", i + 1, arg_string), p_annotation->arguments[i]);
+ return false;
+ } else if (!ClassDB::is_parent_class(native_class, SNAME("Node"))) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_node_path": The class "%s" does not inherit "Node".)", i + 1, arg_string), p_annotation->arguments[i]);
+ return false;
+ }
}
if (i > 0) {
@@ -3923,10 +4095,9 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
if (export_type.builtin_type == Variant::INT) {
variable->export_info.type = Variant::INT;
}
- }
- if (p_annotation->name == SNAME("@export_multiline")) {
- if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) {
- DataType inner_type = export_type.get_container_element_type();
+ } else if (p_annotation->name == SNAME("@export_multiline")) {
+ if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type(0)) {
+ DataType inner_type = export_type.get_container_element_type(0);
if (inner_type.builtin_type != Variant::STRING) {
push_error(vformat(R"("%s" annotation on arrays requires a string type but type "%s" was given instead.)", p_annotation->name.operator String(), inner_type.to_string()), variable);
return false;
@@ -3952,6 +4123,8 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
}
+ // WARNING: Do not merge with the previous `else if`! Otherwise `else` (default variable type check)
+ // will not work for the above annotations. `@export` and `@export_enum` validate the type separately.
if (p_annotation->name == SNAME("@export")) {
if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) {
push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation);
@@ -3960,8 +4133,8 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
bool is_array = false;
- if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) {
- export_type = export_type.get_container_element_type(); // Use inner type for.
+ if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type(0)) {
+ export_type = export_type.get_container_element_type(0); // Use inner type for.
is_array = true;
}
@@ -4026,27 +4199,38 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
} break;
case GDScriptParser::DataType::ENUM: {
- variable->export_info.type = Variant::INT;
- variable->export_info.hint = PROPERTY_HINT_ENUM;
-
- String enum_hint_string;
- bool first = true;
- for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
- if (!first) {
- enum_hint_string += ",";
- } else {
- first = false;
+ if (export_type.is_meta_type) {
+ variable->export_info.type = Variant::DICTIONARY;
+ } else {
+ variable->export_info.type = Variant::INT;
+ variable->export_info.hint = PROPERTY_HINT_ENUM;
+
+ String enum_hint_string;
+ bool first = true;
+ for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
+ if (!first) {
+ enum_hint_string += ",";
+ } else {
+ first = false;
+ }
+ enum_hint_string += E.key.operator String().capitalize().xml_escape();
+ enum_hint_string += ":";
+ enum_hint_string += String::num_int64(E.value).xml_escape();
}
- enum_hint_string += E.key.operator String().capitalize().xml_escape();
- enum_hint_string += ":";
- enum_hint_string += String::num_int64(E.value).xml_escape();
- }
- variable->export_info.hint_string = enum_hint_string;
+ variable->export_info.hint_string = enum_hint_string;
+ variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
+ variable->export_info.class_name = String(export_type.native_type).replace("::", ".");
+ }
} break;
default:
push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable);
- break;
+ return false;
+ }
+
+ if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) {
+ push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), variable);
+ return false;
}
if (is_array) {
@@ -4091,7 +4275,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
template <PropertyUsageFlags t_usage>
-bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_node) {
+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()) {
@@ -4123,7 +4307,7 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation
return true;
}
-bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) {
+bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
#ifdef DEBUG_ENABLED
bool has_error = false;
for (const Variant &warning_name : p_annotation->resolved_arguments) {
@@ -4132,7 +4316,7 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod
push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation);
has_error = true;
} else {
- p_node->ignored_warnings.push_back(warning);
+ p_target->ignored_warnings.push_back(warning);
}
}
@@ -4144,10 +4328,10 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod
#endif // DEBUG_ENABLED
}
-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));
+bool GDScriptParser::rpc_annotation(const 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_node);
+ FunctionNode *function = static_cast<FunctionNode *>(p_target);
if (function->rpc_config.get_type() != Variant::NIL) {
push_error(R"(RPC annotations can only be used once per function.)", p_annotation);
return false;
@@ -4205,14 +4389,14 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
return true;
}
-bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target) {
+bool GDScriptParser::static_unload_annotation(const 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 *p_class = static_cast<ClassNode *>(p_target);
- if (p_class->annotated_static_unload) {
+ ClassNode *class_node = static_cast<ClassNode *>(p_target);
+ if (class_node->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;
+ class_node->annotated_static_unload = true;
return true;
}
@@ -4244,7 +4428,7 @@ String GDScriptParser::SuiteNode::Local::get_name() const {
case SuiteNode::Local::FOR_VARIABLE:
return "for loop iterator";
case SuiteNode::Local::PATTERN_BIND:
- return "pattern_bind";
+ return "pattern bind";
case SuiteNode::Local::UNDEFINED:
return "<undefined>";
default:
@@ -4260,8 +4444,8 @@ String GDScriptParser::DataType::to_string() const {
if (builtin_type == Variant::NIL) {
return "null";
}
- if (builtin_type == Variant::ARRAY && has_container_element_type()) {
- return vformat("Array[%s]", container_element_type->to_string());
+ if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {
+ return vformat("Array[%s]", container_element_types[0].to_string());
}
return Variant::get_type_name(builtin_type);
case NATIVE:
@@ -4301,6 +4485,104 @@ String GDScriptParser::DataType::to_string() const {
ERR_FAIL_V_MSG("<unresolved type>", "Kind set outside the enum range.");
}
+PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) const {
+ PropertyInfo result;
+ result.name = p_name;
+ result.usage = PROPERTY_USAGE_NONE;
+
+ if (!is_hard_type()) {
+ result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ return result;
+ }
+
+ switch (kind) {
+ case BUILTIN:
+ result.type = builtin_type;
+ if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {
+ const DataType elem_type = get_container_element_type(0);
+ switch (elem_type.kind) {
+ case BUILTIN:
+ result.hint = PROPERTY_HINT_ARRAY_TYPE;
+ result.hint_string = Variant::get_type_name(elem_type.builtin_type);
+ break;
+ case NATIVE:
+ result.hint = PROPERTY_HINT_ARRAY_TYPE;
+ result.hint_string = elem_type.native_type;
+ break;
+ case SCRIPT:
+ result.hint = PROPERTY_HINT_ARRAY_TYPE;
+ if (elem_type.script_type.is_valid() && elem_type.script_type->get_global_name() != StringName()) {
+ result.hint_string = elem_type.script_type->get_global_name();
+ } else {
+ result.hint_string = elem_type.native_type;
+ }
+ break;
+ case CLASS:
+ result.hint = PROPERTY_HINT_ARRAY_TYPE;
+ if (elem_type.class_type != nullptr && elem_type.class_type->get_global_name() != StringName()) {
+ result.hint_string = elem_type.class_type->get_global_name();
+ } else {
+ result.hint_string = elem_type.native_type;
+ }
+ break;
+ case ENUM:
+ result.hint = PROPERTY_HINT_ARRAY_TYPE;
+ result.hint_string = String(elem_type.native_type).replace("::", ".");
+ break;
+ case VARIANT:
+ case RESOLVING:
+ case UNRESOLVED:
+ break;
+ }
+ }
+ break;
+ case NATIVE:
+ result.type = Variant::OBJECT;
+ if (is_meta_type) {
+ result.class_name = GDScriptNativeClass::get_class_static();
+ } else {
+ result.class_name = native_type;
+ }
+ break;
+ case SCRIPT:
+ result.type = Variant::OBJECT;
+ if (is_meta_type) {
+ result.class_name = script_type.is_valid() ? script_type->get_class() : Script::get_class_static();
+ } else if (script_type.is_valid() && script_type->get_global_name() != StringName()) {
+ result.class_name = script_type->get_global_name();
+ } else {
+ result.class_name = native_type;
+ }
+ break;
+ case CLASS:
+ result.type = Variant::OBJECT;
+ if (is_meta_type) {
+ result.class_name = GDScript::get_class_static();
+ } else if (class_type != nullptr && class_type->get_global_name() != StringName()) {
+ result.class_name = class_type->get_global_name();
+ } else {
+ result.class_name = native_type;
+ }
+ break;
+ case ENUM:
+ if (is_meta_type) {
+ result.type = Variant::DICTIONARY;
+ } else {
+ result.type = Variant::INT;
+ result.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
+ result.class_name = String(native_type).replace("::", ".");
+ }
+ break;
+ case VARIANT:
+ case RESOLVING:
+ case UNRESOLVED:
+ result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ break;
+ }
+
+ return result;
+}
+
static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) {
switch (p_type) {
case Variant::PACKED_BYTE_ARRAY:
@@ -4804,6 +5086,9 @@ void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const
for (const AnnotationNode *E : p_function->annotations) {
print_annotation(E);
}
+ if (p_function->is_static) {
+ push_text("Static ");
+ }
push_text(p_context);
push_text(" ");
if (p_function->identifier) {
@@ -5148,6 +5433,9 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) {
print_annotation(E);
}
+ if (p_variable->is_static) {
+ push_text("Static ");
+ }
push_text("Variable ");
print_identifier(p_variable->identifier);
@@ -5224,7 +5512,7 @@ void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) {
}
void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) {
- ERR_FAIL_COND_MSG(p_parser.get_tree() == nullptr, "Parse the code before printing the parse tree.");
+ ERR_FAIL_NULL_MSG(p_parser.get_tree(), "Parse the code before printing the parse tree.");
if (p_parser.is_tool()) {
push_line("@tool");