Browse Source

Merge topic 'cmList-class'

72d116ee68 GenEx: list oriented genexes use cmList class
9f60f19ee9 cmList: CMake list implementation

Acked-by: Kitware Robot <[email protected]>
Merge-request: !8375
Brad King 2 years ago
parent
commit
0dcb545602

+ 2 - 0
Source/CMakeLists.txt

@@ -333,6 +333,8 @@ add_library(
   cmLinkLineComputer.h
   cmLinkLineDeviceComputer.cxx
   cmLinkLineDeviceComputer.h
+  cmList.h
+  cmList.cxx
   cmListFileCache.cxx
   cmListFileCache.h
   cmLocalCommonGenerator.cxx

+ 1 - 1
Source/cmAlgorithms.h

@@ -95,7 +95,7 @@ typename Range::const_iterator cmRemoveIndices(Range& r, InputRange const& rem)
 }
 
 template <typename Range, typename MatchRange>
-typename Range::const_iterator cmRemoveMatching(Range& r, MatchRange const& m)
+auto cmRemoveMatching(Range& r, MatchRange const& m) -> decltype(r.begin())
 {
   return std::remove_if(r.begin(), r.end(),
                         ContainerAlgorithms::BinarySearcher<MatchRange>(m));

+ 17 - 28
Source/cmGeneratorExpressionNode.cxx

@@ -12,6 +12,7 @@
 #include <memory>
 #include <set>
 #include <sstream>
+#include <stdexcept>
 #include <unordered_map>
 #include <utility>
 
@@ -25,7 +26,6 @@
 #include "cmsys/RegularExpression.hxx"
 #include "cmsys/String.h"
 
-#include "cmAlgorithms.h"
 #include "cmCMakePath.h"
 #include "cmComputeLinkInformation.h"
 #include "cmGeneratorExpression.h"
@@ -35,6 +35,7 @@
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmLinkItem.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -291,18 +292,18 @@ static const struct InListNode : public cmGeneratorExpressionNode
     const GeneratorExpressionContent* /*content*/,
     cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
   {
-    std::vector<std::string> values;
-    std::vector<std::string> checkValues;
+    cmList values;
+    cmList checkValues;
     bool check = false;
     switch (context->LG->GetPolicyStatus(cmPolicies::CMP0085)) {
       case cmPolicies::WARN:
         if (parameters.front().empty()) {
           check = true;
-          cmExpandList(parameters[1], checkValues, true);
+          checkValues.assign(parameters[1], cmList::EmptyElements::Yes);
         }
         CM_FALLTHROUGH;
       case cmPolicies::OLD:
-        cmExpandList(parameters[1], values);
+        values.assign(parameters[1]);
         if (check && values != checkValues) {
           std::ostringstream e;
           e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0085)
@@ -319,11 +320,11 @@ static const struct InListNode : public cmGeneratorExpressionNode
       case cmPolicies::REQUIRED_IF_USED:
       case cmPolicies::REQUIRED_ALWAYS:
       case cmPolicies::NEW:
-        cmExpandList(parameters[1], values, true);
+        values.assign(parameters[1], cmList::EmptyElements::Yes);
         break;
     }
 
-    return cm::contains(values, parameters.front()) ? "1" : "0";
+    return values.find(parameters.front()) != cmList::npos ? "1" : "0";
   }
 } inListNode;
 
@@ -352,24 +353,17 @@ static const struct FilterNode : public cmGeneratorExpressionNode
       return {};
     }
 
-    const bool exclude = parameters[1] == "EXCLUDE";
-
-    cmsys::RegularExpression re;
-    if (!re.compile(parameters[2])) {
+    try {
+      return cmList{ parameters.front(), cmList::EmptyElements::Yes }
+        .filter(parameters[2],
+                parameters[1] == "EXCLUDE" ? cmList::FilterMode::EXCLUDE
+                                           : cmList::FilterMode::INCLUDE)
+        .to_string();
+    } catch (std::invalid_argument&) {
       reportError(context, content->GetOriginalExpression(),
                   "$<FILTER:...> failed to compile regex");
       return {};
     }
-
-    std::vector<std::string> values;
-    std::vector<std::string> result;
-    cmExpandList(parameters.front(), values, true);
-
-    std::copy_if(values.cbegin(), values.cend(), std::back_inserter(result),
-                 [&re, exclude](std::string const& input) {
-                   return exclude ^ re.find(input);
-                 });
-    return cmJoin(cmMakeRange(result.cbegin(), result.cend()), ";");
   }
 } filterNode;
 
@@ -391,11 +385,7 @@ static const struct RemoveDuplicatesNode : public cmGeneratorExpressionNode
         "$<REMOVE_DUPLICATES:...> expression requires one parameter");
     }
 
-    std::vector<std::string> values = cmExpandedList(parameters.front(), true);
-
-    auto valuesEnd = cmRemoveDuplicates(values);
-    auto valuesBegin = values.cbegin();
-    return cmJoin(cmMakeRange(valuesBegin, valuesEnd), ";");
+    return cmList{ parameters.front() }.remove_duplicates().to_string();
   }
 
 } removeDuplicatesNode;
@@ -1500,8 +1490,7 @@ static const struct JoinNode : public cmGeneratorExpressionNode
     const GeneratorExpressionContent* /*content*/,
     cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
   {
-    std::vector<std::string> list = cmExpandedList(parameters.front());
-    return cmJoin(list, parameters[1]);
+    return cmList{ parameters.front() }.join(parameters[1]);
   }
 } joinNode;
 

+ 996 - 0
Source/cmList.cxx

