Browse Source

introduce cm::CMakeString class as helper for string() command

This class will be used, as helper for:
* string() command
* future $<STRING> generator expression
Marc Chevrier 1 month ago
parent
commit
dab5e6ebb1
5 changed files with 771 additions and 359 deletions
  1. 2 0
      Source/CMakeLists.txt
  2. 326 0
      Source/cmCMakeString.cxx
  3. 259 0
      Source/cmCMakeString.hxx
  4. 183 359
      Source/cmStringCommand.cxx
  5. 1 0
      bootstrap

+ 2 - 0
Source/CMakeLists.txt

@@ -135,6 +135,8 @@ add_library(
   cmCMakePresetsGraphReadJSONPackagePresets.cxx
   cmCMakePresetsGraphReadJSONTestPresets.cxx
   cmCMakePresetsGraphReadJSONWorkflowPresets.cxx
+  cmCMakeString.hxx
+  cmCMakeString.cxx
   cmCommandLineArgument.h
   cmCommonTargetGenerator.cxx
   cmCommonTargetGenerator.h

+ 326 - 0
Source/cmCMakeString.cxx

@@ -0,0 +1,326 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmCMakeString.hxx"
+
+#include <cstdio>
+#include <cstdlib>
+#include <memory>
+#include <stdexcept>
+#include <vector>
+
+#include "cmsys/RegularExpression.hxx"
+
+#include "cmCryptoHash.h"
+#include "cmGeneratorExpression.h"
+#include "cmMakefile.h"
+#include "cmPolicies.h"
+#include "cmStringAlgorithms.h"
+#include "cmStringReplaceHelper.h"
+#include "cmSystemTools.h"
+#include "cmTimestamp.h"
+#include "cmUuid.h"
+
+namespace cm {
+bool CMakeString::Compare(CompOperator op, cm::string_view other)
+{
+  switch (op) {
+    case CompOperator::EQUAL:
+      return this->String_ == other;
+    case CompOperator::LESS:
+      return this->String_ < other;
+    case CompOperator::LESS_EQUAL:
+      return this->String_ <= other;
+    case CompOperator::GREATER:
+      return this->String_ > other;
+    case CompOperator::GREATER_EQUAL:
+      return this->String_ >= other;
+    default:
+      return false;
+  }
+}
+
+CMakeString& CMakeString::Replace(std::string const& matchExpression,
+                                  std::string const& replaceExpression,
+                                  Regex regex, cmMakefile* makefile)
+{
+  if (regex == Regex::Yes) {
+    if (makefile) {
+      makefile->ClearMatches();
+    }
+
+    cmStringReplaceHelper replaceHelper(matchExpression, replaceExpression,
+                                        makefile);
+    if (!replaceHelper.IsReplaceExpressionValid()) {
+      throw std::invalid_argument(replaceHelper.GetError());
+    }
+    if (!replaceHelper.IsRegularExpressionValid()) {
+      throw std::invalid_argument(
+        cmStrCat("Failed to compile regex \"", matchExpression, '"'));
+    }
+
+    std ::string output;
+    if (!replaceHelper.Replace(this->String_, output)) {
+      throw std::runtime_error(replaceHelper.GetError());
+    }
+    this->String_ = std::move(output);
+  } else {
+    std::string output = this->String_.str();
+    cmsys::SystemTools::ReplaceString(output, matchExpression,
+                                      replaceExpression);
+    this->String_ = std::move(output);
+  }
+
+  return *this;
+};
+
+cmList CMakeString::Match(std::string const& matchExpression,
+                          MatchItems matchItems, cmMakefile* makefile) const
+{
+  if (makefile) {
+    makefile->ClearMatches();
+  }
+
+  // Compile the regular expression.
+  cmsys::RegularExpression re;
+  if (!re.compile(matchExpression)) {
+    throw std::invalid_argument(
+      cmStrCat("Failed to compile regex \"", matchExpression, '"'));
+  }
+
+  cmList output;
+  if (matchItems == MatchItems::Once) {
+    if (re.find(this->String_.data())) {
+      if (makefile) {
+        makefile->StoreMatches(re);
+      }
+      output = re.match();
+    }
+  } else {
+    unsigned optAnchor = 0;
+    if (makefile &&
+        makefile->GetPolicyStatus(cmPolicies::CMP0186) != cmPolicies::NEW) {
+      optAnchor = cmsys::RegularExpression::BOL_AT_OFFSET;
+    }
+
+    // Scan through the input for all matches.
+    std::string::size_type base = 0;
+    unsigned optNonEmpty = 0;
+    while (re.find(this->String_.data(), base, optAnchor | optNonEmpty)) {
+      if (makefile) {
+        makefile->ClearMatches();
+        makefile->StoreMatches(re);
+      }
+
+      output.push_back(re.match());
+      base = re.end();
+
+      if (re.start() == this->String_.length()) {
+        break;
+      }
+      if (re.start() == re.end()) {
+        optNonEmpty = cmsys::RegularExpression::NONEMPTY_AT_OFFSET;
+      } else {
+        optNonEmpty = 0;
+      }
+    }
+  }
+
+  return output;
+}
+
+CMakeString CMakeString::Substring(long begin, long count) const
+{
+  if (begin < 0 || static_cast<size_type>(begin) > this->String_.size()) {
+    throw std::out_of_range(cmStrCat(
+      "begin index: ", begin, " is out of range 0 - ", this->String_.size()));
+  }
+  if (count < -1) {
+    throw std::out_of_range(
+      cmStrCat("end index: ", count, " should be -1 or greater"));
+  }
+
+  return this->String_.substr(static_cast<size_type>(begin),
+                              count == -1 ? npos
+                                          : static_cast<size_type>(count));
+}
+
+CMakeString& CMakeString::Strip(StripItems stripItems)
+{
+  if (stripItems == StripItems::Space) {
+    this->String_ = cmTrimWhitespace(this->String_);
+  } else {
+    this->String_ = cmGeneratorExpression::Preprocess(
+      this->String_, cmGeneratorExpression::StripAllGeneratorExpressions);
+  }
+
+  return *this;
+}
+
+CMakeString& CMakeString::Repeat(size_type count)
+{
+  switch (this->Size()) {
+    case 0u:
+      // Nothing to do for zero length input strings
+      break;
+    case 1u:
+      // NOTE If the string to repeat consists of the only character,
+      // use the appropriate constructor.
+      this->String_ = std::string(count, this->String_[0]);
+      break;
+    default:
+      std::string result;
+      auto size = this->Size();
+
+      result.reserve(size * count);
+      for (auto i = 0u; i < count; ++i) {
+        result.insert(i * size, this->String_.data(), size);
+      }
+      this->String_ = std::move(result);
+      break;
+  }
+  return *this;
+}
+
+CMakeString& CMakeString::Quote(QuoteItems)
+{
+  std ::string output;
+  // Escape all regex special characters
+  cmStringReplaceHelper replaceHelper("([][()+*^.$?|\\\\])", R"(\\\1)");
+  if (!replaceHelper.Replace(this->String_, output)) {
+    throw std::runtime_error(replaceHelper.GetError());
+  }
+
+  this->String_ = std::move(output);
+  return *this;
+}
+
+CMakeString& CMakeString::Hash(cm::string_view hashAlgorithm)
+{
+  std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(hashAlgorithm));
+  if (hash) {
+    this->String_ = hash->HashString(this->String_);
+    return *this;
+  }
+
+  throw std::invalid_argument(
+    cmStrCat(hashAlgorithm, ": invalid hash algorithm."));
+}
+
+CMakeString& CMakeString::FromASCII(string_range codes)
+{
+  std::string output;
+  output.reserve(codes.size());
+
+  for (auto const& code : codes) {
+    try {
+      auto ch = std::stoi(code);
+      if (ch > 0 && ch < 256) {
+        output += static_cast<char>(ch);
+      } else {
+        throw std::invalid_argument(
+          cmStrCat("Character with code ", code, " does not exist."));
+      }
+    } catch (...) {
+      throw std::invalid_argument(
+        cmStrCat("Character with code ", code, " does not exist."));
+    }
+  }
+  this->String_ = std::move(output);
+  return *this;
+}
+
+CMakeString& CMakeString::ToHexadecimal(cm::string_view str)
+{
+  std::string output(str.size() * 2, ' ');
+  std::string::size_type hexIndex = 0;
+
+  for (auto const& c : str) {
+    std::snprintf(&output[hexIndex], 3, "%.2x", c & 0xFFu);
+    hexIndex += 2;
+  }
+  this->String_ = output;
+  return *this;
+}
+
+cm::string_view const CMakeString::RandomDefaultAlphabet{
+  "qwertyuiopasdfghjklzxcvbnm"
+  "QWERTYUIOPASDFGHJKLZXCVBNM"
+  "0123456789"
+};
+bool CMakeString::Seeded = false;
+
+CMakeString& CMakeString::Random(unsigned int seed, std::size_t length,
+                                 cm::string_view alphabet)
+{
+  if (alphabet.empty()) {
+    alphabet = RandomDefaultAlphabet;
+  }
+
+  if (length < 1) {
+    throw std::out_of_range("Invoked with bad length.");
+  }
+
+  if (!this->Seeded) {
+    this->Seeded = true;
+    std::srand(seed);
+  }
+
+  double alphabetSize = static_cast<double>(alphabet.size());
+  std::vector<char> result;
+  result.reserve(length + 1);
+
+  for (std::size_t i = 0; i < length; i++) {
+    auto index = static_cast<std::string::size_type>(
+      alphabetSize * std::rand() / (RAND_MAX + 1.0));
+    result.push_back(alphabet[index]);
+  }
+  result.push_back(0);
+
+  this->String_ = result.data();
+  return *this;
+}
+
+CMakeString& CMakeString::Timestamp(cm::string_view format, UTC utc)
+{
+  cmTimestamp timestamp;
+  this->String_ = timestamp.CurrentTime(format, utc == UTC::Yes);
+  return *this;
+}
+
+CMakeString& CMakeString::UUID(cm::string_view nameSpace, cm::string_view name,
+                               UUIDType type, Case uuidCase)
+{
+#if !defined(CMAKE_BOOTSTRAP)
+  cmUuid uuidGenerator;
+  std::vector<unsigned char> uuidNamespace;
+  std::string uuid;
+
+  if (!uuidGenerator.StringToBinary(nameSpace, uuidNamespace)) {
+    throw std::invalid_argument("malformed NAMESPACE UUID");
+  }
+
+  if (type == UUIDType::MD5) {
+    uuid = uuidGenerator.FromMd5(uuidNamespace, name);
+  } else if (type == UUIDType::SHA1) {
+    uuid = uuidGenerator.FromSha1(uuidNamespace, name);
+  }
+
+  if (uuid.empty()) {
+    throw std::runtime_error("generation failed");
+  }
+
+  if (uuidCase == Case::Upper) {
+    uuid = cmSystemTools::UpperCase(uuid);
+  }
+
+  this->String_ = std::move(uuid);
+  return *this;
+#else
+  throw std::runtime_error("not available during bootstrap");
+#endif
+}
+
+}

