|
@@ -986,9 +986,15 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
|
|
signal->identifier = parse_identifier();
|
|
signal->identifier = parse_identifier();
|
|
|
|
|
|
if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
|
|
if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
|
|
- while (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
|
|
|
|
|
|
+ do {
|
|
|
|
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
|
|
|
|
+ // Allow for trailing comma.
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
ParameterNode *parameter = parse_parameter();
|
|
ParameterNode *parameter = parse_parameter();
|
|
if (parameter == nullptr) {
|
|
if (parameter == nullptr) {
|
|
|
|
+ push_error("Expected signal parameter name.");
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
if (parameter->default_value != nullptr) {
|
|
if (parameter->default_value != nullptr) {
|
|
@@ -1000,7 +1006,8 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
|
|
signal->parameters_indices[parameter->identifier->name] = signal->parameters.size();
|
|
signal->parameters_indices[parameter->identifier->name] = signal->parameters.size();
|
|
signal->parameters.push_back(parameter);
|
|
signal->parameters.push_back(parameter);
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
+ } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
|
|
|
|
+
|
|
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*");
|
|
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*");
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1022,6 +1029,8 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
|
|
push_multiline(true);
|
|
push_multiline(true);
|
|
consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
|
|
consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
|
|
|
|
|
|
|
|
+ HashMap<StringName, int> elements;
|
|
|
|
+
|
|
do {
|
|
do {
|
|
if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
|
|
if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
|
|
break; // Allow trailing comma.
|
|
break; // Allow trailing comma.
|
|
@@ -1033,7 +1042,9 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
|
|
item.line = previous.start_line;
|
|
item.line = previous.start_line;
|
|
item.leftmost_column = previous.leftmost_column;
|
|
item.leftmost_column = previous.leftmost_column;
|
|
|
|
|
|
- if (!named) {
|
|
|
|
|
|
+ if (elements.has(item.identifier->name)) {
|
|
|
|
+ push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)", item.identifier->name, elements[item.identifier->name]), item.identifier);
|
|
|
|
+ } else if (!named) {
|
|
// TODO: Abstract this recursive member check.
|
|
// TODO: Abstract this recursive member check.
|
|
ClassNode *parent = current_class;
|
|
ClassNode *parent = current_class;
|
|
while (parent != nullptr) {
|
|
while (parent != nullptr) {
|
|
@@ -1045,6 +1056,8 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ elements[item.identifier->name] = item.line;
|
|
|
|
+
|
|
if (match(GDScriptTokenizer::Token::EQUAL)) {
|
|
if (match(GDScriptTokenizer::Token::EQUAL)) {
|
|
ExpressionNode *value = parse_expression(false);
|
|
ExpressionNode *value = parse_expression(false);
|
|
if (value == nullptr) {
|
|
if (value == nullptr) {
|
|
@@ -1136,6 +1149,9 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
|
|
if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {
|
|
if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {
|
|
make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function);
|
|
make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function);
|
|
function->return_type = parse_type(true);
|
|
function->return_type = parse_type(true);
|
|
|
|
+ if (function->return_type == nullptr) {
|
|
|
|
+ push_error(R"(Expected return type or "void" after "->".)");
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
|
|
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
|
|
@@ -2489,15 +2505,18 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
|
|
make_completion_context(COMPLETION_GET_NODE, get_node);
|
|
make_completion_context(COMPLETION_GET_NODE, get_node);
|
|
get_node->string = parse_literal();
|
|
get_node->string = parse_literal();
|
|
return get_node;
|
|
return get_node;
|
|
- } else if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
|
|
|
|
|
|
+ } else if (current.is_node_name()) {
|
|
GetNodeNode *get_node = alloc_node<GetNodeNode>();
|
|
GetNodeNode *get_node = alloc_node<GetNodeNode>();
|
|
int chain_position = 0;
|
|
int chain_position = 0;
|
|
do {
|
|
do {
|
|
make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++);
|
|
make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++);
|
|
- if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expect node identifer after "/".)")) {
|
|
|
|
|
|
+ if (!current.is_node_name()) {
|
|
|
|
+ push_error(R"(Expect node path after "/".)");
|
|
return nullptr;
|
|
return nullptr;
|
|
}
|
|
}
|
|
- IdentifierNode *identifier = parse_identifier();
|
|
|
|
|
|
+ advance();
|
|
|
|
+ IdentifierNode *identifier = alloc_node<IdentifierNode>();
|
|
|
|
+ identifier->name = previous.get_identifier();
|
|
get_node->chain.push_back(identifier);
|
|
get_node->chain.push_back(identifier);
|
|
} while (match(GDScriptTokenizer::Token::SLASH));
|
|
} while (match(GDScriptTokenizer::Token::SLASH));
|
|
return get_node;
|
|
return get_node;
|
|
@@ -2521,29 +2540,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
|
|
|
|
|
|
if (preload->path == nullptr) {
|
|
if (preload->path == nullptr) {
|
|
push_error(R"(Expected resource path after "(".)");
|
|
push_error(R"(Expected resource path after "(".)");
|
|
- } else if (preload->path->type != Node::LITERAL) {
|
|
|
|
- push_error("Preloaded path must be a constant string.");
|
|
|
|
- } else {
|
|
|
|
- LiteralNode *path = static_cast<LiteralNode *>(preload->path);
|
|
|
|
- if (path->value.get_type() != Variant::STRING) {
|
|
|
|
- push_error("Preloaded path must be a constant string.");
|
|
|
|
- } else {
|
|
|
|
- preload->resolved_path = path->value;
|
|
|
|
- // TODO: Save this as script dependency.
|
|
|
|
- if (preload->resolved_path.is_rel_path()) {
|
|
|
|
- preload->resolved_path = script_path.get_base_dir().plus_file(preload->resolved_path);
|
|
|
|
- }
|
|
|
|
- preload->resolved_path = preload->resolved_path.simplify_path();
|
|
|
|
- if (!FileAccess::exists(preload->resolved_path)) {
|
|
|
|
- push_error(vformat(R"(Preload file "%s" does not exist.)", preload->resolved_path));
|
|
|
|
- } else {
|
|
|
|
- // TODO: Don't load if validating: use completion cache.
|
|
|
|
- preload->resource = ResourceLoader::load(preload->resolved_path);
|
|
|
|
- if (preload->resource.is_null()) {
|
|
|
|
- push_error(vformat(R"(Could not preload resource file "%s".)", preload->resolved_path));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
}
|
|
}
|
|
|
|
|
|
pop_completion_call();
|
|
pop_completion_call();
|