cmStringAlgorithms: Add cmExpandList functions
diff --git a/Source/cmStringAlgorithms.cxx b/Source/cmStringAlgorithms.cxx
index eca761b..c77fde3 100644
--- a/Source/cmStringAlgorithms.cxx
+++ b/Source/cmStringAlgorithms.cxx
@@ -72,6 +72,77 @@
   return tokens;
 }
 
+void cmExpandList(cm::string_view arg, std::vector<std::string>& argsOut,
+                  bool emptyArgs)
+{
+  // If argument is empty, it is an empty list.
+  if (!emptyArgs && arg.empty()) {
+    return;
+  }
+
+  // if there are no ; in the name then just copy the current string
+  if (arg.find(';') == cm::string_view::npos) {
+    argsOut.emplace_back(arg);
+    return;
+  }
+
+  std::string newArg;
+  // Break the string at non-escaped semicolons not nested in [].
+  int squareNesting = 0;
+  cm::string_view::iterator last = arg.begin();
+  cm::string_view::iterator const cend = arg.end();
+  for (cm::string_view::iterator c = last; c != cend; ++c) {
+    switch (*c) {
+      case '\\': {
+        // We only want to allow escaping of semicolons.  Other
+        // escapes should not be processed here.
+        cm::string_view::iterator cnext = c + 1;
+        if ((cnext != cend) && *cnext == ';') {
+          newArg.append(last, c);
+          // Skip over the escape character
+          last = cnext;
+          c = cnext;
+        }
+      } break;
+      case '[': {
+        ++squareNesting;
+      } break;
+      case ']': {
+        --squareNesting;
+      } break;
+      case ';': {
+        // Break the string here if we are not nested inside square
+        // brackets.
+        if (squareNesting == 0) {
+          newArg.append(last, c);
+          // Skip over the semicolon
+          last = c + 1;
+          if (!newArg.empty() || emptyArgs) {
+            // Add the last argument if the string is not empty.
+            argsOut.push_back(newArg);
+            newArg.clear();
+          }
+        }
+      } break;
+      default: {
+        // Just append this character.
+      } break;
+    }
+  }
+  newArg.append(last, cend);
+  if (!newArg.empty() || emptyArgs) {
+    // Add the last argument if the string is not empty.
+    argsOut.push_back(std::move(newArg));
+  }
+}
+
+std::vector<std::string> cmExpandedList(cm::string_view arg, bool emptyArgs)
+{
+  std::vector<std::string> argsOut;
+  cmExpandList(arg, argsOut, emptyArgs);
+  return argsOut;
+}
+
 namespace {
 template <std::size_t N, typename T>
 inline void MakeDigits(cm::string_view& view, char (&digits)[N],
diff --git a/Source/cmStringAlgorithms.h b/Source/cmStringAlgorithms.h
index 44b01b8..52d187c 100644
--- a/Source/cmStringAlgorithms.h
+++ b/Source/cmStringAlgorithms.h
@@ -68,6 +68,48 @@
 /** Extract tokens that are separated by any of the characters in @a sep.  */
 std::vector<std::string> cmTokenize(cm::string_view str, cm::string_view sep);
 
+/**
+ * Expand the ; separated string @a arg into multiple arguments.
+ * All found arguments are appended to @a argsOut.
+ */
+void cmExpandList(cm::string_view arg, std::vector<std::string>& argsOut,
+                  bool emptyArgs = false);
+
+/**
+ * Expand out any arguments in the string range [@a first, @a last) that have
+ * ; separated strings into multiple arguments.  All found arguments are
+ * appended to @a argsOut.
+ */
+template <class InputIt>
+void cmExpandLists(InputIt first, InputIt last,
+                   std::vector<std::string>& argsOut)
+{
+  for (; first != last; ++first) {
+    ExpandList(*first, argsOut);
+  }
+}
+
+/**
+ * Same as cmExpandList but a new vector is created containing
+ * the expanded arguments from the string @a arg.
+ */
+std::vector<std::string> cmExpandedList(cm::string_view arg,
+                                        bool emptyArgs = false);
+
+/**
+ * Same as cmExpandList but a new vector is created containing the expanded
+ * versions of all arguments in the string range [@a first, @a last).
+ */
+template <class InputIt>
+std::vector<std::string> cmExpandedLists(InputIt first, InputIt last)
+{
+  std::vector<std::string> argsOut;
+  for (; first != last; ++first) {
+    cmExpandList(*first, argsOut);
+  }
+  return argsOut;
+}
+
 /** Concatenate string pieces into a single string.  */
 std::string cmCatViews(std::initializer_list<cm::string_view> views);