+ 259 - 0
Source/cmCMakeString.hxx

@@ -0,0 +1,259 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstddef>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <cm/string_view>
+
+#include "cmList.h"
+#include "cmRange.h"
+#include "cmString.hxx"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmValue.h"
+
+class cmMakefile;
+
+//
+// Class offering various string operations which is used by
+// * CMake string() command
+// * $<STRING> generator expression
+//
+
+namespace cm {
+
+class CMakeString
+{
+public:
+  using size_type = cm::String::size_type;
+  static auto const npos = cm::String::npos;
+
+  using string_range = cmRange<std::vector<std::string>::const_iterator>;
+
+  CMakeString(std::string const& str)
+    : String_(cm::String::borrow(str))
+  {
+  }
+  CMakeString(cm::string_view str)
+    : String_(cm::String::borrow(str))
+  {
+  }
+  CMakeString(cm::String const& str)
+    : String_(str)
+  {
+  }
+  CMakeString(cm::String&& str)
+    : String_(std::move(str))
+  {
+  }
+  CMakeString(cmValue value)
+    : String_(cm::String::borrow(*value))
+  {
+  }
+  CMakeString(string_range range,
+              cm::string_view separator = cm::string_view{})
+    : String_(cmJoin(range, separator))
+  {
+  }
+
+  CMakeString() = default;
+  CMakeString(CMakeString const&) = default;
+  CMakeString(CMakeString&&) = default;
+
+  CMakeString& operator=(CMakeString const& string)
+  {
+    if (this != &string) {
+      this->String_ = string.String_;
+    }
+    return *this;
+  }
+  CMakeString& operator=(CMakeString&& string) noexcept
+  {
+    if (this != &string) {
+      this->String_ = std::move(string.String_);
+    }
+    return *this;
+  }
+  CMakeString& operator=(cm::string_view string)
+  {
+    this->String_ = string;
+    return *this;
+  }
+
+  // conversions
+  string_view view() const noexcept { return this->String_.view(); }
+  operator cm::string_view() noexcept { return this->String_.view(); }
+  operator std::string const&() { return this->String_.str(); }
+
+  size_type Size() const { return this->String_.size(); }
+  size_type Length() const { return this->String_.size(); }
+
+  enum class CompOperator
+  {
+    EQUAL,
+    LESS,
+    LESS_EQUAL,
+    GREATER,
+    GREATER_EQUAL
+  };
+  bool Compare(CompOperator op, cm::string_view other);
+
+  enum class FindFrom
+  {
+    Begin,
+    End
+  };
+  size_type Find(cm::string_view substring,
+                 FindFrom from = FindFrom::Begin) const
+  {
+    return from == FindFrom::Begin ? this->String_.find(substring)
+                                   : this->String_.rfind(substring);
+  }
+
+  enum class Regex
+  {
+    No,
+    Yes
+  };
+  // Throw std::invalid_argument if regular expression is invalid
+  //       std::runtime_error if replacement failed
+  CMakeString& Replace(std::string const& matchExpression,
+                       std::string const& replaceExpression,
+                       Regex regex = Regex::No,
+                       cmMakefile* makefile = nullptr);
+
+  enum class MatchItems
+  {
+    Once,
+    All
+  };
+  // Throw std::invalid_argument if regular expression is invalid
+  cmList Match(std::string const& matchExpression,
+               MatchItems matchItems = MatchItems::Once,
+               cmMakefile* makefile = nullptr) const;
+
+  CMakeString& Append(cm::string_view str)
+  {
+    this->String_.append(str);
+    return *this;
+  }
+  CMakeString& Append(string_range range)
+  {
+    this->Append(cmJoin(range, {}));
+    return *this;
+  }
+  CMakeString& Prepend(cm::string_view str)
+  {
+    this->String_.insert(0, str);
+    return *this;
+  }
+  CMakeString& Prepend(string_range range)
+  {
+    this->Prepend(cmJoin(range, {}));
+    return *this;
+  }
+
+  CMakeString& ToLower()
+  {
+    this->String_ = cmSystemTools::LowerCase(this->String_);
+    return *this;
+  }
+  CMakeString& ToLower(cm::string_view str)
+  {
+    this->String_ = cmSystemTools::LowerCase(str);
+    return *this;
+  }
+  CMakeString& ToUpper()
+  {
+    this->String_ = cmSystemTools::UpperCase(this->String_);
+    return *this;
+  }
+  CMakeString& ToUpper(cm::string_view str)
+  {
+    this->String_ = cmSystemTools::UpperCase(str);
+    return *this;
+  }
+
+  // Throw std::out_of_range if pos or count are outside of the expected range
+  CMakeString Substring(long pos = 0, long count = -1) const;
+
+  enum class StripItems
+  {
+    Space,
+    Genex
+  };
+  CMakeString& Strip(StripItems stripItems = StripItems::Space);
+
+  // Throw std::runtime_error  if quoting string failed
+  enum class QuoteItems
+  {
+    Regex
+  };
+  CMakeString& Quote(QuoteItems quoteItems = QuoteItems::Regex);
+
+  CMakeString& Repeat(size_type count);
+
+  // Throw std::invalid_argument if the hash algorithm is invalid
+  CMakeString& Hash(cm::string_view hashAlgorithm);
+
+  // Throw std::invalid_argument if one of the codes is invalid
+  CMakeString& FromASCII(string_range codes);
+  CMakeString& ToHexadecimal() { return this->ToHexadecimal(this->String_); }
+  CMakeString& ToHexadecimal(cm::string_view str);
+
+  CMakeString& MakeCIdentifier()
+  {
+    this->String_ = cmSystemTools::MakeCidentifier(this->String_.str());
+    return *this;
+  }
+  CMakeString& MakeCIdentifier(std::string const& str)
+  {
+    this->String_ = cmSystemTools::MakeCidentifier(str);
+    return *this;
+  }
+
+  static cm::string_view const RandomDefaultAlphabet;
+  // Throw std::invalid_argument if the alphabet is invalid
+  //       std::out_of_range if length is outside valid range
+  CMakeString& Random(std::size_t length = 5,
+                      cm::string_view alphabet = RandomDefaultAlphabet)
+  {
+    return this->Random(cmSystemTools::RandomSeed(), length, alphabet);
+  }
+  CMakeString& Random(unsigned int seed, std::size_t length = 5,
+                      cm::string_view alphabet = RandomDefaultAlphabet);
+
+  enum class UTC
+  {
+    No,
+    Yes
+  };
+  CMakeString& Timestamp(cm::string_view format, UTC utc = UTC::No);
+
+  enum class UUIDType
+  {
+    MD5,
+    SHA1
+  };
+  enum class Case
+  {
+    Lower,
+    Upper
+  };
+  // Throw std::invalid_argument if namespace or the type are invalid
+  //       std::runtime_error if the UUID cannot be generated
+  CMakeString& UUID(cm::string_view nameSpace, cm::string_view name,
+                    UUIDType type, Case uuidCase = Case::Lower);
+
+private:
+  static bool Seeded;
+  cm::String String_;
+};
+}

