From ca65fa9a7f3d72d62ce980ee16b3b20ed7bdedef Mon Sep 17 00:00:00 2001 From: Nikita Nemkin Date: Fri, 14 Feb 2025 22:13:18 +0500 Subject: [PATCH] string: Allow references to unmatched groups in REGEX REPLACE References to unmatched groups will be replaced with empty strings. Issue: #26629 Fixes: #19012 --- Help/command/string.rst | 3 +++ Help/release/dev/regex-fixes.rst | 3 +++ Source/cmStringReplaceHelper.cxx | 6 ++---- Tests/CMakeTests/StringTest.cmake.in | 2 +- Tests/CMakeTests/StringTestScript.cmake | 4 ++++ 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Help/command/string.rst b/Help/command/string.rst index c510ff44d5..b125e4b975 100644 --- a/Help/command/string.rst +++ b/Help/command/string.rst @@ -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 diff --git a/Help/release/dev/regex-fixes.rst b/Help/release/dev/regex-fixes.rst index e979c03efe..67f80ed6c4 100644 --- a/Help/release/dev/regex-fixes.rst +++ b/Help/release/dev/regex-fixes.rst @@ -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. diff --git a/Source/cmStringReplaceHelper.cxx b/Source/cmStringReplaceHelper.cxx index 5cd159e235..e909b05a31 100644 --- a/Source/cmStringReplaceHelper.cxx +++ b/Source/cmStringReplaceHelper.cxx @@ -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); } } diff --git a/Tests/CMakeTests/StringTest.cmake.in b/Tests/CMakeTests/StringTest.cmake.in index 6a94cc5852..874655130f 100644 --- a/Tests/CMakeTests/StringTest.cmake.in +++ b/Tests/CMakeTests/StringTest.cmake.in @@ -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) diff --git a/Tests/CMakeTests/StringTestScript.cmake b/Tests/CMakeTests/StringTestScript.cmake index 7c4585791b..dc7b8ae61f 100644 --- a/Tests/CMakeTests/StringTestScript.cmake +++ b/Tests/CMakeTests/StringTestScript.cmake @@ -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)