diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 6b7403d85477..6666275cd7bd 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -828,6 +828,9 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { case GDScriptParser::Node::FOR: resolve_for(static_cast(p_node)); break; + case GDScriptParser::Node::FOR_IF_CLAUSE: + resolve_for_if_clause(static_cast(p_node)); + break; case GDScriptParser::Node::FUNCTION: resolve_function_signature(static_cast(p_node)); resolve_function_body(static_cast(p_node)); @@ -1105,6 +1108,10 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { #endif } +void GDScriptAnalyzer::resolve_for_if_clause(GDScriptParser::ForIfClauseNode *p_for_if_clause) { + // TODO: This should attempt to resolve for errors +} + void GDScriptAnalyzer::resolve_while(GDScriptParser::WhileNode *p_while) { resolve_node(p_while->condition); @@ -1515,6 +1522,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::CONTINUE: case GDScriptParser::Node::ENUM: case GDScriptParser::Node::FOR: + case GDScriptParser::Node::FOR_IF_CLAUSE: case GDScriptParser::Node::FUNCTION: case GDScriptParser::Node::IF: case GDScriptParser::Node::MATCH: diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 32bf049fa129..cb89b96948cb 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -61,6 +61,7 @@ class GDScriptAnalyzer { void resolve_suite(GDScriptParser::SuiteNode *p_suite); void resolve_if(GDScriptParser::IfNode *p_if); void resolve_for(GDScriptParser::ForNode *p_for); + void resolve_for_if_clause(GDScriptParser::ForIfClauseNode *p_for_if_clause); void resolve_while(GDScriptParser::WhileNode *p_while); void resolve_variable(GDScriptParser::VariableNode *p_variable); void resolve_constant(GDScriptParser::ConstantNode *p_constant); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 595832631533..58e88e55cd58 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -1282,6 +1282,13 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments. } +void GDScriptByteCodeGenerator::write_assign_dictionary_value(const Address &p_target, const Address &p_key, const Address &p_value) { + append(GDScriptFunction::OPCODE_ASSIGN_DICTIONARY_VALUE, 3); + append(p_target); + append(p_key); + append(p_value); +} + void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) { append(GDScriptFunction::OPCODE_AWAIT, 1); append(p_operand); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 3d6fb291ad73..adccb9bc31bd 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -473,6 +473,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { virtual void write_construct_array(const Address &p_target, const Vector
&p_arguments) override; virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector
&p_arguments) override; virtual void write_construct_dictionary(const Address &p_target, const Vector
&p_arguments) override; + virtual void write_assign_dictionary_value(const Address &p_target, const Address& p_key, const Address& p_value) 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; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index ecc86c37f37f..e5d21515847a 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -134,6 +134,7 @@ class GDScriptCodeGenerator { virtual void write_construct_array(const Address &p_target, const Vector
&p_arguments) = 0; virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector
&p_arguments) = 0; virtual void write_construct_dictionary(const Address &p_target, const Vector
&p_arguments) = 0; + virtual void write_assign_dictionary_value(const Address &p_target, const Address& p_key, const Address& p_value) = 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; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index ddf4f281b4d9..fc97822bcd36 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -381,23 +381,90 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype()); GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type); - for (int i = 0; i < an->elements.size(); i++) { - GDScriptCodeGenerator::Address val = _parse_expression(codegen, r_error, an->elements[i]); + if (an->for_if_clauses.size() > 0 && an->template_expression != nullptr) { + gen->write_construct_array(result, values); + + for (int i = 0; i < an->for_if_clauses.size(); i++) { + const GDScriptParser::ForIfClauseNode *for_if_clause = an->for_if_clauses[i]; + + codegen.start_block(); + GDScriptCodeGenerator::Address iterator = codegen.add_local(for_if_clause->variable->name, _gdtype_from_datatype(for_if_clause->variable->get_datatype())); + + gen->start_for(iterator.type, _gdtype_from_datatype(for_if_clause->list->get_datatype())); + + GDScriptCodeGenerator::Address list = _parse_expression(codegen, r_error, for_if_clause->list); + + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + + gen->write_for_assignment(iterator, list); + + if (list.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + + gen->write_for(); + + if (for_if_clause->if_condition != nullptr) { + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, r_error, for_if_clause->if_condition); + + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + + gen->write_if(condition); + + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + + gen->start_block(); + } + } + + GDScriptCodeGenerator::Address template_expression = _parse_expression(codegen, r_error, an->template_expression); if (r_error) { return GDScriptCodeGenerator::Address(); } - values.push_back(val); - } - if (array_type.has_container_element_type()) { - gen->write_construct_typed_array(result, array_type.get_container_element_type(), values); + values.push_back(template_expression); + gen->write_call_builtin_type(result, result, Variant::Type::ARRAY, "push_back", values); + + for (int i = 0; i < an->for_if_clauses.size(); i++) { + const GDScriptParser::ForIfClauseNode *for_if_clause = an->for_if_clauses[i]; + + if (for_if_clause->if_condition != nullptr) { + gen->end_block(); + gen->write_endif(); + } + + gen->write_endfor(); + codegen.end_block(); + } + + if (template_expression.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } } else { - gen->write_construct_array(result, values); - } + for (int i = 0; i < an->elements.size(); i++) { + GDScriptCodeGenerator::Address val = _parse_expression(codegen, r_error, an->elements[i]); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + values.push_back(val); + } - for (int i = 0; i < values.size(); i++) { - if (values[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + if (array_type.has_container_element_type()) { + gen->write_construct_typed_array(result, array_type.get_container_element_type(), values); + } else { + gen->write_construct_array(result, values); + } + + for (int i = 0; i < values.size(); i++) { + if (values[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } } @@ -414,39 +481,126 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code dict_type.builtin_type = Variant::DICTIONARY; GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type); - for (int i = 0; i < dn->elements.size(); i++) { + if (dn->for_if_clauses.size() > 0 && dn->template_keyvalue.key != nullptr && dn->template_keyvalue.value != nullptr) { + gen->write_construct_dictionary(result, elements); + + for (int i = 0; i < dn->for_if_clauses.size(); i++) { + const GDScriptParser::ForIfClauseNode *for_if_clause = dn->for_if_clauses[i]; + + codegen.start_block(); + GDScriptCodeGenerator::Address iterator = codegen.add_local(for_if_clause->variable->name, _gdtype_from_datatype(for_if_clause->variable->get_datatype())); + + gen->start_for(iterator.type, _gdtype_from_datatype(for_if_clause->list->get_datatype())); + + GDScriptCodeGenerator::Address list = _parse_expression(codegen, r_error, for_if_clause->list); + + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + + gen->write_for_assignment(iterator, list); + + if (list.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + + gen->write_for(); + + if (for_if_clause->if_condition != nullptr) { + GDScriptCodeGenerator::Address condition = _parse_expression(codegen, r_error, for_if_clause->if_condition); + + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + + gen->write_if(condition); + + if (condition.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + + gen->start_block(); + } + } + // Key. - GDScriptCodeGenerator::Address element; + GDScriptCodeGenerator::Address key; switch (dn->style) { case GDScriptParser::DictionaryNode::PYTHON_DICT: // Python-style: key is any expression. - element = _parse_expression(codegen, r_error, dn->elements[i].key); + key = _parse_expression(codegen, r_error, dn->template_keyvalue.key); if (r_error) { return GDScriptCodeGenerator::Address(); } break; case GDScriptParser::DictionaryNode::LUA_TABLE: // Lua-style: key is an identifier interpreted as StringName. - StringName key = static_cast(dn->elements[i].key)->name; - element = codegen.add_constant(key); + StringName key_id = static_cast(dn->template_keyvalue.key)->name; + key = codegen.add_constant(key_id); break; } - elements.push_back(element); - - element = _parse_expression(codegen, r_error, dn->elements[i].value); + GDScriptCodeGenerator::Address value = _parse_expression(codegen, r_error, dn->template_keyvalue.value); if (r_error) { return GDScriptCodeGenerator::Address(); } - elements.push_back(element); - } + gen->write_assign_dictionary_value(result, key, value); - gen->write_construct_dictionary(result, elements); + for (int i = 0; i < dn->for_if_clauses.size(); i++) { + const GDScriptParser::ForIfClauseNode *for_if_clause = dn->for_if_clauses[i]; - for (int i = 0; i < elements.size(); i++) { - if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + if (for_if_clause->if_condition != nullptr) { + gen->end_block(); + gen->write_endif(); + } + + gen->write_endfor(); + codegen.end_block(); + } + + if (key.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + + if (value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + } else { + for (int i = 0; i < dn->elements.size(); i++) { + // Key. + GDScriptCodeGenerator::Address element; + switch (dn->style) { + case GDScriptParser::DictionaryNode::PYTHON_DICT: + // Python-style: key is any expression. + element = _parse_expression(codegen, r_error, dn->elements[i].key); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + break; + case GDScriptParser::DictionaryNode::LUA_TABLE: + // Lua-style: key is an identifier interpreted as StringName. + StringName key = static_cast(dn->elements[i].key)->name; + element = codegen.add_constant(key); + break; + } + + elements.push_back(element); + + element = _parse_expression(codegen, r_error, dn->elements[i].value); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + + elements.push_back(element); + } + + gen->write_construct_dictionary(result, elements); + + for (int i = 0; i < elements.size(); i++) { + if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } } diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 87d8c0349467..748fdc401360 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -249,6 +249,7 @@ class GDScriptFunction { OPCODE_CONSTRUCT_ARRAY, OPCODE_CONSTRUCT_TYPED_ARRAY, OPCODE_CONSTRUCT_DICTIONARY, + OPCODE_ASSIGN_DICTIONARY_VALUE, OPCODE_CALL, OPCODE_CALL_RETURN, OPCODE_CALL_ASYNC, diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index d21caf4389bf..9351f9974fdc 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -2399,7 +2399,13 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_array(ExpressionNode *p_pr if (element == nullptr) { push_error(R"(Expected expression as array element.)"); } else { - array->elements.push_back(element); + if (array->elements.size() == 0 && check(GDScriptTokenizer::Token::FOR)) { + array->template_expression = element; + parse_for_if_clauses(array->for_if_clauses); + break; + } else { + array->elements.push_back(element); + } } } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); } @@ -2409,6 +2415,30 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_array(ExpressionNode *p_pr return array; } +void GDScriptParser::parse_for_if_clauses(Vector& clauses) { + do { + ForIfClauseNode *clause = alloc_node(); + + consume(GDScriptTokenizer::Token::FOR, R"(Expected "for" for for-if-clause)"); + + if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected loop variable name after "for".)")) { + clause->variable = parse_identifier(); + } + + consume(GDScriptTokenizer::Token::IN, R"(Expected "in")"); + + clause->list = parse_precedence(PREC_LOGIC_OR, false); + + if (check(GDScriptTokenizer::Token::IF)) { + consume(GDScriptTokenizer::Token::IF, "R(Expected If)"); + clause->if_condition = parse_expression(false); + } + + clauses.push_back(clause); + + } while (check(GDScriptTokenizer::Token::FOR)); +} + GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode *p_previous_operand, bool p_can_assign) { DictionaryNode *dictionary = alloc_node(); @@ -2478,6 +2508,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode push_error(R"(Expected expression as dictionary value.)"); } + if (dictionary->elements.size() == 0 && check(GDScriptTokenizer::Token::FOR)) { + dictionary->template_keyvalue = { key, value }; + parse_for_if_clauses(dictionary->for_if_clauses); + break; + } + if (key != nullptr && value != nullptr) { dictionary->elements.push_back({ key, value }); } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 0bce8d7ddd85..181228746a5e 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -72,6 +72,7 @@ class GDScriptParser { struct EnumNode; struct ExpressionNode; struct ForNode; + struct ForIfClauseNode; struct FunctionNode; struct GetNodeNode; struct IdentifierNode; @@ -264,6 +265,7 @@ class GDScriptParser { DICTIONARY, ENUM, FOR, + FOR_IF_CLAUSE, FUNCTION, GET_NODE, IDENTIFIER, @@ -335,6 +337,9 @@ class GDScriptParser { struct ArrayNode : public ExpressionNode { Vector elements; + ExpressionNode* template_expression; + Vector for_if_clauses; + ArrayNode() { type = ARRAY; @@ -700,6 +705,9 @@ class GDScriptParser { }; Vector elements; + Pair template_keyvalue; + Vector for_if_clauses; + enum Style { LUA_TABLE, PYTHON_DICT, @@ -721,6 +729,16 @@ class GDScriptParser { } }; + struct ForIfClauseNode : public Node { + IdentifierNode *variable = nullptr; + ExpressionNode *list = nullptr; + ExpressionNode *if_condition = nullptr; + + ForIfClauseNode() { + type = FOR_IF_CLAUSE; + } + }; + struct FunctionNode : public Node { IdentifierNode *identifier = nullptr; Vector parameters; @@ -1354,6 +1372,7 @@ class GDScriptParser { BreakNode *parse_break(); ContinueNode *parse_continue(); ForNode *parse_for(); + void parse_for_if_clauses(Vector& output_clauses); IfNode *parse_if(const String &p_token = "if"); MatchNode *parse_match(); MatchBranchNode *parse_match_branch(); diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 882256b7e34b..49b7e477e89b 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -224,6 +224,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_CONSTRUCT_ARRAY, \ &&OPCODE_CONSTRUCT_TYPED_ARRAY, \ &&OPCODE_CONSTRUCT_DICTIONARY, \ + &&OPCODE_ASSIGN_DICTIONARY_VALUE, \ &&OPCODE_CALL, \ &&OPCODE_CALL_RETURN, \ &&OPCODE_CALL_ASYNC, \ @@ -1436,6 +1437,20 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_DICTIONARY_VALUE) { + CHECK_SPACE(4); + + GET_INSTRUCTION_ARG(variant, 0); + GET_INSTRUCTION_ARG(k, 1); + GET_INSTRUCTION_ARG(v, 2); + + Dictionary *dict = VariantInternal::get_dictionary(variant); + (*dict)[*k] = *v; + + ip += 4; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_CALL_ASYNC) OPCODE(OPCODE_CALL_RETURN) OPCODE(OPCODE_CALL) { diff --git a/modules/gdscript/tests/scripts/parser/features/for_if_clause.gd b/modules/gdscript/tests/scripts/parser/features/for_if_clause.gd new file mode 100644 index 000000000000..022389ea41b3 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/for_if_clause.gd @@ -0,0 +1,12 @@ +func test() -> void: + var numbers := range(10) + + prints('numbers array:', numbers) + prints('straight copy:', [i for i in numbers]) + prints('only evens:', [i for i in numbers if i % 2 == 0]) + prints('2x2 grid coordinates:', [Vector2(x, y) for x in range(2) for y in range(2)]) + prints('3x3 grid only diagonals:',[Vector2(x, y) for x in range(3) for y in range(3) if x == y]) + prints('only pickout the strings:', [s for s in [1, '1', 1.0, Vector2(), true, "hello world"] if s is String]) + + prints('number to string version dictionary:', { i : Vector2(i, i) for i in range(10) }) + prints('using lua syntax:', { id = i for i in range(10) }) \ No newline at end of file