+ 183 - 359
Source/cmStringCommand.cxx

@@ -5,9 +5,9 @@
 
 #include "cmStringCommand.h"
 
-#include <algorithm>
 #include <cstdio>
 #include <cstdlib>
+#include <exception>
 #include <limits>
 #include <memory>
 #include <stdexcept>
@@ -22,22 +22,14 @@
 #include <cm3p/json/value.h>
 #include <cm3p/json/writer.h>
 
-#include "cmsys/RegularExpression.hxx"
-
-#include "cmCryptoHash.h"
+#include "cmCMakeString.hxx"
 #include "cmExecutionStatus.h"
-#include "cmGeneratorExpression.h"
+#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
-#include "cmPolicies.h"
 #include "cmRange.h"
 #include "cmStringAlgorithms.h"
-#include "cmStringReplaceHelper.h"
 #include "cmSubcommandTable.h"
-#include "cmSystemTools.h"
-#include "cmTimestamp.h"
-#include "cmUuid.h"
-#include "cmValue.h"
 
 namespace {
 
@@ -62,13 +54,16 @@ bool HandleHashCommand(std::vector<std::string> const& args,
     return false;
   }
 
-  std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0]));
-  if (hash) {
-    std::string out = hash->HashString(args[2]);
-    status.GetMakefile().AddDefinition(args[1], out);
+  cm::CMakeString data{ args[2] };
+
+  try {
+    data.Hash(args[0]);
+    status.GetMakefile().AddDefinition(args[1], data);
     return true;
+  } catch (std::exception const& e) {
+    status.SetError(e.what());
+    return false;
   }
-  return false;
 }
 
 bool HandleToUpperLowerCommand(std::vector<std::string> const& args,
@@ -80,16 +75,16 @@ bool HandleToUpperLowerCommand(std::vector<std::string> const& args,
   }
 
   std::string const& outvar = args[2];
-  std::string output;
+  cm::CMakeString data{ args[1] };
 
   if (toUpper) {
-    output = cmSystemTools::UpperCase(args[1]);
+    data.ToUpper();
   } else {
-    output = cmSystemTools::LowerCase(args[1]);
+    data.ToLower();
   }
 
   // Store the output in the provided variable.
-  status.GetMakefile().AddDefinition(outvar, output);
+  status.GetMakefile().AddDefinition(outvar, data);
   return true;
 }
 