@@ -0,0 +1,996 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmList.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <functional>
+#include <iterator>
+#include <set>
+#include <stdexcept>
+#include <utility>
+
+#include <cm/memory>
+
+#include "cmsys/RegularExpression.hxx"
+
+#include "cmAlgorithms.h"
+#include "cmGeneratorExpression.h"
+#include "cmRange.h"
+#include "cmStringAlgorithms.h"
+#include "cmStringReplaceHelper.h"
+#include "cmSystemTools.h"
+
+cm::string_view cmList::element_separator{ ";" };
+
+cmList cmList::sublist(size_type pos, size_type length) const
+{
+  if (pos >= this->Values.size()) {
+    throw std::out_of_range(cmStrCat(
+      "begin index: ", pos, " is out of range 0 - ", this->Values.size() - 1));
+  }
+
+  size_type count = (length == npos || pos + length > this->size())
+    ? this->size()
+    : pos + length;
+  return this->sublist(this->begin() + pos, this->begin() + count);
+}
+
+cmList::size_type cmList::find(cm::string_view value) const
+{
+  auto res = std::find(this->Values.begin(), this->Values.end(), value);
+  if (res == this->Values.end()) {
+    return npos;
+  }
+
+  return std::distance(this->Values.begin(), res);
+}
+
+cmList& cmList::remove_duplicates()
+{
+  auto newEnd = cmRemoveDuplicates(this->Values);
+  this->Values.erase(newEnd, this->Values.end());
+
+  return *this;
+}
+
+namespace {
+class MatchesRegex
+{
+public:
+  MatchesRegex(cmsys::RegularExpression& regex, cmList::FilterMode mode)
+    : Regex(regex)
+    , IncludeMatches(mode == cmList::FilterMode::INCLUDE)
+  {
+  }
+
+  bool operator()(const std::string& target)
+  {
+    return this->Regex.find(target) ^ this->IncludeMatches;
+  }
+
+private:
+  cmsys::RegularExpression& Regex;
+  const bool IncludeMatches;
+};
+}
+
+cmList& cmList::filter(cm::string_view pattern, FilterMode mode)
+{
+  cmsys::RegularExpression regex(std::string{ pattern });
+  if (!regex.is_valid()) {
+    throw std::invalid_argument(cmStrCat("invalid regex: ", pattern));
+  }
+
+  auto it = std::remove_if(this->Values.begin(), this->Values.end(),
+                           MatchesRegex{ regex, mode });
+  this->Values.erase(it, this->Values.end());
+
+  return *this;
+}
+
+namespace {
+class StringSorter
+{
+protected:
+  using StringFilter = std::function<std::string(const std::string&)>;
+
+  using OrderMode = cmList::SortConfiguration::OrderMode;
+  using CompareMethod = cmList::SortConfiguration::CompareMethod;
+  using CaseSensitivity = cmList::SortConfiguration::CaseSensitivity;
+
+  StringFilter GetCompareFilter(CompareMethod compare)
+  {
+    return (compare == CompareMethod::FILE_BASENAME)
+      ? cmSystemTools::GetFilenameName
+      : nullptr;
+  }
+
+  StringFilter GetCaseFilter(CaseSensitivity sensitivity)
+  {
+    return (sensitivity == CaseSensitivity::INSENSITIVE)
+      ? cmSystemTools::LowerCase
+      : nullptr;
+  }
+
+  using ComparisonFunction =
+    std::function<bool(const std::string&, const std::string&)>;
+  ComparisonFunction GetComparisonFunction(CompareMethod compare)
+  {
+    if (compare == CompareMethod::NATURAL) {
+      return std::function<bool(const std::string&, const std::string&)>(
+        [](const std::string& x, const std::string& y) {
+          return cmSystemTools::strverscmp(x, y) < 0;
+        });
+    }
+    return std::function<bool(const std::string&, const std::string&)>(
+      [](const std::string& x, const std::string& y) { return x < y; });
+  }
+
+public:
+  StringSorter(cmList::SortConfiguration const& config)
+    : Filters{ this->GetCompareFilter(config.Compare),
+               this->GetCaseFilter(config.Case) }
+    , SortMethod(this->GetComparisonFunction(config.Compare))
+    , Descending(config.Order == OrderMode::DESCENDING)
+  {
+  }
+
+  std::string ApplyFilter(const std::string& argument)
+  {
+    std::string result = argument;
+    for (auto const& filter : this->Filters) {
+      if (filter != nullptr) {
+        result = filter(result);
+      }
+    }
+    return result;
+  }
+
+  bool operator()(const std::string& a, const std::string& b)
+  {
+    std::string af = this->ApplyFilter(a);
+    std::string bf = this->ApplyFilter(b);
+    bool result;
+    if (this->Descending) {
+      result = this->SortMethod(bf, af);
+    } else {
+      result = this->SortMethod(af, bf);
+    }
+    return result;
+  }
+
+private:
+  StringFilter Filters[2] = { nullptr, nullptr };
+  ComparisonFunction SortMethod;
+  bool Descending;
+};
+}
+
+cmList::SortConfiguration::SortConfiguration() = default;
+
+cmList& cmList::sort(const SortConfiguration& cfg)
+{
+  SortConfiguration config{ cfg };
+
+  if (config.Order == SortConfiguration::OrderMode::DEFAULT) {
+    config.Order = SortConfiguration::OrderMode::ASCENDING;
+  }
+  if (config.Compare == SortConfiguration::CompareMethod::DEFAULT) {
+    config.Compare = SortConfiguration::CompareMethod::STRING;
+  }
+  if (config.Case == SortConfiguration::CaseSensitivity::DEFAULT) {
+    config.Case = SortConfiguration::CaseSensitivity::SENSITIVE;
+  }
+
+  if ((config.Compare == SortConfiguration::CompareMethod::STRING) &&
+      (config.Case == SortConfiguration::CaseSensitivity::SENSITIVE) &&
+      (config.Order == SortConfiguration::OrderMode::ASCENDING)) {
+    std::sort(this->Values.begin(), this->Values.end());
+  } else {
+    StringSorter sorter(config);
+    std::sort(this->Values.begin(), this->Values.end(), sorter);
+  }
+
+  return *this;
+}
+
+namespace {
+using transform_type = std::function<std::string(const std::string&)>;
+using transform_error = cmList::transform_error;
+
+class TransformSelector : public cmList::TransformSelector
+{
+public:
+  ~TransformSelector() override = default;
+
+  std::string Tag;
+
+  const std::string& GetTag() override { return this->Tag; }
+
+  virtual bool Validate(std::size_t count = 0) = 0;
+
+  virtual bool InSelection(const std::string&) = 0;
+
+  virtual void Transform(cmList::container_type& list,
+                         const transform_type& transform)
+  {
+    std::transform(list.begin(), list.end(), list.begin(), transform);
+  }
+
+protected:
+  TransformSelector(std::string&& tag)
+    : Tag(std::move(tag))
+  {
+  }
+};
+
+class TransformNoSelector : public TransformSelector
+{
+public:
+  TransformNoSelector()
+    : TransformSelector("NO SELECTOR")
+  {
+  }
+
+  bool Validate(std::size_t) override { return true; }
+
+  bool InSelection(const std::string&) override { return true; }
+};
+class TransformSelectorRegex : public TransformSelector
+{
+public:
+  TransformSelectorRegex(const std::string& regex)
+    : TransformSelector("REGEX")
+    , Regex(regex)
+  {
+  }
+  TransformSelectorRegex(std::string&& regex)
+    : TransformSelector("REGEX")
+    , Regex(regex)
+  {
+  }
+
+  bool Validate(std::size_t) override { return this->Regex.is_valid(); }
+
+  bool InSelection(const std::string& value) override
+  {
+    return this->Regex.find(value);
+  }
+
+  cmsys::RegularExpression Regex;
+};
+class TransformSelectorIndexes : public TransformSelector
+{
+public:
+  std::vector<index_type> Indexes;
+
+  bool InSelection(const std::string&) override { return true; }
+
+  void Transform(std::vector<std::string>& list,
+                 const transform_type& transform) override
+  {
+    this->Validate(list.size());
+
+    for (auto index : this->Indexes) {
+      list[index] = transform(list[index]);
+    }
+  }
+
+protected:
+  TransformSelectorIndexes(std::string&& tag)
+    : TransformSelector(std::move(tag))
+  {
+  }
+  TransformSelectorIndexes(std::string&& tag, std::vector<int> const& indexes)
+    : TransformSelector(std::move(tag))
+    , Indexes(indexes)
+  {
+  }
+  TransformSelectorIndexes(std::string&& tag, std::vector<int>&& indexes)
+    : TransformSelector(std::move(tag))
+    , Indexes(indexes)
+  {
+  }
+
+  int NormalizeIndex(index_type index, std::size_t count)
+  {
+    if (index < 0) {
+      index = static_cast<index_type>(count) + index;
+    }
+    if (index < 0 || count <= static_cast<std::size_t>(index)) {
+      throw transform_error(cmStrCat(
+        "sub-command TRANSFORM, selector ", this->Tag, ", index: ", index,
+        " out of range (-", count, ", ", count - 1, ")."));
+    }
+    return index;
+  }
+};
+class TransformSelectorAt : public TransformSelectorIndexes
+{
+public:
+  TransformSelectorAt(std::vector<index_type> const& indexes)
+    : TransformSelectorIndexes("AT", indexes)
+  {
+  }
+  TransformSelectorAt(std::vector<index_type>&& indexes)
+    : TransformSelectorIndexes("AT", std::move(indexes))
+  {
+  }
+
+  bool Validate(std::size_t count) override
+  {
+    decltype(this->Indexes) indexes;
+
+    for (auto index : this->Indexes) {
+      indexes.push_back(this->NormalizeIndex(index, count));
+    }
+    this->Indexes = std::move(indexes);
+
+    return true;
+  }
+};
+class TransformSelectorFor : public TransformSelectorIndexes
+{
+public:
+  TransformSelectorFor(int start, int stop, int step)
+    : TransformSelectorIndexes("FOR")
+    , Start(start)
+    , Stop(stop)
+    , Step(step)
+  {
+  }
+
+  bool Validate(std::size_t count) override
+  {
+    this->Start = this->NormalizeIndex(this->Start, count);
+    this->Stop = this->NormalizeIndex(this->Stop, count);
+
+    // Does stepping move us further from the end?
+    if (this->Start > this->Stop) {
+      throw transform_error(
+        cmStrCat("sub-command TRANSFORM, selector FOR "
+                 "expects <start> to be no greater than <stop> (",
+                 this->Start, " > ", this->Stop, ")"));
+    }
+
+    // compute indexes
+    auto size = (this->Stop - this->Start + 1) / this->Step;
+    if ((this->Stop - this->Start + 1) % this->Step != 0) {
+      size += 1;
+    }
+
+    this->Indexes.resize(size);
+    auto start = this->Start;
+    auto step = this->Step;
+    std::generate(this->Indexes.begin(), this->Indexes.end(),
+                  [&start, step]() -> int {
+                    auto r = start;
+                    start += step;
+                    return r;
+                  });
+
+    return true;
+  }
+
+private:
+  index_type Start, Stop, Step;
+};
+
+class TransformAction
+{
+public:
+  virtual ~TransformAction() = default;
+
+  void Initialize(TransformSelector* selector) { this->Selector = selector; }
+  virtual void Initialize(TransformSelector*, const std::string&) {}
+  virtual void Initialize(TransformSelector*, const std::string&,
+                          const std::string&)
+  {
+  }
+  virtual void Initialize(TransformSelector* selector,
+                          const std::vector<std::string>&)
+  {
+    this->Initialize(selector);
+  }
+
+  virtual std::string operator()(const std::string& s) = 0;
+
+protected:
+  TransformSelector* Selector;
+};
+class TransformActionAppend : public TransformAction
+{
+public:
+  using TransformAction::Initialize;
+
+  void Initialize(TransformSelector* selector,
+                  const std::string& append) override
+  {
+    TransformAction::Initialize(selector);
+    this->Append = append;
+  }
+  void Initialize(TransformSelector* selector,
+                  const std::vector<std::string>& append) override
+  {
+    this->Initialize(selector, append.front());
+  }
+
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      return cmStrCat(s, this->Append);
+    }
+
+    return s;
+  }
+
+private:
+  std::string Append;
+};
+class TransformActionPrepend : public TransformAction
+{
+public:
+  using TransformAction::Initialize;
+
+  void Initialize(TransformSelector* selector,
+                  const std::string& prepend) override
+  {
+    TransformAction::Initialize(selector);
+    this->Prepend = prepend;
+  }
+  void Initialize(TransformSelector* selector,
+                  const std::vector<std::string>& prepend) override
+  {
+    this->Initialize(selector, prepend.front());
+  }
+
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      return cmStrCat(this->Prepend, s);
+    }
+
+    return s;
+  }
+
+private:
+  std::string Prepend;
+};
+class TransformActionToUpper : public TransformAction
+{
+public:
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      return cmSystemTools::UpperCase(s);
+    }
+
+    return s;
+  }
+};
+class TransformActionToLower : public TransformAction
+{
+public:
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      return cmSystemTools::LowerCase(s);
+    }
+
+    return s;
+  }
+};
+class TransformActionStrip : public TransformAction
+{
+public:
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      return cmTrimWhitespace(s);
+    }
+
+    return s;
+  }
+};
+class TransformActionGenexStrip : public TransformAction
+{
+public:
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      return cmGeneratorExpression::Preprocess(
+        s, cmGeneratorExpression::StripAllGeneratorExpressions);
+    }
+
+    return s;
+  }
+};
+class TransformActionReplace : public TransformAction
+{
+public:
+  using TransformAction::Initialize;
+
+  void Initialize(TransformSelector* selector, const std::string& regex,
+                  const std::string& replace) override
+  {
+    TransformAction::Initialize(selector);
+    this->ReplaceHelper =
+      cm::make_unique<cmStringReplaceHelper>(regex, replace);
+
+    if (!this->ReplaceHelper->IsRegularExpressionValid()) {
+      throw transform_error(
+        cmStrCat("sub-command TRANSFORM, action REPLACE: Failed to compile "
+                 "regex \"",
+                 regex, "\"."));
+    }
+    if (!this->ReplaceHelper->IsReplaceExpressionValid()) {
+      throw transform_error(cmStrCat("sub-command TRANSFORM, action REPLACE: ",
+                                     this->ReplaceHelper->GetError(), "."));
+    }
+  }
+  void Initialize(TransformSelector* selector,
+                  const std::vector<std::string>& args) override
+  {
+    this->Initialize(selector, args[0], args[1]);
+  }
+
+  std::string operator()(const std::string& s) override
+  {
+    if (this->Selector->InSelection(s)) {
+      // Scan through the input for all matches.
+      std::string output;
+
+      if (!this->ReplaceHelper->Replace(s, output)) {
+        throw transform_error(
+          cmStrCat("sub-command TRANSFORM, action REPLACE: ",
+                   this->ReplaceHelper->GetError(), "."));
+      }
+
+      return output;
+    }
+
+    return s;
+  }
+
+private:
+  std::unique_ptr<cmStringReplaceHelper> ReplaceHelper;
+};
+
+// Descriptor of action
+// Arity: number of arguments required for the action
+// Transform: Object implementing the action
+struct ActionDescriptor
+{
+  ActionDescriptor(cmList::TransformAction action)
+    : Action(action)
+  {
+  }
+  ActionDescriptor(cmList::TransformAction action, std::string name,
+                   std::size_t arity,
+                   std::unique_ptr<TransformAction> transform)
+    : Action(action)
+    , Name(std::move(name))
+    , Arity(arity)
+    , Transform(std::move(transform))
+  {
+  }
+
+  operator cmList::TransformAction() const { return this->Action; }
+
+  cmList::TransformAction Action;
+  std::string Name;
+  std::size_t Arity = 0;
+  std::unique_ptr<TransformAction> Transform;
+};
+
+// Build a set of supported actions.
+using ActionDescriptorSet = std::set<
+  ActionDescriptor,
+  std::function<bool(cmList::TransformAction, cmList::TransformAction)>>;
+
+ActionDescriptorSet Descriptors([](cmList::TransformAction x,
+                                   cmList::TransformAction y) {
+  return x < y;
+});
+
+ActionDescriptorSet::iterator TransformConfigure(
+  cmList::TransformAction action,
+  std::unique_ptr<cmList::TransformSelector>& selector, std::size_t arity)
+{
+  if (Descriptors.empty()) {
+    Descriptors.emplace(cmList::TransformAction::APPEND, "APPEND", 1,
+                        cm::make_unique<TransformActionAppend>());
+    Descriptors.emplace(cmList::TransformAction::PREPEND, "PREPEND", 1,
+                        cm::make_unique<TransformActionPrepend>());
+    Descriptors.emplace(cmList::TransformAction::TOUPPER, "TOUPPER", 0,
+                        cm::make_unique<TransformActionToUpper>());
+    Descriptors.emplace(cmList::TransformAction::TOLOWER, "TOLOWER", 0,
+                        cm::make_unique<TransformActionToLower>());
+    Descriptors.emplace(cmList::TransformAction::STRIP, "STRIP", 0,
+                        cm::make_unique<TransformActionStrip>());
+    Descriptors.emplace(cmList::TransformAction::GENEX_STRIP, "GENEX_STRIP", 0,
+                        cm::make_unique<TransformActionGenexStrip>());
+    Descriptors.emplace(cmList::TransformAction::REPLACE, "REPLACE", 2,
+                        cm::make_unique<TransformActionReplace>());
+  }
+
+  auto descriptor = Descriptors.find(action);
+  if (descriptor == Descriptors.end()) {
+    throw transform_error(cmStrCat(" sub-command TRANSFORM, ",
+                                   std::to_string(static_cast<int>(action)),
+                                   " invalid action."));
+  }
+
+  if (descriptor->Arity != arity) {
+    throw transform_error(cmStrCat("sub-command TRANSFORM, action ",
+                                   descriptor->Name, " expects ",
+                                   descriptor->Arity, " argument(s)."));
+  }
+  if (!selector) {
+    selector = cm::make_unique<TransformNoSelector>();
+  }
+
+  return descriptor;
+}
+}
+
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewAT(
+  std::initializer_list<index_type> indexes)
+{
+  return cm::make_unique<TransformSelectorAt>(
+    std::vector<index_type>{ indexes.begin(), indexes.end() });
+  ;
+}
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewAT(
+  std::vector<index_type> const& indexes)
+{
+  return cm::make_unique<TransformSelectorAt>(indexes);
+}
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewAT(
+  std::vector<index_type>&& indexes)
+{
+  return cm::make_unique<TransformSelectorAt>(std::move(indexes));
+}
+
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewFOR(
+  std::initializer_list<index_type> indexes)
+{
+  if (indexes.size() < 2 || indexes.size() > 3) {
+    throw transform_error("sub-command TRANSFORM, selector FOR "
+                          "expects 2 or 3 arguments");
+  }
+  if (indexes.size() == 3 && *(indexes.begin() + 2) < 0) {
+    throw transform_error("sub-command TRANSFORM, selector FOR expects "
+                          "positive numeric value for <step>.");
+  }
+
+  return cm::make_unique<TransformSelectorFor>(
+    *indexes.begin(), *(indexes.begin() + 1),
+    indexes.size() == 3 ? *(indexes.begin() + 2) : 1);
+}
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewFOR(
+  std::vector<index_type> const& indexes)
+{
+  if (indexes.size() < 2 || indexes.size() > 3) {
+    throw transform_error("sub-command TRANSFORM, selector FOR "
+                          "expects 2 or 3 arguments");
+  }
+  if (indexes.size() == 3 && indexes[2] < 0) {
+    throw transform_error("sub-command TRANSFORM, selector FOR expects "
+                          "positive numeric value for <step>.");
+  }
+
+  return cm::make_unique<TransformSelectorFor>(
+    indexes[0], indexes[1], indexes.size() == 3 ? indexes[2] : 1);
+}
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewFOR(
+  std::vector<index_type>&& indexes)
+{
+  if (indexes.size() < 2 || indexes.size() > 3) {
+    throw transform_error("sub-command TRANSFORM, selector FOR "
+                          "expects 2 or 3 arguments");
+  }
+  if (indexes.size() == 3 && indexes[2] < 0) {
+    throw transform_error("sub-command TRANSFORM, selector FOR expects "
+                          "positive numeric value for <step>.");
+  }
+
+  return cm::make_unique<TransformSelectorFor>(
+    indexes[0], indexes[1], indexes.size() == 3 ? indexes[2] : 1);
+}
+
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewREGEX(
+  std::string const& regex)
+{
+  std::unique_ptr<::TransformSelector> selector =
+    cm::make_unique<TransformSelectorRegex>(regex);
+  if (!selector->Validate()) {
+    throw transform_error(
+      cmStrCat("sub-command TRANSFORM, selector REGEX failed to compile "
+               "regex \"",
+               regex, "\"."));
+  }
+  // weird construct to please all compilers
+  return std::unique_ptr<cmList::TransformSelector>(selector.release());
+}
+std::unique_ptr<cmList::TransformSelector> cmList::TransformSelector::NewREGEX(
+  std::string&& regex)
+{
+  std::unique_ptr<::TransformSelector> selector =
+    cm::make_unique<TransformSelectorRegex>(std::move(regex));
+  if (!selector->Validate()) {
+    throw transform_error(
+      cmStrCat("sub-command TRANSFORM, selector REGEX failed to compile "
+               "regex \"",
+               regex, "\"."));
+  }
+  // weird construct to please all compilers
+  return std::unique_ptr<cmList::TransformSelector>(selector.release());
+}
+
+cmList& cmList::transform(TransformAction action,
+                          std::unique_ptr<TransformSelector> selector)
+{
+  auto descriptor = TransformConfigure(action, selector, 0);
+
+  descriptor->Transform->Initialize(
+    static_cast<::TransformSelector*>(selector.get()));
+
+  static_cast<::TransformSelector&>(*selector).Transform(
+    this->Values, [&descriptor](const std::string& s) -> std::string {
+      return (*descriptor->Transform)(s);
+    });
+
+  return *this;
+}
+
+cmList& cmList::transform(TransformAction action, std::string const& arg,
+                          std::unique_ptr<TransformSelector> selector)
+{
+  auto descriptor = TransformConfigure(action, selector, 1);
+
+  descriptor->Transform->Initialize(
+    static_cast<::TransformSelector*>(selector.get()), arg);
+
+  static_cast<::TransformSelector&>(*selector).Transform(
+    this->Values, [&descriptor](const std::string& s) -> std::string {
+      return (*descriptor->Transform)(s);
+    });
+
+  return *this;
+}
+
+cmList& cmList::transform(TransformAction action, std::string const& arg1,
+                          std::string const& arg2,
+                          std::unique_ptr<TransformSelector> selector)
+{
+  auto descriptor = TransformConfigure(action, selector, 2);
+
+  descriptor->Transform->Initialize(
+    static_cast<::TransformSelector*>(selector.get()), arg1, arg2);
+
+  static_cast<::TransformSelector&>(*selector).Transform(
+    this->Values, [&descriptor](const std::string& s) -> std::string {
+      return (*descriptor->Transform)(s);
+    });
+
+  return *this;
+}
+
+cmList& cmList::transform(TransformAction action,
+                          std::vector<std::string> const& args,
+                          std::unique_ptr<TransformSelector> selector)
+{
+  auto descriptor = TransformConfigure(action, selector, args.size());
+
+  descriptor->Transform->Initialize(
+    static_cast<::TransformSelector*>(selector.get()), args);
+
+  static_cast<::TransformSelector&>(*selector).Transform(
+    this->Values, [&descriptor](const std::string& s) -> std::string {
+      return (*descriptor->Transform)(s);
+    });
+
+  return *this;
+}
+
+std::string cmList::join(cm::string_view glue) const
+{
+  return cmJoin(this->Values, glue);
+}
+
+std::string& cmList::append(cm::string_view value, std::string& list)
+{
+  if (list.empty()) {
+    list = std::string(value);
+  } else {
+    list += cmStrCat(cmList::element_separator, value);
+  }
+
+  return list;
+}
+
+std::string& cmList::prepend(cm::string_view value, std::string& list)
+{
+  if (list.empty()) {
+    list = std::string(value);
+  } else {
+    list.insert(0, cmStrCat(value, cmList::element_separator));
+  }
+
+  return list;
+}
+
+cmList::size_type cmList::ComputeIndex(index_type pos, bool boundCheck) const
+{
+  if (boundCheck) {
+    if (this->Values.empty()) {
+      throw std::out_of_range(
+        cmStrCat("index: ", pos, " out of range (0, 0)"));
+    }
+
+    if (!this->Values.empty()) {
+      auto length = this->Values.size();
+      if (pos < 0) {
+        pos = static_cast<index_type>(length) + pos;
+      }
+      if (pos < 0 || length <= static_cast<size_type>(pos)) {
+        throw std::out_of_range(cmStrCat("index: ", pos, " out of range (-",
+                                         this->Values.size(), ", ",
+                                         this->Values.size() - 1, ")"));
+      }
+    }
+    return pos;
+  }
+
+  return pos < 0 ? this->Values.size() + pos : pos;
+}
+cmList::size_type cmList::ComputeInsertIndex(index_type pos,
+                                             bool boundCheck) const
+{
+  if (boundCheck) {
+    if (this->Values.empty() && pos != 0) {
+      throw std::out_of_range(
+        cmStrCat("index: ", pos, " out of range (0, 0)"));
+    }
+
+    if (!this->Values.empty()) {
+      auto length = this->Values.size();
+      if (pos < 0) {
+        pos = static_cast<index_type>(length) + pos;
+      }
+      if (pos < 0 || length < static_cast<size_type>(pos)) {
+        throw std::out_of_range(cmStrCat("index: ", pos, " out of range (-",
+                                         this->Values.size(), ", ",
+                                         this->Values.size(), ")"));
+      }
+    }
+    return pos;
+  }
+
+  return pos < 0 ? this->Values.size() + pos : pos;
+}
+
+cmList cmList::GetItems(std::vector<index_type>&& indexes) const
+{
+  cmList listItems;
+
+  for (auto index : indexes) {
+    listItems.emplace_back(this->at(index));
+  }
+
+  return listItems;
+}
+
+cmList& cmList::RemoveItems(std::vector<index_type>&& indexes)
+{
+  if (indexes.empty()) {
+    return *this;
+  }
+
+  // compute all indexes
+  std::vector<size_type> idx(indexes.size());
+  std::transform(
+    indexes.cbegin(), indexes.cend(), idx.begin(),
+    [this](const index_type& index) { return this->ComputeIndex(index); });
+
+  std::sort(idx.begin(), idx.end(),
+            [](size_type l, size_type r) { return l > r; });
+  auto newEnd = std::unique(idx.begin(), idx.end());
+  idx.erase(newEnd, idx.end());
+
+  for (auto index : idx) {
+    this->erase(this->begin() + index);
+  }
+
+  return *this;
+}
+
+cmList& cmList::RemoveItems(std::vector<std::string>&& items)
+{
+  std::sort(items.begin(), items.end());
+  auto last = std::unique(items.begin(), items.end());
+  auto first = items.begin();
+
+  auto newEnd = cmRemoveMatching(this->Values, cmMakeRange(first, last));
+  this->Values.erase(newEnd, this->Values.end());
+
+  return *this;
+}
+
+cmList::container_type::iterator cmList::Insert(
+  container_type::const_iterator pos, std::string&& value,
+  container_type& container, ExpandElements expandElements,
+  EmptyElements emptyElements)
+{
+  auto delta = std::distance(container.cbegin(), pos);
+  auto insertPos = container.begin() + delta;
+
+  if (expandElements == ExpandElements::Yes) {
+    // If argument is empty, it is an empty list.
+    if (emptyElements == EmptyElements::No && value.empty()) {
+      return insertPos;
+    }
+
+    // if there are no ; in the name then just copy the current string
+    if (value.find(';') == std::string::npos) {
+      return container.insert(insertPos, std::move(value));
+    }
+
+    std::string newValue;
+    // Break the string at non-escaped semicolons not nested in [].
+    int squareNesting = 0;
+    auto last = value.begin();
+    auto const cend = value.end();
+    for (auto c = last; c != cend; ++c) {
+      switch (*c) {
+        case '\\': {
+          // We only want to allow escaping of semicolons.  Other
+          // escapes should not be processed here.
+          auto cnext = c + 1;
+          if ((cnext != cend) && *cnext == ';') {
+            newValue.append(last, c);
+            // Skip over the escape character
+            last = cnext;
+            c = cnext;
+          }
+        } break;
+        case '[': {
+          ++squareNesting;
+        } break;
+        case ']': {
+          --squareNesting;
+        } break;
+        case ';': {
+          // brackets.
+          if (squareNesting == 0) {
+            newValue.append(last, c);
+            // Skip over the semicolon
+            last = c + 1;
+            if (!newValue.empty() || emptyElements == EmptyElements::Yes) {
+              // Add the last argument.
+              insertPos = container.insert(insertPos, newValue);
+              insertPos++;
+              newValue.clear();
+            }
+          }
+        } break;
+        default: {
+          // Just append this character.
+        } break;
+      }
+    }
+    newValue.append(last, cend);
+    if (!newValue.empty() || emptyElements == EmptyElements::Yes) {
+      // Add the last argument.
+      container.insert(insertPos, std::move(newValue));
+    }
+  } else if (!value.empty() || emptyElements == EmptyElements::Yes) {
+    return container.insert(insertPos, std::move(value));
+  }
+  return container.begin() + delta;
+}

