diff options
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 208 |
1 files changed, 197 insertions, 11 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index d6867f1f87..d25a8688be 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -41,6 +41,10 @@ #include "core/string_builder.h" #endif // DEBUG_ENABLED +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + static HashMap<StringName, Variant::Type> builtin_types; Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { if (builtin_types.empty()) { @@ -99,6 +103,14 @@ GDScriptFunctions::Function GDScriptParser::get_builtin_function(const StringNam return GDScriptFunctions::FUNC_MAX; } +void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { + List<StringName> keys; + valid_annotations.get_key_list(&keys); + for (const List<StringName>::Element *E = keys.front(); E != nullptr; E = E->next()) { + r_annotations->push_back(valid_annotations[E->get()].info); + } +} + GDScriptParser::GDScriptParser() { // Register valid annotations. // TODO: Should this be static? @@ -239,9 +251,114 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ } } +void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) { + if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { + return; + } + if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) { + return; + } + CompletionContext context; + context.type = p_type; + context.current_class = current_class; + context.current_function = current_function; + context.current_suite = current_suite; + context.current_line = tokenizer.get_cursor_line(); + context.current_argument = p_argument; + context.node = p_node; + completion_context = context; +} + +void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) { + if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { + return; + } + if (previous.cursor_place != GDScriptTokenizer::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizer::CURSOR_END && current.cursor_place == GDScriptTokenizer::CURSOR_NONE) { + return; + } + CompletionContext context; + context.type = p_type; + context.current_class = current_class; + context.current_function = current_function; + context.current_suite = current_suite; + context.current_line = tokenizer.get_cursor_line(); + context.builtin_type = p_builtin_type; + completion_context = context; +} + +void GDScriptParser::push_completion_call(Node *p_call) { + if (!for_completion) { + return; + } + CompletionCall 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) { + completion_call = call; + } +} + +void GDScriptParser::pop_completion_call() { + if (!for_completion) { + return; + } + ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to pop empty completion call stack"); + completion_call_stack.pop_back(); +} + +void GDScriptParser::set_last_completion_call_arg(int p_argument) { + if (!for_completion || passed_cursor) { + return; + } + ERR_FAIL_COND_MSG(completion_call_stack.empty(), "Trying to set argument on empty completion call stack"); + completion_call_stack.back()->get().argument = p_argument; +} + Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) { clear(); - tokenizer.set_source_code(p_source_code); + + String source = p_source_code; + int cursor_line = -1; + int cursor_column = -1; + for_completion = p_for_completion; + + int tab_size = 4; +#ifdef TOOLS_ENABLED + if (EditorSettings::get_singleton()) { + tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size"); + } +#endif // TOOLS_ENABLED + + if (p_for_completion) { + // Remove cursor sentinel char. + const Vector<String> lines = p_source_code.split("\n"); + cursor_line = 1; + cursor_column = 1; + for (int i = 0; i < lines.size(); i++) { + bool found = false; + const String &line = lines[i]; + for (int j = 0; j < line.size(); j++) { + if (line[j] == CharType(0xFFFF)) { + found = true; + break; + } else if (line[j] == '\t') { + cursor_column += tab_size - 1; + } + cursor_column++; + } + if (found) { + break; + } + cursor_line++; + cursor_column = 1; + } + + 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(); // Avoid error as the first token. @@ -271,6 +388,12 @@ GDScriptTokenizer::Token GDScriptParser::advance() { if (current.type == GDScriptTokenizer::Token::TK_EOF) { 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.empty()) { + if (completion_call.call == nullptr && tokenizer.is_past_cursor()) { + completion_call = completion_call_stack.back()->get(); + passed_cursor = true; + } + } previous = current; current = tokenizer.scan(); while (current.type == GDScriptTokenizer::Token::ERROR) { @@ -513,6 +636,8 @@ void GDScriptParser::parse_class_name() { void GDScriptParser::parse_extends() { current_class->extends_used = true; + int chain_index = 0; + if (match(GDScriptTokenizer::Token::LITERAL)) { if (previous.literal.get_type() != Variant::STRING) { push_error(vformat(R"(Only strings or identifiers can be used after "extends", found "%s" instead.)", Variant::get_type_name(previous.literal.get_type()))); @@ -525,12 +650,15 @@ void GDScriptParser::parse_extends() { } } + make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) { return; } current_class->extends.push_back(previous.literal); while (match(GDScriptTokenizer::Token::PERIOD)) { + make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)")) { return; } @@ -645,10 +773,13 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper // Infer type. variable->infer_datatype = true; } else { - if (p_allow_property && check(GDScriptTokenizer::Token::IDENTIFIER)) { - // Check if get or set. - if (current.get_identifier() == "get" || current.get_identifier() == "set") { - return parse_property(variable, false); + if (p_allow_property) { + make_completion_context(COMPLETION_PROPERTY_DECLARATION_OR_TYPE, variable); + if (check(GDScriptTokenizer::Token::IDENTIFIER)) { + // Check if get or set. + if (current.get_identifier() == "get" || current.get_identifier() == "set") { + return parse_property(variable, false); + } } } @@ -685,12 +816,14 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var } } + VariableNode *property = p_variable; + + make_completion_context(COMPLETION_PROPERTY_DECLARATION, property); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected "get" or "set" for property declaration.)")) { return nullptr; } - VariableNode *property = p_variable; - IdentifierNode *function = parse_identifier(); if (check(GDScriptTokenizer::Token::EQUAL)) { @@ -770,6 +903,7 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) { case VariableNode::PROP_SETGET: consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "set")"); + make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable); if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected setter function name after "=".)")) { p_variable->setter_pointer = parse_identifier(); } @@ -788,6 +922,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) { break; case VariableNode::PROP_SETGET: consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")"); + make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable); if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected getter function name after "=".)")) { p_variable->getter_pointer = parse_identifier(); } @@ -844,6 +979,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() { parameter->infer_datatype = true; } else { // Parse type. + make_completion_context(COMPLETION_TYPE_NAME, parameter); parameter->datatype_specifier = parse_type(); } } @@ -964,11 +1100,13 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() { _static = true; } + FunctionNode *function = alloc_node<FunctionNode>(); + make_completion_context(COMPLETION_OVERRIDE_METHOD, function); + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) { return nullptr; } - FunctionNode *function = alloc_node<FunctionNode>(); FunctionNode *previous_function = current_function; current_function = function; @@ -1015,6 +1153,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() { consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after function parameters.)*"); if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) { + make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function); function->return_type = parse_type(true); } @@ -1033,6 +1172,8 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali annotation->name = previous.literal; + make_completion_context(COMPLETION_ANNOTATION, annotation); + bool valid = true; if (!valid_annotations.has(annotation->name)) { @@ -1049,8 +1190,13 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { // Arguments. + push_completion_call(annotation); + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true); if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { + int argument = 0; do { + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument, true); + set_last_completion_call_arg(argument++, true); ExpressionNode *argument = parse_expression(false); if (argument == nullptr) { valid = false; @@ -1061,6 +1207,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*"); } + pop_completion_call(); } match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional. @@ -1075,7 +1222,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali void GDScriptParser::clear_unused_annotations() { for (const List<AnnotationNode *>::Element *E = annotation_stack.front(); E != nullptr; E = E->next()) { AnnotationNode *annotation = E->get(); - push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name)); + push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name), annotation); } annotation_stack.clear(); @@ -1342,6 +1489,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { SuiteNode *suite = alloc_node<SuiteNode>(); suite->add_local(SuiteNode::Local(n_for->variable)); + suite->parent_for = n_for; n_for->loop = parse_suite(R"("for" block)", suite); @@ -1363,6 +1511,7 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) { consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after "%s" condition.)", p_token)); n_if->true_block = parse_suite(vformat(R"("%s" block)", p_token)); + n_if->true_block->parent_if = n_if; if (n_if->true_block->has_continue) { current_suite->has_continue = true; @@ -1722,6 +1871,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode IdentifierNode *identifier = alloc_node<IdentifierNode>(); identifier->name = previous.get_identifier(); + make_completion_context(COMPLETION_IDENTIFIER, identifier); + if (current_suite != nullptr && current_suite->has_local(identifier->name)) { const SuiteNode::Local &declaration = current_suite->get_local(identifier->name); switch (declaration.type) { @@ -2000,6 +2151,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } AssignmentNode *assignment = alloc_node<AssignmentNode>(); + make_completion_context(COMPLETION_ASSIGN, assignment); bool has_operator = true; switch (previous.type) { case GDScriptTokenizer::Token::EQUAL: @@ -2168,11 +2320,26 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_grouping(ExpressionNode *p GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign) { SubscriptNode *attribute = alloc_node<SubscriptNode>(); + if (for_completion) { + bool is_builtin = false; + if (p_previous_operand->type == Node::IDENTIFIER) { + const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand); + Variant::Type builtin_type = get_builtin_type(id->name); + if (builtin_type < Variant::VARIANT_MAX) { + make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT, builtin_type, true); + is_builtin = true; + } + } + if (!is_builtin) { + make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true); + } + } + attribute->is_attribute = true; attribute->base = p_previous_operand; if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) { - return nullptr; + return attribute; } attribute->attribute = parse_identifier(); @@ -2182,6 +2349,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign) { SubscriptNode *subscript = alloc_node<SubscriptNode>(); + make_completion_context(COMPLETION_SUBSCRIPT, subscript); + subscript->base = p_previous_operand; subscript->index = parse_expression(false); @@ -2222,6 +2391,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre call->function_name = current_function->identifier->name; } else { consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)"); + make_completion_context(COMPLETION_SUPER_METHOD, call); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) { pop_multiline(); return nullptr; @@ -2251,7 +2421,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Arguments. + push_completion_call(call); + make_completion_context(COMPLETION_CALL_ARGUMENTS, call, 0); + int argument = 0; do { + make_completion_context(COMPLETION_CALL_ARGUMENTS, call, argument++); if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Allow for trailing comma. break; @@ -2263,6 +2437,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre call->arguments.push_back(argument); } } while (match(GDScriptTokenizer::Token::COMMA)); + pop_completion_call(); } pop_multiline(); @@ -2278,11 +2453,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p return nullptr; } GetNodeNode *get_node = alloc_node<GetNodeNode>(); + make_completion_context(COMPLETION_GET_NODE, get_node); get_node->string = parse_literal(); return get_node; } else if (check(GDScriptTokenizer::Token::IDENTIFIER)) { GetNodeNode *get_node = alloc_node<GetNodeNode>(); + int chain_position = 0; do { + make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expect node identifer after "/".)")) { return nullptr; } @@ -2303,6 +2481,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_ push_multiline(true); consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "preload".)"); + make_completion_context(COMPLETION_RESOURCE_PATH, preload); + push_completion_call(preload); + preload->path = parse_expression(false); if (preload->path == nullptr) { @@ -2332,6 +2513,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_ } } + pop_completion_call(); + pop_multiline(); consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after preload path.)*"); @@ -2355,6 +2538,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNo } GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { + TypeNode *type = alloc_node<TypeNode>(); + make_completion_context(p_allow_void ? COMPLETION_TYPE_NAME_OR_VOID : COMPLETION_TYPE_NAME, type); if (!match(GDScriptTokenizer::Token::IDENTIFIER)) { if (match(GDScriptTokenizer::Token::VOID)) { if (p_allow_void) { @@ -2368,12 +2553,13 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { return nullptr; } - TypeNode *type = alloc_node<TypeNode>(); IdentifierNode *type_element = parse_identifier(); type->type_chain.push_back(type_element); + int chain_index = 1; while (match(GDScriptTokenizer::Token::PERIOD)) { + make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++); if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected inner type name after ".".)")) { type_element = parse_identifier(); type->type_chain.push_back(type_element); |