@@ -112,23 +107,17 @@ bool HandleAsciiCommand(std::vector<std::string> const& args,
     status.SetError("No output variable specified");
     return false;
   }
-  std::string::size_type cc;
-  std::string const& outvar = args.back();
-  std::string output;
-  for (cc = 1; cc < args.size() - 1; cc++) {
-    int ch = atoi(args[cc].c_str());
-    if (ch > 0 && ch < 256) {
-      output += static_cast<char>(ch);
-    } else {
-      std::string error =
-        cmStrCat("Character with code ", args[cc], " does not exist.");
-      status.SetError(error);
-      return false;
-    }
+
+  try {
+    std::string const& outvar = args.back();
+    cm::CMakeString data;
+    data.FromASCII(cmMakeRange(args).advance(1).retreat(1));
+    status.GetMakefile().AddDefinition(outvar, data);
+    return true;
+  } catch (std::exception const& e) {
+    status.SetError(e.what());
+    return false;
   }
-  // Store the output in the provided variable.
-  status.GetMakefile().AddDefinition(outvar, output);
-  return true;
 }
 
 bool HandleHexCommand(std::vector<std::string> const& args,
@@ -138,17 +127,13 @@ bool HandleHexCommand(std::vector<std::string> const& args,
     status.SetError("Incorrect number of arguments");
     return false;
   }
-  auto const& instr = args[1];
+
   auto const& outvar = args[2];
-  std::string output(instr.size() * 2, ' ');
+  cm::CMakeString data{ args[1] };
 
-  std::string::size_type hexIndex = 0;
-  for (auto const& c : instr) {
-    snprintf(&output[hexIndex], 3, "%.2x", c & 0xFFu);
-    hexIndex += 2;
-  }
+  data.ToHexadecimal();
 
-  status.GetMakefile().AddDefinition(outvar, output);
+  status.GetMakefile().AddDefinition(outvar, data);
   return true;
 }
 
