Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Python-style list comprehensions to GDScript #51997

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions modules/gdscript/gdscript_analyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,9 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) {
case GDScriptParser::Node::FOR:
resolve_for(static_cast<GDScriptParser::ForNode *>(p_node));
break;
case GDScriptParser::Node::FOR_IF_CLAUSE:
resolve_for_if_clause(static_cast<GDScriptParser::ForIfClauseNode *>(p_node));
break;
case GDScriptParser::Node::FUNCTION:
resolve_function_signature(static_cast<GDScriptParser::FunctionNode *>(p_node));
resolve_function_body(static_cast<GDScriptParser::FunctionNode *>(p_node));
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions modules/gdscript/gdscript_analyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions modules/gdscript/gdscript_byte_codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions modules/gdscript/gdscript_byte_codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &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;
Expand Down
1 change: 1 addition & 0 deletions modules/gdscript/gdscript_codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class GDScriptCodeGenerator {
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &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;
Expand Down
204 changes: 179 additions & 25 deletions modules/gdscript/gdscript_compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}

Expand All @@ -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<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name;
element = codegen.add_constant(key);
StringName key_id = static_cast<const GDScriptParser::IdentifierNode *>(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<const GDScriptParser::IdentifierNode *>(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();
}
}
}

Expand Down
1 change: 1 addition & 0 deletions modules/gdscript/gdscript_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
38 changes: 37 additions & 1 deletion modules/gdscript/gdscript_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand All @@ -2409,6 +2415,30 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_array(ExpressionNode *p_pr
return array;
}

void GDScriptParser::parse_for_if_clauses(Vector<ForIfClauseNode *>& clauses) {
do {
ForIfClauseNode *clause = alloc_node<ForIfClauseNode>();

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<DictionaryNode>();

Expand Down Expand Up @@ -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 });
}
Expand Down
Loading