From cdae12f8f86730e075598118ebe5fd2f11746af7 Mon Sep 17 00:00:00 2001
From: Marc Chevrier <marc.chevrier@sap.com>
Date: Tue, 3 Apr 2018 15:09:53 +0200
Subject: [PATCH] string() Refactoring: creates an helper for REGEX REPLACE

---
 Source/CMakeLists.txt            |   1 +
 Source/cmStringCommand.cxx       | 100 ++++----------------------
 Source/cmStringCommand.h         |  23 ------
 Source/cmStringReplaceHelper.cxx | 117 +++++++++++++++++++++++++++++++
 Source/cmStringReplaceHelper.h   |  69 ++++++++++++++++++
 bootstrap                        |   1 +
 6 files changed, 202 insertions(+), 109 deletions(-)
 create mode 100644 Source/cmStringReplaceHelper.cxx
 create mode 100644 Source/cmStringReplaceHelper.h

diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index e23b070566..a007098378 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -557,6 +557,7 @@ set(SRCS
   cmSiteNameCommand.h
   cmSourceGroupCommand.cxx
   cmSourceGroupCommand.h
+  cmStringReplaceHelper.cxx
   cmStringCommand.cxx
   cmStringCommand.h
   cmSubdirCommand.cxx
diff --git a/Source/cmStringCommand.cxx b/Source/cmStringCommand.cxx
index 9631912e0e..dabec47b8d 100644
--- a/Source/cmStringCommand.cxx
+++ b/Source/cmStringCommand.cxx
@@ -13,6 +13,7 @@
 #include "cmCryptoHash.h"
 #include "cmGeneratorExpression.h"
 #include "cmMakefile.h"
+#include "cmStringReplaceHelper.h"
 #include "cmSystemTools.h"
 #include "cmTimestamp.h"
 #include "cmUuid.h"
@@ -344,46 +345,17 @@ bool cmStringCommand::RegexReplace(std::vector<std::string> const& args)
   std::string const& regex = args[2];
   std::string const& replace = args[3];
   std::string const& outvar = args[4];
+  cmStringReplaceHelper replaceHelper(regex, replace, this->Makefile);
 
-  // Pull apart the replace expression to find the escaped [0-9] values.
-  std::vector<RegexReplacement> replacement;
-  std::string::size_type l = 0;
-  while (l < replace.length()) {
-    std::string::size_type r = replace.find('\\', l);
-    if (r == std::string::npos) {
-      r = replace.length();
-      replacement.push_back(replace.substr(l, r - l));
-    } else {
-      if (r - l > 0) {
-        replacement.push_back(replace.substr(l, r - l));
-      }
-      if (r == (replace.length() - 1)) {
-        this->SetError("sub-command REGEX, mode REPLACE: "
-                       "replace-expression ends in a backslash.");
-        return false;
-      }
-      if ((replace[r + 1] >= '0') && (replace[r + 1] <= '9')) {
-        replacement.push_back(replace[r + 1] - '0');
-      } else if (replace[r + 1] == 'n') {
-        replacement.push_back("\n");
-      } else if (replace[r + 1] == '\\') {
-        replacement.push_back("\\");
-      } else {
-        std::string e = "sub-command REGEX, mode REPLACE: Unknown escape \"";
-        e += replace.substr(r, 2);
-        e += "\" in replace-expression.";
-        this->SetError(e);
-        return false;
-      }
-      r += 2;
-    }
-    l = r;
+  if (!replaceHelper.IsReplaceExpressionValid()) {
+    this->SetError("sub-command REGEX, mode REPLACE: " +
+                   replaceHelper.GetError() + ".");
+    return false;
   }
 
   this->Makefile->ClearMatches();
-  // Compile the regular expression.
-  cmsys::RegularExpression re;
-  if (!re.compile(regex.c_str())) {
+
+  if (!replaceHelper.IsRegularExpressionValid()) {
     std::string e =
       "sub-command REGEX, mode REPLACE failed to compile regex \"" + regex +
       "\".";
@@ -392,60 +364,16 @@ bool cmStringCommand::RegexReplace(std::vector<std::string> const& args)
   }
 
   // Concatenate all the last arguments together.
-  std::string input = cmJoin(cmMakeRange(args).advance(5), std::string());
-
-  // Scan through the input for all matches.
+  const std::string input =
+    cmJoin(cmMakeRange(args).advance(5), std::string());
   std::string output;
-  std::string::size_type base = 0;
-  while (re.find(input.c_str() + base)) {
-    this->Makefile->ClearMatches();
-    this->Makefile->StoreMatches(re);
-    std::string::size_type l2 = re.start();
-    std::string::size_type r = re.end();
-
-    // Concatenate the part of the input that was not matched.
-    output += input.substr(base, l2);
-
-    // Make sure the match had some text.
-    if (r - l2 == 0) {
-      std::string e = "sub-command REGEX, mode REPLACE regex \"" + regex +
-        "\" matched an empty string.";
-      this->SetError(e);
-      return false;
-    }
 
-    // Concatenate the replacement for the match.
-    for (RegexReplacement const& i : replacement) {
-      if (i.number < 0) {
-        // This is just a plain-text part of the replacement.
-        output += i.value;
-      } else {
-        // Replace with part of the match.
-        int n = i.number;
-        std::string::size_type start = re.start(n);
-        std::string::size_type end = re.end(n);
-        std::string::size_type len = input.length() - base;
-        if ((start != std::string::npos) && (end != std::string::npos) &&
-            (start <= len) && (end <= len)) {
-          output += input.substr(base + start, end - start);
-        } else {
-          std::string e =
-            "sub-command REGEX, mode REPLACE: replace expression \"" +
-            replace + "\" contains an out-of-range escape for regex \"" +
-            regex + "\".";
-          this->SetError(e);
-          return false;
-        }
-      }
-    }
-
-    // Move past the match.
-    base += r;
+  if (!replaceHelper.Replace(input, output)) {
+    this->SetError("sub-command REGEX, mode REPLACE: " +
+                   replaceHelper.GetError() + ".");
+    return false;
   }
 
-  // Concatenate the text after the last match.
-  output += input.substr(base, input.length() - base);
-
   // Store the output in the provided variable.
   this->Makefile->AddDefinition(outvar, output.c_str());
   return true;
diff --git a/Source/cmStringCommand.h b/Source/cmStringCommand.h
index 569ed83ec8..cbff73e99d 100644
--- a/Source/cmStringCommand.h
+++ b/Source/cmStringCommand.h
@@ -60,29 +60,6 @@ protected:
 
   bool joinImpl(std::vector<std::string> const& args, std::string const& glue,
                 size_t varIdx);
-
-  class RegexReplacement
-  {
-  public:
-    RegexReplacement(const char* s)
-      : number(-1)
-      , value(s)
-    {
-    }
-    RegexReplacement(const std::string& s)
-      : number(-1)
-      , value(s)
-    {
-    }
-    RegexReplacement(int n)
-      : number(n)
-      , value()
-    {
-    }
-    RegexReplacement() {}
-    int number;
-    std::string value;
-  };
 };
 
 #endif
diff --git a/Source/cmStringReplaceHelper.cxx b/Source/cmStringReplaceHelper.cxx
new file mode 100644
index 0000000000..69b7cedd69
--- /dev/null
+++ b/Source/cmStringReplaceHelper.cxx
@@ -0,0 +1,117 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmStringReplaceHelper.h"
+
+#include "cmMakefile.h"
+#include <sstream>
+
+cmStringReplaceHelper::cmStringReplaceHelper(const std::string& regex,
+                                             const std::string& replace_expr,
+                                             cmMakefile* makefile)
+  : RegExString(regex)
+  , RegularExpression(regex)
+  , ReplaceExpression(replace_expr)
+  , Makefile(makefile)
+{
+  this->ParseReplaceExpression();
+}
+
+bool cmStringReplaceHelper::Replace(const std::string& input,
+                                    std::string& output)
+{
+  output.clear();
+
+  // Scan through the input for all matches.
+  std::string::size_type base = 0;
+  while (this->RegularExpression.find(input.c_str() + base)) {
+    if (this->Makefile != nullptr) {
+      this->Makefile->ClearMatches();
+      this->Makefile->StoreMatches(this->RegularExpression);
+    }
+    auto l2 = this->RegularExpression.start();
+    auto r = this->RegularExpression.end();
+
+    // Concatenate the part of the input that was not matched.
+    output += input.substr(base, l2);
+
+    // Make sure the match had some text.
+    if (r - l2 == 0) {
+      std::ostringstream error;
+      error << "regex \"" << this->RegExString << "\" matched an empty string";
+      this->ErrorString = error.str();
+      return false;
+    }
+
+    // Concatenate the replacement for the match.
+    for (const auto& replacement : this->Replacements) {
+      if (replacement.Number < 0) {
+        // This is just a plain-text part of the replacement.
+        output += replacement.Value;
+      } else {
+        // Replace with part of the match.
+        auto n = replacement.Number;
+        auto start = this->RegularExpression.start(n);
+        auto end = this->RegularExpression.end(n);
+        auto len = input.length() - base;
+        if ((start != std::string::npos) && (end != std::string::npos) &&
+            (start <= len) && (end <= len)) {
+          output += input.substr(base + start, end - start);
+        } else {
+          std::ostringstream error;
+          error << "replace expression \"" << this->ReplaceExpression
+                << "\" contains an out-of-range escape for regex \""
+                << this->RegExString << "\"";
+          this->ErrorString = error.str();
+          return false;
+        }
+      }
+    }
+
+    // Move past the match.
+    base += r;
+  }
+
+  // Concatenate the text after the last match.
+  output += input.substr(base, input.length() - base);
+
+  return true;
+}
+
+void cmStringReplaceHelper::ParseReplaceExpression()
+{
+  std::string::size_type l = 0;
+  while (l < this->ReplaceExpression.length()) {
+    auto r = this->ReplaceExpression.find('\\', l);
+    if (r == std::string::npos) {
+      r = this->ReplaceExpression.length();
+      this->Replacements.push_back(this->ReplaceExpression.substr(l, r - l));
+    } else {
+      if (r - l > 0) {
+        this->Replacements.push_back(this->ReplaceExpression.substr(l, r - l));
+      }
+      if (r == (this->ReplaceExpression.length() - 1)) {
+        this->ValidReplaceExpression = false;
+        this->ErrorString = "replace-expression ends in a backslash";
+        return;
+      }
+      if ((this->ReplaceExpression[r + 1] >= '0') &&
+          (this->ReplaceExpression[r + 1] <= '9')) {
+        this->Replacements.push_back(this->ReplaceExpression[r + 1] - '0');
+      } else if (this->ReplaceExpression[r + 1] == 'n') {
+        this->Replacements.push_back("\n");
+      } else if (this->ReplaceExpression[r + 1] == '\\') {
+        this->Replacements.push_back("\\");
+      } else {
+        this->ValidReplaceExpression = false;
+        std::ostringstream error;
+        error << "Unknown escape \"" << this->ReplaceExpression.substr(r, 2)
+              << "\" in replace-expression";
+        this->ErrorString = error.str();
+        return;
+      }
+      r += 2;
+    }
+    l = r;
+  }
+}
diff --git a/Source/cmStringReplaceHelper.h b/Source/cmStringReplaceHelper.h
new file mode 100644
index 0000000000..938325a31a
--- /dev/null
+++ b/Source/cmStringReplaceHelper.h
@@ -0,0 +1,69 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmStringReplaceHelper_h
+#define cmStringReplaceHelper_h
+
+#include "cmsys/RegularExpression.hxx"
+
+#include <string>
+#include <vector>
+
+class cmMakefile;
+
+class cmStringReplaceHelper
+{
+public:
+  cmStringReplaceHelper(const std::string& regex,
+                        const std::string& replace_expr,
+                        cmMakefile* makefile = nullptr);
+
+  bool IsRegularExpressionValid() const
+  {
+    return this->RegularExpression.is_valid();
+  }
+  bool IsReplaceExpressionValid() const
+  {
+    return this->ValidReplaceExpression;
+  }
+
+  bool Replace(const std::string& input, std::string& output);
+
+  const std::string& GetError() { return this->ErrorString; }
+
+private:
+  class RegexReplacement
+  {
+  public:
+    RegexReplacement(const char* s)
+      : Number(-1)
+      , Value(s)
+    {
+    }
+    RegexReplacement(const std::string& s)
+      : Number(-1)
+      , Value(s)
+    {
+    }
+    RegexReplacement(int n)
+      : Number(n)
+      , Value()
+    {
+    }
+    RegexReplacement() {}
+
+    int Number;
+    std::string Value;
+  };
+
+  void ParseReplaceExpression();
+
+  std::string ErrorString;
+  std::string RegExString;
+  cmsys::RegularExpression RegularExpression;
+  bool ValidReplaceExpression = true;
+  std::string ReplaceExpression;
+  std::vector<RegexReplacement> Replacements;
+  cmMakefile* Makefile = nullptr;
+};
+
+#endif
diff --git a/bootstrap b/bootstrap
index 3bcab6024c..465d4d47f7 100755
--- a/bootstrap
+++ b/bootstrap
@@ -400,6 +400,7 @@ CMAKE_CXX_SOURCES="\
   cmState \
   cmStateDirectory \
   cmStateSnapshot \
+  cmStringReplaceHelper \
   cmStringCommand \
   cmSubdirCommand \
   cmSystemTools \
-- 
GitLab