+ 1198 - 0
Source/cmList.h

@@ -0,0 +1,1198 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <algorithm>
+#include <initializer_list>
+#include <iterator>
+#include <memory>
+#include <numeric>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <cm/string_view>
+
+#include "cmValue.h"
+
+/**
+ * CMake lists management
+ *
+ * For all operations, input arguments (single value like cm::string_view or
+ * multiple values specified through pair of iterators) are, by default,
+ * expanded. The expansion can be controlled by the cmList::ExpandElements
+ * option.
+ *
+ * There is an exception to this rule. The following methods do not expand
+ * their argument: cmList::push_back, cmList::emplace and cmList::emplace_back.
+ */
+
+class cmList
+{
+public:
+  using container_type = std::vector<std::string>;
+
+  using value_type = container_type::value_type;
+  using allocator_type = container_type::allocator_type;
+  using index_type = int;
+  using size_type = container_type::size_type;
+  using difference_type = container_type::difference_type;
+  using reference = container_type::reference;
+  using const_reference = container_type::const_reference;
+  using iterator = container_type::iterator;
+  using const_iterator = container_type::const_iterator;
+  using reverse_iterator = container_type::reverse_iterator;
+  using const_reverse_iterator = container_type::const_reverse_iterator;
+
+  static const size_type npos = static_cast<size_type>(-1);
+
+  static cm::string_view element_separator;
+
+  enum class EmptyElements
+  {
+    No,
+    Yes,
+  };
+  enum class ExpandElements
+  {
+    No,
+    Yes,
+  };
+
+  cmList() = default;
+  cmList(const cmList&) = default;
+  cmList(cmList&&) = default;
+
+  cmList(cm::string_view value,
+         ExpandElements expandElements = ExpandElements::Yes,
+         EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(value, expandElements, emptyElements);
+  }
+  cmList(cm::string_view value, EmptyElements emptyElements)
+  {
+    this->assign(value, ExpandElements::Yes, emptyElements);
+  }
+  cmList(cmValue list, ExpandElements expandElements = ExpandElements::Yes,
+         EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (list) {
+      this->assign(*list, expandElements, emptyElements);
+    }
+  }
+  cmList(cmValue list, EmptyElements emptyElements)
+    : cmList(list, ExpandElements::Yes, emptyElements)
+  {
+  }
+  template <typename InputIterator>
+  cmList(InputIterator first, InputIterator last,
+         ExpandElements expandElements = ExpandElements::Yes,
+         EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(first, last, expandElements, emptyElements);
+  }
+  template <typename InputIterator>
+  cmList(InputIterator first, InputIterator last, EmptyElements emptyElements)
+    : cmList(first, last, ExpandElements::Yes, emptyElements)
+  {
+  }
+  cmList(const container_type& init,
+         ExpandElements expandElements = ExpandElements::Yes,
+         EmptyElements emptyElements = EmptyElements::No)
+    : cmList(init.begin(), init.end(), expandElements, emptyElements)
+  {
+  }
+  cmList(const container_type& init, EmptyElements emptyElements)
+    : cmList(init, ExpandElements::Yes, emptyElements)
+  {
+  }
+  cmList(container_type&& init,
+         ExpandElements expandElements = ExpandElements::Yes,
+         EmptyElements emptyElements = EmptyElements::No)
+    : cmList(std::make_move_iterator(init.begin()),
+             std::make_move_iterator(init.end()), expandElements,
+             emptyElements)
+  {
+    init.clear();
+  }
+  cmList(container_type&& init, EmptyElements emptyElements)
+    : cmList(std::move(init), ExpandElements::Yes, emptyElements)
+  {
+  }
+  cmList(std::initializer_list<std::string> init) { this->assign(init); }
+
+  ~cmList() = default;
+
+  cmList& operator=(const cmList&) = default;
+  cmList& operator=(cmList&&) = default;
+
+  cmList& operator=(cm::string_view value)
+  {
+    this->assign(value);
+    return *this;
+  }
+  cmList& operator=(cmValue value)
+  {
+    if (value) {
+      this->operator=(*value);
+    } else {
+      this->clear();
+    }
+
+    return *this;
+  }
+
+  cmList& operator=(const container_type& init)
+  {
+    this->assign(init);
+    return *this;
+  }
+  cmList& operator=(container_type&& init)
+  {
+    this->assign(std::move(init));
+
+    return *this;
+  }
+
+  cmList& operator=(std::initializer_list<std::string> init)
+  {
+    this->assign(init);
+    return *this;
+  }
+
+  void assign(cm::string_view value,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->clear();
+    this->append(value, expandElements, emptyElements);
+  }
+  void assign(cm::string_view value, EmptyElements emptyElements)
+  {
+    this->assign(value, ExpandElements::Yes, emptyElements);
+  }
+  void assign(cmValue value,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      this->assign(*value, expandElements, emptyElements);
+    } else {
+      this->clear();
+    }
+  }
+  void assign(cmValue value, EmptyElements emptyElements)
+  {
+    this->assign(value, ExpandElements::Yes, emptyElements);
+  }
+  template <typename InputIterator>
+  void assign(InputIterator first, InputIterator last,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->clear();
+    this->append(first, last, expandElements, emptyElements);
+  }
+  template <typename InputIterator>
+  void assign(InputIterator first, InputIterator last,
+              EmptyElements emptyElements)
+  {
+    this->assign(first, last, ExpandElements::Yes, emptyElements);
+  }
+  void assign(const cmList& init,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(init.begin(), init.end(), expandElements, emptyElements);
+  }
+  void assign(const cmList& init, EmptyElements emptyElements)
+  {
+    this->assign(init, ExpandElements::Yes, emptyElements);
+  }
+  void assign(cmList&& init,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(std::make_move_iterator(init.begin()),
+                 std::make_move_iterator(init.end()), expandElements,
+                 emptyElements);
+    init.clear();
+  }
+  void assign(cmList&& init, EmptyElements emptyElements)
+  {
+    this->assign(std::move(init), ExpandElements::Yes, emptyElements);
+  }
+  void assign(const container_type& init,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(init.begin(), init.end(), expandElements, emptyElements);
+  }
+  void assign(const container_type& init, EmptyElements emptyElements)
+  {
+    this->assign(init, ExpandElements::Yes, emptyElements);
+  }
+  void assign(container_type&& init,
+              ExpandElements expandElements = ExpandElements::Yes,
+              EmptyElements emptyElements = EmptyElements::No)
+  {
+    this->assign(std::make_move_iterator(init.begin()),
+                 std::make_move_iterator(init.end()), expandElements,
+                 emptyElements);
+    init.clear();
+  }
+  void assign(container_type&& init, EmptyElements emptyElements)
+  {
+    this->assign(std::move(init), ExpandElements::Yes, emptyElements);
+  }
+  void assign(std::initializer_list<std::string> init)
+  {
+    this->assign(init.begin(), init.end());
+  }
+
+  // Conversions
+  std::string to_string() const
+  {
+    return this->join(cmList::element_separator);
+  }
+
+  operator container_type&() & noexcept { return this->Values; }
+  operator const container_type&() const& noexcept { return this->Values; }
+  operator container_type&&() && noexcept { return std::move(this->Values); }
+
+  // Element access
+  reference at(index_type pos)
+  {
+    return this->Values.at(this->ComputeIndex(pos));
+  }
+  const_reference at(index_type pos) const
+  {
+    return this->Values.at(this->ComputeIndex(pos));
+  }
+
+  reference operator[](index_type pos)
+  {
+    return this->Values[this->ComputeIndex(pos, false)];
+  }
+  const_reference operator[](index_type pos) const
+  {
+    return this->Values[this->ComputeIndex(pos, false)];
+  }
+
+  reference front() { return this->Values.front(); }
+  const_reference front() const { return this->Values.front(); }
+
+  reference back() { return this->Values.back(); }
+  const_reference back() const { return this->Values.back(); }
+
+  // extract sublist in range [first, last)
+  cmList sublist(const_iterator first, const_iterator last) const
+  {
+    return cmList{ first, last };
+  }
+  // Extract sublist in range [first, last)
+  // Throw std::out_of_range if pos is invalid
+  cmList sublist(size_type pos = 0, size_type length = npos) const;
+
+  // Returns the list of elements
+  // Throw std::out_of_range if any index is invalid
+  cmList get_items(std::initializer_list<index_type> indexes) const
+  {
+    return this->GetItems(
+      std::vector<index_type>{ indexes.begin(), indexes.end() });
+  }
+  template <typename InputIterator>
+  cmList get_items(InputIterator first, InputIterator last) const
+  {
+    return this->GetItems(std::vector<index_type>{ first, last });
+  }
+
+  size_type find(cm::string_view value) const;
+  size_type find(cmValue value) const
+  {
+    if (value) {
+      return this->find(*value);
+    }
+
+    return npos;
+  }
+
+  container_type& data() noexcept { return this->Values; }
+  const container_type& data() const noexcept { return this->Values; }
+
+  // Iterators
+  iterator begin() noexcept { return this->Values.begin(); }
+  const_iterator begin() const noexcept { return this->Values.begin(); }
+  const_iterator cbegin() const noexcept { return this->Values.cbegin(); }
+
+  iterator end() noexcept { return this->Values.end(); }
+  const_iterator end() const noexcept { return this->Values.end(); }
+  const_iterator cend() const noexcept { return this->Values.cend(); }
+
+  reverse_iterator rbegin() noexcept { return this->Values.rbegin(); }
+  const_reverse_iterator rbegin() const noexcept
+  {
+    return this->Values.rbegin();
+  }
+  const_reverse_iterator crbegin() const noexcept
+  {
+    return this->Values.crbegin();
+  }
+
+  reverse_iterator rend() noexcept { return this->Values.rend(); }
+  const_reverse_iterator rend() const noexcept { return this->Values.rend(); }
+  const_reverse_iterator crend() const noexcept
+  {
+    return this->Values.crend();
+  }
+
+  // Capacity
+  bool empty() const noexcept { return this->Values.empty(); }
+  size_type size() const noexcept { return this->Values.size(); }
+
+  // Modifiers
+  void clear() noexcept { this->Values.clear(); }
+
+  iterator insert(const_iterator pos, cm::string_view value,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::Insert(pos, std::string(value), this->Values,
+                          expandElements, emptyElements);
+  }
+  iterator insert(const_iterator pos, cm::string_view value,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, value, ExpandElements::Yes, emptyElements);
+  }
+  iterator insert(const_iterator pos, cmValue value,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      return this->insert(pos, *value, expandElements, emptyElements);
+    }
+
+    auto delta = std::distance(this->cbegin(), pos);
+    return this->begin() + delta;
+  }
+  iterator insert(const_iterator pos, cmValue value,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, value, ExpandElements::Yes, emptyElements);
+  }
+  template <typename InputIterator>
+  iterator insert(const_iterator pos, InputIterator first, InputIterator last,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::Insert(pos, first, last, this->Values, expandElements,
+                          emptyElements);
+  }
+  template <typename InputIterator>
+  iterator insert(const_iterator pos, InputIterator first, InputIterator last,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, first, last, ExpandElements::Yes, emptyElements);
+  }
+  iterator insert(const_iterator pos, const cmList& values,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(pos, values.begin(), values.end(), expandElements,
+                        emptyElements);
+  }
+  iterator insert(const_iterator pos, const cmList& values,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, values, ExpandElements::Yes, emptyElements);
+  }
+  iterator insert(const_iterator pos, cmList&& values,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    auto result = this->insert(pos, std::make_move_iterator(values.begin()),
+                               std::make_move_iterator(values.end()),
+                               expandElements, emptyElements);
+    values.clear();
+
+    return result;
+  }
+  iterator insert(const_iterator pos, cmList&& values,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, std::move(values), ExpandElements::Yes,
+                        emptyElements);
+  }
+  iterator insert(const_iterator pos, const container_type& values,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(pos, values.begin(), values.end(), expandElements,
+                        emptyElements);
+  }
+  iterator insert(const_iterator pos, const container_type& values,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, values, ExpandElements::Yes, emptyElements);
+  }
+  iterator insert(const_iterator pos, container_type&& values,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    auto result = this->insert(pos, std::make_move_iterator(values.begin()),
+                               std::make_move_iterator(values.end()),
+                               expandElements, emptyElements);
+    values.clear();
+
+    return result;
+  }
+  iterator insert(const_iterator pos, container_type&& values,
+                  EmptyElements emptyElements)
+  {
+    return this->insert(pos, std::move(values), ExpandElements::Yes,
+                        emptyElements);
+  }
+  iterator insert(const_iterator pos, std::initializer_list<std::string> ilist)
+  {
+    return this->insert(pos, ilist.begin(), ilist.end());
+  }
+
+  iterator append(cm::string_view value,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(this->cend(), value, expandElements, emptyElements);
+  }
+  iterator append(cm::string_view value, EmptyElements emptyElements)
+  {
+    return this->append(value, ExpandElements::Yes, emptyElements);
+  }
+  iterator append(cmValue value,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      return this->append(*value, expandElements, emptyElements);
+    }
+
+    return this->end();
+  }
+  iterator append(cmValue value, EmptyElements emptyElements)
+  {
+    return this->append(value, ExpandElements::Yes, emptyElements);
+  }
+  template <typename InputIterator>
+  iterator append(InputIterator first, InputIterator last,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(this->cend(), first, last, expandElements,
+                        emptyElements);
+  }
+  template <typename InputIterator>
+  iterator append(InputIterator first, InputIterator last,
+                  EmptyElements emptyElements)
+  {
+    return this->append(first, last, ExpandElements::Yes, emptyElements);
+  }
+  iterator append(const cmList& values,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->append(values.begin(), values.end(), expandElements,
+                        emptyElements);
+  }
+  iterator append(const cmList& values, EmptyElements emptyElements)
+  {
+    return this->append(values, ExpandElements::Yes, emptyElements);
+  }
+  iterator append(cmList&& values,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    auto result = this->append(std::make_move_iterator(values.begin()),
+                               std::make_move_iterator(values.end()),
+                               expandElements, emptyElements);
+    values.clear();
+
+    return result;
+  }
+  iterator append(cmList&& values, EmptyElements emptyElements)
+  {
+    return this->append(std::move(values), ExpandElements::Yes, emptyElements);
+  }
+  iterator append(const container_type& values,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->append(values.begin(), values.end(), expandElements,
+                        emptyElements);
+  }
+  iterator append(const container_type& values, EmptyElements emptyElements)
+  {
+    return this->append(values, ExpandElements::Yes, emptyElements);
+  }
+  iterator append(container_type&& values,
+                  ExpandElements expandElements = ExpandElements::Yes,
+                  EmptyElements emptyElements = EmptyElements::No)
+  {
+    auto result = this->append(std::make_move_iterator(values.begin()),
+                               std::make_move_iterator(values.end()),
+                               expandElements, emptyElements);
+    values.clear();
+
+    return result;
+  }
+  iterator append(container_type&& values, EmptyElements emptyElements)
+  {
+    return this->append(std::move(values), ExpandElements::Yes, emptyElements);
+  }
+  iterator append(std::initializer_list<std::string> ilist)
+  {
+    return this->insert(this->cend(), ilist);
+  }
+
+  iterator prepend(cm::string_view value,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(this->cbegin(), value, expandElements, emptyElements);
+  }
+  iterator prepend(cm::string_view value, EmptyElements emptyElements)
+  {
+    return this->prepend(value, ExpandElements::Yes, emptyElements);
+  }
+  iterator prepend(cmValue value,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      return this->prepend(*value, expandElements, emptyElements);
+    }
+
+    return this->begin();
+  }
+  iterator prepend(cmValue value, EmptyElements emptyElements)
+  {
+    return this->prepend(value, ExpandElements::Yes, emptyElements);
+  }
+  template <typename InputIterator>
+  iterator prepend(InputIterator first, InputIterator last,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->insert(this->cbegin(), first, last, expandElements,
+                        emptyElements);
+  }
+  template <typename InputIterator>
+  iterator prepend(InputIterator first, InputIterator last,
+                   EmptyElements emptyElements)
+  {
+    return this->prepend(first, last, ExpandElements::Yes, emptyElements);
+  }
+  iterator prepend(const cmList& values,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->prepend(values.begin(), values.end(), expandElements,
+                         emptyElements);
+  }
+  iterator prepend(const cmList& values, EmptyElements emptyElements)
+  {
+    return this->prepend(values, ExpandElements::Yes, emptyElements);
+  }
+  iterator prepend(cmList&& values,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    auto result = this->prepend(std::make_move_iterator(values.begin()),
+                                std::make_move_iterator(values.end()),
+                                expandElements, emptyElements);
+    values.clear();
+
+    return result;
+  }
+  iterator prepend(cmList&& values, EmptyElements emptyElements)
+  {
+    return this->prepend(std::move(values), ExpandElements::Yes,
+                         emptyElements);
+  }
+  iterator prepend(const container_type& values,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    return this->prepend(values.begin(), values.end(), expandElements,
+                         emptyElements);
+  }
+  iterator prepend(const container_type& values, EmptyElements emptyElements)
+  {
+    return this->prepend(values, ExpandElements::Yes, emptyElements);
+  }
+  iterator prepend(container_type&& values,
+                   ExpandElements expandElements = ExpandElements::Yes,
+                   EmptyElements emptyElements = EmptyElements::No)
+  {
+    auto result = this->prepend(std::make_move_iterator(values.begin()),
+                                std::make_move_iterator(values.end()),
+                                expandElements, emptyElements);
+    values.clear();
+
+    return result;
+  }
+  iterator prepend(container_type&& values, EmptyElements emptyElements)
+  {
+    return this->prepend(std::move(values), ExpandElements::Yes,
+                         emptyElements);
+  }
+  iterator prepend(std::initializer_list<std::string> ilist)
+  {
+    return this->insert(this->cbegin(), ilist);
+  }
+
+  void push_back(cm::string_view value)
+  {
+    this->Values.push_back(std::string{ value });
+  }
+  void push_back(std::string&& value)
+  {
+    this->Values.push_back(std::move(value));
+  }
+
+  template <typename... Args>
+  iterator emplace(const_iterator pos, Args&&... args)
+  {
+    return this->Values.emplace(pos, std::forward<Args>(args)...);
+  }
+
+  template <typename... Args>
+  void emplace_back(Args&&... args)
+  {
+    this->Values.emplace_back(std::forward<Args>(args)...);
+  }
+
+  // Inserts elements in the list
+  // Throw std::out_of_range if index is invalid
+  template <typename InputIterator>
+  cmList& insert_items(index_type index, InputIterator first,
+                       InputIterator last)
+  {
+    this->insert(this->begin() + this->ComputeInsertIndex(index), first, last);
+    return *this;
+  }
+  cmList& insert_items(index_type index,
+                       std::initializer_list<std::string> values)
+  {
+    return this->insert_items(index, values.begin(), values.end());
+  }
+
+  iterator erase(const_iterator pos)
+  {
+    // convert const_iterator in iterator to please non standard c++11
+    // compilers (gcc 4.8 for example)
+    auto pos2 =
+      this->Values.begin() + std::distance(this->Values.cbegin(), pos);
+    return this->Values.erase(pos2);
+  }
+  iterator erase(const_iterator first, const_iterator last)
+  {
+    // convert const_iterator in iterator to please non standard c++11
+    // compilers (gcc 4.8 for example)
+    auto first2 =
+      this->Values.begin() + std::distance(this->Values.cbegin(), first);
+    auto last2 =
+      this->Values.begin() + std::distance(this->Values.cbegin(), last);
+    return this->Values.erase(first2, last2);
+  }
+
+  void pop_back() { this->Values.pop_back(); }
+  void pop_front() { this->Values.erase(this->begin()); }
+
+  // Removes elements from the list
+  // Throw std::out_of_range if any index is invalid
+  cmList& remove_items(std::initializer_list<index_type> indexes)
+  {
+    return this->RemoveItems(
+      std::vector<index_type>{ indexes.begin(), indexes.end() });
+  }
+  cmList& remove_items(std::initializer_list<std::string> values)
+  {
+    return this->RemoveItems(
+      std::vector<std::string>{ values.begin(), values.end() });
+  }
+  template <typename InputIterator>
+  cmList& remove_items(InputIterator first, InputIterator last)
+  {
+    return this->RemoveItems(
+      std::vector<typename InputIterator::value_type>{ first, last });
+  }
+
+  cmList& remove_duplicates();
+
+  enum class FilterMode
+  {
+    INCLUDE,
+    EXCLUDE
+  };
+  // Includes or removes items from the list
+  // Throw std::invalid_argument if regular expression is invalid
+  cmList& filter(cm::string_view regex, FilterMode mode);
+
+  cmList& reverse()
+  {
+    std::reverse(this->Values.begin(), this->Values.end());
+    return *this;
+  }
+
+  struct SortConfiguration
+  {
+    enum class OrderMode
+    {
+      DEFAULT,
+      ASCENDING,
+      DESCENDING,
+    } Order = OrderMode::DEFAULT;
+
+    enum class CompareMethod
+    {
+      DEFAULT,
+      STRING,
+      FILE_BASENAME,
+      NATURAL,
+    } Compare = CompareMethod::DEFAULT;
+
+    enum class CaseSensitivity
+    {
+      DEFAULT,
+      SENSITIVE,
+      INSENSITIVE,
+    } Case = CaseSensitivity::DEFAULT;
+
+    // declare the default constructor to work-around clang bug
+    SortConfiguration();
+
+    SortConfiguration(OrderMode order, CompareMethod compare,
+                      CaseSensitivity caseSensitivity)
+      : Order(order)
+      , Compare(compare)
+      , Case(caseSensitivity)
+    {
+    }
+  };
+  cmList& sort(const SortConfiguration& config = SortConfiguration{});
+
+  // exception raised on error during transform operations
+  class transform_error : public std::runtime_error
+  {
+  public:
+    transform_error(const std::string& error)
+      : std::runtime_error(error)
+    {
+    }
+  };
+
+  class TransformSelector
+  {
+  public:
+    using index_type = cmList::index_type;
+
+    // define some structs used as template selector
+    struct AT;
+    struct FOR;
+    struct REGEX;
+
+    virtual ~TransformSelector() = default;
+
+    virtual const std::string& GetTag() = 0;
+
+    // method NEW is used to allocate a selector of the needed type.
+    // For example:
+    // cmList::TransformSelector::New<AT>({1, 2, 5, 6});
+    //  or
+    // cmList::TransformSelector::New<REGEX>("^XX.*");
+    template <typename Type>
+    static std::unique_ptr<TransformSelector> New(
+      std::initializer_list<index_type>);
+    template <typename Type>
+    static std::unique_ptr<TransformSelector> New(
+      std::vector<index_type> const&);
+    template <typename Type>
+    static std::unique_ptr<TransformSelector> New(std::vector<index_type>&&);
+
+    template <typename Type>
+    static std::unique_ptr<TransformSelector> New(std::string const&);
+    template <typename Type>
+    static std::unique_ptr<TransformSelector> New(std::string&&);
+
+  private:
+    static std::unique_ptr<TransformSelector> NewAT(
+      std::initializer_list<index_type> init);
+    static std::unique_ptr<TransformSelector> NewAT(
+      std::vector<index_type> const& init);
+    static std::unique_ptr<TransformSelector> NewAT(
+      std::vector<index_type>&& init);
+
+    static std::unique_ptr<TransformSelector> NewFOR(
+      std::initializer_list<index_type> init);
+    static std::unique_ptr<TransformSelector> NewFOR(
+      std::vector<index_type> const& init);
+    static std::unique_ptr<TransformSelector> NewFOR(
+      std::vector<index_type>&& init);
+
+    static std::unique_ptr<TransformSelector> NewREGEX(
+      std::string const& init);
+    static std::unique_ptr<TransformSelector> NewREGEX(std::string&& init);
+  };
+
+  enum class TransformAction
+  {
+    APPEND,
+    PREPEND,
+    TOLOWER,
+    TOUPPER,
+    STRIP,
+    GENEX_STRIP,
+    REPLACE
+  };
+
+  // Transforms the list by applying an action
+  // Throw std::transform_error is any of arguments specified are invalid
+  cmList& transform(TransformAction action,
+                    std::unique_ptr<TransformSelector> = {});
+  cmList& transform(TransformAction action, std::string const& arg,
+                    std::unique_ptr<TransformSelector> = {});
+  cmList& transform(TransformAction action, std::string const& arg1,
+                    std::string const& arg2,
+                    std::unique_ptr<TransformSelector> = {});
+  cmList& transform(TransformAction action,
+                    std::vector<std::string> const& args,
+                    std::unique_ptr<TransformSelector> = {});
+
+  std::string join(cm::string_view glue) const;
+
+  void swap(cmList& other) noexcept { this->Values.swap(other.Values); }
+
+  // static members
+  // ==============
+  // these methods can be used to store CMake list expansion directly in a
+  // std::vector.
+  static void assign(cm::string_view value,
+                     std::vector<std::string>& container,
+                     EmptyElements emptyElements = EmptyElements::No)
+  {
+    container.clear();
+    cmList::append(value, container, emptyElements);
+  }
+  static void assign(cmValue value, std::vector<std::string>& container,
+                     EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      cmList::assign(*value, container, emptyElements);
+    } else {
+      container.clear();
+    }
+  }
+  template <typename InputIterator>
+  static void assign(InputIterator first, InputIterator last,
+                     std::vector<std::string>& container,
+                     EmptyElements emptyElements = EmptyElements::No)
+  {
+    container.clear();
+    cmList::append(first, last, container, emptyElements);
+  }
+
+  static std::vector<std::string>::iterator insert(
+    std::vector<std::string>::const_iterator pos, cm::string_view value,
+    std::vector<std::string>& container,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::Insert(pos, std::string(value), container,
+                          ExpandElements::Yes, emptyElements);
+  }
+  static std::vector<std::string>::iterator insert(
+    std::vector<std::string>::const_iterator pos, cmValue value,
+    std::vector<std::string>& container,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      return cmList::insert(pos, *value, container, emptyElements);
+    }
+
+    auto delta = std::distance(container.cbegin(), pos);
+    return container.begin() + delta;
+  }
+  template <typename InputIterator>
+  static std::vector<std::string>::iterator insert(
+    std::vector<std::string>::const_iterator pos, InputIterator first,
+    InputIterator last, std::vector<std::string>& container,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::Insert(pos, first, last, container, ExpandElements::Yes,
+                          emptyElements);
+  }
+
+  static std::vector<std::string>::iterator append(
+    cm::string_view value, std::vector<std::string>& container,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::insert(container.cend(), value, container, emptyElements);
+  }
+  static std::vector<std::string>::iterator append(
+    cmValue value, std::vector<std::string>& container,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      return cmList::append(*value, container, emptyElements);
+    }
+
+    return container.end();
+  }
+  template <typename InputIterator>
+  static std::vector<std::string>::iterator append(
+    InputIterator first, InputIterator last,
+    std::vector<std::string>& container,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::insert(container.cend(), first, last, container,
+                          emptyElements);
+  }
+
+  static std::vector<std::string>::iterator prepend(
+    cm::string_view value, std::vector<std::string>& container,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::insert(container.cbegin(), value, container, emptyElements);
+  }
+  static std::vector<std::string>::iterator prepend(
+    cmValue value, std::vector<std::string>& container,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    if (value) {
+      return cmList::prepend(*value, container, emptyElements);
+    }
+
+    return container.begin();
+  }
+  template <typename InputIterator>
+  static std::vector<std::string>::iterator prepend(
+    InputIterator first, InputIterator last,
+    std::vector<std::string>& container,
+    EmptyElements emptyElements = EmptyElements::No)
+  {
+    return cmList::insert(container.cbegin(), first, last, container,
+                          emptyElements);
+  }
+
+  // The following methods offer the possibility to extend a CMake list
+  // but without any intermediate expansion. So the operation is simply a
+  // string concatenation with special handling for the CMake list item
+  // separator
+  static std::string& append(cm::string_view value, std::string& list);
+  template <typename InputIterator>
+  static std::string& append(InputIterator first, InputIterator last,
+                             std::string& list)
+  {
+    if (first == last) {
+      return list;
+    }
+
+    return cmList::append(cm::string_view{ std::accumulate(
+                            std::next(first), last, *first,
+                            [](std::string a, const std::string& b) {
+                              return std::move(a) +
+                                std::string(cmList::element_separator) + b;
+                            }) },
+                          list);
+  }
+
+  static std::string& prepend(cm::string_view value, std::string& list);
+  template <typename InputIterator>
+  static std::string& prepend(InputIterator first, InputIterator last,
+                              std::string& list)
+  {
+    if (first == last) {
+      return list;
+    }
+
+    return cmList::prepend(cm::string_view{ std::accumulate(
+                             std::next(first), last, *first,
+                             [](std::string a, const std::string& b) {
+                               return std::move(a) +
+                                 std::string(cmList::element_separator) + b;
+                             }) },
+                           list);
+  }
+
+  // Non-members
+  // ===========
+  friend inline bool operator==(const cmList& lhs, const cmList& rhs) noexcept
+  {
+    return lhs.Values == rhs.Values;
+  }
+  friend inline bool operator!=(const cmList& lhs, const cmList& rhs) noexcept
+  {
+    return lhs.Values != rhs.Values;
+  }
+
+private:
+  size_type ComputeIndex(index_type pos, bool boundCheck = true) const;
+  size_type ComputeInsertIndex(index_type pos, bool boundCheck = true) const;
+
+  cmList GetItems(std::vector<index_type>&& indexes) const;
+
+  cmList& RemoveItems(std::vector<index_type>&& indexes);
+  cmList& RemoveItems(std::vector<std::string>&& items);
+
+  static container_type::iterator Insert(container_type::const_iterator pos,
+                                         std::string&& value,
+                                         container_type& container,
+                                         ExpandElements expandElements,
+                                         EmptyElements emptyElements);
+  static container_type::iterator Insert(container_type::const_iterator pos,
+                                         const std::string& value,
+                                         container_type& container,
+                                         ExpandElements expandElements,
+                                         EmptyElements emptyElements)
+  {
+    auto tmp = value;
+    return cmList::Insert(pos, std::move(tmp), container, expandElements,
+                          emptyElements);
+  }
+  template <typename InputIterator>
+  static container_type::iterator Insert(container_type::const_iterator pos,
+                                         InputIterator first,
+                                         InputIterator last,
+                                         container_type& container,
+                                         ExpandElements expandElements,
+                                         EmptyElements emptyElements)
+  {
+    auto delta = std::distance(container.cbegin(), pos);
+
+    if (first == last) {
+      return container.begin() + delta;
+    }
+
+    auto insertPos = container.begin() + delta;
+    if (expandElements == ExpandElements::Yes) {
+      for (; first != last; ++first) {
+        auto size = container.size();
+        insertPos = cmList::Insert(insertPos, *first, container,
+                                   expandElements, emptyElements);
+        insertPos += container.size() - size;
+      }
+    } else {
+      for (; first != last; ++first) {
+        if (!first->empty() || emptyElements == EmptyElements::Yes) {
+          insertPos = container.insert(insertPos, *first);
+          insertPos++;
+        }
+      }
+    }
+
+    return container.begin() + delta;
+  }
+
+  container_type Values;
+};
+
+// specializations for cmList::TransformSelector allocators
+// ========================================================
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::AT>(
+  std::initializer_list<index_type> init)
+{
+  return cmList::TransformSelector::NewAT(init);
+}
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::AT>(
+  std::vector<index_type> const& init)
+{
+  return cmList::TransformSelector::NewAT(init);
+}
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::AT>(
+  std::vector<index_type>&& init)
+{
+  return cmList::TransformSelector::NewAT(std::move(init));
+}
+
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::FOR>(
+  std::initializer_list<index_type> init)
+{
+  return cmList::TransformSelector::NewFOR(init);
+}
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::FOR>(
+  std::vector<index_type> const& init)
+{
+  return cmList::TransformSelector::NewFOR(init);
+}
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::FOR>(
+  std::vector<index_type>&& init)
+{
+  return cmList::TransformSelector::NewFOR(std::move(init));
+}
+
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::REGEX>(
+  std::string const& init)
+{
+  return cmList::TransformSelector::NewREGEX(init);
+}
+template <>
+inline std::unique_ptr<cmList::TransformSelector>
+cmList::TransformSelector::New<cmList::TransformSelector::REGEX>(
+  std::string&& init)
+{
+  return cmList::TransformSelector::NewREGEX(std::move(init));
+}
+
+// Non-member functions
+// ====================
+inline std::vector<std::string>& operator+=(std::vector<std::string>& l,
+                                            const cmList& r)
+{
+  l.insert(l.end(), r.begin(), r.end());
+  return l;
+}
+inline std::vector<std::string>& operator+=(std::vector<std::string>& l,
+                                            cmList&& r)
+{
+  std::move(r.begin(), r.end(), std::back_inserter(l));
+  r.clear();
+
+  return l;
+}
+
+namespace cm {
+inline void erase(cmList& list, const std::string& value)
+{
+  list.erase(std::remove(list.begin(), list.end(), value), list.end());
+}
+
+template <typename Predicate>
+inline void erase_if(cmList& list, Predicate pred)
+{
+  list.erase(std::remove_if(list.begin(), list.end(), pred), list.end());
+}
+}
+
+namespace srd {
+inline void swap(cmList& lhs, cmList& rhs) noexcept
+{
+  lhs.swap(rhs);
+}
+}

