Fix logic errors in match-statement Array & Dictionary Patterns

This commit is contained in:
cdemirer 2022-03-02 19:05:18 +08:00
parent b7a852a05d
commit 3afe50c2fa
5 changed files with 193 additions and 105 deletions

View file

@ -1359,6 +1359,44 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
codegen.generator->pop_temporary();
codegen.generator->pop_temporary();
// Create temporaries outside the loop so they can be reused.
GDScriptCodeGenerator::Address element_addr = codegen.add_temporary();
GDScriptCodeGenerator::Address element_type_addr = codegen.add_temporary();
// Evaluate element by element.
for (int i = 0; i < p_pattern->array.size(); i++) {
if (p_pattern->array[i]->pattern_type == GDScriptParser::PatternNode::PT_REST) {
// Don't want to access an extra element of the user array.
break;
}
// Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get).
codegen.generator->write_and_left_operand(result_addr);
// Add index to constant map.
GDScriptCodeGenerator::Address index_addr = codegen.add_constant(i);
// Get the actual element from the user-sent array.
codegen.generator->write_get(element_addr, index_addr, p_value_addr);
// Also get type of element.
Vector<GDScriptCodeGenerator::Address> typeof_args;
typeof_args.push_back(element_addr);
codegen.generator->write_call_utility(element_type_addr, "typeof", typeof_args);
// Try the pattern inside the element.
result_addr = _parse_match_pattern(codegen, r_error, p_pattern->array[i], element_addr, element_type_addr, result_addr, false, true);
if (r_error != OK) {
return GDScriptCodeGenerator::Address();
}
codegen.generator->write_and_right_operand(result_addr);
codegen.generator->write_end_and(result_addr);
}
// Remove element temporaries.
codegen.generator->pop_temporary();
codegen.generator->pop_temporary();
// If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead.
if (p_is_nested) {
// Use the previous value as target, since we only need one temporary variable.
@ -1374,46 +1412,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
}
codegen.generator->pop_temporary(); // Remove temp result addr.
// Create temporaries outside the loop so they can be reused.
GDScriptCodeGenerator::Address element_addr = codegen.add_temporary();
GDScriptCodeGenerator::Address element_type_addr = codegen.add_temporary();
GDScriptCodeGenerator::Address test_addr = p_previous_test;
// Evaluate element by element.
for (int i = 0; i < p_pattern->array.size(); i++) {
if (p_pattern->array[i]->pattern_type == GDScriptParser::PatternNode::PT_REST) {
// Don't want to access an extra element of the user array.
break;
}
// Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get).
codegen.generator->write_and_left_operand(test_addr);
// Add index to constant map.
GDScriptCodeGenerator::Address index_addr = codegen.add_constant(i);
// Get the actual element from the user-sent array.
codegen.generator->write_get(element_addr, index_addr, p_value_addr);
// Also get type of element.
Vector<GDScriptCodeGenerator::Address> typeof_args;
typeof_args.push_back(element_addr);
codegen.generator->write_call_utility(element_type_addr, "typeof", typeof_args);
// Try the pattern inside the element.
test_addr = _parse_match_pattern(codegen, r_error, p_pattern->array[i], element_addr, element_type_addr, p_previous_test, false, true);
if (r_error != OK) {
return GDScriptCodeGenerator::Address();
}
codegen.generator->write_and_right_operand(test_addr);
codegen.generator->write_end_and(test_addr);
}
// Remove element temporaries.
codegen.generator->pop_temporary();
codegen.generator->pop_temporary();
return test_addr;
return p_previous_test;
} break;
case GDScriptParser::PatternNode::PT_DICTIONARY: {
if (p_is_nested) {
@ -1458,6 +1457,66 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
codegen.generator->pop_temporary();
codegen.generator->pop_temporary();
// Create temporaries outside the loop so they can be reused.
GDScriptCodeGenerator::Address element_addr = codegen.add_temporary();
GDScriptCodeGenerator::Address element_type_addr = codegen.add_temporary();
// Evaluate element by element.
for (int i = 0; i < p_pattern->dictionary.size(); i++) {
const GDScriptParser::PatternNode::Pair &element = p_pattern->dictionary[i];
if (element.value_pattern && element.value_pattern->pattern_type == GDScriptParser::PatternNode::PT_REST) {
// Ignore rest pattern.
break;
}
// Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get).
codegen.generator->write_and_left_operand(result_addr);
// Get the pattern key.
GDScriptCodeGenerator::Address pattern_key_addr = _parse_expression(codegen, r_error, element.key);
if (r_error) {
return GDScriptCodeGenerator::Address();
}
// Check if pattern key exists in user's dictionary. This will be AND-ed with next result.
func_args.clear();
func_args.push_back(pattern_key_addr);
codegen.generator->write_call(result_addr, p_value_addr, "has", func_args);
if (element.value_pattern != nullptr) {
// Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get).
codegen.generator->write_and_left_operand(result_addr);
// Get actual value from user dictionary.
codegen.generator->write_get(element_addr, pattern_key_addr, p_value_addr);
// Also get type of value.
func_args.clear();
func_args.push_back(element_addr);
codegen.generator->write_call_utility(element_type_addr, "typeof", func_args);
// Try the pattern inside the value.
result_addr = _parse_match_pattern(codegen, r_error, element.value_pattern, element_addr, element_type_addr, result_addr, false, true);
if (r_error != OK) {
return GDScriptCodeGenerator::Address();
}
codegen.generator->write_and_right_operand(result_addr);
codegen.generator->write_end_and(result_addr);
}
codegen.generator->write_and_right_operand(result_addr);
codegen.generator->write_end_and(result_addr);
// Remove pattern key temporary.
if (pattern_key_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
}
// Remove element temporaries.
codegen.generator->pop_temporary();
codegen.generator->pop_temporary();
// If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead.
if (p_is_nested) {
// Use the previous value as target, since we only need one temporary variable.
@ -1473,71 +1532,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
}
codegen.generator->pop_temporary(); // Remove temp result addr.
// Create temporaries outside the loop so they can be reused.
temp_type.builtin_type = Variant::BOOL;
GDScriptCodeGenerator::Address test_result = codegen.add_temporary(temp_type);
GDScriptCodeGenerator::Address element_addr = codegen.add_temporary();
GDScriptCodeGenerator::Address element_type_addr = codegen.add_temporary();
GDScriptCodeGenerator::Address test_addr = p_previous_test;
// Evaluate element by element.
for (int i = 0; i < p_pattern->dictionary.size(); i++) {
const GDScriptParser::PatternNode::Pair &element = p_pattern->dictionary[i];
if (element.value_pattern && element.value_pattern->pattern_type == GDScriptParser::PatternNode::PT_REST) {
// Ignore rest pattern.
break;
}
// Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get).
codegen.generator->write_and_left_operand(test_addr);
// Get the pattern key.
GDScriptCodeGenerator::Address pattern_key_addr = _parse_expression(codegen, r_error, element.key);
if (r_error) {
return GDScriptCodeGenerator::Address();
}
// Check if pattern key exists in user's dictionary. This will be AND-ed with next result.
func_args.clear();
func_args.push_back(pattern_key_addr);
codegen.generator->write_call(test_result, p_value_addr, "has", func_args);
if (element.value_pattern != nullptr) {
// Use AND here too, as we don't want to be checking elements if previous test failed (which means this might be an invalid get).
codegen.generator->write_and_left_operand(test_result);
// Get actual value from user dictionary.
codegen.generator->write_get(element_addr, pattern_key_addr, p_value_addr);
// Also get type of value.
func_args.clear();
func_args.push_back(element_addr);
codegen.generator->write_call_utility(element_type_addr, "typeof", func_args);
// Try the pattern inside the value.
test_addr = _parse_match_pattern(codegen, r_error, element.value_pattern, element_addr, element_type_addr, test_addr, false, true);
if (r_error != OK) {
return GDScriptCodeGenerator::Address();
}
codegen.generator->write_and_right_operand(test_addr);
codegen.generator->write_end_and(test_addr);
}
codegen.generator->write_and_right_operand(test_addr);
codegen.generator->write_end_and(test_addr);
// Remove pattern key temporary.
if (pattern_key_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) {
codegen.generator->pop_temporary();
}
}
// Remove element temporaries.
codegen.generator->pop_temporary();
codegen.generator->pop_temporary();
codegen.generator->pop_temporary();
return test_addr;
return p_previous_test;
} break;
case GDScriptParser::PatternNode::PT_REST:
// Do nothing.