@@ -239,33 +224,21 @@ bool RegexMatch(std::vector<std::string> const& args,
 {
   //"STRING(REGEX MATCH <regular_expression> <output variable>
   // <input> [<input>...])\n";
-  std::string const& regex = args[2];
-  std::string const& outvar = args[3];
-
-  status.GetMakefile().ClearMatches();
-  // Compile the regular expression.
-  cmsys::RegularExpression re;
-  if (!re.compile(regex)) {
-    std::string e =
-      "sub-command REGEX, mode MATCH failed to compile regex \"" + regex +
-      "\".";
-    status.SetError(e);
+  try {
+    std::string const& regex = args[2];
+    std::string const& outvar = args[3];
+    cm::CMakeString data{ cmMakeRange(args).advance(4) };
+
+    auto result = data.Match(regex, cm::CMakeString::MatchItems::Once,
+                             &status.GetMakefile());
+    // Store the result in the provided variable.
+    status.GetMakefile().AddDefinition(outvar, result.to_string());
+    return true;
+  } catch (std::exception const& e) {
+    status.SetError(
+      cmStrCat("sub-command REGEX, mode MATCH: ", e.what(), '.'));
     return false;
   }
-
-  // Concatenate all the last arguments together.
-  std::string input = cmJoin(cmMakeRange(args).advance(4), std::string());
-
-  // Scan through the input for all matches.
-  std::string output;
-  if (re.find(input)) {
-    status.GetMakefile().StoreMatches(re);
-    output = re.match();
-  }
-
-  // Store the output in the provided variable.
-  status.GetMakefile().AddDefinition(outvar, output);
-  return true;
 }
 
 bool RegexMatchAll(std::vector<std::string> const& args,
@@ -273,55 +246,21 @@ bool RegexMatchAll(std::vector<std::string> const& args,
 {
   //"STRING(REGEX MATCHALL <regular_expression> <output variable> <input>
   // [<input>...])\n";
-  std::string const& regex = args[2];
-  std::string const& outvar = args[3];
-
-  status.GetMakefile().ClearMatches();
-  // Compile the regular expression.
-  cmsys::RegularExpression re;
-  if (!re.compile(regex)) {
-    std::string e =
-      "sub-command REGEX, mode MATCHALL failed to compile regex \"" + regex +
-      "\".";
-    status.SetError(e);
+  try {
+    std::string const& regex = args[2];
+    std::string const& outvar = args[3];
+    cm::CMakeString data{ cmMakeRange(args).advance(4) };
+
+    auto result = data.Match(regex, cm::CMakeString::MatchItems::All,
+                             &status.GetMakefile());
+    // Store the result in the provided variable.
+    status.GetMakefile().AddDefinition(outvar, result.to_string());
+    return true;
+  } catch (std::exception const& e) {
+    status.SetError(
+      cmStrCat("sub-command REGEX, mode MATCHALL: ", e.what(), '.'));
     return false;
   }
-
-  // Concatenate all the last arguments together.
-  std::string input = cmJoin(cmMakeRange(args).advance(4), std::string());
-
-  unsigned optAnchor = 0;
-  if (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0186) !=
-      cmPolicies::NEW) {
-    optAnchor = cmsys::RegularExpression::BOL_AT_OFFSET;
-  }
-
-  // Scan through the input for all matches.
-  std::string output;
-  std::string::size_type base = 0;
-  unsigned optNonEmpty = 0;
-  while (re.find(input, base, optAnchor | optNonEmpty)) {
-    status.GetMakefile().ClearMatches();
-    status.GetMakefile().StoreMatches(re);
-    if (!output.empty() || optNonEmpty) {
-      output += ";";
-    }
-    output += re.match();
-    base = re.end();
-
-    if (re.start() == input.length()) {
-      break;
-    }
-    if (re.start() == re.end()) {
-      optNonEmpty = cmsys::RegularExpression::NONEMPTY_AT_OFFSET;
-    } else {
-      optNonEmpty = 0;
-    }
-  }
-
-  // Store the output in the provided variable.
-  status.GetMakefile().AddDefinition(outvar, output);
-  return true;
 }
 
 bool RegexReplace(std::vector<std::string> const& args,
@@ -332,38 +271,20 @@ bool 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, &status.GetMakefile());
-
-  if (!replaceHelper.IsReplaceExpressionValid()) {
-    status.SetError(
-      "sub-command REGEX, mode REPLACE: " + replaceHelper.GetError() + ".");
-    return false;
-  }
-
-  status.GetMakefile().ClearMatches();
 
-  if (!replaceHelper.IsRegularExpressionValid()) {
-    std::string e =
-      "sub-command REGEX, mode REPLACE failed to compile regex \"" + regex +
-      "\".";
-    status.SetError(e);
-    return false;
-  }
-
-  // Concatenate all the last arguments together.
-  std::string const input =
-    cmJoin(cmMakeRange(args).advance(5), std::string());
-  std::string output;
+  try {
+    cm::CMakeString data{ cmMakeRange(args).advance(5) };
 
-  if (!replaceHelper.Replace(input, output)) {
+    data.Replace(regex, replace, cm::CMakeString::Regex::Yes,
+                 &status.GetMakefile());
+    // Store the result in the provided variable.
+    status.GetMakefile().AddDefinition(outvar, data);
+    return true;
+  } catch (std::exception const& e) {
     status.SetError(
-      "sub-command REGEX, mode REPLACE: " + replaceHelper.GetError() + ".");
+      cmStrCat("sub-command REGEX, mode REPLACE: ", e.what(), '.'));
     return false;
   }
-
-  // Store the output in the provided variable.
-  status.GetMakefile().AddDefinition(outvar, output);
-  return true;
 }
 
 bool RegexQuote(std::vector<std::string> const& args,
@@ -371,21 +292,19 @@ bool RegexQuote(std::vector<std::string> const& args,
 {
   //"STRING(REGEX QUOTE <output variable> <input> [<input>...]\n"
   std::string const& outvar = args[2];
-  std::string const input =
-    cmJoin(cmMakeRange(args).advance(3), std::string());
-  std::string output;
+  cm::CMakeString data{ cmMakeRange(args).advance(3) };
 
-  // Escape all regex special characters
-  cmStringReplaceHelper replaceHelper("([][()+*^.$?|\\\\])", R"(\\\1)");
-  if (!replaceHelper.Replace(input, output)) {
+  try {
+    // Escape all regex special characters
+    data.Quote();
+    // Store the output in the provided variable.
+    status.GetMakefile().AddDefinition(outvar, data);
+    return true;
+  } catch (std::exception const& e) {
     status.SetError(
-      "sub-command REGEX, mode QUOTE: " + replaceHelper.GetError() + ".");
+      cmStrCat("sub-command REGEX, mode QUOTE: ", e.what(), '.'));
     return false;
   }
-
-  // Store the output in the provided variable.
-  status.GetMakefile().AddDefinition(outvar, output);
-  return true;
 }
 
 bool HandleFindCommand(std::vector<std::string> const& args,
@@ -423,19 +342,14 @@ bool HandleFindCommand(std::vector<std::string> const& args,
   }
 
   // try to find the character and return its position
-  size_t pos;
-  if (!reverseMode) {
-    pos = sstring.find(schar);
-  } else {
-    pos = sstring.rfind(schar);
-  }
-  if (std::string::npos != pos) {
-    status.GetMakefile().AddDefinition(outvar, std::to_string(pos));
-    return true;
-  }
+  auto pos = cm::CMakeString{ sstring }.Find(
+    schar,
+    reverseMode ? cm::CMakeString::FindFrom::End
+                : cm::CMakeString::FindFrom::Begin);
+
+  status.GetMakefile().AddDefinition(
+    outvar, pos != cm::CMakeString::npos ? std::to_string(pos) : "-1");
 
-  // the character was not found, but this is not really an error
-  status.GetMakefile().AddDefinition(outvar, "-1");
   return true;
 }
 
@@ -462,25 +376,22 @@ bool HandleCompareCommand(std::vector<std::string> const& args,
     std::string const& right = args[3];
     std::string const& outvar = args[4];
     bool result;
+    cm::CMakeString::CompOperator op = cm::CMakeString::CompOperator::EQUAL;
     if (mode == "LESS") {
-      result = (left < right);
+      op = cm::CMakeString::CompOperator::LESS;
     } else if (mode == "LESS_EQUAL") {
-      result = (left <= right);
+      op = cm::CMakeString::CompOperator::LESS_EQUAL;
     } else if (mode == "GREATER") {
-      result = (left > right);
+      op = cm::CMakeString::CompOperator::GREATER;
     } else if (mode == "GREATER_EQUAL") {
-      result = (left >= right);
-    } else if (mode == "EQUAL") {
-      result = (left == right);
-    } else // if(mode == "NOTEQUAL")
-    {
-      result = !(left == right);
+      op = cm::CMakeString::CompOperator::GREATER_EQUAL;
     }
-    if (result) {
-      status.GetMakefile().AddDefinition(outvar, "1");
-    } else {
-      status.GetMakefile().AddDefinition(outvar, "0");
+    result = cm::CMakeString{ left }.Compare(op, right);
+    if (mode == "NOTEQUAL") {
+      result = !result;
     }
+
+    status.GetMakefile().AddDefinition(outvar, result ? "1" : "0");
     return true;
   }
   std::string e = "sub-command COMPARE does not recognize mode " + mode;
@@ -496,17 +407,19 @@ bool HandleReplaceCommand(std::vector<std::string> const& args,
     return false;
   }
 
-  std::string const& matchExpression = args[1];
-  std::string const& replaceExpression = args[2];
-  std::string const& variableName = args[3];
-
-  std::string input = cmJoin(cmMakeRange(args).advance(4), std::string());
-
-  cmsys::SystemTools::ReplaceString(input, matchExpression.c_str(),
-                                    replaceExpression.c_str());
+  try {
+    std::string const& matchExpression = args[1];
+    std::string const& replaceExpression = args[2];
+    std::string const& variableName = args[3];
+    cm::CMakeString data{ cmMakeRange(args).advance(4) };
 
-  status.GetMakefile().AddDefinition(variableName, input);
-  return true;
+    data.Replace(matchExpression, replaceExpression);
+    status.GetMakefile().AddDefinition(variableName, data);
+    return true;
+  } catch (std::exception const& e) {
+    status.SetError(cmStrCat("sub-command REPLACE: ", e.what(), '.'));
+    return false;
+  }
 }
 
 bool HandleSubstringCommand(std::vector<std::string> const& args,
@@ -517,25 +430,19 @@ bool HandleSubstringCommand(std::vector<std::string> const& args,
     return false;
   }
 
-  std::string const& stringValue = args[1];
-  int begin = atoi(args[2].c_str());
-  int end = atoi(args[3].c_str());
-  std::string const& variableName = args[4];
-
-  size_t stringLength = stringValue.size();
-  int intStringLength = static_cast<int>(stringLength);
-  if (begin < 0 || begin > intStringLength) {
-    status.SetError(
-      cmStrCat("begin index: ", begin, " is out of range 0 - ", stringLength));
-    return false;
-  }
-  if (end < -1) {
-    status.SetError(cmStrCat("end index: ", end, " should be -1 or greater"));
+  try {
+    std::string const& stringValue = args[1];
+    int begin = atoi(args[2].c_str());
+    int end = atoi(args[3].c_str());
+    std::string const& variableName = args[4];
+
+    cm::CMakeString data{ stringValue };
+    status.GetMakefile().AddDefinition(variableName,
+                                       data.Substring(begin, end));
+  } catch (std::exception const& e) {
+    status.SetError(e.what());
     return false;
   }
-
-  status.GetMakefile().AddDefinition(variableName,
-                                     stringValue.substr(begin, end));
   return true;
 }
 
@@ -550,11 +457,8 @@ bool HandleLengthCommand(std::vector<std::string> const& args,
   std::string const& stringValue = args[1];
   std::string const& variableName = args[2];
 
-  size_t length = stringValue.size();
-  char buffer[1024];
-  snprintf(buffer, sizeof(buffer), "%d", static_cast<int>(length));
-
-  status.GetMakefile().AddDefinition(variableName, buffer);
+  status.GetMakefile().AddDefinition(
+    variableName, std::to_string(cm::CMakeString{ stringValue }.Length()));
   return true;
 }
 
@@ -572,12 +476,10 @@ bool HandleAppendCommand(std::vector<std::string> const& args,
   }
 
   auto const& variableName = args[1];
+  cm::CMakeString data{ status.GetMakefile().GetDefinition(variableName) };
 
-  cm::string_view oldView{ status.GetMakefile().GetSafeDefinition(
-    variableName) };
-
-  auto const newValue = cmJoin(cmMakeRange(args).advance(2), {}, oldView);
-  status.GetMakefile().AddDefinition(variableName, newValue);
+  data.Append(cmMakeRange(args).advance(2));
+  status.GetMakefile().AddDefinition(variableName, data);
 
   return true;
 }
@@ -596,13 +498,10 @@ bool HandlePrependCommand(std::vector<std::string> const& args,
   }
 
   std::string const& variable = args[1];
+  cm::CMakeString data{ status.GetMakefile().GetDefinition(variable) };
 
-  std::string value = cmJoin(cmMakeRange(args).advance(2), std::string());
-  cmValue oldValue = status.GetMakefile().GetDefinition(variable);
-  if (oldValue) {
-    value += *oldValue;
-  }
-  status.GetMakefile().AddDefinition(variable, value);
+  data.Prepend(cmMakeRange(args).advance(2));
+  status.GetMakefile().AddDefinition(variable, data);
   return true;
 }
 
@@ -634,9 +533,9 @@ bool joinImpl(std::vector<std::string> const& args, std::string const& glue,
   std::string const& variableName = args[varIdx];
   // NOTE Items to concat/join placed right after the variable for
   // both `CONCAT` and `JOIN` sub-commands.
-  std::string value = cmJoin(cmMakeRange(args).advance(varIdx + 1), glue);
+  cm::CMakeString data{ cmMakeRange(args).advance(varIdx + 1), glue };
 
-  makefile.AddDefinition(variableName, value);
+  makefile.AddDefinition(variableName, data);
   return true;
 }
 
@@ -652,7 +551,7 @@ bool HandleMakeCIdentifierCommand(std::vector<std::string> const& args,
   std::string const& variableName = args[2];
 
   status.GetMakefile().AddDefinition(variableName,
-                                     cmSystemTools::MakeCidentifier(input));
+                                     cm::CMakeString{}.MakeCIdentifier(input));
   return true;
 }
 
@@ -664,14 +563,11 @@ bool HandleGenexStripCommand(std::vector<std::string> const& args,
     return false;
   }
 
-  std::string const& input = args[1];
-
-  std::string result = cmGeneratorExpression::Preprocess(
-    input, cmGeneratorExpression::StripAllGeneratorExpressions);
-
+  cm::CMakeString data{ args[1] };
   std::string const& variableName = args[2];
 
-  status.GetMakefile().AddDefinition(variableName, result);
+  status.GetMakefile().AddDefinition(
+    variableName, data.Strip(cm::CMakeString::StripItems::Genex));
   return true;
 }
 
@@ -683,36 +579,10 @@ bool HandleStripCommand(std::vector<std::string> const& args,
     return false;
   }
 
-  std::string const& stringValue = args[1];
+  cm::CMakeString data{ args[1] };
   std::string const& variableName = args[2];
-  size_t inStringLength = stringValue.size();
-  size_t startPos = inStringLength + 1;
-  size_t endPos = 0;
-  char const* ptr = stringValue.c_str();
-  size_t cc;
-  for (cc = 0; cc < inStringLength; ++cc) {
-    if (!cmIsSpace(*ptr)) {
-      if (startPos > inStringLength) {
-        startPos = cc;
-      }
-      endPos = cc;
-    }
-    ++ptr;
-  }
-
-  size_t outLength = 0;
-
-  // if the input string didn't contain any non-space characters, return
-  // an empty string
-  if (startPos > inStringLength) {
-    outLength = 0;
-    startPos = 0;
-  } else {
-    outLength = endPos - startPos + 1;
-  }
 
-  status.GetMakefile().AddDefinition(variableName,
-                                     stringValue.substr(startPos, outLength));
+  status.GetMakefile().AddDefinition(variableName, data.Strip());
   return true;
 }
 
@@ -744,30 +614,12 @@ bool HandleRepeatCommand(std::vector<std::string> const& args,
     return true;
   }
 
-  auto const& stringValue = args[ArgPos::VALUE];
+  cm::CMakeString data{ args[ArgPos::VALUE] };
+  data.Repeat(times);
+
   auto const& variableName = args[ArgPos::OUTPUT_VARIABLE];
-  auto const inStringLength = stringValue.size();
-
-  std::string result;
-  switch (inStringLength) {
-    case 0u:
-      // Nothing to do for zero length input strings
-      break;
-    case 1u:
-      // NOTE If the string to repeat consists of the only character,
-      // use the appropriate constructor.
-      result = std::string(times, stringValue[0]);
-      break;
-    default:
-      result = std::string(inStringLength * times, char{});
-      for (auto i = 0u; i < times; ++i) {
-        std::copy(cm::cbegin(stringValue), cm::cend(stringValue),
-                  &result[i * inStringLength]);
-      }
-      break;
-  }
 
-  makefile.AddDefinition(variableName, result);
+  makefile.AddDefinition(variableName, data);
   return true;
 }
 