+ 1 - 0
Tests/CMakeLib/CMakeLists.txt

@@ -30,6 +30,7 @@ set(CMakeLib_TESTS
   testCMExtMemory.cxx
   testCMExtAlgorithm.cxx
   testCMExtEnumSet.cxx
+  testList.cxx
   )
 if (CMake_TEST_FILESYSTEM_PATH OR NOT CMake_HAVE_CXX_FILESYSTEM)
   list(APPEND CMakeLib_TESTS testCMFilesystemPath.cxx)

+ 995 - 0
Tests/CMakeLib/testList.cxx

@@ -0,0 +1,995 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <iostream>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <cm/string_view>
+
+#include "cmList.h"
+
+namespace {
+
+void checkResult(bool success)
+{
+  if (!success) {
+    std::cout << " => failed";
+  }
+  std::cout << std::endl;
+}
+
+bool testConstructors()
+{
+  std::cout << "testConstructors()";
+
+  bool result = true;
+
+  {
+    cmList list;
+    if (!list.empty() || list != cmList{}) {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "aa;bb" };
+    if (list.size() != 2 || list.to_string() != "aa;bb") {
+      result = false;
+    }
+  }
+  {
+    cmList list1{ "aa", "bb" };
+    cmList list2("aa;bb");
+
+    if (list1.size() != 2 || list2.size() != 2 || list1 != list2) {
+      result = false;
+    }
+    if (list1.to_string() != "aa;bb") {
+      result = false;
+    }
+    if (list1.to_string() != list2.to_string()) {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> v{ "aa", "bb", "cc" };
+    cmList list(v.begin(), v.end());
+    if (list.size() != 3 || list.to_string() != "aa;bb;cc") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> values{ "aa;bb", "cc", "dd;ee" };
+    cmList list1(values.begin(), values.end());
+    cmList list2(values.begin(), values.end(), cmList::ExpandElements::No);
+
+    if (list1.size() != 5 || list1.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+    if (list2.size() != 3 || list2.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> values{ "aa;bb;;cc", "", "dd;ee" };
+    cmList list1(values.begin(), values.end(), cmList::ExpandElements::No,
+                 cmList::EmptyElements::No);
+    cmList list2(values.begin(), values.end(), cmList::ExpandElements::No,
+                 cmList::EmptyElements::Yes);
+    cmList list3(values.begin(), values.end(), cmList::ExpandElements::Yes,
+                 cmList::EmptyElements::No);
+    cmList list4(values.begin(), values.end(), cmList::ExpandElements::Yes,
+                 cmList::EmptyElements::Yes);
+
+    if (list1.size() != 2 || list1.to_string() != "aa;bb;;cc;dd;ee") {
+      result = false;
+    }
+    if (list2.size() != 3 || list2.to_string() != "aa;bb;;cc;;dd;ee") {
+      result = false;
+    }
+    if (list3.size() != 5 || list3.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+    if (list4.size() != 7 || list4.to_string() != "aa;bb;;cc;;dd;ee") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> values{ "aa;bb", "cc", "dd;ee" };
+    cmList list1(values);
+    cmList list2(values, cmList::ExpandElements::No);
+
+    if (list1.size() != 5 || list1.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+    if (list2.size() != 3 || list2.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> values{ "aa", "bb", "cc", "dd", "ee" };
+    cmList list(std::move(values));
+
+    if (list.size() != 5 || list.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+    if (!values.empty()) {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testAssign()
+{
+  std::cout << "testAssign()";
+
+  bool result = true;
+
+  {
+    cmList list1{ "aa", "bb" };
+    cmList list2{ "cc", "dd" };
+
+    list2 = list1;
+    if (list1.size() != 2 || list2.size() != 2 || list1 != list2) {
+      result = false;
+    }
+    if (list1.to_string() != "aa;bb") {
+      result = false;
+    }
+    if (list1.to_string() != list2.to_string()) {
+      result = false;
+    }
+  }
+  {
+    cmList list1{ "aa", "bb" };
+    cmList list2{ "cc", "dd" };
+
+    list2 = std::move(list1);
+    if (!list1.empty() || list2.size() != 2) {
+      result = false;
+    }
+    if (list2.to_string() != "aa;bb") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> v{ "aa", "bb" };
+    cmList list{ "cc", "dd" };
+
+    list = std::move(v);
+    if (!v.empty() || list.size() != 2) {
+      result = false;
+    }
+    if (list.to_string() != "aa;bb") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "cc", "dd" };
+
+    list = "aa;bb";
+    if (list.size() != 2) {
+      result = false;
+    }
+    if (list.to_string() != "aa;bb") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testConversions()
+{
+  std::cout << "testConversions()";
+
+  bool result = true;
+
+  {
+    cmList list("a;b;c");
+    std::string s = list.to_string();
+
+    if (s != "a;b;c") {
+      result = false;
+    }
+  }
+  {
+    cmList list("a;b;c");
+    std::vector<std::string> v = list;
+
+    if (list.size() != 3 || v.size() != 3) {
+      result = false;
+    }
+  }
+  {
+    cmList list("a;b;c");
+    std::vector<std::string> v = std::move(list);
+
+    // Microsoft compiler is not able to handle correctly the move semantics
+    // so the initial list is not moved, so do not check its size...
+    if (v.size() != 3) {
+      result = false;
+    }
+  }
+  {
+    cmList list("a;b;c");
+    std::vector<std::string> v;
+
+    // compiler is not able to select the cmList conversion operator
+    // and the std::vector assignment operator using the move semantics
+    // v = std::move(list);
+    v = std::move(list.data());
+
+    if (!list.empty() || v.size() != 3) {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testAccess()
+{
+  std::cout << "testAccess()";
+
+  bool result = true;
+
+  {
+    cmList list{ "a", "b", "c" };
+    if (list.at(1) != "b") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "a", "b", "c" };
+    if (list.at(-3) != "a") {
+      result = false;
+    }
+  }
+  {
+    try {
+      cmList list{ "a", "b", "c" };
+      if (list.at(4) != "a") {
+        result = false;
+      }
+    } catch (std::out_of_range&) {
+    }
+  }
+  {
+    try {
+      cmList list{ "a", "b", "c" };
+      if (list.at(-4) != "a") {
+        result = false;
+      }
+    } catch (std::out_of_range&) {
+    }
+  }
+  {
+    cmList list{ "a", "b", "c", "d", "e" };
+    auto sublist = list.sublist(list.begin() + 1, list.begin() + 3);
+    if (sublist.size() != 2 || sublist != cmList{ "b", "c" }) {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "a", "b", "c", "d", "e" };
+    auto sublist = list.sublist(1, 2);
+    if (sublist.size() != 2 || sublist != cmList{ "b", "c" }) {
+      result = false;
+    }
+
+    sublist = list.sublist(1, cmList::npos);
+    if (sublist.size() != 4 || sublist != cmList{ "b", "c", "d", "e" }) {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "a", "b", "c", "d", "e", "f" };
+    auto sublist = list.get_items({ 1, 3, 5 });
+    if (sublist.size() != 3 || sublist != cmList{ "b", "d", "f" }) {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "a", "b", "c", "d", "e", "f" };
+    auto sublist = list.get_items({ 1, -3, 5, -3 });
+    if (sublist.size() != 4 || sublist != cmList{ "b", "d", "f", "d" }) {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "a", "b", "c", "d", "e", "f" };
+    try {
+      if (list.get_items({ 1, -3, 5, -3, 10 }).size() != 5) {
+        result = false;
+      }
+    } catch (std::out_of_range&) {
+    }
+  }
+  {
+    cmList list{ "a", "b", "c", "d", "e", "f" };
+
+    if (list.find("b") != 1) {
+      result = false;
+    }
+    if (list.find("x") != cmList::npos) {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testModifiers()
+{
+  std::cout << "testModifiers()";
+
+  bool result = true;
+
+  {
+    cmList list{ "1;2;3;4;5" };
+
+    auto it = list.insert(list.begin() + 2, "6;7;8");
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+
+    auto it =
+      list.insert(list.begin() + 2, "6;7;8", cmList::ExpandElements::No);
+    if (list.size() != 6 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6;7;8") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    cmList v{ "6", "7", "8" };
+
+    auto it = list.insert(list.begin() + 2, v);
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    cmList v{ "6", "7", "8" };
+
+    auto it = list.insert(list.begin() + 2, std::move(v));
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+
+    if (!v.empty()) {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    std::vector<std::string> v{ "6", "7", "8" };
+
+    auto it = list.insert(list.begin() + 2, v);
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    std::vector<std::string> v{ "6;7", "8" };
+
+    auto it = list.insert(list.begin() + 2, v);
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    std::vector<std::string> v{ "6;7", "8" };
+
+    auto it = list.insert(list.begin() + 2, v, cmList::ExpandElements::No);
+    if (list.size() != 7 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6;7") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    std::vector<std::string> v{ "6;;7", "8" };
+
+    auto it = list.insert(list.begin() + 2, v);
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    std::vector<std::string> v{ "6;;7", "8" };
+
+    auto it = list.insert(list.begin() + 2, v, cmList::EmptyElements::Yes);
+    if (list.size() != 9 || list.to_string() != "1;2;6;;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "1;2;3;4;5" };
+    std::vector<std::string> v{ "6", "7", "8" };
+
+    auto it = list.insert(list.begin() + 2, std::move(v));
+    if (list.size() != 8 || list.to_string() != "1;2;6;7;8;3;4;5") {
+      result = false;
+    }
+    if (*it != "6") {
+      result = false;
+    }
+
+    if (!v.empty()) {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testRemoveItems()
+{
+  std::cout << "testRemoveItems()";
+
+  bool result = true;
+
+  {
+    cmList list("a;b;c;d;e;f;g;h");
+
+    list.remove_items({ 1, 3, 5 });
+
+    if (list.size() != 5 || list.to_string() != "a;c;e;g;h") {
+      result = false;
+    }
+  }
+  {
+    cmList list("a;b;c;b;a;d;e;f");
+
+    list.remove_items({ "a", "b", "h" });
+
+    if (list.size() != 4 || list.to_string() != "c;d;e;f") {
+      result = false;
+    }
+  }
+  {
+    cmList list("a;b;c;d;e;f;g;h");
+    std::vector<cmList::index_type> remove{ 1, 3, 5 };
+
+    list.remove_items(remove.begin(), remove.end());
+
+    if (list.size() != 5 || list.to_string() != "a;c;e;g;h") {
+      result = false;
+    }
+  }
+  {
+    cmList list("a;b;c;b;a;d;e;f");
+    std::vector<std::string> remove{ "b", "a", "h" };
+
+    list.remove_items(remove.begin(), remove.end());
+
+    if (list.size() != 4 || list.to_string() != "c;d;e;f") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testRemoveDuplicates()
+{
+  std::cout << "testRemoveDuplicates()";
+
+  bool result = true;
+
+  {
+    cmList list("b;c;b;a;a;c;b;a;c;b");
+
+    list.remove_duplicates();
+
+    if (list.size() != 3 || list.to_string() != "b;c;a") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testFilter()
+{
+  std::cout << "testFilter()";
+
+  bool result = true;
+
+  {
+    cmList list{ "AA", "Aa", "aA" };
+
+    list.filter("^A", cmList::FilterMode::INCLUDE);
+    if (list.size() != 2 || list.to_string() != "AA;Aa") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "AA", "Aa", "aA" };
+
+    list.filter("^A", cmList::FilterMode::EXCLUDE);
+    if (list.size() != 1 || list.to_string() != "aA") {
+      result = false;
+    }
+  }
+  {
+    cmList list{ "AA", "Aa", "aA" };
+
+    try {
+      list.filter("^(A", cmList::FilterMode::EXCLUDE);
+      if (list.size() != 1) {
+        result = false;
+      }
+    } catch (const std::invalid_argument&) {
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testReverse()
+{
+  std::cout << "testReverse()";
+
+  bool result = true;
+
+  {
+    cmList list{ "a", "b", "c" };
+    if (list.reverse().to_string() != "c;b;a") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testSort()
+{
+  std::cout << "testSort()";
+
+  bool result = true;
+
+  using SortConfiguration = cmList::SortConfiguration;
+
+  {
+    cmList list{ "A", "D", "C", "B", "A" };
+
+    list.sort();
+    if (list.to_string() != "A;A;B;C;D") {
+      result = false;
+    }
+
+    list.sort({ SortConfiguration::OrderMode::DESCENDING,
+                SortConfiguration::CompareMethod::DEFAULT,
+                SortConfiguration::CaseSensitivity::DEFAULT });
+    if (list.to_string() != "D;C;B;A;A") {
+      result = false;
+    }
+  }
+  {
+    SortConfiguration sortCfg;
+    cmList list{ "1.0", "1.1", "2.5", "10.2" };
+
+    list.sort(sortCfg);
+    if (list.to_string() != "1.0;1.1;10.2;2.5") {
+      result = false;
+    }
+
+    sortCfg.Compare = SortConfiguration::CompareMethod::NATURAL;
+    list.sort(sortCfg);
+    if (list.to_string() != "1.0;1.1;2.5;10.2") {
+      result = false;
+    }
+
+    sortCfg.Order = SortConfiguration::OrderMode::DESCENDING;
+    list.sort(sortCfg);
+    if (list.to_string() != "10.2;2.5;1.1;1.0") {
+      result = false;
+    }
+  }
+  {
+    SortConfiguration sortCfg;
+    cmList list{ "/zz/bb.cc", "/xx/yy/dd.cc", "/aa/cc.aa" };
+
+    list.sort(sortCfg);
+    if (list.to_string() != "/aa/cc.aa;/xx/yy/dd.cc;/zz/bb.cc") {
+      result = false;
+    }
+
+    sortCfg.Compare = SortConfiguration::CompareMethod::FILE_BASENAME;
+    if (list.sort(sortCfg).to_string() != "/zz/bb.cc;/aa/cc.aa;/xx/yy/dd.cc") {
+      result = false;
+    }
+  }
+  {
+    SortConfiguration sortCfg;
+    cmList list{ "c/B", "a/c", "B/a" };
+
+    if (list.sort().to_string() != "B/a;a/c;c/B") {
+      result = false;
+    }
+
+    sortCfg.Case = SortConfiguration::CaseSensitivity::INSENSITIVE;
+    if (list.sort(sortCfg).to_string() != "a/c;B/a;c/B") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testTransform()
+{
+  std::cout << "testTransform()";
+
+  bool result = true;
+
+  using AT = cmList::TransformSelector::AT;
+  using FOR = cmList::TransformSelector::FOR;
+  using REGEX = cmList::TransformSelector::REGEX;
+
+  {
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::APPEND, "-X");
+    if (list.to_string() != "AA-X;BB-X;CC-X;DD-X;EE-X") {
+      result = false;
+    }
+  }
+  {
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::PREPEND, "X-");
+    if (list.to_string() != "X-AA;X-BB;X-CC;X-DD;X-EE") {
+      result = false;
+    }
+  }
+  {
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::TOLOWER);
+    if (list.to_string() != "aa;bb;cc;dd;ee") {
+      result = false;
+    }
+  }
+  {
+    cmList list({ "aa", "bb", "cc", "dd", "ee" });
+
+    list.transform(cmList::TransformAction::TOUPPER);
+    if (list.to_string() != "AA;BB;CC;DD;EE") {
+      result = false;
+    }
+  }
+  {
+    cmList list({ "  AA", "BB  ", "  CC  ", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::STRIP);
+    if (list.to_string() != "AA;BB;CC;DD;EE") {
+      result = false;
+    }
+  }
+  {
+    cmList list({ "$<CONFIG>AA", "BB$<OR>", "C$<AND>C", "$<OR>DD$<AND>",
+                  "$<>E$<>E$<>" });
+
+    list.transform(cmList::TransformAction::GENEX_STRIP);
+    if (list.to_string() != "AA;BB;CC;DD;EE") {
+      result = false;
+    }
+  }
+  {
+    cmList list({ "ABC", "BBCB", "BCCCBC", "BCBCDD", "EBCBCEBC" });
+
+    list.transform(cmList::TransformAction::REPLACE, "^BC|BC$", "X");
+    if (list.to_string() != "AX;BBCB;XCCX;XXDD;EBCBCEX") {
+      result = false;
+    }
+  }
+  {
+    auto atSelector = cmList::TransformSelector::New<AT>({ 1, 2, 4 });
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::TOLOWER, std::move(atSelector));
+    if (list.to_string() != "AA;bb;cc;DD;ee") {
+      result = false;
+    }
+  }
+  {
+    auto atSelector = cmList::TransformSelector::New<AT>({ 1, 2, -1 });
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::TOLOWER, std::move(atSelector));
+    if (list.to_string() != "AA;bb;cc;DD;ee") {
+      result = false;
+    }
+  }
+  {
+    auto forSelector = cmList::TransformSelector::New<FOR>({ 1, 3 });
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::TOLOWER, std::move(forSelector));
+    if (list.to_string() != "AA;bb;cc;dd;EE") {
+      result = false;
+    }
+  }
+  {
+    auto forSelector = cmList::TransformSelector::New<FOR>({ 0, 4, 2 });
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::TOLOWER, std::move(forSelector));
+    if (list.to_string() != "aa;BB;cc;DD;ee") {
+      result = false;
+    }
+  }
+  {
+    auto regexSelector = cmList::TransformSelector::New<REGEX>("^(A|D|E)");
+    cmList list({ "AA", "BB", "CC", "DD", "EE" });
+
+    list.transform(cmList::TransformAction::TOLOWER, std::move(regexSelector));
+    if (list.to_string() != "aa;BB;CC;dd;ee") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+
+bool testStaticModifiers()
+{
+  std::cout << "testStaticModifiers()";
+
+  bool result = true;
+
+  {
+    std::vector<std::string> v{ "a", "b", "c" };
+    cmList::assign("d;e", v);
+
+    if (v.size() != 2 || v[0] != "d" || v[1] != "e") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> v{ "a", "b", "c" };
+    cmList::append("d;;e", v);
+
+    if (v.size() != 5 || v[3] != "d" || v[4] != "e") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> v{ "a", "b", "c" };
+    cmList::append("d;;e", v, cmList::EmptyElements::Yes);
+
+    if (v.size() != 6 || v[3] != "d" || !v[4].empty() || v[5] != "e") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> v{ "a", "b", "c" };
+    cmList::prepend("d;e", v);
+
+    if (v.size() != 5 || v[0] != "d" || v[1] != "e") {
+      result = false;
+    }
+  }
+  {
+    std::vector<std::string> v{ "a", "b", "c" };
+    cmList::prepend("d;;e", v, cmList::EmptyElements::Yes);
+
+    if (v.size() != 6 || v[0] != "d" || !v[1].empty() || v[2] != "e") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    cmList::append("d;e", list);
+
+    if (list != "a;b;c;d;e") {
+      result = false;
+    }
+  }
+  {
+    std::string list;
+    cmList::append("d;e", list);
+
+    if (list != "d;e") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    cmList::append("", list);
+
+    if (list != "a;b;c;") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    std::vector<std::string> v{ "d", "e" };
+    cmList::append(v.begin(), v.end(), list);
+
+    if (list != "a;b;c;d;e") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    std::vector<std::string> v;
+    cmList::append(v.begin(), v.end(), list);
+
+    if (list != "a;b;c") {
+      result = false;
+    }
+  }
+  {
+    std::string list;
+    std::vector<std::string> v{ "d", "e" };
+    cmList::append(v.begin(), v.end(), list);
+
+    if (list != "d;e") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    cmList::prepend("d;e", list);
+
+    if (list != "d;e;a;b;c") {
+      result = false;
+    }
+  }
+  {
+    std::string list;
+    cmList::prepend("d;e", list);
+
+    if (list != "d;e") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    cmList::prepend("", list);
+
+    if (list != ";a;b;c") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    std::vector<std::string> v{ "d", "e" };
+    cmList::prepend(v.begin(), v.end(), list);
+
+    if (list != "d;e;a;b;c") {
+      result = false;
+    }
+  }
+  {
+    std::string list{ "a;b;c" };
+    std::vector<std::string> v;
+    cmList::prepend(v.begin(), v.end(), list);
+
+    if (list != "a;b;c") {
+      result = false;
+    }
+  }
+  {
+    std::string list;
+    std::vector<std::string> v{ "d", "e" };
+    cmList::prepend(v.begin(), v.end(), list);
+
+    if (list != "d;e") {
+      result = false;
+    }
+  }
+
+  checkResult(result);
+
+  return result;
+}
+}
+
+int testList(int /*unused*/, char* /*unused*/[])
+{
+  int result = 0;
+
+  if (!testConstructors()) {
+    result = 1;
+  }
+  if (!testAssign()) {
+    result = 1;
+  }
+  if (!testConversions()) {
+    result = 1;
+  }
+  if (!testAccess()) {
+    result = 1;
+  }
+  if (!testModifiers()) {
+    result = 1;
+  }
+  if (!testRemoveItems()) {
+    result = 1;
+  }
+  if (!testRemoveDuplicates()) {
+    result = 1;
+  }
+  if (!testFilter()) {
+    result = 1;
+  }
+  if (!testReverse()) {
+    result = 1;
+  }
+  if (!testSort()) {
+    result = 1;
+  }
+  if (!testTransform()) {
+    result = 1;
+  }
+  if (!testStaticModifiers()) {
+    result = 1;
+  }
+
+  return result;
+}

+ 1 - 0
bootstrap

@@ -307,6 +307,7 @@ CMAKE_CXX_SOURCES="\
   cmBuildCommand \
   cmCMakeLanguageCommand \
   cmCMakeMinimumRequired \
+  cmList \
   cmCMakePath \
   cmCMakePathCommand \
   cmCMakePolicyCommand \