string: Allow references to unmatched groups in REGEX REPLACE

References to unmatched groups will be replaced with empty strings.

Issue: #26629
Fixes: #19012
This commit is contained in:
Nikita Nemkin 2025-02-14 22:13:18 +05:00
parent 8845d33292
commit ca65fa9a7f
5 changed files with 13 additions and 5 deletions

View File

@ -122,6 +122,9 @@ Search and Replace With Regular Expressions
string instead of the beginning of each repeated search.
See policy :policy:`CMP0186`.
The replacement expression may contain references to subexpressions that
didn't match anything. Previously, such references triggered an error.
.. _`Regex Specification`:
Regex Specification

View File

@ -3,3 +3,6 @@ regex-fixes
* Regular expressions match the ``^`` anchor at most once in repeated
searches, at the start of the input. See policy :policy:`CMP0186`.
* References to unmatched groups are allowed, they are replaced with empty
strings.

View File

@ -61,10 +61,7 @@ bool cmStringReplaceHelper::Replace(std::string const& input,
} else {
// Replace with part of the match.
auto n = replacement.Number;
auto start = this->RegularExpression.start(n);
if (start != std::string::npos) {
output += this->RegularExpression.match(n);
} else {
if (n > this->RegularExpression.num_groups()) {
std::ostringstream error;
error << "replace expression \"" << this->ReplaceExpression
<< "\" contains an out-of-range escape for regex \""
@ -72,6 +69,7 @@ bool cmStringReplaceHelper::Replace(std::string const& input,
this->ErrorString = error.str();
return false;
}
output += this->RegularExpression.match(n);
}
}

View File

@ -84,7 +84,7 @@ check_cmake_test(String
# Execute each test listed in StringTestScript.cmake:
#
set(scriptname "@CMAKE_CURRENT_SOURCE_DIR@/StringTestScript.cmake")
set(number_of_tests_expected 72)
set(number_of_tests_expected 73)
include("@CMAKE_CURRENT_SOURCE_DIR@/ExecuteScriptTests.cmake")
execute_all_script_tests(${scriptname} number_of_tests_executed)

View File

@ -116,6 +116,10 @@ elseif(testname STREQUAL regex_replace_index_too_small) # fail
elseif(testname STREQUAL regex_replace_index_too_large) # fail
string(REGEX REPLACE "^this (.*)$" "with \\1 \\2" v "this input")
elseif(testname STREQUAL regex_replace_index_no_match) # pass
string(REGEX REPLACE "^(this (.*)|(that .*))$" "with \\1 \\2 \\3" v "this input")
message(STATUS "v='${v}'")
elseif(testname STREQUAL compare_no_mode) # fail
string(COMPARE)