|
@@ -540,43 +540,28 @@ void GDScriptParser::parse_program() {
|
|
head = alloc_node<ClassNode>();
|
|
head = alloc_node<ClassNode>();
|
|
head->fqcn = script_path;
|
|
head->fqcn = script_path;
|
|
current_class = head;
|
|
current_class = head;
|
|
|
|
+ bool can_have_class_or_extends = true;
|
|
|
|
|
|
- // If we happen to parse an annotation before extends or class_name keywords, track it.
|
|
|
|
- // @tool is allowed, but others should fail.
|
|
|
|
- AnnotationNode *premature_annotation = nullptr;
|
|
|
|
-
|
|
|
|
- if (match(GDScriptTokenizer::Token::ANNOTATION)) {
|
|
|
|
- // Check for @tool, script-level, or standalone annotation.
|
|
|
|
|
|
+ while (match(GDScriptTokenizer::Token::ANNOTATION)) {
|
|
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
|
|
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
|
|
if (annotation != nullptr) {
|
|
if (annotation != nullptr) {
|
|
- if (annotation->name == SNAME("@tool")) {
|
|
|
|
- // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?).
|
|
|
|
- _is_tool = true;
|
|
|
|
- if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
|
|
|
|
- push_error(R"(Expected newline after "@tool" annotation.)");
|
|
|
|
- }
|
|
|
|
- // @tool annotation has no specific target.
|
|
|
|
- annotation->apply(this, nullptr);
|
|
|
|
- } else if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) {
|
|
|
|
- premature_annotation = annotation;
|
|
|
|
- if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
|
|
|
|
- push_error(R"(Expected newline after a standalone annotation.)");
|
|
|
|
- }
|
|
|
|
|
|
+ if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
|
|
annotation->apply(this, head);
|
|
annotation->apply(this, head);
|
|
} else {
|
|
} else {
|
|
- premature_annotation = annotation;
|
|
|
|
annotation_stack.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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- for (bool should_break = false; !should_break;) {
|
|
|
|
|
|
+ while (can_have_class_or_extends) {
|
|
// Order here doesn't matter, but there should be only one of each at most.
|
|
// Order here doesn't matter, but there should be only one of each at most.
|
|
switch (current.type) {
|
|
switch (current.type) {
|
|
case GDScriptTokenizer::Token::CLASS_NAME:
|
|
case GDScriptTokenizer::Token::CLASS_NAME:
|
|
- if (premature_annotation != nullptr) {
|
|
|
|
- push_error(R"("class_name" should be used before annotations (except @tool).)");
|
|
|
|
- }
|
|
|
|
advance();
|
|
advance();
|
|
if (head->identifier != nullptr) {
|
|
if (head->identifier != nullptr) {
|
|
push_error(R"("class_name" can only be used once.)");
|
|
push_error(R"("class_name" can only be used once.)");
|
|
@@ -585,9 +570,6 @@ void GDScriptParser::parse_program() {
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
case GDScriptTokenizer::Token::EXTENDS:
|
|
case GDScriptTokenizer::Token::EXTENDS:
|
|
- if (premature_annotation != nullptr) {
|
|
|
|
- push_error(R"("extends" should be used before annotations (except @tool).)");
|
|
|
|
- }
|
|
|
|
advance();
|
|
advance();
|
|
if (head->extends_used) {
|
|
if (head->extends_used) {
|
|
push_error(R"("extends" can only be used once.)");
|
|
push_error(R"("extends" can only be used once.)");
|
|
@@ -597,7 +579,8 @@ void GDScriptParser::parse_program() {
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
default:
|
|
default:
|
|
- should_break = true;
|
|
|
|
|
|
+ // No tokens are allowed between script annotations and class/extends.
|
|
|
|
+ can_have_class_or_extends = false;
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -606,21 +589,6 @@ void GDScriptParser::parse_program() {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- if (match(GDScriptTokenizer::Token::ANNOTATION)) {
|
|
|
|
- // Check for a script-level, or standalone annotation.
|
|
|
|
- AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
|
|
|
|
- if (annotation != nullptr) {
|
|
|
|
- if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) {
|
|
|
|
- if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
|
|
|
|
- push_error(R"(Expected newline after a standalone annotation.)");
|
|
|
|
- }
|
|
|
|
- annotation->apply(this, head);
|
|
|
|
- } else {
|
|
|
|
- annotation_stack.push_back(annotation);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
parse_class_body(true);
|
|
parse_class_body(true);
|
|
complete_extents(head);
|
|
complete_extents(head);
|
|
|
|
|