@@ -779,14 +631,10 @@ bool HandleRandomCommand(std::vector<std::string> const& args,
     return false;
   }
 
-  static bool seeded = false;
+  int length = 5;
+  cm::string_view alphabet;
   bool force_seed = false;
   unsigned int seed = 0;
-  int length = 5;
-  char const cmStringCommandDefaultAlphabet[] = "qwertyuiopasdfghjklzxcvbnm"
-                                                "QWERTYUIOPASDFGHJKLZXCVBNM"
-                                                "0123456789";
-  std::string alphabet;
 
   if (args.size() > 3) {
     size_t i = 1;
@@ -806,37 +654,22 @@ bool HandleRandomCommand(std::vector<std::string> const& args,
       }
     }
   }
-  if (alphabet.empty()) {
-    alphabet = cmStringCommandDefaultAlphabet;
-  }
-
-  double sizeofAlphabet = static_cast<double>(alphabet.size());
-  if (sizeofAlphabet < 1) {
-    status.SetError("sub-command RANDOM invoked with bad alphabet.");
-    return false;
-  }
-  if (length < 1) {
-    status.SetError("sub-command RANDOM invoked with bad length.");
-    return false;
-  }
-  std::string const& variableName = args.back();
-
-  std::vector<char> result;
 
-  if (!seeded || force_seed) {
-    seeded = true;
-    srand(force_seed ? seed : cmSystemTools::RandomSeed());
-  }
+  try {
+    cm::CMakeString data;
+    std::string const& variableName = args.back();
 
-  char const* alphaPtr = alphabet.c_str();
-  for (int cc = 0; cc < length; cc++) {
-    int idx = static_cast<int>(sizeofAlphabet * rand() / (RAND_MAX + 1.0));
-    result.push_back(*(alphaPtr + idx));
+    if (force_seed) {
+      data.Random(seed, length, alphabet);
+    } else {
+      data.Random(length, alphabet);
+    }
+    status.GetMakefile().AddDefinition(variableName, data);
+    return true;
+  } catch (std::exception const& e) {
+    status.SetError(cmStrCat("sub-command RANDOM: ", e.what(), '.'));
+    return false;
   }
-  result.push_back(0);
-
-  status.GetMakefile().AddDefinition(variableName, result.data());
-  return true;
 }
 
 bool HandleTimestampCommand(std::vector<std::string> const& args,
@@ -855,26 +688,28 @@ bool HandleTimestampCommand(std::vector<std::string> const& args,
 
   std::string const& outputVariable = args[argsIndex++];
 
-  std::string formatString;
+  cm::string_view formatString;
   if (args.size() > argsIndex && args[argsIndex] != "UTC") {
     formatString = args[argsIndex++];
   }
 
-  bool utcFlag = false;
+  cm::CMakeString::UTC utcFlag = cm::CMakeString::UTC::No;
   if (args.size() > argsIndex) {
     if (args[argsIndex] == "UTC") {
-      utcFlag = true;
+      utcFlag = cm::CMakeString::UTC::Yes;
     } else {
-      std::string e = " TIMESTAMP sub-command does not recognize option " +
-        args[argsIndex] + ".";
+      std::string e =
+        cmStrCat(" TIMESTAMP sub-command does not recognize option ",
+                 args[argsIndex], '.');
       status.SetError(e);
       return false;
     }
   }
 
-  cmTimestamp timestamp;
-  std::string result = timestamp.CurrentTime(formatString, utcFlag);
-  status.GetMakefile().AddDefinition(outputVariable, result);
+  cm::CMakeString data;
+
+  status.GetMakefile().AddDefinition(outputVariable,
+                                     data.Timestamp(formatString, utcFlag));
 
   return true;
 }
@@ -892,10 +727,10 @@ bool HandleUuidCommand(std::vector<std::string> const& args,
 
   std::string const& outputVariable = args[argsIndex++];
 
-  std::string uuidNamespaceString;
-  std::string uuidName;
-  std::string uuidType;
-  bool uuidUpperCase = false;
+  cm::string_view uuidNamespaceString;
+  cm::string_view uuidName;
+  cm::CMakeString::UUIDType uuidType = cm::CMakeString::UUIDType::MD5;
+  cm::CMakeString::Case uuidCase = cm::CMakeString::Case::Lower;
 
   while (args.size() > argsIndex) {
     if (args[argsIndex] == "NAMESPACE") {
@@ -918,50 +753,39 @@ bool HandleUuidCommand(std::vector<std::string> const& args,
         status.SetError("UUID sub-command, TYPE requires a value.");
         return false;
       }
-      uuidType = args[argsIndex++];
+      if (args[argsIndex] == "MD5") {
+        uuidType = cm::CMakeString::UUIDType::MD5;
+      } else if (args[argsIndex] == "SHA1") {
+        uuidType = cm::CMakeString::UUIDType::SHA1;
+      } else {
+        status.SetError(
+          cmStrCat("UUID sub-command, unknown TYPE '", args[argsIndex], "'."));
+        return false;
+      }
+      argsIndex++;
     } else if (args[argsIndex] == "UPPER") {
       ++argsIndex;
-      uuidUpperCase = true;
+      uuidCase = cm::CMakeString::Case::Upper;
     } else {
-      std::string e =
-        "UUID sub-command does not recognize option " + args[argsIndex] + ".";
+      std::string e = cmStrCat("UUID sub-command does not recognize option ",
+                               args[argsIndex], '.');
       status.SetError(e);
       return false;
     }
   }
 
-  std::string uuid;
-  cmUuid uuidGenerator;
-
-  std::vector<unsigned char> uuidNamespace;
-  if (!uuidGenerator.StringToBinary(uuidNamespaceString, uuidNamespace)) {
-    status.SetError("UUID sub-command, malformed NAMESPACE UUID.");
-    return false;
-  }
-
-  if (uuidType == "MD5") {
-    uuid = uuidGenerator.FromMd5(uuidNamespace, uuidName);
-  } else if (uuidType == "SHA1") {
-    uuid = uuidGenerator.FromSha1(uuidNamespace, uuidName);
-  } else {
-    std::string e = "UUID sub-command, unknown TYPE '" + uuidType + "'.";
-    status.SetError(e);
-    return false;
-  }
+  try {
+    cm::CMakeString data;
 
-  if (uuid.empty()) {
-    status.SetError("UUID sub-command, generation failed.");
+    data.UUID(uuidNamespaceString, uuidName, uuidType, uuidCase);
+    status.GetMakefile().AddDefinition(outputVariable, data);
+    return true;
+  } catch (std::exception const& e) {
+    status.SetError(cmStrCat("UUID sub-command, ", e.what(), '.'));
     return false;
   }
-
-  if (uuidUpperCase) {
-    uuid = cmSystemTools::UpperCase(uuid);
-  }
-
-  status.GetMakefile().AddDefinition(outputVariable, uuid);
-  return true;
 #else
-  status.SetError(cmStrCat(args[0], " not available during bootstrap"));
+  status.SetError("UUID sub-command not available during bootstrap.");
   return false;
 #endif
 }

+ 1 - 0
bootstrap

@@ -314,6 +314,7 @@ CMAKE_CXX_SOURCES="\
   cmCMakePath \
   cmCMakePathCommand \
   cmCMakePolicyCommand \
+  cmCMakeString \
   cmCPackPropertiesGenerator \
   cmCacheManager \
   cmCommands \