GDScript: support variable definitions in if-statement
This commit is contained in:
parent
44fa552343
commit
590a8cba02
24 changed files with 394 additions and 102 deletions
|
@ -135,7 +135,16 @@ void GDScriptEditorTranslationParserPlugin::_traverse_block(const GDScriptParser
|
|||
} break;
|
||||
case GDScriptParser::Node::IF: {
|
||||
const GDScriptParser::IfNode *if_node = static_cast<const GDScriptParser::IfNode *>(statement);
|
||||
_assess_expression(if_node->condition);
|
||||
const List<GDScriptParser::Node *>::Element *E = if_node->conditions.front();
|
||||
while (E) {
|
||||
const GDScriptParser::Node *node = E->get();
|
||||
if (node->is_expression()) {
|
||||
_assess_expression(static_cast<const GDScriptParser::ExpressionNode *>(node));
|
||||
} else if (node->type == GDScriptParser::Node::VARIABLE) {
|
||||
_assess_expression(static_cast<const GDScriptParser::VariableNode *>(node)->initializer);
|
||||
}
|
||||
E = E->next();
|
||||
}
|
||||
_traverse_block(if_node->true_block);
|
||||
_traverse_block(if_node->false_block);
|
||||
} break;
|
||||
|
|
|
@ -2096,7 +2096,16 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame
|
|||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_if(GDScriptParser::IfNode *p_if) {
|
||||
reduce_expression(p_if->condition);
|
||||
List<GDScriptParser::Node *>::Element *E = p_if->conditions.front();
|
||||
while (E) {
|
||||
GDScriptParser::Node *condition = E->get();
|
||||
if (condition->is_expression()) {
|
||||
reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(condition));
|
||||
} else if (condition->type == GDScriptParser::Node::VARIABLE) {
|
||||
resolve_variable(static_cast<GDScriptParser::VariableNode *>(condition), true);
|
||||
}
|
||||
E = E->next();
|
||||
}
|
||||
|
||||
resolve_suite(p_if->true_block);
|
||||
p_if->set_datatype(p_if->true_block->get_datatype());
|
||||
|
|
|
@ -1501,13 +1501,16 @@ void GDScriptByteCodeGenerator::write_if(const Address &p_condition) {
|
|||
append(0); // Jump destination, will be patched.
|
||||
}
|
||||
|
||||
void GDScriptByteCodeGenerator::write_else() {
|
||||
void GDScriptByteCodeGenerator::write_else(int count) {
|
||||
append_opcode(GDScriptFunction::OPCODE_JUMP); // Jump from true if block;
|
||||
int else_jmp_addr = opcodes.size();
|
||||
append(0); // Jump destination, will be patched.
|
||||
|
||||
patch_jump(if_jmp_addrs.back()->get());
|
||||
if_jmp_addrs.pop_back();
|
||||
for (int i = 0; i < count; i++) {
|
||||
patch_jump(if_jmp_addrs.back()->get());
|
||||
if_jmp_addrs.pop_back();
|
||||
}
|
||||
|
||||
if_jmp_addrs.push_back(else_jmp_addr);
|
||||
}
|
||||
|
||||
|
|
|
@ -532,7 +532,7 @@ public:
|
|||
virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) override;
|
||||
virtual void write_await(const Address &p_target, const Address &p_operand) override;
|
||||
virtual void write_if(const Address &p_condition) override;
|
||||
virtual void write_else() override;
|
||||
virtual void write_else(int count) override;
|
||||
virtual void write_endif() override;
|
||||
virtual void write_jump_if_shared(const Address &p_value) override;
|
||||
virtual void write_end_jump_if_shared() override;
|
||||
|
|
|
@ -145,7 +145,7 @@ public:
|
|||
virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) = 0;
|
||||
virtual void write_await(const Address &p_target, const Address &p_operand) = 0;
|
||||
virtual void write_if(const Address &p_condition) = 0;
|
||||
virtual void write_else() = 0;
|
||||
virtual void write_else(int count) = 0;
|
||||
virtual void write_endif() = 0;
|
||||
virtual void write_jump_if_shared(const Address &p_value) = 0;
|
||||
virtual void write_end_jump_if_shared() = 0;
|
||||
|
|
|
@ -251,6 +251,45 @@ static bool _can_use_validate_call(const MethodBind *p_method, const Vector<GDSc
|
|||
return true;
|
||||
}
|
||||
|
||||
Error GDScriptCompiler::_parse_variable(CodeGen &codegen, const GDScriptParser::VariableNode *p_variable, const GDScriptParser::SuiteNode *p_block) {
|
||||
GDScriptCodeGenerator *gen = codegen.generator;
|
||||
|
||||
// Should be already in stack when the block began.
|
||||
GDScriptCodeGenerator::Address local = codegen.locals[p_variable->identifier->name];
|
||||
GDScriptDataType local_type = _gdtype_from_datatype(p_variable->get_datatype(), codegen.script);
|
||||
|
||||
Error err = OK;
|
||||
|
||||
bool initialized = false;
|
||||
if (p_variable->initializer != nullptr) {
|
||||
GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, err, p_variable->initializer);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
if (p_variable->use_conversion_assign) {
|
||||
gen->write_assign_with_conversion(local, src_address);
|
||||
} else {
|
||||
gen->write_assign(local, src_address);
|
||||
}
|
||||
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
||||
codegen.generator->pop_temporary();
|
||||
}
|
||||
initialized = true;
|
||||
} else if ((local_type.has_type && local_type.kind == GDScriptDataType::BUILTIN) || codegen.generator->is_local_dirty(local)) {
|
||||
// Initialize with default for the type. Built-in types must always be cleared (they cannot be `null`).
|
||||
// Objects and untyped variables are assigned to `null` only if the stack address has been re-used and not cleared.
|
||||
codegen.generator->clear_address(local);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// Don't check `is_local_dirty()` since the variable must be assigned to `null` **on each iteration**.
|
||||
if (!initialized && p_block->is_in_loop) {
|
||||
codegen.generator->clear_address(local);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer) {
|
||||
if (p_expression->is_constant && !(p_expression->get_datatype().is_meta_type && p_expression->get_datatype().kind == GDScriptParser::DataType::CLASS)) {
|
||||
return codegen.add_constant(p_expression->reduced_value);
|
||||
|
@ -1878,6 +1917,72 @@ void GDScriptCompiler::_clear_block_locals(CodeGen &codegen, const List<GDScript
|
|||
}
|
||||
}
|
||||
|
||||
Error GDScriptCompiler::_parse_if(CodeGen &codegen, const GDScriptParser::IfNode *if_n) {
|
||||
Error err = OK;
|
||||
GDScriptCodeGenerator *gen = codegen.generator;
|
||||
List<GDScriptCodeGenerator::Address> block_locals;
|
||||
|
||||
gen->clear_temporaries();
|
||||
codegen.start_block();
|
||||
block_locals = _add_block_locals(codegen, if_n->condition_block);
|
||||
|
||||
int count = 0;
|
||||
const List<GDScriptParser::Node *>::Element *E = if_n->conditions.front();
|
||||
while (E) {
|
||||
const GDScriptParser::Node *condition_n = E->get();
|
||||
if (condition_n->is_expression()) {
|
||||
const GDScriptParser::ExpressionNode *expression_n = static_cast<const GDScriptParser::ExpressionNode *>(condition_n);
|
||||
|
||||
GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, expression_n);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
gen->write_if(condition);
|
||||
|
||||
if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
||||
codegen.generator->pop_temporary();
|
||||
}
|
||||
|
||||
count++;
|
||||
} else if (condition_n->type == GDScriptParser::Node::VARIABLE) {
|
||||
const GDScriptParser::VariableNode *variable_n = static_cast<const GDScriptParser::VariableNode *>(condition_n);
|
||||
|
||||
err = _parse_variable(codegen, variable_n, if_n->condition_block);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
gen->clear_temporaries();
|
||||
}
|
||||
E = E->next();
|
||||
}
|
||||
|
||||
err = _parse_block(codegen, if_n->true_block);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
_clear_block_locals(codegen, block_locals);
|
||||
codegen.end_block();
|
||||
|
||||
if (if_n->false_block) {
|
||||
gen->write_else(count);
|
||||
|
||||
err = _parse_block(codegen, if_n->false_block);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
gen->write_endif();
|
||||
} else {
|
||||
for (int i = 0; i < count; i++) {
|
||||
gen->write_endif();
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals, bool p_clear_locals) {
|
||||
Error err = OK;
|
||||
GDScriptCodeGenerator *gen = codegen.generator;
|
||||
|
@ -1935,7 +2040,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
|
|||
for (int j = 0; j < match->branches.size(); j++) {
|
||||
if (j > 0) {
|
||||
// Use `else` to not check the next branch after matching.
|
||||
gen->write_else();
|
||||
gen->write_else(1);
|
||||
}
|
||||
|
||||
const GDScriptParser::MatchBranchNode *branch = match->branches[j];
|
||||
|
@ -2004,32 +2109,10 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
|
|||
} break;
|
||||
case GDScriptParser::Node::IF: {
|
||||
const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s);
|
||||
GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, if_n->condition);
|
||||
err = _parse_if(codegen, if_n);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
gen->write_if(condition);
|
||||
|
||||
if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
||||
codegen.generator->pop_temporary();
|
||||
}
|
||||
|
||||
err = _parse_block(codegen, if_n->true_block);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (if_n->false_block) {
|
||||
gen->write_else();
|
||||
|
||||
err = _parse_block(codegen, if_n->false_block);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
gen->write_endif();
|
||||
} break;
|
||||
case GDScriptParser::Node::FOR: {
|
||||
const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s);
|
||||
|
@ -2166,36 +2249,10 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
|
|||
#endif
|
||||
} break;
|
||||
case GDScriptParser::Node::VARIABLE: {
|
||||
const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s);
|
||||
// Should be already in stack when the block began.
|
||||
GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name];
|
||||
GDScriptDataType local_type = _gdtype_from_datatype(lv->get_datatype(), codegen.script);
|
||||
|
||||
bool initialized = false;
|
||||
if (lv->initializer != nullptr) {
|
||||
GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, err, lv->initializer);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
if (lv->use_conversion_assign) {
|
||||
gen->write_assign_with_conversion(local, src_address);
|
||||
} else {
|
||||
gen->write_assign(local, src_address);
|
||||
}
|
||||
if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
|
||||
codegen.generator->pop_temporary();
|
||||
}
|
||||
initialized = true;
|
||||
} else if ((local_type.has_type && local_type.kind == GDScriptDataType::BUILTIN) || codegen.generator->is_local_dirty(local)) {
|
||||
// Initialize with default for the type. Built-in types must always be cleared (they cannot be `null`).
|
||||
// Objects and untyped variables are assigned to `null` only if the stack address has been re-used and not cleared.
|
||||
codegen.generator->clear_address(local);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// Don't check `is_local_dirty()` since the variable must be assigned to `null` **on each iteration**.
|
||||
if (!initialized && p_block->is_in_loop) {
|
||||
codegen.generator->clear_address(local);
|
||||
const GDScriptParser::VariableNode *variable_n = static_cast<const GDScriptParser::VariableNode *>(s);
|
||||
err = _parse_variable(codegen, variable_n, p_block);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
} break;
|
||||
case GDScriptParser::Node::CONSTANT: {
|
||||
|
|
|
@ -155,6 +155,8 @@ class GDScriptCompiler {
|
|||
GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested);
|
||||
List<GDScriptCodeGenerator::Address> _add_block_locals(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
|
||||
void _clear_block_locals(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_locals);
|
||||
Error _parse_variable(CodeGen &codegen, const GDScriptParser::VariableNode *p_variable, const GDScriptParser::SuiteNode *p_block);
|
||||
Error _parse_if(CodeGen &codegen, const GDScriptParser::IfNode *if_n);
|
||||
Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true, bool p_clear_locals = true);
|
||||
GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false);
|
||||
GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class);
|
||||
|
|
|
@ -2205,25 +2205,33 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
|
|||
}
|
||||
}
|
||||
|
||||
if (suite->parent_if && suite->parent_if->condition && suite->parent_if->condition->type == GDScriptParser::Node::TYPE_TEST) {
|
||||
// Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common..
|
||||
// Super dirty hack, but very useful.
|
||||
// Credit: Zylann.
|
||||
// TODO: this could be hacked to detect ANDed conditions too...
|
||||
const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(suite->parent_if->condition);
|
||||
if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->name == p_identifier->name && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->source == p_identifier->source) {
|
||||
// Bingo.
|
||||
GDScriptParser::CompletionContext c = p_context;
|
||||
c.current_line = type_test->operand->start_line;
|
||||
c.current_suite = suite;
|
||||
if (type_test->test_datatype.is_hard_type()) {
|
||||
id_type.type = type_test->test_datatype;
|
||||
if (last_assign_line < c.current_line) {
|
||||
// Override last assignment.
|
||||
last_assign_line = c.current_line;
|
||||
last_assigned_expression = nullptr;
|
||||
if (suite->parent_if) {
|
||||
List<GDScriptParser::Node *>::Element *E = suite->parent_if->conditions.front();
|
||||
while (E) {
|
||||
GDScriptParser::Node *condition = E->get();
|
||||
if (condition->is_expression() && condition->type == GDScriptParser::Node::TYPE_TEST) {
|
||||
// Operator `is` used, check if identifier is in there! this helps resolve in blocks that are (if (identifier is value)): which are very common..
|
||||
// Super dirty hack, but very useful.
|
||||
// Credit: Zylann.
|
||||
// TODO: this could be hacked to detect ANDed conditions too...
|
||||
const GDScriptParser::TypeTestNode *type_test = static_cast<const GDScriptParser::TypeTestNode *>(condition);
|
||||
if (type_test->operand && type_test->test_type && type_test->operand->type == GDScriptParser::Node::IDENTIFIER && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->name == p_identifier->name && static_cast<const GDScriptParser::IdentifierNode *>(type_test->operand)->source == p_identifier->source) {
|
||||
// Bingo.
|
||||
GDScriptParser::CompletionContext c = p_context;
|
||||
c.current_line = type_test->operand->start_line;
|
||||
c.current_suite = suite;
|
||||
if (type_test->test_datatype.is_hard_type()) {
|
||||
id_type.type = type_test->test_datatype;
|
||||
if (last_assign_line < c.current_line) {
|
||||
// Override last assignment.
|
||||
last_assign_line = c.current_line;
|
||||
last_assigned_expression = nullptr;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
E = E->next();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1076,7 +1076,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) {
|
|||
return parse_variable(p_is_static, true);
|
||||
}
|
||||
|
||||
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) {
|
||||
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property, bool p_is_if_condition) {
|
||||
VariableNode *variable = alloc_node<VariableNode>();
|
||||
|
||||
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
|
||||
|
@ -1124,6 +1124,8 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, b
|
|||
push_error(R"(Expected expression for variable initial value after "=".)");
|
||||
}
|
||||
variable->assignments++;
|
||||
} else if (p_is_if_condition) {
|
||||
push_error(R"(Expected expression for initial value after variable declaration.)");
|
||||
}
|
||||
|
||||
if (p_allow_property && match(GDScriptTokenizer::Token::COLON)) {
|
||||
|
@ -1135,7 +1137,10 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, b
|
|||
}
|
||||
|
||||
complete_extents(variable);
|
||||
end_statement("variable declaration");
|
||||
|
||||
if (p_is_if_condition == false) {
|
||||
end_statement("variable declaration");
|
||||
}
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
@ -2085,21 +2090,70 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
|
|||
}
|
||||
|
||||
GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
|
||||
IfNode *n_if = alloc_node<IfNode>();
|
||||
SuiteNode *condition_block = alloc_node<SuiteNode>();
|
||||
condition_block->parent_block = current_suite;
|
||||
condition_block->parent_function = current_function;
|
||||
SuiteNode *saved_suite = current_suite;
|
||||
current_suite = condition_block;
|
||||
|
||||
n_if->condition = parse_expression(false);
|
||||
if (n_if->condition == nullptr) {
|
||||
push_error(vformat(R"(Expected conditional expression after "%s".)", p_token));
|
||||
}
|
||||
List<Node *> conditions;
|
||||
do {
|
||||
if (match(GDScriptTokenizer::Token::VAR)) {
|
||||
// Variable declaration
|
||||
VariableNode *variable = parse_variable(false, false, true);
|
||||
if (variable == nullptr) {
|
||||
push_error(vformat(R"(Expected variable definition after "%s".)", p_token));
|
||||
break;
|
||||
} else {
|
||||
const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name);
|
||||
if (local.type != SuiteNode::Local::UNDEFINED) {
|
||||
push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name), variable->identifier);
|
||||
break;
|
||||
}
|
||||
condition_block->add_local(variable, current_function);
|
||||
|
||||
IdentifierNode *identifier = alloc_node<IdentifierNode>();
|
||||
identifier->name = variable->identifier->name;
|
||||
identifier->suite = condition_block;
|
||||
identifier->source = IdentifierNode::Source::LOCAL_VARIABLE;
|
||||
const SuiteNode::Local &declaration = condition_block->get_local(identifier->name);
|
||||
identifier->variable_source = declaration.variable;
|
||||
declaration.variable->usages++;
|
||||
complete_extents(identifier);
|
||||
|
||||
condition_block->statements.push_back(variable);
|
||||
|
||||
conditions.push_back(variable);
|
||||
conditions.push_back(identifier);
|
||||
}
|
||||
} else {
|
||||
// Expression
|
||||
ExpressionNode *expression = parse_expression(false);
|
||||
if (expression == nullptr) {
|
||||
push_error(vformat(R"(Expected conditional expression after "%s".)", p_token));
|
||||
break;
|
||||
} else {
|
||||
conditions.push_back(expression);
|
||||
}
|
||||
}
|
||||
} while (match(GDScriptTokenizer::Token::COMMA));
|
||||
|
||||
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;
|
||||
SuiteNode *true_block = parse_suite(vformat(R"("%s" block)", p_token));
|
||||
|
||||
if (n_if->true_block->has_continue) {
|
||||
current_suite->has_continue = true;
|
||||
}
|
||||
complete_extents(condition_block);
|
||||
current_suite = saved_suite;
|
||||
|
||||
IfNode *n_if = alloc_node<IfNode>();
|
||||
|
||||
true_block->parent_function = current_function;
|
||||
true_block->parent_block = condition_block;
|
||||
true_block->parent_if = n_if;
|
||||
|
||||
n_if->conditions = conditions;
|
||||
n_if->condition_block = condition_block;
|
||||
n_if->true_block = true_block;
|
||||
|
||||
if (match(GDScriptTokenizer::Token::ELIF)) {
|
||||
SuiteNode *else_block = alloc_node<SuiteNode>();
|
||||
|
@ -2121,10 +2175,10 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
|
|||
}
|
||||
complete_extents(n_if);
|
||||
|
||||
if (n_if->false_block != nullptr && n_if->false_block->has_return && n_if->true_block->has_return) {
|
||||
if ((n_if->false_block != nullptr && n_if->false_block->has_return) && n_if->true_block->has_return) {
|
||||
current_suite->has_return = true;
|
||||
}
|
||||
if (n_if->false_block != nullptr && n_if->false_block->has_continue) {
|
||||
if ((n_if->false_block != nullptr && n_if->false_block->has_continue) || n_if->true_block->has_continue) {
|
||||
current_suite->has_continue = true;
|
||||
}
|
||||
|
||||
|
@ -4785,11 +4839,19 @@ bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_t
|
|||
|
||||
// Contain bodies.
|
||||
SIMPLE_CASE(Node::FOR, ForNode, list)
|
||||
SIMPLE_CASE(Node::IF, IfNode, condition)
|
||||
SIMPLE_CASE(Node::MATCH, MatchNode, test)
|
||||
SIMPLE_CASE(Node::WHILE, WhileNode, condition)
|
||||
#undef SIMPLE_CASE
|
||||
|
||||
case Node::IF: {
|
||||
IfNode *node = static_cast<IfNode *>(p_target);
|
||||
if (node->conditions.is_empty()) {
|
||||
end_line = node->start_line;
|
||||
} else {
|
||||
end_line = node->conditions.back()->get()->end_line;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Node::CLASS: {
|
||||
end_line = p_target->start_line;
|
||||
for (const AnnotationNode *annotation : p_target->annotations) {
|
||||
|
@ -5742,7 +5804,27 @@ void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) {
|
|||
} else {
|
||||
push_text("If ");
|
||||
}
|
||||
print_expression(p_if->condition);
|
||||
List<Node *>::Element *E = p_if->conditions.front();
|
||||
bool first = true;
|
||||
while (E) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
push_text(", ");
|
||||
}
|
||||
Node *node = E->get();
|
||||
if (node->is_expression()) {
|
||||
print_expression(static_cast<ExpressionNode *>(node));
|
||||
} else if (node->type == Node::VARIABLE) {
|
||||
print_variable(static_cast<VariableNode *>(node));
|
||||
// Skip next identifier condition
|
||||
E = E->next();
|
||||
} else {
|
||||
ERR_PRINT("BUG: invalid condition");
|
||||
}
|
||||
E = E->next();
|
||||
}
|
||||
|
||||
push_line(" :");
|
||||
|
||||
increase_indent();
|
||||
|
|
|
@ -918,7 +918,8 @@ public:
|
|||
};
|
||||
|
||||
struct IfNode : public Node {
|
||||
ExpressionNode *condition = nullptr;
|
||||
List<Node *> conditions;
|
||||
SuiteNode *condition_block = nullptr;
|
||||
SuiteNode *true_block = nullptr;
|
||||
SuiteNode *false_block = nullptr;
|
||||
|
||||
|
@ -1516,7 +1517,7 @@ private:
|
|||
// Statements.
|
||||
Node *parse_statement();
|
||||
VariableNode *parse_variable(bool p_is_static);
|
||||
VariableNode *parse_variable(bool p_is_static, bool p_allow_property);
|
||||
VariableNode *parse_variable(bool p_is_static, bool p_allow_property, bool p_is_if_condition = false);
|
||||
VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent);
|
||||
void parse_property_getter(VariableNode *p_variable);
|
||||
void parse_property_setter(VariableNode *p_variable);
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
func test():
|
||||
if var x = 100:
|
||||
print("t")
|
||||
elif x > 0:
|
||||
print("f")
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_ANALYZER_ERROR
|
||||
Identifier "x" not declared in the current scope.
|
|
@ -0,0 +1,5 @@
|
|||
func test():
|
||||
if var x = 100:
|
||||
print("t")
|
||||
else:
|
||||
print(x)
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_ANALYZER_ERROR
|
||||
Identifier "x" not declared in the current scope.
|
|
@ -0,0 +1,5 @@
|
|||
func test():
|
||||
if var x = 100:
|
||||
print("t")
|
||||
|
||||
print(x)
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_ANALYZER_ERROR
|
||||
Identifier "x" not declared in the current scope.
|
|
@ -1,4 +0,0 @@
|
|||
func test():
|
||||
# Error here.
|
||||
if var foo = 25:
|
||||
print(foo)
|
|
@ -1,2 +0,0 @@
|
|||
GDTEST_PARSER_ERROR
|
||||
Expected conditional expression after "if".
|
|
@ -0,0 +1,3 @@
|
|||
func test():
|
||||
if var x := 1, var x := 2:
|
||||
print("t")
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_PARSER_ERROR
|
||||
There is already a variable named "x" declared in this scope.
|
|
@ -0,0 +1,3 @@
|
|||
func test():
|
||||
if var x, true:
|
||||
pass
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_PARSER_ERROR
|
||||
Expected expression for initial value after variable declaration.
|
|
@ -0,0 +1,53 @@
|
|||
func foo(n: int) -> bool:
|
||||
print("foo")
|
||||
return n > 0
|
||||
|
||||
func bar(n: int) -> int:
|
||||
print("bar")
|
||||
return n
|
||||
|
||||
func add(a: int, b: int) -> int:
|
||||
print("add")
|
||||
return a + b
|
||||
|
||||
func test_if(f: bool, a: int, b: int) -> void:
|
||||
print("--")
|
||||
if f, foo(a), var n := bar(b):
|
||||
print("t:%s" % n)
|
||||
elif var n = add(a, b), n >= 1:
|
||||
print("tt:%s" % n)
|
||||
else:
|
||||
print("f")
|
||||
|
||||
func test():
|
||||
if var x = 25:
|
||||
print(x)
|
||||
|
||||
if var x = 25:
|
||||
print(x)
|
||||
else:
|
||||
print("ff")
|
||||
|
||||
if var x = 0:
|
||||
print(x)
|
||||
|
||||
if var x = 0:
|
||||
print(x)
|
||||
else:
|
||||
print("fff")
|
||||
|
||||
if var x := 100:
|
||||
if x >= 1, var y := "ttt":
|
||||
print(y)
|
||||
print(100 + signi(x))
|
||||
|
||||
test_if(false, 0, 0);
|
||||
test_if(false, 0, 1);
|
||||
test_if(false, 1, 0);
|
||||
test_if(false, 1, 1);
|
||||
test_if(true, 0, 0);
|
||||
test_if(true, 0, 1);
|
||||
test_if(true, 0, 2);
|
||||
test_if(true, 1, 0);
|
||||
test_if(true, 1, 1);
|
||||
test_if(true, 1, 2);
|
|
@ -0,0 +1,43 @@
|
|||
GDTEST_OK
|
||||
25
|
||||
25
|
||||
fff
|
||||
ttt
|
||||
101
|
||||
--
|
||||
add
|
||||
f
|
||||
--
|
||||
add
|
||||
tt:1
|
||||
--
|
||||
add
|
||||
tt:1
|
||||
--
|
||||
add
|
||||
tt:2
|
||||
--
|
||||
foo
|
||||
add
|
||||
f
|
||||
--
|
||||
foo
|
||||
add
|
||||
tt:1
|
||||
--
|
||||
foo
|
||||
add
|
||||
tt:2
|
||||
--
|
||||
foo
|
||||
bar
|
||||
add
|
||||
tt:1
|
||||
--
|
||||
foo
|
||||
bar
|
||||
t:1
|
||||
--
|
||||
foo
|
||||
bar
|
||||
t:2
|
Loading…
Reference in a new issue