diff options
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 170 |
1 files changed, 132 insertions, 38 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 0a1a64cb59..b32313dad4 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -175,7 +175,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ warning.leftmost_column = p_source->leftmost_column; warning.rightmost_column = p_source->rightmost_column; - if (warn_level == GDScriptWarning::WarnLevel::ERROR || bool(GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors"))) { + if (warn_level == GDScriptWarning::WarnLevel::ERROR) { push_error(warning.get_message() + String(" (Warning treated as error.)"), p_source); return; } @@ -483,24 +483,34 @@ void GDScriptParser::parse_program() { current_class = head; bool can_have_class_or_extends = true; - while (match(GDScriptTokenizer::Token::ANNOTATION)) { - AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); - if (annotation != nullptr) { - 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); + while (!check(GDScriptTokenizer::Token::TK_EOF)) { + if (match(GDScriptTokenizer::Token::ANNOTATION)) { + AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); + if (annotation != nullptr) { + 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); + } else { + head->annotations.push_back(annotation); + } } else { - head->annotations.push_back(annotation); + annotation_stack.push_back(annotation); + // This annotation must appear after script-level annotations + // and class_name/extends (ex: could be @onready or @export), + // so we stop looking for script-level stuff. + can_have_class_or_extends = false; + break; } - } else { - annotation_stack.push_back(annotation); - // This annotation must appear after script-level annotations - // and class_name/extends (ex: could be @onready or @export), - // so we stop looking for script-level stuff. - can_have_class_or_extends = false; - break; } + } else if (check(GDScriptTokenizer::Token::LITERAL) && current.literal.get_type() == Variant::STRING) { + // Allow strings in class body as multiline comments. + advance(); + if (!match(GDScriptTokenizer::Token::NEWLINE)) { + push_error("Expected newline after comment string."); + } + } else { + break; } } @@ -524,6 +534,16 @@ void GDScriptParser::parse_program() { end_statement("superclass"); } break; + case GDScriptTokenizer::Token::LITERAL: + if (current.literal.get_type() == Variant::STRING) { + // Allow strings in class body as multiline comments. + advance(); + if (!match(GDScriptTokenizer::Token::NEWLINE)) { + push_error("Expected newline after comment string."); + } + break; + } + [[fallthrough]]; default: // No tokens are allowed between script annotations and class/extends. can_have_class_or_extends = false; @@ -829,6 +849,16 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { case GDScriptTokenizer::Token::DEDENT: class_end = true; break; + case GDScriptTokenizer::Token::LITERAL: + if (current.literal.get_type() == Variant::STRING) { + // Allow strings in class body as multiline comments. + advance(); + if (!match(GDScriptTokenizer::Token::NEWLINE)) { + push_error("Expected newline after comment string."); + } + break; + } + [[fallthrough]]; default: // Display a completion with identifiers. make_completion_context(COMPLETION_IDENTIFIER, nullptr); @@ -1675,6 +1705,12 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { // Standalone lambdas can't be used, so make this an error. push_error("Standalone lambdas cannot be accessed. Consider assigning it to a variable.", expression); break; + case Node::LITERAL: + if (static_cast<GDScriptParser::LiteralNode *>(expression)->value.get_type() == Variant::STRING) { + // Allow strings as multiline comments. + break; + } + [[fallthrough]]; default: push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION); } @@ -2145,7 +2181,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr make_completion_context(COMPLETION_IDENTIFIER, nullptr); GDScriptTokenizer::Token token = current; - ParseFunction prefix_rule = get_rule(token.type)->prefix; + GDScriptTokenizer::Token::Type token_type = token.type; + if (token.is_identifier()) { + // Allow keywords that can be treated as identifiers. + token_type = GDScriptTokenizer::Token::IDENTIFIER; + } + ParseFunction prefix_rule = get_rule(token_type)->prefix; if (prefix_rule == nullptr) { // Expected expression. Let the caller give the proper error message. @@ -2422,9 +2463,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression operation->operation = BinaryOpNode::OP_LOGIC_OR; operation->variant_op = Variant::OP_OR; break; - case GDScriptTokenizer::Token::IS: - operation->operation = BinaryOpNode::OP_TYPE_TEST; - break; case GDScriptTokenizer::Token::IN: operation->operation = BinaryOpNode::OP_CONTENT_TEST; operation->variant_op = Variant::OP_IN; @@ -3010,7 +3048,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p path_state = PATH_STATE_NODE_NAME; } else if (current.is_node_name()) { advance(); - get_node->full_path += previous.get_identifier(); + String identifier = previous.get_identifier(); +#ifdef DEBUG_ENABLED + // Check spoofing. + if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier)) { + push_warning(get_node, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier); + } +#endif + get_node->full_path += identifier; path_state = PATH_STATE_NODE_NAME; } else if (!check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) { @@ -3113,6 +3158,22 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p return lambda; } +GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign) { + TypeTestNode *type_test = alloc_node<TypeTestNode>(); + reset_extents(type_test, p_previous_operand); + update_extents(type_test); + + type_test->operand = p_previous_operand; + type_test->test_type = parse_type(); + complete_extents(type_test); + + if (type_test->test_type == nullptr) { + push_error(R"(Expected type specifier after "is".)"); + } + + return type_test; +} + 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; @@ -3481,7 +3542,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // EXTENDS, { &GDScriptParser::parse_lambda, nullptr, PREC_NONE }, // FUNC, { nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // IN, - { nullptr, &GDScriptParser::parse_binary_operator, PREC_TYPE_TEST }, // IS, + { nullptr, &GDScriptParser::parse_type_test, PREC_TYPE_TEST }, // IS, { nullptr, nullptr, PREC_NONE }, // NAMESPACE, { &GDScriptParser::parse_preload, nullptr, PREC_NONE }, // PRELOAD, { &GDScriptParser::parse_self, nullptr, PREC_NONE }, // SELF, @@ -3637,15 +3698,6 @@ 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)); - { - const int max_flags = 32; - - if (t_hint == PropertyHint::PROPERTY_HINT_FLAGS && p_annotation->resolved_arguments.size() > max_flags) { - push_error(vformat(R"(The argument count limit for "@export_flags" is exceeded (%d/%d).)", p_annotation->resolved_arguments.size(), max_flags), p_annotation); - return false; - } - } - VariableNode *variable = static_cast<VariableNode *>(p_node); if (variable->exported) { push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); @@ -3659,14 +3711,50 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node String hint_string; for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { - if (p_annotation->name != SNAME("@export_placeholder") && String(p_annotation->resolved_arguments[i]).contains(",")) { - push_error(vformat(R"(Argument %d of annotation "%s" contains a comma. Use separate arguments instead.)", i + 1, p_annotation->name), p_annotation->arguments[i]); - return false; + String arg_string = String(p_annotation->resolved_arguments[i]); + + if (p_annotation->name != SNAME("@export_placeholder")) { + if (arg_string.is_empty()) { + push_error(vformat(R"(Argument %d of annotation "%s" is empty.)", i + 1, p_annotation->name), p_annotation->arguments[i]); + return false; + } + if (arg_string.contains(",")) { + push_error(vformat(R"(Argument %d of annotation "%s" contains a comma. Use separate arguments instead.)", i + 1, p_annotation->name), p_annotation->arguments[i]); + return false; + } + } + + if (p_annotation->name == SNAME("@export_flags")) { + const int64_t max_flags = 32; + Vector<String> t = arg_string.split(":", true, 1); + if (t[0].is_empty()) { + push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Expected flag name.)", i + 1), p_annotation->arguments[i]); + return false; + } + if (t.size() == 2) { + if (t[1].is_empty()) { + push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Expected flag value.)", i + 1), p_annotation->arguments[i]); + return false; + } + if (!t[1].is_valid_int()) { + push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": The flag value must be a valid integer.)", i + 1), p_annotation->arguments[i]); + return false; + } + int64_t value = t[1].to_int(); + if (value < 1 || value >= (1LL << max_flags)) { + push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": The flag value must be at least 1 and at most 2 ** %d - 1.)", i + 1, max_flags), p_annotation->arguments[i]); + return false; + } + } else if (i >= max_flags) { + 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; + } } + if (i > 0) { hint_string += ","; } - hint_string += String(p_annotation->resolved_arguments[i]); + hint_string += arg_string; } variable->export_info.hint_string = hint_string; @@ -4304,9 +4392,6 @@ void GDScriptParser::TreePrinter::print_binary_op(BinaryOpNode *p_binary_op) { case BinaryOpNode::OP_LOGIC_OR: push_text(" OR "); break; - case BinaryOpNode::OP_TYPE_TEST: - push_text(" IS "); - break; case BinaryOpNode::OP_CONTENT_TEST: push_text(" IN "); break; @@ -4509,6 +4594,9 @@ void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) case Node::TERNARY_OPERATOR: print_ternary_op(static_cast<TernaryOpNode *>(p_expression)); break; + case Node::TYPE_TEST: + print_type_test(static_cast<TypeTestNode *>(p_expression)); + break; case Node::UNARY_OPERATOR: print_unary_op(static_cast<UnaryOpNode *>(p_expression)); break; @@ -4868,6 +4956,12 @@ void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) { } } +void GDScriptParser::TreePrinter::print_type_test(TypeTestNode *p_test) { + print_expression(p_test->operand); + push_text(" IS "); + print_type(p_test->test_type); +} + void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) { // Surround in parenthesis for disambiguation. push_text("("); |