Browse Source

Merge topic 'GenEx-STRING'

fd7e305097 GenEx: Add $<STRING> generator expression
7564cbae12 GenEx: add new expressions for string comparisons
dab5e6ebb1 introduce cm::CMakeString class as helper for string() command
34c0c7754f cmString: add methods append and insert
ab60f1a010 Extend usage for cm::string_view for more flexibility

Acked-by: Kitware Robot <[email protected]>
Merge-request: !11382
Brad King 2 weeks ago
parent
commit
6087f56da9
100 changed files with 3122 additions and 457 deletions
  1. 395 18
      Help/manual/cmake-generator-expressions.7.rst
  2. 8 0
      Help/release/dev/GenEx-STRING.rst
  3. 5 0
      Help/release/dev/GenEx-string-comparisons.rst
  4. 2 0
      Source/CMakeLists.txt
  5. 326 0
      Source/cmCMakeString.cxx
  6. 259 0
      Source/cmCMakeString.hxx
  7. 10 9
      Source/cmGeneratorExpression.cxx
  8. 1 1
      Source/cmGeneratorExpression.h
  9. 679 48
      Source/cmGeneratorExpressionNode.cxx
  10. 24 0
      Source/cmString.hxx
  11. 183 359
      Source/cmStringCommand.cxx
  12. 2 3
      Source/cmStringReplaceHelper.cxx
  13. 3 1
      Source/cmStringReplaceHelper.h
  14. 6 5
      Source/cmTimestamp.cxx
  15. 4 2
      Source/cmTimestamp.h
  16. 6 6
      Source/cmUuid.cxx
  17. 7 5
      Source/cmUuid.h
  18. 1 0
      Tests/RunCMake/CMakeLists.txt
  19. 22 0
      Tests/RunCMake/GenEx-STRING/APPEND.cmake.in
  20. 1 0
      Tests/RunCMake/GenEx-STRING/ASCII-WrongArguments-result.txt
  21. 38 0
      Tests/RunCMake/GenEx-STRING/ASCII-WrongArguments-stderr.txt
  22. 7 0
      Tests/RunCMake/GenEx-STRING/ASCII-WrongArguments.cmake
  23. 20 0
      Tests/RunCMake/GenEx-STRING/ASCII.cmake.in
  24. 5 0
      Tests/RunCMake/GenEx-STRING/CMakeLists.txt
  25. 1 0
      Tests/RunCMake/GenEx-STRING/FIND-WrongArguments-result.txt
  26. 18 0
      Tests/RunCMake/GenEx-STRING/FIND-WrongArguments-stderr.txt
  27. 5 0
      Tests/RunCMake/GenEx-STRING/FIND-WrongArguments.cmake
  28. 48 0
      Tests/RunCMake/GenEx-STRING/FIND.cmake.in
  29. 1 0
      Tests/RunCMake/GenEx-STRING/HASH-WrongArguments-result.txt
  30. 18 0
      Tests/RunCMake/GenEx-STRING/HASH-WrongArguments-stderr.txt
  31. 5 0
      Tests/RunCMake/GenEx-STRING/HASH-WrongArguments.cmake
  32. 69 0
      Tests/RunCMake/GenEx-STRING/HASH.cmake.in
  33. 20 0
      Tests/RunCMake/GenEx-STRING/HEX.cmake.in
  34. 32 0
      Tests/RunCMake/GenEx-STRING/JOIN.cmake.in
  35. 21 0
      Tests/RunCMake/GenEx-STRING/LENGTH.cmake.in
  36. 1 0
      Tests/RunCMake/GenEx-STRING/MATCH-WrongArguments-result.txt
  37. 28 0
      Tests/RunCMake/GenEx-STRING/MATCH-WrongArguments-stderr.txt
  38. 8 0
      Tests/RunCMake/GenEx-STRING/MATCH-WrongArguments.cmake
  39. 40 0
      Tests/RunCMake/GenEx-STRING/MATCH.cmake.in
  40. 22 0
      Tests/RunCMake/GenEx-STRING/PREPEND.cmake.in
  41. 1 0
      Tests/RunCMake/GenEx-STRING/QUOTE-WrongArguments-result.txt
  42. 8 0
      Tests/RunCMake/GenEx-STRING/QUOTE-WrongArguments-stderr.txt
  43. 6 0
      Tests/RunCMake/GenEx-STRING/QUOTE-WrongArguments.cmake
  44. 21 0
      Tests/RunCMake/GenEx-STRING/QUOTE.cmake.in
  45. 1 0
      Tests/RunCMake/GenEx-STRING/RANDOM-WrongArguments-result.txt
  46. 18 0
      Tests/RunCMake/GenEx-STRING/RANDOM-WrongArguments-stderr.txt
  47. 5 0
      Tests/RunCMake/GenEx-STRING/RANDOM-WrongArguments.cmake
  48. 32 0
      Tests/RunCMake/GenEx-STRING/RANDOM.cmake.in
  49. 1 0
      Tests/RunCMake/GenEx-STRING/REPLACE-WrongArguments-result.txt
  50. 18 0
      Tests/RunCMake/GenEx-STRING/REPLACE-WrongArguments-stderr.txt
  51. 7 0
      Tests/RunCMake/GenEx-STRING/REPLACE-WrongArguments.cmake
  52. 44 0
      Tests/RunCMake/GenEx-STRING/REPLACE.cmake.in
  53. 80 0
      Tests/RunCMake/GenEx-STRING/RunCMakeTest.cmake
  54. 1 0
      Tests/RunCMake/GenEx-STRING/STRIP-WrongArguments-result.txt
  55. 8 0
      Tests/RunCMake/GenEx-STRING/STRIP-WrongArguments-stderr.txt
  56. 6 0
      Tests/RunCMake/GenEx-STRING/STRIP-WrongArguments.cmake
  57. 14 0
      Tests/RunCMake/GenEx-STRING/STRIP.cmake.in
  58. 1 0
      Tests/RunCMake/GenEx-STRING/SUBSTRING-WrongArguments-result.txt
  59. 38 0
      Tests/RunCMake/GenEx-STRING/SUBSTRING-WrongArguments-stderr.txt
  60. 7 0
      Tests/RunCMake/GenEx-STRING/SUBSTRING-WrongArguments.cmake
  61. 26 0
      Tests/RunCMake/GenEx-STRING/SUBSTRING.cmake.in
  62. 1 0
      Tests/RunCMake/GenEx-STRING/TIMESTAMP-WrongArguments-result.txt
  63. 8 0
      Tests/RunCMake/GenEx-STRING/TIMESTAMP-WrongArguments-stderr.txt
  64. 4 0
      Tests/RunCMake/GenEx-STRING/TIMESTAMP-WrongArguments.cmake
  65. 27 0
      Tests/RunCMake/GenEx-STRING/TIMESTAMP.cmake.in
  66. 26 0
      Tests/RunCMake/GenEx-STRING/TOLOWER.cmake.in
  67. 26 0
      Tests/RunCMake/GenEx-STRING/TOUPPER.cmake.in
  68. 1 0
      Tests/RunCMake/GenEx-STRING/UUID-WrongArguments-result.txt
  69. 28 0
      Tests/RunCMake/GenEx-STRING/UUID-WrongArguments-stderr.txt
  70. 6 0
      Tests/RunCMake/GenEx-STRING/UUID-WrongArguments.cmake
  71. 32 0
      Tests/RunCMake/GenEx-STRING/UUID.cmake.in
  72. 1 0
      Tests/RunCMake/GenEx-STRING/bad-option-result.txt
  73. 8 0
      Tests/RunCMake/GenEx-STRING/bad-option-stderr.txt
  74. 2 0
      Tests/RunCMake/GenEx-STRING/bad-option.cmake
  75. 13 0
      Tests/RunCMake/GenEx-STRING/check_errors.cmake
  76. 6 0
      Tests/RunCMake/GenEx-STRING/generate.cmake
  77. 1 0
      Tests/RunCMake/GenEx-STRING/no-arguments-result.txt
  78. 8 0
      Tests/RunCMake/GenEx-STRING/no-arguments-stderr.txt
  79. 2 0
      Tests/RunCMake/GenEx-STRING/no-arguments.cmake
  80. 1 0
      Tests/RunCMake/GenEx-STRING/unexpected-arg-result.txt
  81. 8 0
      Tests/RunCMake/GenEx-STRING/unexpected-arg-stderr.txt
  82. 2 0
      Tests/RunCMake/GenEx-STRING/unexpected-arg.cmake
  83. 1 0
      Tests/RunCMake/GenEx-STRING/unexpected-arg2-result.txt
  84. 8 0
      Tests/RunCMake/GenEx-STRING/unexpected-arg2-stderr.txt
  85. 2 0
      Tests/RunCMake/GenEx-STRING/unexpected-arg2.cmake
  86. 1 0
      Tests/RunCMake/GeneratorExpression/BadStrGreater-result.txt
  87. 40 0
      Tests/RunCMake/GeneratorExpression/BadStrGreater-stderr.txt
  88. 6 0
      Tests/RunCMake/GeneratorExpression/BadStrGreater.cmake
  89. 1 0
      Tests/RunCMake/GeneratorExpression/BadStrGreaterEqual-result.txt
  90. 40 0
      Tests/RunCMake/GeneratorExpression/BadStrGreaterEqual-stderr.txt
  91. 6 0
      Tests/RunCMake/GeneratorExpression/BadStrGreaterEqual.cmake
  92. 1 0
      Tests/RunCMake/GeneratorExpression/BadStrLess-result.txt
  93. 40 0
      Tests/RunCMake/GeneratorExpression/BadStrLess-stderr.txt
  94. 6 0
      Tests/RunCMake/GeneratorExpression/BadStrLess.cmake
  95. 1 0
      Tests/RunCMake/GeneratorExpression/BadStrLessEqual-result.txt
  96. 40 0
      Tests/RunCMake/GeneratorExpression/BadStrLessEqual-stderr.txt
  97. 6 0
      Tests/RunCMake/GeneratorExpression/BadStrLessEqual.cmake
  98. 9 0
      Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake
  99. 6 0
      Tests/RunCMake/GeneratorExpression/STREQUAL-check.cmake
  100. 3 0
      Tests/RunCMake/GeneratorExpression/STREQUAL.cmake

+ 395 - 18
Help/manual/cmake-generator-expressions.7.rst

@@ -284,20 +284,8 @@ This section covers the primary and most widely used comparison types.
 Other more specific comparison types are documented in their own separate
 sections further below.
 
-String Comparisons
-^^^^^^^^^^^^^^^^^^
-
-.. genex:: $<STREQUAL:string1,string2>
-
-  ``1`` if ``string1`` and ``string2`` are equal, else ``0``.
-  The comparison is case-sensitive.  For a case-insensitive comparison,
-  combine with a :ref:`string transforming generator expression
-  <String Transforming Generator Expressions>`.  For example, the following
-  evaluates to ``1`` if ``${foo}`` is any of ``BAR``, ``Bar``, ``bar``, etc.
-
-  .. code-block:: cmake
-
-    $<STREQUAL:$<UPPER_CASE:${foo}>,BAR>
+Numeric Comparisons
+^^^^^^^^^^^^^^^^^^^
 
 .. genex:: $<EQUAL:value1,value2>
 
@@ -330,10 +318,399 @@ Version Comparisons
 
   ``1`` if ``v1`` is a version greater than or equal to ``v2``, else ``0``.
 
+String Expressions
+------------------
+
+Most of the expressions in this section are closely associated with the
+:command:`string` command, providing the same capabilities, but in
+the form of a generator expression.
+
+In each of the following string-related generator expressions, the ``string``
+must not contain any commas if that generator expression expects something to
+be provided after the ``string``.  For example, the expression
+``$<STRING:FIND,string,value>`` requires a ``value`` after the ``string``.
+Since a comma is used to separate the ``string`` and the ``value``, the
+``string`` cannot itself contain a comma.  This restriction does not apply to
+the :command:`string` command, it is specific to the string-handling generator
+expressions only. The :genex:`$<COMMA>` generator expression can be used to
+specify a comma as part of the arguments of the string-related generator
+expressions.
+
+.. _`String Comparisons Generator Expressions`:
+
+String Comparisons
+^^^^^^^^^^^^^^^^^^
+
+The comparisons are case-sensitive.  For a case-insensitive comparison,
+combine with a :ref:`string transforming generator expression
+<String Transforming Generator Expressions>`.  For example, the following
+evaluates to ``1`` if ``${foo}`` is any of ``BAR``, ``Bar``, ``bar``, etc.
+
+  .. code-block:: cmake
+
+    $<STREQUAL:$<STRING:TOUPPER,${foo}>,BAR>
+
+.. genex:: $<STREQUAL:string1,string2>
+
+  ``1`` if ``string1`` and ``string2`` are lexicographically equal, else ``0``.
+
+.. genex:: $<STRLESS:string1,string2>
+
+  .. versionadded:: 4.3
+
+  ``1`` if ``string1`` is lexicographically less than ``string2``, else ``0``.
+
+.. genex:: $<STRGREATER:string1,string2>
+
+  .. versionadded:: 4.3
+
+  ``1`` if ``string1`` is lexicographically greater than ``string2``, else
+  ``0``.
+
+.. genex:: $<STRLESS_EQUAL:string1,string2>
+
+  .. versionadded:: 4.3
+
+  ``1`` if ``string1`` is lexicographically less than or equal to ``string2``,
+  else ``0``.
+
+.. genex:: $<STRGREATER_EQUAL:string1,string2>
+
+  .. versionadded:: 4.3
+
+  ``1`` if ``string1`` is lexicographically greater than or equal to
+  ``string2``, else ``0``.
+
+.. _`String Queries Generator Expressions`:
+
+String Queries
+^^^^^^^^^^^^^^
+
+.. genex:: $<STRING:LENGTH,string>
+
+  .. versionadded:: 4.3
+
+  The given string's length in bytes. Note that this means, if ``string``
+  contains multi-byte characters, the result will *not* be the number of
+  characters.
+
+.. genex:: $<STRING:SUBSTRING,string,begin,length>
+
+  .. versionadded:: 4.3
+
+  The substring of the given ``string``. If ``length`` is ``-1`` or greater
+  than the ``string`` length the remainder of the string starting at ``begin``
+  will be returned.
+
+  Both ``begin`` and ``length`` are counted in bytes, so care must
+  be exercised if ``string`` could contain multi-byte characters.
+
+.. genex:: $<STRING:FIND,string[,FROM:(BEGIN|END)],substring>
+
+  The position where the given ``substring`` was found in the supplied
+  ``string``. If the ``substring`` is not found, a position of -1 is returned.
+
+  The ``FROM:`` option defines how the search will be done:
+
+  ``BEGIN``
+    The search will start at the beginning of the ``string``. This the default.
+
+  ``END``
+    The search will start from the end of the ``string``.
+
+  The ``$<STRING:FIND>`` generator expression treats all strings as ASCII-only
+  characters. The index returned will also be counted in bytes, so strings
+  containing multi-byte characters may lead to unexpected results.
+
+.. genex:: $<STRING:MATCH,string[,SEEK:(ONCE|ALL)],regular_expression>
+
+  .. versionadded:: 4.3
+
+  Match, in the ``string``, the ``regular_expression``.
+
+  The ``SEEK:`` option specifies the match behavior:
+
+  ``ONCE``
+    Match only the first occurrence. This is the default.
+
+  ``ALL``
+    Match as many times as possible and return the matches as a list.
+
+  See the :ref:`Regular expressions specification <Regex Specification>` for
+  the syntax of the ``regular_expression`` parameter.
+
+.. _`String Generating Generator Expressions`:
+
+String Generations
+^^^^^^^^^^^^^^^^^^
+
+.. genex:: $<STRING:JOIN,glue,input[,input]...>
+
+  .. versionadded:: 4.3
+
+  Join all the ``input`` arguments together using the ``glue`` string.
+
+.. genex:: $<STRING:ASCII,number[,number]...>
+
+  .. versionadded:: 4.3
+
+  Convert all numbers, in the range 1-255, into corresponding ASCII
+  characters. Any number outside this range will raise an error.
+
+.. genex:: $<STRING:TIMESTAMP[,(UTC|format)]...>
+
+  .. versionadded:: 4.3
+
+  Produce a string representation of the current date and/or time.
+
+  If the generator expression is unable to obtain a timestamp, the result will
+  be the empty string ``""``.
+
+  The optional ``UTC`` flag requests the current date/time representation to
+  be in Coordinated Universal Time (UTC) rather than local time.
+
+  If the ``SOURCE_DATE_EPOCH`` environment variable is set, its value will be
+  used instead of the current time.
+  See https://reproducible-builds.org/specs/source-date-epoch/ for details.
+
+  The optional ``<format>`` may contain the following format specifiers:
+
+  ``%%``
+    A literal percent sign (%).
+
+  ``%d``
+    The day of the current month (01-31).
+
+  ``%H``
+    The hour on a 24-hour clock (00-23).
+
+  ``%I``
+    The hour on a 12-hour clock (01-12).
+
+  ``%j``
+    The day of the current year (001-366).
+
+  ``%m``
+    The month of the current year (01-12).
+
+  ``%b``
+    Abbreviated month name (e.g. Oct).
+
+  ``%B``
+    Full month name (e.g. October).
+
+  ``%M``
+    The minute of the current hour (00-59).
+
+  ``%s``
+    Seconds since midnight (UTC) 1-Jan-1970 (UNIX time).
+
+  ``%S``
+    The second of the current minute.  60 represents a leap second. (00-60)
+
+  ``%f``
+    The microsecond of the current second (000000-999999).
+
+  ``%U``
+    The week number of the current year (00-53).
+
+  ``%V``
+    The ISO 8601 week number of the current year (01-53).
+
+  ``%w``
+    The day of the current week. 0 is Sunday. (0-6)
+
+  ``%a``
+    Abbreviated weekday name (e.g. Fri).
+
+  ``%A``
+    Full weekday name (e.g. Friday).
+
+  ``%y``
+    The last two digits of the current year (00-99).
+
+  ``%Y``
+    The current year.
+
+  ``%z``
+    The offset of the time zone from UTC, in hours and minutes,
+    with format ``+hhmm`` or ``-hhmm``.
+
+  ``%Z``
+    The time zone name.
+
+  Unknown format specifiers will be ignored and copied to the output
+  as-is.
+
+  If no explicit ``format`` is given, it will default to:
+
+  * ``%Y-%m-%dT%H:%M:%S`` for local time.
+  * ``%Y-%m-%dT%H:%M:%SZ`` for UTC.
+
+.. genex:: $<STRING:RANDOM[,(LENGTH:length|ALPHABET:alphabet|RANDOM_SEED:seed)]...>
+
+  .. versionadded:: 4.3
+
+  Produce a random string of ASCII characters. The possible options are:
+
+  ``LENGTH:length``
+    Define the length of the string. The default length is 5 characters.
+
+  ``ALPHABET:alphabet``
+    Define the characters used for the generation. The alphabet is always
+    interpreted as holding ASCII characters. The default alphabet is all
+    numbers and upper and lower case letters.
+
+  ``RANDOM_SEED:seed``
+    Specify an integer which will be used to seed the random number generator.
+
+.. genex:: $<STRING:UUID,NAMESPACE:namespace,TYPE:(MD5|SHA1)[,NAME:name][,CASE:(LOWER|UPPER)]>
+
+  .. versionadded:: 4.3
+
+  Create a universally unique identifier (aka GUID) as per RFC4122.
+  A UUID has the format ``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx``
+  where each ``x`` represents an hexadecimal character.
+
+  The UUID is based on the hash of the combined values of:
+
+  ``NAMESPACE:namespace``
+    ``namespace`` which has to be a valid UUID.
+
+  ``NAME:name``
+    ``name`` is an arbitrary string.
+
+  ``TYPE:``
+    The hash algorithm can be either:
+
+    ``MD5``
+      Version 3 UUID.
+
+    ``SHA1``
+      Version 5 UUID.
+
+  ``CASE:``
+    Specify the case of the hexadecimal characters.
+
+    ``LOWER``
+      Hexadecimal characters are all of lowercase. This is the default.
+
+    ``UPPER``
+      Hexadecimal characters are all of uppercase.
+
 .. _`String Transforming Generator Expressions`:
 
 String Transformations
-----------------------
+^^^^^^^^^^^^^^^^^^^^^^
+
+.. genex:: $<STRING:REPLACE[,(STRING|REGEX)],string,match_string,replace_string>
+
+  .. versionadded:: 4.3
+
+  Replace all occurrences of ``match_string`` in the ``string`` with
+  ``replace_string``.
+
+  The ``match_string`` can be of two different types:
+
+  ``STRING``
+    ``match_string`` is a literal string and match will be done by simple
+    string comparison. This is the default.
+
+  ``REGEX``
+    ``match_string`` is a regular expression. Match this regular_expression as
+    many times as possible and substitute the ``replace_string`` for the match
+    in the ``string``.
+
+    The ``replace_string`` may refer to parenthesis-delimited subexpressions of
+    the match using \\1, \\2, ..., \\9. Note that two backslashes (\\\\1) are
+    required in CMake code to get a backslash through argument parsing.
+
+.. genex:: $<STRING:APPEND,string,input[,input]...>
+
+  .. versionadded:: 4.3
+
+  Append all the ``input`` arguments to the ``string``.
+
+.. genex:: $<STRING:PREPEND,string,input[,input]...>
+
+  .. versionadded:: 4.3
+
+  Prepend all the ``input`` arguments to the ``string``.
+
+.. genex:: $<STRING:TOLOWER,string>
+
+  .. versionadded:: 4.3
+
+  Content of ``string`` converted to lower case.
+
+.. genex:: $<STRING:TOUPPER,string>
+
+  .. versionadded:: 4.3
+
+  Content of ``string`` converted to upper case.
+
+.. genex:: $<STRING:STRIP,SPACES,string>
+
+  .. versionadded:: 4.3
+
+  Remove the specified elements from the ``string``. The possible options are:
+
+  ``SPACES``
+    Remove the leading and trailing spaces of the ``string``.
+
+.. genex:: $<STRING:QUOTE,REGEX,string>
+
+  .. versionadded:: 4.3
+
+  Escape the specified elements of the ``string``. The possible options are:
+
+  ``REGEX``
+    Escape all characters that have special meaning in a regular expressions,
+    such that the ``string`` can be used as part of a regular expression to
+    match the input literally.
+
+.. genex:: $<STRING:HEX,string>
+
+  .. versionadded:: 4.3
+
+  Convert each byte in the ``string`` to its hexadecimal representation.
+  Letters in the result (a through f) are in lowercase.
+
+.. genex:: $<STRING:HASH,string,ALGORITHM:algorithm>
+
+  .. versionadded:: 4.3
+
+  Compute a cryptographic hash of the ``string``. The supported algorithm
+  names, as specified by the ``ALGORITHM:`` option are:
+
+  ``MD5``
+    Message-Digest Algorithm 5, RFC 1321.
+  ``SHA1``
+    US Secure Hash Algorithm 1, RFC 3174.
+  ``SHA224``
+    US Secure Hash Algorithms, RFC 4634.
+  ``SHA256``
+    US Secure Hash Algorithms, RFC 4634.
+  ``SHA384``
+    US Secure Hash Algorithms, RFC 4634.
+  ``SHA512``
+    US Secure Hash Algorithms, RFC 4634.
+  ``SHA3_224``
+    Keccak SHA-3.
+  ``SHA3_256``
+    Keccak SHA-3.
+  ``SHA3_384``
+    Keccak SHA-3.
+  ``SHA3_512``
+    Keccak SHA-3.
+
+.. genex:: $<STRING:MAKE_C_IDENTIFIER,string>
+
+  .. versionadded:: 4.3
+
+  Convert each non-alphanumeric character in the ``string`` to an underscore.
+  If the first character of the ``string`` is a digit, an underscore will also
+  be prepended.
 
 .. genex:: $<LOWER_CASE:string>
 
@@ -343,10 +720,10 @@ String Transformations
 
   Content of ``string`` converted to upper case.
 
-.. genex:: $<MAKE_C_IDENTIFIER:...>
+.. genex:: $<MAKE_C_IDENTIFIER:string>
 
-  Content of ``...`` converted to a C identifier.  The conversion follows the
-  same behavior as :command:`string(MAKE_C_IDENTIFIER)`.
+  Content of ``string`` converted to a C identifier.  The conversion follows
+  the same behavior as :command:`string(MAKE_C_IDENTIFIER)`.
 
 List Expressions
 ----------------

+ 8 - 0
Help/release/dev/GenEx-STRING.rst

@@ -0,0 +1,8 @@
+GenEx-STRING
+------------
+
+* :genex:`$<STRING:...>` generator expressions were added for
+  :ref:`query <String Queries Generator Expressions>`,
+  :ref:`generation <String Generating Generator Expressions>`, and
+  :ref:`transformation <String Transforming Generator Expressions>` operations
+  on strings.

+ 5 - 0
Help/release/dev/GenEx-string-comparisons.rst

@@ -0,0 +1,5 @@
+GenEx-string-comparisons
+------------------------
+
+* CMake gains new :ref:`generator expressions
+  <String Comparisons Generator Expressions>` for string comparisons.

+ 2 - 0
Source/CMakeLists.txt

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

+ 326 - 0
Source/cmCMakeString.cxx

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

+ 259 - 0
Source/cmCMakeString.hxx

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

+ 10 - 9
Source/cmGeneratorExpression.cxx

@@ -166,7 +166,7 @@ std::string cmGeneratorExpression::StripEmptyListElements(
 }
 
 static std::string extractAllGeneratorExpressions(
-  std::string const& input,
+  cm::string_view input,
   std::map<std::string, std::vector<std::string>>* collected)
 {
   std::string result;
@@ -176,9 +176,9 @@ static std::string extractAllGeneratorExpressions(
   std::stack<char const*> colons; // indices of ":"
   while ((pos = input.find("$<", lastPos)) != std::string::npos) {
     result += input.substr(lastPos, pos - lastPos);
-    starts.push(input.c_str() + pos);
+    starts.push(input.data() + pos);
     pos += 2;
-    char const* c = input.c_str() + pos;
+    char const* c = input.data() + pos;
     char const* const cStart = c;
     for (; *c; ++c) {
       if (cmGeneratorExpression::StartsWithGeneratorExpression(c)) {
@@ -209,7 +209,7 @@ static std::string extractAllGeneratorExpressions(
     }
     std::string::size_type const traversed = (c - cStart) + 1;
     if (!*c) {
-      result += "$<" + input.substr(pos, traversed);
+      result += cmStrCat("$<", input.substr(pos, traversed));
     }
     pos += traversed;
     lastPos = pos;
@@ -220,7 +220,7 @@ static std::string extractAllGeneratorExpressions(
   return cmGeneratorExpression::StripEmptyListElements(result);
 }
 
-static std::string stripAllGeneratorExpressions(std::string const& input)
+static std::string stripAllGeneratorExpressions(cm::string_view input)
 {
   return extractAllGeneratorExpressions(input, nullptr);
 }
@@ -243,7 +243,7 @@ static void prefixItems(std::string const& content, std::string& result,
 }
 
 static std::string stripExportInterface(
-  std::string const& input, cmGeneratorExpression::PreprocessContext context,
+  cm::string_view input, cmGeneratorExpression::PreprocessContext context,
   cm::string_view importPrefix)
 {
   std::string result;
@@ -282,7 +282,7 @@ static std::string stripExportInterface(
       assert(false && "Invalid position found");
     }
     nestingLevel = 1;
-    char const* c = input.c_str() + pos;
+    char const* c = input.data() + pos;
     char const* const cStart = c;
     for (; *c; ++c) {
       if (cmGeneratorExpression::StartsWithGeneratorExpression(c)) {
@@ -300,7 +300,8 @@ static std::string stripExportInterface(
           result += input.substr(pos, c - cStart);
         } else if (context == cmGeneratorExpression::InstallInterface &&
                    foundGenex == FoundGenex::InstallInterface) {
-          std::string const content = input.substr(pos, c - cStart);
+          std::string const content =
+            static_cast<std::string>(input.substr(pos, c - cStart));
           if (!importPrefix.empty()) {
             prefixItems(content, result, importPrefix);
           } else {
@@ -390,7 +391,7 @@ void cmGeneratorExpression::Split(std::string const& input,
   }
 }
 
-std::string cmGeneratorExpression::Preprocess(std::string const& input,
+std::string cmGeneratorExpression::Preprocess(cm::string_view input,
                                               PreprocessContext context,
                                               cm::string_view importPrefix)
 {

+ 1 - 1
Source/cmGeneratorExpression.h

@@ -65,7 +65,7 @@ public:
     InstallInterface
   };
 
-  static std::string Preprocess(std::string const& input,
+  static std::string Preprocess(cm::string_view input,
                                 PreprocessContext context,
                                 cm::string_view importPrefix = {});
 

+ 679 - 48
Source/cmGeneratorExpressionNode.cxx

@@ -7,6 +7,7 @@
 #include <cerrno>
 #include <cstdlib>
 #include <cstring>
+#include <exception>
 #include <functional>
 #include <map>
 #include <memory>
@@ -26,6 +27,7 @@
 #include "cmsys/String.h"
 
 #include "cmCMakePath.h"
+#include "cmCMakeString.hxx"
 #include "cmComputeLinkInformation.h"
 #include "cmGenExContext.h"
 #include "cmGenExEvaluation.h"
@@ -265,9 +267,84 @@ static const struct StrEqualNode : public cmGeneratorExpressionNode
     GeneratorExpressionContent const* /*content*/,
     cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
   {
-    return parameters.front() == parameters[1] ? "1" : "0";
+    return cm::CMakeString{ parameters.front() }.Compare(
+             cm::CMakeString::CompOperator::EQUAL, parameters[1])
+      ? "1"
+      : "0";
   }
 } strEqualNode;
+static const struct StrLessNode : public cmGeneratorExpressionNode
+{
+  StrLessNode() {} // NOLINT(modernize-use-equals-default)
+
+  int NumExpectedParameters() const override { return 2; }
+
+  std::string Evaluate(
+    std::vector<std::string> const& parameters,
+    cm::GenEx::Evaluation* /*eval*/,
+    GeneratorExpressionContent const* /*content*/,
+    cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
+  {
+    return cm::CMakeString{ parameters.front() }.Compare(
+             cm::CMakeString::CompOperator::LESS, parameters[1])
+      ? "1"
+      : "0";
+  }
+} strLessNode;
+static const struct StrLessEqualNode : public cmGeneratorExpressionNode
+{
+  StrLessEqualNode() {} // NOLINT(modernize-use-equals-default)
+
+  int NumExpectedParameters() const override { return 2; }
+
+  std::string Evaluate(
+    std::vector<std::string> const& parameters,
+    cm::GenEx::Evaluation* /*eval*/,
+    GeneratorExpressionContent const* /*content*/,
+    cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
+  {
+    return cm::CMakeString{ parameters.front() }.Compare(
+             cm::CMakeString::CompOperator::LESS_EQUAL, parameters[1])
+      ? "1"
+      : "0";
+  }
+} strLessEqualNode;
+static const struct StrGreaterNode : public cmGeneratorExpressionNode
+{
+  StrGreaterNode() {} // NOLINT(modernize-use-equals-default)
+
+  int NumExpectedParameters() const override { return 2; }
+
+  std::string Evaluate(
+    std::vector<std::string> const& parameters,
+    cm::GenEx::Evaluation* /*eval*/,
+    GeneratorExpressionContent const* /*content*/,
+    cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
+  {
+    return cm::CMakeString{ parameters.front() }.Compare(
+             cm::CMakeString::CompOperator::GREATER, parameters[1])
+      ? "1"
+      : "0";
+  }
+} strGreaterNode;
+static const struct StrGreaterEqualNode : public cmGeneratorExpressionNode
+{
+  StrGreaterEqualNode() {} // NOLINT(modernize-use-equals-default)
+
+  int NumExpectedParameters() const override { return 2; }
+
+  std::string Evaluate(
+    std::vector<std::string> const& parameters,
+    cm::GenEx::Evaluation* /*eval*/,
+    GeneratorExpressionContent const* /*content*/,
+    cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
+  {
+    return cm::CMakeString{ parameters.front() }.Compare(
+             cm::CMakeString::CompOperator::GREATER_EQUAL, parameters[1])
+      ? "1"
+      : "0";
+  }
+} strGreaterEqualNode;
 
 static const struct EqualNode : public cmGeneratorExpressionNode
 {
@@ -706,6 +783,55 @@ bool CheckGenExParameters(cm::GenEx::Evaluation* eval,
   return true;
 };
 
+template <typename IndexType>
+bool GetNumericArgument(std::string const& arg, IndexType& value)
+{
+  try {
+    std::size_t pos;
+
+    if (sizeof(IndexType) == sizeof(long)) {
+      value = std::stol(arg, &pos);
+    } else {
+      value = std::stoll(arg, &pos);
+    }
+
+    if (pos != arg.length()) {
+      // this is not a number
+      return false;
+    }
+  } catch (std::invalid_argument const&) {
+    return false;
+  }
+
+  return true;
+}
+
+template <typename IndexType>
+bool GetNumericArguments(
+  cm::GenEx::Evaluation* eval, GeneratorExpressionContent const* cnt,
+  Arguments args, std::vector<IndexType>& indexes,
+  cmList::ExpandElements expandElements = cmList::ExpandElements::No)
+{
+  using IndexRange = cmRange<Arguments::const_iterator>;
+  IndexRange arguments(args.begin(), args.end());
+  cmList list;
+  if (expandElements == cmList::ExpandElements::Yes) {
+    list = cmList{ args.begin(), args.end(), expandElements };
+    arguments = IndexRange{ list.begin(), list.end() };
+  }
+
+  for (auto const& value : arguments) {
+    IndexType index;
+    if (!GetNumericArgument(value, index)) {
+      reportError(eval, cnt->GetOriginalExpression(),
+                  cmStrCat("index: \"", value, "\" is not a valid index"));
+      return false;
+    }
+    indexes.push_back(index);
+  }
+  return true;
+}
+
 bool CheckPathParametersEx(cm::GenEx::Evaluation* eval,
                            GeneratorExpressionContent const* cnt,
                            cm::string_view option, std::size_t count,
@@ -1162,6 +1288,553 @@ static const struct PathEqualNode : public cmGeneratorExpressionNode
   }
 } pathEqualNode;
 
+namespace {
+inline bool CheckStringParametersEx(cm::GenEx::Evaluation* eval,
+                                    GeneratorExpressionContent const* cnt,
+                                    cm::string_view option, std::size_t count,
+                                    int required = 1, bool exactly = true)
+{
+  return CheckGenExParameters(eval, cnt, "STRING"_s, option, count, required,
+                              exactly);
+}
+inline bool CheckStringParameters(cm::GenEx::Evaluation* eval,
+                                  GeneratorExpressionContent const* cnt,
+                                  cm::string_view option, Arguments args,
+                                  int required = 1)
+{
+  return CheckStringParametersEx(eval, cnt, option, args.size(), required);
+};
+}
+
+static const struct StringNode : public cmGeneratorExpressionNode
+{
+  StringNode() {} // NOLINT(modernize-use-equals-default)
+
+  int NumExpectedParameters() const override { return OneOrMoreParameters; }
+
+  bool AcceptsArbitraryContentParameter() const override { return true; }
+
+  std::string Evaluate(
+    std::vector<std::string> const& parameters, cm::GenEx::Evaluation* eval,
+    GeneratorExpressionContent const* content,
+    cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
+  {
+    static std::unordered_map<
+      cm::string_view,
+      std::function<std::string(cm::GenEx::Evaluation*,
+                                GeneratorExpressionContent const*,
+                                Arguments&)>>
+      stringCommands{
+        { "LENGTH"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParameters(ev, cnt, "LENGTH"_s, args)) {
+              return std::to_string(cm::CMakeString{ args.front() }.Length());
+            }
+            return std::string{};
+          } },
+        { "SUBSTRING"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParameters(ev, cnt, "SUBSTRING"_s, args, 3)) {
+              cm::CMakeString str{ args.front() };
+              std::vector<long> indexes;
+              if (GetNumericArguments(ev, cnt, args.advance(1), indexes)) {
+                try {
+                  return str.Substring(indexes.front(), indexes.back());
+                } catch (std::out_of_range const& e) {
+                  reportError(ev, cnt->GetOriginalExpression(), e.what());
+                  return std::string{};
+                }
+              }
+            }
+            return std::string{};
+          } },
+        { "FIND"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParametersEx(ev, cnt, "FIND"_s, args.size(), 2,
+                                        false)) {
+              if (args.size() > 3) {
+                reportError(ev, cnt->GetOriginalExpression(),
+                            "$<STRING:FIND> expression expects at "
+                            "most three parameters.");
+                return std::string{};
+              }
+
+              auto const FROM = "FROM:"_s;
+
+              cm::CMakeString str{ args.front() };
+              cm::CMakeString::FindFrom from =
+                cm::CMakeString::FindFrom::Begin;
+              cm::string_view substring;
+
+              args.advance(1);
+              if (args.size() == 2) {
+                cm::CMakeString::FindFrom opt =
+                  static_cast<cm::CMakeString::FindFrom>(-1);
+
+                for (auto const& arg : args) {
+                  if (cmHasPrefix(arg, FROM)) {
+                    if (arg != "FROM:BEGIN"_s && arg != "FROM:END"_s) {
+                      reportError(
+                        ev, cnt->GetOriginalExpression(),
+                        cmStrCat("Invalid value for '", FROM,
+                                 "' option. 'BEGIN' or 'END' expected."));
+                      return std::string{};
+                    }
+                    opt = arg == "FROM:BEGIN"_s
+                      ? cm::CMakeString::FindFrom::Begin
+                      : cm::CMakeString::FindFrom::End;
+                  } else {
+                    substring = arg;
+                  }
+                }
+                if (opt == static_cast<cm::CMakeString::FindFrom>(-1)) {
+                  reportError(
+                    ev, cnt->GetOriginalExpression(),
+                    cmStrCat("Expected option '", FROM, "' is missing."));
+                  return std::string{};
+                }
+                from = opt;
+              } else {
+                substring = args.front();
+              }
+              auto pos = str.Find(substring, from);
+              return pos == cm::CMakeString::npos ? "-1" : std::to_string(pos);
+            }
+            return std::string{};
+          } },
+        { "MATCH"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParametersEx(ev, cnt, "MATCH"_s, args.size(), 2,
+                                        false)) {
+              if (args.size() > 3) {
+                reportError(ev, cnt->GetOriginalExpression(),
+                            "$<STRING:MATCH> expression expects at "
+                            "most three parameters.");
+                return std::string{};
+              }
+
+              auto const SEEK = "SEEK:"_s;
+
+              cm::CMakeString str{ args.front() };
+              cm::CMakeString::MatchItems seek =
+                cm::CMakeString::MatchItems::Once;
+              auto const* regex = &args[1];
+
+              args.advance(1);
+              if (args.size() == 2) {
+                cm::CMakeString::MatchItems opt =
+                  static_cast<cm::CMakeString::MatchItems>(-1);
+
+                for (auto const& arg : args) {
+                  if (cmHasPrefix(arg, SEEK)) {
+                    if (arg != "SEEK:ONCE"_s && arg != "SEEK:ALL"_s) {
+                      reportError(
+                        ev, cnt->GetOriginalExpression(),
+                        cmStrCat("Invalid value for '", SEEK,
+                                 "' option. 'ONCE' or 'ALL' expected."));
+                      return std::string{};
+                    }
+                    opt = arg == "SEEK:ONCE"_s
+                      ? cm::CMakeString::MatchItems::Once
+                      : cm::CMakeString::MatchItems::All;
+                  } else {
+                    regex = &arg;
+                  }
+                }
+                if (opt == static_cast<cm::CMakeString::MatchItems>(-1)) {
+                  reportError(
+                    ev, cnt->GetOriginalExpression(),
+                    cmStrCat("Expected option '", SEEK, "' is missing."));
+                  return std::string{};
+                }
+                seek = opt;
+              }
+
+              try {
+                return str.Match(*regex, seek).to_string();
+              } catch (std::invalid_argument const& e) {
+                reportError(ev, cnt->GetOriginalExpression(), e.what());
+                return std::string{};
+              }
+            }
+            return std::string{};
+          } },
+        { "JOIN"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParametersEx(ev, cnt, "JOIN"_s, args.size(), 2,
+                                        false)) {
+              auto const& glue = args.front();
+              return cm::CMakeString{ args.advance(1), glue };
+            }
+            return std::string{};
+          } },
+        { "ASCII"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParametersEx(ev, cnt, "ASCII"_s, args.size(), 1,
+                                        false)) {
+              try {
+                return cm::CMakeString{}.FromASCII(args);
+              } catch (std::invalid_argument const& e) {
+                reportError(ev, cnt->GetOriginalExpression(), e.what());
+                return std::string{};
+              }
+            }
+            return std::string{};
+          } },
+        { "TIMESTAMP"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            cm::string_view format;
+            cm::CMakeString::UTC utc = cm::CMakeString::UTC::No;
+
+            if (args.size() == 2 && args.front() != "UTC"_s &&
+                args.back() != "UTC"_s) {
+              reportError(ev, cnt->GetOriginalExpression(),
+                          "'UTC' option is expected.");
+              return std::string{};
+            }
+            if (args.size() > 2) {
+              reportError(ev, cnt->GetOriginalExpression(),
+                          "$<STRING:TIMESTAMP> expression expects at most two "
+                          "parameters.");
+              return std::string{};
+            }
+
+            for (auto const& arg : args) {
+              if (arg == "UTC"_s) {
+                utc = cm::CMakeString::UTC::Yes;
+              } else {
+                format = arg;
+              }
+            }
+            return cm::CMakeString{}.Timestamp(format, utc);
+          } },
+        { "RANDOM"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            auto const ALPHABET = "ALPHABET:"_s;
+            auto const LENGTH = "LENGTH:"_s;
+            auto const RANDOM_SEED = "RANDOM_SEED:"_s;
+
+            if (args.size() > 3) {
+              reportError(ev, cnt->GetOriginalExpression(),
+                          "$<STRING:RANDOM> expression expects at most three "
+                          "parameters.");
+              return std::string{};
+            }
+
+            cm::string_view alphabet;
+            std::size_t length = 5;
+            bool seed_specified = false;
+            unsigned int seed = 0;
+            for (auto const& arg : args) {
+              if (cmHasPrefix(arg, ALPHABET)) {
+                alphabet = cm::string_view{ arg.c_str() + ALPHABET.length() };
+                continue;
+              }
+              if (cmHasPrefix(arg, LENGTH)) {
+                try {
+                  length = std::stoul(arg.substr(LENGTH.size()));
+                } catch (std::exception const&) {
+                  reportError(ev, cnt->GetOriginalExpression(),
+                              cmStrCat(arg, ": invalid numeric value for '",
+                                       LENGTH, "' option."));
+                  return std::string{};
+                }
+                continue;
+              }
+              if (cmHasPrefix(arg, RANDOM_SEED)) {
+                try {
+                  seed_specified = true;
+                  seed = static_cast<unsigned int>(
+                    std::stoul(arg.substr(RANDOM_SEED.size())));
+                } catch (std::exception const&) {
+                  reportError(ev, cnt->GetOriginalExpression(),
+                              cmStrCat(arg, ": invalid numeric value for '",
+                                       RANDOM_SEED, "' option."));
+                  return std::string{};
+                }
+                continue;
+              }
+              reportError(ev, cnt->GetOriginalExpression(),
+                          cmStrCat(arg, ": invalid parameter."));
+              return std::string{};
+            }
+
+            try {
+              if (seed_specified) {
+                return cm::CMakeString{}.Random(seed, length, alphabet);
+              }
+              return cm::CMakeString{}.Random(length, alphabet);
+            } catch (std::exception const& e) {
+              reportError(ev, cnt->GetOriginalExpression(), e.what());
+              return std::string{};
+            }
+          } },
+        { "UUID"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParametersEx(ev, cnt, "UUID"_s, args.size(), 2,
+                                        false)) {
+              auto const NAMESPACE = "NAMESPACE:"_s;
+              auto const NAME = "NAME:"_s;
+              auto const TYPE = "TYPE:"_s;
+              auto const CASE = "CASE:"_s;
+
+              if (args.size() > 4) {
+                reportError(ev, cnt->GetOriginalExpression(),
+                            "$<STRING:UUID> expression expects at most four "
+                            "parameters.");
+                return std::string{};
+              }
+
+              cm::string_view nameSpace;
+              cm::string_view name;
+              cm::CMakeString::UUIDType type =
+                static_cast<cm::CMakeString::UUIDType>(-1);
+              cm::CMakeString::Case uuidCase = cm::CMakeString::Case::Lower;
+              for (auto const& arg : args) {
+                if (cmHasPrefix(arg, NAMESPACE)) {
+                  nameSpace =
+                    cm::string_view{ arg.c_str() + NAMESPACE.length() };
+                  if (nameSpace.empty()) {
+                    reportError(
+                      ev, cnt->GetOriginalExpression(),
+                      cmStrCat("Invalid value for '", NAMESPACE, "' option."));
+                    return std::string{};
+                  }
+                  continue;
+                }
+                if (cmHasPrefix(arg, NAME)) {
+                  name = cm::string_view{ arg.c_str() + NAME.length() };
+                  continue;
+                }
+                if (cmHasPrefix(arg, TYPE)) {
+                  auto value = cm::string_view{ arg.c_str() + TYPE.length() };
+                  if (value != "MD5"_s && value != "SHA1"_s) {
+                    reportError(
+                      ev, cnt->GetOriginalExpression(),
+                      cmStrCat("Invalid value for '", TYPE,
+                               "' option. 'MD5' or 'SHA1' expected."));
+                    return std::string{};
+                  }
+                  type = value == "MD5"_s ? cm::CMakeString::UUIDType::MD5
+                                          : cm::CMakeString::UUIDType::SHA1;
+                  continue;
+                }
+                if (cmHasPrefix(arg, CASE)) {
+                  auto value = cm::string_view{ arg.c_str() + CASE.length() };
+                  if (value != "UPPER"_s && value != "LOWER"_s) {
+                    reportError(
+                      ev, cnt->GetOriginalExpression(),
+                      cmStrCat("Invalid value for '", CASE,
+                               "' option. 'UPPER' or 'LOWER' expected."));
+                    return std::string{};
+                  }
+                  uuidCase = value == "UPPER"_s ? cm::CMakeString::Case::Upper
+                                                : cm::CMakeString::Case::Lower;
+                  continue;
+                }
+                reportError(ev, cnt->GetOriginalExpression(),
+                            cmStrCat(arg, ": invalid parameter."));
+                return std::string{};
+              }
+              if (nameSpace.empty()) {
+                reportError(
+                  ev, cnt->GetOriginalExpression(),
+                  cmStrCat("Required option '", NAMESPACE, "' is missing."));
+                return std::string{};
+              }
+              if (type == static_cast<cm::CMakeString::UUIDType>(-1)) {
+                reportError(
+                  ev, cnt->GetOriginalExpression(),
+                  cmStrCat("Required option '", TYPE, "' is missing."));
+                return std::string{};
+              }
+
+              try {
+                return cm::CMakeString{}.UUID(nameSpace, name, type, uuidCase);
+              } catch (std::exception const& e) {
+                reportError(ev, cnt->GetOriginalExpression(), e.what());
+                return std::string{};
+              }
+            }
+            return std::string{};
+          } },
+        { "REPLACE"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParametersEx(ev, cnt, "REPLACE"_s, args.size(), 3,
+                                        false)) {
+              if (args.size() > 4) {
+                reportError(ev, cnt->GetOriginalExpression(),
+                            "$<STRING:REPLACE> expression expects at "
+                            "most four parameters.");
+                return std::string{};
+              }
+
+              cm::CMakeString::Regex isRegex = cm::CMakeString::Regex::No;
+              if (args.size() == 4) {
+                cm::string_view type = args.front();
+                if (type != "STRING"_s && type != "REGEX"_s) {
+                  reportError(
+                    ev, cnt->GetOriginalExpression(),
+                    cmStrCat(
+                      '\'', type,
+                      "' is unexpected. 'STRING' or 'REGEX' expected."));
+                  return std::string{};
+                }
+                isRegex = type == "STRING"_s ? cm::CMakeString::Regex::No
+                                             : cm::CMakeString::Regex::Yes;
+                args.advance(1);
+              }
+
+              try {
+                return cm::CMakeString{ args.front() }.Replace(
+                  args[1], args[2], isRegex);
+              } catch (std::invalid_argument const& e) {
+                reportError(ev, cnt->GetOriginalExpression(), e.what());
+                return std::string{};
+              }
+            }
+            return std::string{};
+          } },
+        { "APPEND"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParametersEx(ev, cnt, "APPEND"_s, args.size(), 2,
+                                        false)) {
+              cm::CMakeString data{ args.front() };
+              return data.Append(args.advance(1));
+            }
+            return std::string{};
+          } },
+        { "PREPEND"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParametersEx(ev, cnt, "PREPEND "_s, args.size(), 2,
+                                        false)) {
+              cm::CMakeString data{ args.front() };
+              return data.Prepend(args.advance(1));
+            }
+            return std::string{};
+          } },
+        { "TOLOWER"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParameters(ev, cnt, "TOLOWER"_s, args, 1)) {
+              return cm::CMakeString{}.ToLower(args.front());
+            }
+            return std::string{};
+          } },
+        { "TOUPPER"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParameters(ev, cnt, "TOUPPER"_s, args, 1)) {
+              return cm::CMakeString{}.ToUpper(args.front());
+            }
+            return std::string{};
+          } },
+        { "STRIP"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParameters(ev, cnt, "STRIP"_s, args, 2)) {
+              if (args.front() != "SPACES"_s) {
+                reportError(ev, cnt->GetOriginalExpression(),
+                            cmStrCat('\'', args.front(),
+                                     "' is unexpected. 'SPACES' expected."));
+                return std::string{};
+              }
+
+              return cm::CMakeString{ args[1] }.Strip();
+            }
+            return std::string{};
+          } },
+        { "QUOTE"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParameters(ev, cnt, "QUOTE"_s, args, 2)) {
+              if (args.front() != "REGEX"_s) {
+                reportError(ev, cnt->GetOriginalExpression(),
+                            cmStrCat('\'', args.front(),
+                                     "' is unexpected. 'REGEX' expected."));
+                return std::string{};
+              }
+
+              return cm::CMakeString{ args[1] }.Quote();
+            }
+            return std::string{};
+          } },
+        { "HEX"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParameters(ev, cnt, "HEX"_s, args, 1)) {
+              return cm::CMakeString{ args.front() }.ToHexadecimal();
+            }
+            return std::string{};
+          } },
+        { "HASH"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParameters(ev, cnt, "HASH"_s, args, 2)) {
+              auto const ALGORITHM = "ALGORITHM:"_s;
+
+              if (cmHasPrefix(args[1], ALGORITHM)) {
+                try {
+                  auto const algo =
+                    cm::string_view{ args[1].c_str() + ALGORITHM.length() };
+                  if (algo.empty()) {
+                    reportError(
+                      ev, cnt->GetOriginalExpression(),
+                      cmStrCat("Missing value for '", ALGORITHM, "' option."));
+                    return std::string{};
+                  }
+                  return cm::CMakeString{ args.front() }.Hash(algo);
+                } catch (std::exception const& e) {
+                  reportError(ev, cnt->GetOriginalExpression(), e.what());
+                  return std::string{};
+                }
+              }
+              reportError(ev, cnt->GetOriginalExpression(),
+                          cmStrCat(args[1], ": invalid parameter. Option '",
+                                   ALGORITHM, "' expected."));
+            }
+            return std::string{};
+          } },
+        { "MAKE_C_IDENTIFIER"_s,
+          [](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
+             Arguments& args) -> std::string {
+            if (CheckStringParameters(ev, cnt, "MAKE_C_IDENTIFIER"_s, args,
+                                      1)) {
+              return cm::CMakeString{ args.front() }.MakeCIdentifier();
+            }
+            return std::string{};
+          } }
+      };
+
+    if (parameters.front().empty()) {
+      reportError(eval, content->GetOriginalExpression(),
+                  "$<STRING> expression requires at least one parameter.");
+      return std::string{};
+    }
+
+    if (cm::contains(stringCommands, parameters.front())) {
+      auto args = Arguments{ parameters }.advance(1);
+      return stringCommands[parameters.front()](eval, content, args);
+    }
+
+    reportError(eval, content->GetOriginalExpression(),
+                cmStrCat(parameters.front(), ": invalid option."));
+    return std::string{};
+  }
+} stringNode;
+
 namespace {
 inline bool CheckListParametersEx(cm::GenEx::Evaluation* eval,
                                   GeneratorExpressionContent const* cnt,
@@ -1183,53 +1856,6 @@ inline cmList GetList(std::string const& list)
 {
   return list.empty() ? cmList{} : cmList{ list, cmList::EmptyElements::Yes };
 }
-
-bool GetNumericArgument(std::string const& arg, cmList::index_type& value)
-{
-  try {
-    std::size_t pos;
-
-    if (sizeof(cmList::index_type) == sizeof(long)) {
-      value = std::stol(arg, &pos);
-    } else {
-      value = std::stoll(arg, &pos);
-    }
-
-    if (pos != arg.length()) {
-      // this is not a number
-      return false;
-    }
-  } catch (std::invalid_argument const&) {
-    return false;
-  }
-
-  return true;
-}
-
-bool GetNumericArguments(
-  cm::GenEx::Evaluation* eval, GeneratorExpressionContent const* cnt,
-  Arguments args, std::vector<cmList::index_type>& indexes,
-  cmList::ExpandElements expandElements = cmList::ExpandElements::No)
-{
-  using IndexRange = cmRange<Arguments::const_iterator>;
-  IndexRange arguments(args.begin(), args.end());
-  cmList list;
-  if (expandElements == cmList::ExpandElements::Yes) {
-    list = cmList{ args.begin(), args.end(), expandElements };
-    arguments = IndexRange{ list.begin(), list.end() };
-  }
-
-  for (auto const& value : arguments) {
-    cmList::index_type index;
-    if (!GetNumericArgument(value, index)) {
-      reportError(eval, cnt->GetOriginalExpression(),
-                  cmStrCat("index: \"", value, "\" is not a valid index"));
-      return false;
-    }
-    indexes.push_back(index);
-  }
-  return true;
-}
 }
 
 static const struct ListNode : public cmGeneratorExpressionNode
@@ -4872,6 +5498,11 @@ cmGeneratorExpressionNode const* cmGeneratorExpressionNode::GetNode(
     { "TARGET_BUNDLE_DIR_NAME", &targetBundleDirNameNode },
     { "TARGET_BUNDLE_CONTENT_DIR", &targetBundleContentDirNode },
     { "STREQUAL", &strEqualNode },
+    { "STRLESS", &strLessNode },
+    { "STRLESS_EQUAL", &strLessEqualNode },
+    { "STRGREATER", &strGreaterNode },
+    { "STRGREATER_EQUAL", &strGreaterEqualNode },
+    { "STRING", &stringNode },
     { "EQUAL", &equalNode },
     { "IN_LIST", &inListNode },
     { "FILTER", &filterNode },

+ 24 - 0
Source/cmString.hxx

@@ -425,6 +425,16 @@ public:
     r.append(v.data(), v.size());
     return *this = std::move(r);
   }
+  template <typename T>
+  typename std::enable_if<AsStringView<T>::value, String&>::type append(T&& s)
+  {
+    string_view v = AsStringView<T>::view(std::forward<T>(s));
+    std::string r;
+    r.reserve(this->size() + v.size());
+    r.assign(this->data(), this->size());
+    r.append(v.data(), v.size());
+    return *this = std::move(r);
+  }
 
   /** Assign to an empty string.  */
   void clear() { *this = ""_s; }
@@ -432,6 +442,20 @@ public:
   /** Insert 'count' copies of 'ch' at position 'index'.  */
   String& insert(size_type index, size_type count, char ch);
 
+  /** Insert into the string using any type that implements the
+      AsStringView trait.  */
+  template <typename T>
+  typename std::enable_if<AsStringView<T>::value, String&>::type insert(
+    size_type index, T&& s)
+  {
+    string_view v = AsStringView<T>::view(std::forward<T>(s));
+    std::string r;
+    r.reserve(this->size() + v.size());
+    r.assign(this->data(), this->size());
+    r.insert(index, v.data(), v.size());
+    return *this = std::move(r);
+  }
+
   /** Erase 'count' characters starting at position 'index'.  */
   String& erase(size_type index = 0, size_type count = npos);
 

+ 183 - 359
Source/cmStringCommand.cxx

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

+ 2 - 3
Source/cmStringReplaceHelper.cxx

@@ -20,8 +20,7 @@ cmStringReplaceHelper::cmStringReplaceHelper(std::string const& regex,
   this->ParseReplaceExpression();
 }
 
-bool cmStringReplaceHelper::Replace(std::string const& input,
-                                    std::string& output)
+bool cmStringReplaceHelper::Replace(cm::string_view input, std::string& output)
 {
   output.clear();
 
@@ -36,7 +35,7 @@ bool cmStringReplaceHelper::Replace(std::string const& input,
   auto& re = this->RegularExpression;
   std::string::size_type base = 0;
   unsigned optNonEmpty = 0;
-  while (re.find(input, base, optAnchor | optNonEmpty)) {
+  while (re.find(input.data(), base, optAnchor | optNonEmpty)) {
     if (this->Makefile) {
       this->Makefile->ClearMatches();
       this->Makefile->StoreMatches(re);

+ 3 - 1
Source/cmStringReplaceHelper.h

@@ -6,6 +6,8 @@
 #include <utility>
 #include <vector>
 
+#include <cm/string_view>
+
 #include "cmsys/RegularExpression.hxx"
 
 class cmMakefile;
@@ -25,7 +27,7 @@ public:
     return this->ValidReplaceExpression;
   }
 
-  bool Replace(std::string const& input, std::string& output);
+  bool Replace(cm::string_view input, std::string& output);
 
   std::string const& GetError() { return this->ErrorString; }
 

+ 6 - 5
Source/cmTimestamp.cxx

@@ -36,7 +36,7 @@
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
-std::string cmTimestamp::CurrentTime(std::string const& formatString,
+std::string cmTimestamp::CurrentTime(cm::string_view formatString,
                                      bool utcFlag) const
 {
   // get current time with microsecond resolution
@@ -63,11 +63,12 @@ std::string cmTimestamp::CurrentTime(std::string const& formatString,
   }
 
   return this->CreateTimestampFromTimeT(currentTimeT, microseconds,
-                                        formatString, utcFlag);
+                                        static_cast<std::string>(formatString),
+                                        utcFlag);
 }
 
 std::string cmTimestamp::FileModificationTime(char const* path,
-                                              std::string const& formatString,
+                                              cm::string_view formatString,
                                               bool utcFlag) const
 {
   std::string real_path =
@@ -89,8 +90,8 @@ std::string cmTimestamp::FileModificationTime(char const* path,
   }
   uv_fs_req_cleanup(&req);
 
-  return this->CreateTimestampFromTimeT(mtime, microseconds, formatString,
-                                        utcFlag);
+  return this->CreateTimestampFromTimeT(
+    mtime, microseconds, static_cast<std::string>(formatString), utcFlag);
 }
 
 std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,

+ 4 - 2
Source/cmTimestamp.h

@@ -8,6 +8,8 @@
 #include <ctime>
 #include <string>
 
+#include <cm/string_view>
+
 /** \class cmTimestamp
  * \brief Utility class to generate string representation of a timestamp
  *
@@ -15,10 +17,10 @@
 class cmTimestamp
 {
 public:
-  std::string CurrentTime(std::string const& formatString, bool utcFlag) const;
+  std::string CurrentTime(cm::string_view formatString, bool utcFlag) const;
 
   std::string FileModificationTime(char const* path,
-                                   std::string const& formatString,
+                                   cm::string_view formatString,
                                    bool utcFlag) const;
 
   std::string CreateTimestampFromTimeT(time_t timeT, std::string formatString,

+ 6 - 6
Source/cmUuid.cxx

@@ -10,7 +10,7 @@
 static std::array<int, 5> const kUuidGroups = { { 4, 2, 2, 2, 6 } };
 
 std::string cmUuid::FromMd5(std::vector<unsigned char> const& uuidNamespace,
-                            std::string const& name) const
+                            cm::string_view name) const
 {
   std::vector<unsigned char> hashInput;
   this->CreateHashInput(uuidNamespace, name, hashInput);
@@ -24,7 +24,7 @@ std::string cmUuid::FromMd5(std::vector<unsigned char> const& uuidNamespace,
 }
 
 std::string cmUuid::FromSha1(std::vector<unsigned char> const& uuidNamespace,
-                             std::string const& name) const
+                             cm::string_view name) const
 {
   std::vector<unsigned char> hashInput;
   this->CreateHashInput(uuidNamespace, name, hashInput);
@@ -38,7 +38,7 @@ std::string cmUuid::FromSha1(std::vector<unsigned char> const& uuidNamespace,
 }
 
 void cmUuid::CreateHashInput(std::vector<unsigned char> const& uuidNamespace,
-                             std::string const& name,
+                             cm::string_view name,
                              std::vector<unsigned char>& output) const
 {
   output = uuidNamespace;
@@ -46,7 +46,7 @@ void cmUuid::CreateHashInput(std::vector<unsigned char> const& uuidNamespace,
   if (!name.empty()) {
     output.resize(output.size() + name.size());
 
-    memcpy(output.data() + uuidNamespace.size(), name.c_str(), name.size());
+    memcpy(output.data() + uuidNamespace.size(), name.data(), name.size());
   }
 }
 
@@ -67,7 +67,7 @@ std::string cmUuid::FromDigest(unsigned char const* digest,
   return this->BinaryToString(uuid);
 }
 
-bool cmUuid::StringToBinary(std::string const& input,
+bool cmUuid::StringToBinary(cm::string_view input,
                             std::vector<unsigned char>& output) const
 {
   output.clear();
@@ -126,7 +126,7 @@ std::string cmUuid::ByteToHex(unsigned char inputByte) const
   return result;
 }
 
-bool cmUuid::StringToBinaryImpl(std::string const& input,
+bool cmUuid::StringToBinaryImpl(cm::string_view input,
                                 std::vector<unsigned char>& output) const
 {
   if (input.size() % 2) {

+ 7 - 5
Source/cmUuid.h

@@ -7,6 +7,8 @@
 #include <string>
 #include <vector>
 
+#include <cm/string_view>
+
 /** \class cmUuid
  * \brief Utility class to generate UUIDs as defined by RFC4122
  *
@@ -15,25 +17,25 @@ class cmUuid
 {
 public:
   std::string FromMd5(std::vector<unsigned char> const& uuidNamespace,
-                      std::string const& name) const;
+                      cm::string_view name) const;
 
   std::string FromSha1(std::vector<unsigned char> const& uuidNamespace,
-                       std::string const& name) const;
+                       cm::string_view name) const;
 
-  bool StringToBinary(std::string const& input,
+  bool StringToBinary(cm::string_view input,
                       std::vector<unsigned char>& output) const;
 
 private:
   std::string ByteToHex(unsigned char byte) const;
 
   void CreateHashInput(std::vector<unsigned char> const& uuidNamespace,
-                       std::string const& name,
+                       cm::string_view name,
                        std::vector<unsigned char>& output) const;
 
   std::string FromDigest(unsigned char const* digest,
                          unsigned char version) const;
 
-  bool StringToBinaryImpl(std::string const& input,
+  bool StringToBinaryImpl(cm::string_view input,
                           std::vector<unsigned char>& output) const;
 
   std::string BinaryToString(unsigned char const* input) const;

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

@@ -523,6 +523,7 @@ add_RunCMake_test(GenEx-TARGET_IMPORT_FILE)
 add_RunCMake_test(GenEx-GENEX_EVAL)
 add_RunCMake_test(GenEx-TARGET_PROPERTY)
 add_RunCMake_test(GenEx-TARGET_RUNTIME_DLLS)
+add_RunCMake_test(GenEx-STRING)
 add_RunCMake_test(GenEx-PATH)
 add_RunCMake_test(GenEx-PATH_EQUAL)
 add_RunCMake_test(GenEx-LIST)

+ 22 - 0
Tests/RunCMake/GenEx-STRING/APPEND.cmake.in

@@ -0,0 +1,22 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(reference "abcd")
+string(APPEND reference  "efg" "ABCD")
+set(output "$<STRING:APPEND,abcd,efg,ABCD>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:APPEND,abcd,efg,ABCD> returns bad data: ${output}")
+endif()
+
+unset(reference)
+string(APPEND reference  "efg" "ABCD")
+set(output "$<STRING:APPEND,,efg,ABCD>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:APPEND,,efg,ABCD> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:APPEND" ${errors})

+ 1 - 0
Tests/RunCMake/GenEx-STRING/ASCII-WrongArguments-result.txt

@@ -0,0 +1 @@
+1

+ 38 - 0
Tests/RunCMake/GenEx-STRING/ASCII-WrongArguments-stderr.txt

@@ -0,0 +1,38 @@
+CMake Error at ASCII-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:ASCII,foo>
+
+  Character with code foo does not exist.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at ASCII-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:ASCII,256>
+
+  Character with code 256 does not exist.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at ASCII-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:ASCII,32,256>
+
+  Character with code 256 does not exist.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at ASCII-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:ASCII,32,-1>
+
+  Character with code -1 does not exist.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 7 - 0
Tests/RunCMake/GenEx-STRING/ASCII-WrongArguments.cmake

@@ -0,0 +1,7 @@
+
+add_custom_target(check ALL COMMAND check
+  $<STRING:ASCII,foo>
+  $<STRING:ASCII,256>
+  $<STRING:ASCII,32,256>
+  $<STRING:ASCII,32,-1>
+VERBATIM)

+ 20 - 0
Tests/RunCMake/GenEx-STRING/ASCII.cmake.in

@@ -0,0 +1,20 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(ASCII 32 reference)
+set(output "$<STRING:ASCII,32>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:ASCII,32> returns bad data: ${output}")
+endif()
+
+string(ASCII 32 64 reference)
+set(output "$<STRING:ASCII,32,64>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:ASCII,32,64> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:ASCII" ${errors})

+ 5 - 0
Tests/RunCMake/GenEx-STRING/CMakeLists.txt

@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.18...3.25)
+
+project(${RunCMake_TEST} NONE)
+
+include(${RunCMake_TEST}.cmake)

+ 1 - 0
Tests/RunCMake/GenEx-STRING/FIND-WrongArguments-result.txt

@@ -0,0 +1 @@
+1

+ 18 - 0
Tests/RunCMake/GenEx-STRING/FIND-WrongArguments-stderr.txt

@@ -0,0 +1,18 @@
+CMake Error at FIND-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:FIND,string,OTHER,foo>
+
+  Expected option 'FROM:' is missing.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at FIND-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:FIND,string,FROM:OTHER,foo>
+
+  Invalid value for 'FROM:' option.  'BEGIN' or 'END' expected.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 5 - 0
Tests/RunCMake/GenEx-STRING/FIND-WrongArguments.cmake

@@ -0,0 +1,5 @@
+
+add_custom_target(check ALL COMMAND check
+  $<STRING:FIND,string,OTHER,foo>
+  $<STRING:FIND,string,FROM:OTHER,foo>
+VERBATIM)

+ 48 - 0
Tests/RunCMake/GenEx-STRING/FIND.cmake.in

@@ -0,0 +1,48 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(FIND  "abcdabcd" "cd" reference)
+set(output "$<STRING:FIND,abcdabcd,cd>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:FIND,abcdabcd,cd> returns bad data: ${output}")
+endif()
+set(output "$<STRING:FIND,abcdabcd,FROM:BEGIN,cd>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:FIND,abcdabcd,FROM:BEGIN,cd> returns bad data: ${output}")
+endif()
+set(output "$<STRING:FIND,abcdabcd,cd,FROM:BEGIN>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:FIND,abcdabcd,cd,FROM:BEGIN> returns bad data: ${output}")
+endif()
+
+string(FIND  "abcdabcd" "cd" reference REVERSE)
+set(output "$<STRING:FIND,abcdabcd,FROM:END,cd>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:FIND,abcdabcd,FROM:END,cd> returns bad data: ${output}")
+endif()
+set(output "$<STRING:FIND,abcdabcd,cd,FROM:END>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:FIND,abcdabcd,cd,FROM:END> returns bad data: ${output}")
+endif()
+
+string(FIND  "abcdabcd" "xy" reference)
+set(output "$<STRING:FIND,abcdabcd,xy>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:FIND,abcdabcd,xy> returns bad data: ${output}")
+endif()
+set(output "$<STRING:FIND,abcdabcd,xy,FROM:BEGIN>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:FIND,abcdabcd,xy,FROM:BEGIN> returns bad data: ${output}")
+endif()
+
+string(FIND  "abcdabcd" "xy" reference REVERSE)
+set(output "$<STRING:FIND,abcdabcd,xy,FROM:END>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:FIND,abcdabcd,xy,FROM:END> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:FIND" ${errors})

+ 1 - 0
Tests/RunCMake/GenEx-STRING/HASH-WrongArguments-result.txt

@@ -0,0 +1 @@
+1

+ 18 - 0
Tests/RunCMake/GenEx-STRING/HASH-WrongArguments-stderr.txt

@@ -0,0 +1,18 @@
+CMake Error at HASH-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:HASH,string,ALGORITHM:>
+
+  Missing value for 'ALGORITHM:' option.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at HASH-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:HASH,string,ALGORITHM:FOO>
+
+  FOO: invalid hash algorithm.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 5 - 0
Tests/RunCMake/GenEx-STRING/HASH-WrongArguments.cmake

@@ -0,0 +1,5 @@
+
+add_custom_target(check ALL COMMAND check
+  $<STRING:HASH,string,ALGORITHM:>
+  $<STRING:HASH,string,ALGORITHM:FOO>
+VERBATIM)

+ 69 - 0
Tests/RunCMake/GenEx-STRING/HASH.cmake.in

@@ -0,0 +1,69 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+# foreach(hash IN ITEMS MD5 SHA1 SHA224 SHA256 SHA384 SHA512 SHA3_224 SHA3_256 SHA3_384 SHA3_512)
+
+string(MD5 reference "sample input string")
+set(output "$<STRING:HASH,sample input string,ALGORITHM:MD5>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:HASH,sample input string,MD5> returns bad data: ${output}")
+endif()
+
+string(SHA1 reference "sample input string")
+set(output "$<STRING:HASH,sample input string,ALGORITHM:SHA1>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:HASH,sample input string,SHA1> returns bad data: ${output}")
+endif()
+
+string(SHA224 reference "sample input string")
+set(output "$<STRING:HASH,sample input string,ALGORITHM:SHA224>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:HASH,sample input string,SHA224> returns bad data: ${output}")
+endif()
+
+string(SHA256 reference "sample input string")
+set(output "$<STRING:HASH,sample input string,ALGORITHM:SHA256>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:HASH,sample input string,SHA256> returns bad data: ${output}")
+endif()
+
+string(SHA384 reference "sample input string")
+set(output "$<STRING:HASH,sample input string,ALGORITHM:SHA384>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:HASH,sample input string,SHA384> returns bad data: ${output}")
+endif()
+
+string(SHA512 reference "sample input string")
+set(output "$<STRING:HASH,sample input string,ALGORITHM:SHA512>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:HASH,sample input string,SHA512> returns bad data: ${output}")
+endif()
+
+string(SHA3_224 reference "sample input string")
+set(output "$<STRING:HASH,sample input string,ALGORITHM:SHA3_224>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:HASH,sample input string,SHA3_224> returns bad data: ${output}")
+endif()
+
+string(SHA3_256 reference "sample input string")
+set(output "$<STRING:HASH,sample input string,ALGORITHM:SHA3_256>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:HASH,sample input string,SHA3_256> returns bad data: ${output}")
+endif()
+
+string(SHA3_384 reference "sample input string")
+set(output "$<STRING:HASH,sample input string,ALGORITHM:SHA3_384>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:HASH,sample input string,SHA3_384> returns bad data: ${output}")
+endif()
+
+string(SHA3_512 reference "sample input string")
+set(output "$<STRING:HASH,sample input string,ALGORITHM:SHA3_512>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:HASH,sample input string,SHA3_512> returns bad data: ${output}")
+endif()
+
+check_errors("STRING:HASH" ${errors})

+ 20 - 0
Tests/RunCMake/GenEx-STRING/HEX.cmake.in

@@ -0,0 +1,20 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(HEX "The quick brown fox jumps over the lazy dog." reference)
+set(output "$<STRING:HEX,The quick brown fox jumps over the lazy dog.>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:HEX,The quick brown fox jumps over the lazy dog.> returns bad data: ${output}")
+endif()
+
+string(HEX "Ash nazg durbatulûk. Ash nazg gimbatul. Ash nazg thrakatulûk. Agh burzum-ishi krimpatul" reference)
+set(output "$<STRING:HEX,Ash nazg durbatulûk. Ash nazg gimbatul. Ash nazg thrakatulûk. Agh burzum-ishi krimpatul>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:HEX,Ash nazg durbatulûk. Ash nazg gimbatul. Ash nazg thrakatulûk. Agh burzum-ishi krimpatul> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:HEX" ${errors})

+ 32 - 0
Tests/RunCMake/GenEx-STRING/JOIN.cmake.in

@@ -0,0 +1,32 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(JOIN "|" reference "ab" "cd")
+set(output "$<STRING:JOIN,|,ab,cd>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:JOIN,|,ab,cd> returns bad data: ${output}")
+endif()
+
+string(JOIN "" reference "ab" "cd")
+set(output "$<STRING:JOIN,,ab,cd>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:JOIN,,ab,cd> returns bad data: ${output}")
+endif()
+
+string(JOIN "|" reference)
+set(output "$<STRING:JOIN,|,>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:JOIN,|,> returns bad data: ${output}")
+endif()
+
+string(JOIN "" reference)
+set(output "$<STRING:JOIN,,>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:JOIN,,> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:JOIN" ${errors})

+ 21 - 0
Tests/RunCMake/GenEx-STRING/LENGTH.cmake.in

@@ -0,0 +1,21 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(LENGTH  "abc" reference)
+set(output "$<STRING:LENGTH,abc>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:LENGTH,abc> returns bad data: ${output}")
+endif()
+
+
+string(LENGTH  "" reference)
+set(output "$<STRING:LENGTH,>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:LENGTH,> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:LENGTH" ${errors})

+ 1 - 0
Tests/RunCMake/GenEx-STRING/MATCH-WrongArguments-result.txt

@@ -0,0 +1 @@
+1

+ 28 - 0
Tests/RunCMake/GenEx-STRING/MATCH-WrongArguments-stderr.txt

@@ -0,0 +1,28 @@
+CMake Error at MATCH-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:MATCH,string,\(>
+
+  Failed to compile regex "\("
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at MATCH-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:MATCH,string,FOO,\.\+>
+
+  Expected option 'SEEK:' is missing.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at MATCH-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:MATCH,string,SEEK:FOO,\.\+>
+
+  Invalid value for 'SEEK:' option.  'ONCE' or 'ALL' expected.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 8 - 0
Tests/RunCMake/GenEx-STRING/MATCH-WrongArguments.cmake

@@ -0,0 +1,8 @@
+
+add_custom_target(check ALL COMMAND check
+  [[
+     $<STRING:MATCH,string,(>
+     $<STRING:MATCH,string,FOO,.+>
+     $<STRING:MATCH,string,SEEK:FOO,.+>
+  ]]
+VERBATIM)

+ 40 - 0
Tests/RunCMake/GenEx-STRING/MATCH.cmake.in

@@ -0,0 +1,40 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(REGEX MATCH "cd" reference "abcdabcd")
+set(output "$<STRING:MATCH,abcdabcd,cd>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:MATCH,abcdabcd,cd> returns bad data: ${output}")
+endif()
+set(output "$<STRING:MATCH,abcdabcd,SEEK:ONCE,cd>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:MATCH,abcdabcd,SEEK:ONCE,cd> returns bad data: ${output}")
+endif()
+
+string(REGEX MATCH "(b|B)cd" reference "abcdABcd")
+set(output "$<STRING:MATCH,abcdABcd,(b|B)cd>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:MATCH,abcdABcd,B(cd)> returns bad data: ${output}")
+endif()
+set(output "$<STRING:MATCH,abcdABcd,(b|B)cd,SEEK:ONCE>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:MATCH,abcdABcd,B(cd),SEEK:ONCE> returns bad data: ${output}")
+endif()
+
+string(REGEX MATCHALL "cd" reference "abcdabcd")
+set(output "$<STRING:MATCH,abcdabcd,SEEK:ALL,cd>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:MATCH,abcdabcd,SEEK:ALL,cd> returns bad data: ${output}")
+endif()
+
+string(REGEX MATCHALL "(b|B)cd" reference "abcdABcd")
+set(output "$<STRING:MATCH,abcdABcd,(b|B)cd,SEEK:ALL>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:MATCH,abcdABcd,B(cd),SEEK:ALL> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:MATCH" ${errors})

+ 22 - 0
Tests/RunCMake/GenEx-STRING/PREPEND.cmake.in

@@ -0,0 +1,22 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(reference "abcd")
+string(PREPEND reference  "efg" "ABCD")
+set(output "$<STRING:PREPEND,abcd,efg,ABCD>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:PREPEND,abcd,efg,ABCD> returns bad data: ${output}")
+endif()
+
+unset(reference)
+string(PREPEND reference  "efg" "ABCD")
+set(output "$<STRING:PREPEND,,efg,ABCD>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:PREPEND,,efg,ABCD> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:PREPEND" ${errors})

+ 1 - 0
Tests/RunCMake/GenEx-STRING/QUOTE-WrongArguments-result.txt

@@ -0,0 +1 @@
+1

+ 8 - 0
Tests/RunCMake/GenEx-STRING/QUOTE-WrongArguments-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at QUOTE-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:QUOTE,FOO,string>
+
+  'FOO' is unexpected.  'REGEX' expected.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 6 - 0
Tests/RunCMake/GenEx-STRING/QUOTE-WrongArguments.cmake

@@ -0,0 +1,6 @@
+
+add_custom_target(check ALL COMMAND check
+  [[
+     $<STRING:QUOTE,FOO,string>
+  ]]
+VERBATIM)

+ 21 - 0
Tests/RunCMake/GenEx-STRING/QUOTE.cmake.in

@@ -0,0 +1,21 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(REGEX QUOTE reference "abcd")
+set(output "$<STRING:QUOTE,REGEX,abcd>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:QUOTE,REGEX,abcd> returns bad data: ${output}")
+endif()
+
+string(REGEX QUOTE reference "ab|c+12?3[x-z]$(y)\\t\\r\\n.cma*ke^[:alpha:] ")
+set(output [[
+$<STRING:QUOTE,REGEX,ab|c+12?3[x-z]$(y)\t\r\n.cma*ke^[:alpha:]> ]])
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:QUOTE,REGEX,ab|c+12?3[x-z]$(y)\\t\\r\\n.cma*ke^[:alpha:]> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:QUOTE" ${errors})

+ 1 - 0
Tests/RunCMake/GenEx-STRING/RANDOM-WrongArguments-result.txt

@@ -0,0 +1 @@
+1

+ 18 - 0
Tests/RunCMake/GenEx-STRING/RANDOM-WrongArguments-stderr.txt

@@ -0,0 +1,18 @@
+CMake Error at RANDOM-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:RANDOM,LENGTH:foo>
+
+  LENGTH:foo: invalid numeric value for 'LENGTH:' option.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at RANDOM-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:RANDOM,RANDOM_SEED:foo>
+
+  RANDOM_SEED:foo: invalid numeric value for 'RANDOM_SEED:' option.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 5 - 0
Tests/RunCMake/GenEx-STRING/RANDOM-WrongArguments.cmake

@@ -0,0 +1,5 @@
+
+add_custom_target(check ALL COMMAND check
+  $<STRING:RANDOM,LENGTH:foo>
+   $<STRING:RANDOM,RANDOM_SEED:foo>
+VERBATIM)

+ 32 - 0
Tests/RunCMake/GenEx-STRING/RANDOM.cmake.in

@@ -0,0 +1,32 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(RANDOM RANDOM_SEED 123 reference)
+set(output "$<STRING:RANDOM,RANDOM_SEED:123>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:RANDOM,RANDOM_SEED:123> returns bad data: ${output}")
+endif()
+
+string(RANDOM LENGTH 9 RANDOM_SEED 5 reference)
+set(output "$<STRING:RANDOM,LENGTH:9,RANDOM_SEED:5>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:RANDOM,LENGTH:9,RANDOM_SEED:5> returns bad data: ${output}")
+endif()
+
+string(RANDOM LENGTH 9 RANDOM_SEED 5 ALPHABET "" reference)
+set(output "$<STRING:RANDOM,LENGTH:9,RANDOM_SEED:5,ALPHABET:>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:RANDOM,LENGTH:9,RANDOM_SEED:5,ALPHABET:> returns bad data: ${output}")
+endif()
+
+string(RANDOM LENGTH 9 RANDOM_SEED 5 ALPHABET "abcdef123456789" reference)
+set(output "$<STRING:RANDOM,LENGTH:9,RANDOM_SEED:5,ALPHABET:abcdef123456789>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:RANDOM,LENGTH:9,RANDOM_SEED:5,ALPHABET:abcdef123456789> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:RANDOM" ${errors})

+ 1 - 0
Tests/RunCMake/GenEx-STRING/REPLACE-WrongArguments-result.txt

@@ -0,0 +1 @@
+1

+ 18 - 0
Tests/RunCMake/GenEx-STRING/REPLACE-WrongArguments-stderr.txt

@@ -0,0 +1,18 @@
+CMake Error at REPLACE-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:REPLACE,FOO,string,\.\+,>
+
+  'FOO' is unexpected.  'STRING' or 'REGEX' expected.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at REPLACE-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:REPLACE,REGEX,string,\(,>
+
+  Failed to compile regex "\("
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 7 - 0
Tests/RunCMake/GenEx-STRING/REPLACE-WrongArguments.cmake

@@ -0,0 +1,7 @@
+
+add_custom_target(check ALL COMMAND check
+  [[
+     $<STRING:REPLACE,FOO,string,.+,>
+     $<STRING:REPLACE,REGEX,string,(,>
+  ]]
+VERBATIM)

+ 44 - 0
Tests/RunCMake/GenEx-STRING/REPLACE.cmake.in

@@ -0,0 +1,44 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(REPLACE  "bcd" "BCD" reference "abcdabcd")
+set(output "$<STRING:REPLACE,abcdabcd,bcd,BCD>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:REPLACE,abcdabcd,bcd,BCD> returns bad data: ${output}")
+endif()
+set(output "$<STRING:REPLACE,STRING,abcdabcd,bcd,BCD>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:REPLACE,STRING,abcdabcd,bcd,BCD> returns bad data: ${output}")
+endif()
+
+string(REPLACE  "bcd" "" reference "abcdabcd")
+set(output "$<STRING:REPLACE,abcdabcd,bcd,>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:REPLACE,abcdabcd,bcd,> returns bad data: ${output}")
+endif()
+set(output "$<STRING:REPLACE,STRING,abcdabcd,bcd,>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:REPLACE,STRING,abcdabcd,bcd,> returns bad data: ${output}")
+endif()
+
+string(REPLACE  "xyz" "BCD" reference "abcdabcd")
+set(output "$<STRING:REPLACE,abcdabcd,xyz,BCD>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:REPLACE,abcdabcd,xyz,BCD> returns bad data: ${output}")
+endif()
+set(output "$<STRING:REPLACE,STRING,abcdabcd,xyz,BCD>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:REPLACE,STRING,abcdabcd,xyz,BCD> returns bad data: ${output}")
+endif()
+
+string(REGEX REPLACE  "bcd" "BCD" reference "abcdabcd")
+set(output "$<STRING:REPLACE,REGEX,abcdabcd,bcd,BCD>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:REPLACE,REGEX,abcdabcd,bcd,BCD> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:REPLACE" ${errors})

+ 80 - 0
Tests/RunCMake/GenEx-STRING/RunCMakeTest.cmake

@@ -0,0 +1,80 @@
+
+include(RunCMake)
+
+run_cmake(no-arguments)
+run_cmake(bad-option)
+
+function(check_string_syntax name test)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${name}-${test}-build)
+  set(RunCMake_TEST_VARIANT_DESCRIPTION " - ${name}")
+  run_cmake_with_options(${test} ${ARGN})
+endfunction()
+
+## Unexpected arguments
+### sub-commands with one argument
+foreach (subcommand IN ITEMS LENGTH TOLOWER TOUPPER HEX MAKE_C_IDENTIFIER)
+  check_string_syntax (${subcommand} unexpected-arg "-DSTRING_ARGUMENTS=${subcommand},ARG1,ARG2")
+endforeach()
+
+### sub-commands with two arguments
+foreach (subcommand IN ITEMS HASH STRIP QUOTE)
+  check_string_syntax (${subcommand} unexpected-arg "-DSTRING_ARGUMENTS=${subcommand},ARG1,ARG2,ARG3")
+endforeach()
+
+### sub-commands with three arguments
+foreach (subcommand IN ITEMS SUBSTRING)
+  check_string_syntax (${subcommand} unexpected-arg "-DSTRING_ARGUMENTS=${subcommand},ARG1,ARG2,ARG3,ARG4")
+endforeach()
+foreach (subcommand IN ITEMS FIND MATCH RANDOM)
+  check_string_syntax (${subcommand} unexpected-arg2 "-DSTRING_ARGUMENTS=${subcommand},ARG1,ARG2,ARG3,ARG4")
+endforeach()
+
+### sub-commands with four arguments
+foreach (subcommand IN ITEMS REPLACE)
+  check_string_syntax (${subcommand} unexpected-arg2 "-DSTRING_ARGUMENTS=${subcommand},ARG1,ARG2,ARG3,ARG4,ARG5")
+endforeach()
+
+run_cmake(SUBSTRING-WrongArguments)
+run_cmake(FIND-WrongArguments)
+run_cmake(MATCH-WrongArguments)
+run_cmaKE(ASCII-WrongArguments)
+run_cmake(TIMESTAMP-WrongArguments)
+run_cmake(RANDOM-WrongArguments)
+run_cmake(UUID-WrongArguments)
+run_cmake(REPLACE-WrongArguments)
+run_cmake(STRIP-WrongArguments)
+run_cmake(QUOTE-WrongArguments)
+run_cmake(HASH-WrongArguments)
+
+
+function(check_string_execution name)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${name}-build)
+  if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+    set(CONFIG_OPTION -DCMAKE_CONFIGURATION_TYPES=Debug)
+  else()
+    set(CONFIG_OPTION -DCMAKE_BUILD_TYPE=Debug)
+  endif()
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_VARIANT_DESCRIPTION " - ${name}")
+  run_cmake_with_options(generate -DSTRING_TEST=${name} ${CONFIG_OPTION})
+  run_cmake_command(check "${CMAKE_COMMAND}" "-DRunCMake_SOURCE_DIR=${RunCMake_SOURCE_DIR}" -P "${RunCMake_TEST_BINARY_DIR}/${name}.cmake")
+endfunction()
+
+check_string_execution (LENGTH)
+check_string_execution (SUBSTRING)
+check_string_execution (FIND)
+check_string_execution (MATCH)
+check_string_execution (JOIN)
+check_string_execution (ASCII)
+check_string_execution (TIMESTAMP)
+check_string_execution (RANDOM)
+check_string_execution (UUID)
+check_string_execution (REPLACE)
+check_string_execution (APPEND)
+check_string_execution (PREPEND)
+check_string_execution (TOLOWER)
+check_string_execution (TOUPPER)
+check_string_execution (STRIP)
+check_string_execution (QUOTE)
+check_string_execution (HEX)
+check_string_execution (HASH)

+ 1 - 0
Tests/RunCMake/GenEx-STRING/STRIP-WrongArguments-result.txt

@@ -0,0 +1 @@
+1

+ 8 - 0
Tests/RunCMake/GenEx-STRING/STRIP-WrongArguments-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at STRIP-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:STRIP,FOO,string>
+
+  'FOO' is unexpected.  'SPACES' expected.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 6 - 0
Tests/RunCMake/GenEx-STRING/STRIP-WrongArguments.cmake

@@ -0,0 +1,6 @@
+
+add_custom_target(check ALL COMMAND check
+  [[
+     $<STRING:STRIP,FOO,string>
+  ]]
+VERBATIM)

+ 14 - 0
Tests/RunCMake/GenEx-STRING/STRIP.cmake.in

@@ -0,0 +1,14 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(STRIP " ABCD  "  reference)
+set(output "$<STRING:STRIP,SPACES, ABCD  >")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:STRIP,SPACES, ABCD  > returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:STRIP" ${errors})

+ 1 - 0
Tests/RunCMake/GenEx-STRING/SUBSTRING-WrongArguments-result.txt

@@ -0,0 +1 @@
+1

+ 38 - 0
Tests/RunCMake/GenEx-STRING/SUBSTRING-WrongArguments-stderr.txt

@@ -0,0 +1,38 @@
+CMake Error at SUBSTRING-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:SUBSTRING,string,foo,1>
+
+  index: "foo" is not a valid index
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at SUBSTRING-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:SUBSTRING,string,1,foo>
+
+  index: "foo" is not a valid index
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at SUBSTRING-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:SUBSTRING,string,-1,2>
+
+  begin index: -1 is out of range 0 - 6
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at SUBSTRING-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:SUBSTRING,string,1,-2>
+
+  end index: -2 should be -1 or greater
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 7 - 0
Tests/RunCMake/GenEx-STRING/SUBSTRING-WrongArguments.cmake

@@ -0,0 +1,7 @@
+
+add_custom_target(check ALL COMMAND check
+  $<STRING:SUBSTRING,string,foo,1>
+  $<STRING:SUBSTRING,string,1,foo>
+  $<STRING:SUBSTRING,string,-1,2>
+  $<STRING:SUBSTRING,string,1,-2>
+VERBATIM)

+ 26 - 0
Tests/RunCMake/GenEx-STRING/SUBSTRING.cmake.in

@@ -0,0 +1,26 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(SUBSTRING  "abcd" 1 2 reference)
+set(output "$<STRING:SUBSTRING,abcd,1,2>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:SUBSTRING,abcd,1,2> returns bad data: ${output}")
+endif()
+
+string(SUBSTRING  "abcd" 1 -1 reference)
+set(output "$<STRING:SUBSTRING,abcd,1,-1>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:SUBSTRING,abcd,1,-1> returns bad data: ${output}")
+endif()
+
+string(SUBSTRING  "abcd" 1 5 reference)
+set(output "$<STRING:SUBSTRING,abcd,1,5>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:SUBSTRING,abcd,1,5> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:SUBSTRING" ${errors})

+ 1 - 0
Tests/RunCMake/GenEx-STRING/TIMESTAMP-WrongArguments-result.txt

@@ -0,0 +1 @@
+1

+ 8 - 0
Tests/RunCMake/GenEx-STRING/TIMESTAMP-WrongArguments-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at TIMESTAMP-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:TIMESTAMP,%s,%s>
+
+  'UTC' option is expected.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 4 - 0
Tests/RunCMake/GenEx-STRING/TIMESTAMP-WrongArguments.cmake

@@ -0,0 +1,4 @@
+
+add_custom_target(check ALL COMMAND check
+  $<STRING:TIMESTAMP,%s,%s>
+VERBATIM)

+ 27 - 0
Tests/RunCMake/GenEx-STRING/TIMESTAMP.cmake.in

@@ -0,0 +1,27 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(ENV{SOURCE_DATE_EPOCH} "1123456789")
+
+string(TIMESTAMP reference "%Y-%m-%d %H:%M:%S.%f %A=%a %B=%b %y day=%j wd=%w week=%U w_iso=%V %%I=%I epoch=%s TZ=%Z tz=%z")
+set(output "$<STRING:TIMESTAMP,%Y-%m-%d %H:%M:%S.%f %A=%a %B=%b %y day=%j wd=%w week=%U w_iso=%V %%I=%I epoch=%s TZ=%Z tz=%z>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:TIMESTAMP,%Y-%m-%d %H:%M:%S.%f %A=%a %B=%b %y day=%j wd=%w week=%U w_iso=%V %%I=%I epoch=%s TZ=%Z tz=%z> returns bad data: ${output}")
+endif()
+
+
+string(TIMESTAMP reference "%Y-%m-%d %H:%M:%S.%f %A=%a %B=%b %y day=%j wd=%w week=%U w_iso=%V %%I=%I epoch=%s TZ=%Z tz=%z" UTC)
+set(output "$<STRING:TIMESTAMP,%Y-%m-%d %H:%M:%S.%f %A=%a %B=%b %y day=%j wd=%w week=%U w_iso=%V %%I=%I epoch=%s TZ=%Z tz=%z,UTC>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:TIMESTAMP,%Y-%m-%d %H:%M:%S.%f %A=%a %B=%b %y day=%j wd=%w week=%U w_iso=%V %%I=%I epoch=%s TZ=%Z tz=%z,UTC> returns bad data: ${output}")
+endif()
+set(output "$<STRING:TIMESTAMP,UTC,%Y-%m-%d %H:%M:%S.%f %A=%a %B=%b %y day=%j wd=%w week=%U w_iso=%V %%I=%I epoch=%s TZ=%Z tz=%z>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:TIMESTAMP,UTC,%Y-%m-%d %H:%M:%S.%f %A=%a %B=%b %y day=%j wd=%w week=%U w_iso=%V %%I=%I epoch=%s TZ=%Z tz=%z> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:TIMESTAMP" ${errors})

+ 26 - 0
Tests/RunCMake/GenEx-STRING/TOLOWER.cmake.in

@@ -0,0 +1,26 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(TOLOWER "ABCD"  reference)
+set(output "$<STRING:TOLOWER,ABCD>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:TOLOWER,ABCD> returns bad data: ${output}")
+endif()
+
+string(TOLOWER "abcd"  reference)
+set(output "$<STRING:TOLOWER,abcd>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:TOLOWER,abcd> returns bad data: ${output}")
+endif()
+
+string(TOLOWER "abcdABCD12@!"  reference)
+set(output "$<STRING:TOLOWER,abcdABCD12@!>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:TOLOWER,abcdABCD12@!> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:TOLOWER" ${errors})

+ 26 - 0
Tests/RunCMake/GenEx-STRING/TOUPPER.cmake.in

@@ -0,0 +1,26 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+string(TOUPPER "abcd"  reference)
+set(output "$<STRING:TOUPPER,abcd>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:TOUPPER,abcd> returns bad data: ${output}")
+endif()
+
+string(TOUPPER "ABCD"  reference)
+set(output "$<STRING:TOUPPER,ABCD>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:TOUPPER,ABCD> returns bad data: ${output}")
+endif()
+
+string(TOUPPER "abcdABCD12@!"  reference)
+set(output "$<STRING:TOUPPER,abcdABCD12@!>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:TOUPPER,abcdABCD12@!> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:TOUPPER" ${errors})

+ 1 - 0
Tests/RunCMake/GenEx-STRING/UUID-WrongArguments-result.txt

@@ -0,0 +1 @@
+1

+ 28 - 0
Tests/RunCMake/GenEx-STRING/UUID-WrongArguments-stderr.txt

@@ -0,0 +1,28 @@
+CMake Error at UUID-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:UUID,TYPE:MD5,NAMESPACE:>
+
+  Invalid value for 'NAMESPACE:' option.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at UUID-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:UUID,NAMESPACE:foo,TYPE:FOO>
+
+  Invalid value for 'TYPE:' option.  'MD5' or 'SHA1' expected.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at UUID-WrongArguments\.cmake:[0-9]+ \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRING:UUID,NAMESPACE:foo,TYPE:MD5,CASE:FOO>
+
+  Invalid value for 'CASE:' option.  'UPPER' or 'LOWER' expected.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 6 - 0
Tests/RunCMake/GenEx-STRING/UUID-WrongArguments.cmake

@@ -0,0 +1,6 @@
+
+add_custom_target(check ALL COMMAND check
+  $<STRING:UUID,TYPE:MD5,NAMESPACE:>
+  $<STRING:UUID,NAMESPACE:foo,TYPE:FOO>
+  $<STRING:UUID,NAMESPACE:foo,TYPE:MD5,CASE:FOO>
+VERBATIM)

+ 32 - 0
Tests/RunCMake/GenEx-STRING/UUID.cmake.in

@@ -0,0 +1,32 @@
+
+cmake_minimum_required(VERSION 4.2...4.3)
+
+include ("${RunCMake_SOURCE_DIR}/check_errors.cmake")
+unset (errors)
+
+set(UUID_DNS_NAMESPACE 6ba7b810-9dad-11d1-80b4-00c04fd430c8)
+
+string(UUID reference NAMESPACE ${UUID_DNS_NAMESPACE} NAME www.example.com TYPE MD5)
+set(output "$<STRING:UUID,NAMESPACE:6ba7b810-9dad-11d1-80b4-00c04fd430c8,NAME:www.example.com,TYPE:MD5>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:UUID,NAMESPACE:6ba7b810-9dad-11d1-80b4-00c04fd430c8,NAME:www.example.com,TYPE:MD5> returns bad data: ${output}")
+endif()
+set(output "$<STRING:UUID,NAMESPACE:6ba7b810-9dad-11d1-80b4-00c04fd430c8,NAME:www.example.com,TYPE:MD5,CASE:LOWER>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:UUID,NAMESPACE:6ba7b810-9dad-11d1-80b4-00c04fd430c8,NAME:www.example.com,TYPE:MD5,CASE:LOWER> returns bad data: ${output}")
+endif()
+
+string(UUID reference NAMESPACE ${UUID_DNS_NAMESPACE} NAME www.example.com TYPE MD5 UPPER)
+set(output "$<STRING:UUID,NAMESPACE:6ba7b810-9dad-11d1-80b4-00c04fd430c8,NAME:www.example.com,TYPE:MD5,CASE:UPPER>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:UUID,NAMESPACE:6ba7b810-9dad-11d1-80b4-00c04fd430c8,NAME:www.example.com,TYPE:MD5,CASE:UPPER> returns bad data: ${output}")
+endif()
+
+string(UUID reference NAMESPACE ${UUID_DNS_NAMESPACE} NAME www.example.com TYPE SHA1)
+set(output "$<STRING:UUID,NAMESPACE:6ba7b810-9dad-11d1-80b4-00c04fd430c8,NAME:www.example.com,TYPE:SHA1>")
+if (NOT output STREQUAL reference)
+  list (APPEND errors "<STRING:UUID,NAMESPACE:6ba7b810-9dad-11d1-80b4-00c04fd430c8,NAME:www.example.com,TYPE:SHA1> returns bad data: ${output}")
+endif()
+
+
+check_errors("STRING:UUID" ${errors})

+ 1 - 0
Tests/RunCMake/GenEx-STRING/bad-option-result.txt

@@ -0,0 +1 @@
+1

+ 8 - 0
Tests/RunCMake/GenEx-STRING/bad-option-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at bad-option\.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<STRING:BAD_OPTION>
+
+  BAD_OPTION: invalid option.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 2 - 0
Tests/RunCMake/GenEx-STRING/bad-option.cmake

@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<STRING:BAD_OPTION>")

+ 13 - 0
Tests/RunCMake/GenEx-STRING/check_errors.cmake

@@ -0,0 +1,13 @@
+
+function (CHECK_ERRORS command)
+  set (errors ${ARGN})
+  set (command "$<${command}>")
+  if (errors)
+    string (LENGTH "${command}" length)
+    math (EXPR count "${length} + 2")
+    string (REPEAT " " ${count} shift)
+    list (TRANSFORM errors PREPEND "${shift}$")
+    list (JOIN errors "\n" msg)
+    message (FATAL_ERROR "${command}: ${msg}")
+  endif()
+endfunction()

+ 6 - 0
Tests/RunCMake/GenEx-STRING/generate.cmake

@@ -0,0 +1,6 @@
+
+if (STRING_TEST STREQUAL "TIMESTAMP")
+  set(ENV{SOURCE_DATE_EPOCH} "1123456789")
+endif()
+
+file(GENERATE OUTPUT "${STRING_TEST}.cmake" INPUT "${STRING_TEST}.cmake.in")

+ 1 - 0
Tests/RunCMake/GenEx-STRING/no-arguments-result.txt

@@ -0,0 +1 @@
+1

+ 8 - 0
Tests/RunCMake/GenEx-STRING/no-arguments-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at no-arguments\.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<STRING:>
+
+  \$<STRING> expression requires at least one parameter.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 2 - 0
Tests/RunCMake/GenEx-STRING/no-arguments.cmake

@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<STRING:>")

+ 1 - 0
Tests/RunCMake/GenEx-STRING/unexpected-arg-result.txt

@@ -0,0 +1 @@
+1

+ 8 - 0
Tests/RunCMake/GenEx-STRING/unexpected-arg-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at unexpected-arg\.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<STRING:[A-Z_]+,.+>
+
+  \$<STRING:[A-Z_:]+(,[A-Z_:]+(,[A-Z_:]+)?)?> expression requires exactly (one|two|three) parameters?.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 2 - 0
Tests/RunCMake/GenEx-STRING/unexpected-arg.cmake

@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<STRING:${STRING_ARGUMENTS}>")

+ 1 - 0
Tests/RunCMake/GenEx-STRING/unexpected-arg2-result.txt

@@ -0,0 +1 @@
+1

+ 8 - 0
Tests/RunCMake/GenEx-STRING/unexpected-arg2-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at unexpected-arg2\.cmake:[0-9]+ \(file\):
+  Error evaluating generator expression:
+
+    \$<STRING:[A-Z_]+,.+>
+
+  \$<STRING:[A-Z_:]+(,[A-Z_:]+(,[A-Z_:]+)?)?> expression expects at most (two|three|four) parameters?.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 2 - 0
Tests/RunCMake/GenEx-STRING/unexpected-arg2.cmake

@@ -0,0 +1,2 @@
+
+file(GENERATE OUTPUT result.txt CONTENT "$<STRING:${STRING_ARGUMENTS}>")

+ 1 - 0
Tests/RunCMake/GeneratorExpression/BadStrGreater-result.txt

@@ -0,0 +1 @@
+1

+ 40 - 0
Tests/RunCMake/GeneratorExpression/BadStrGreater-stderr.txt

@@ -0,0 +1,40 @@
+CMake Error at BadStrGreater.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRGREATER>
+
+  \$<STRGREATER> expression requires 2 comma separated parameters, but got 0
+  instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++CMake Error at BadStrGreater.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRGREATER:>
+
+  \$<STRGREATER> expression requires 2 comma separated parameters, but got 1
+  instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Error at BadStrGreater.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRGREATER:,,>
+
+  \$<STRGREATER> expression requires 2 comma separated parameters, but got 3
+  instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Error at BadStrGreater.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRGREATER:something,,>
+
+  \$<STRGREATER> expression requires 2 comma separated parameters, but got 3
+  instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Generate step failed\.  Build files cannot be regenerated correctly\.$

+ 6 - 0
Tests/RunCMake/GeneratorExpression/BadStrGreater.cmake

@@ -0,0 +1,6 @@
+add_custom_target(check ALL COMMAND check
+  $<STRGREATER>
+  $<STRGREATER:>
+  $<STRGREATER:,,>
+  $<STRGREATER:something,,>
+  VERBATIM)

+ 1 - 0
Tests/RunCMake/GeneratorExpression/BadStrGreaterEqual-result.txt

@@ -0,0 +1 @@
+1

+ 40 - 0
Tests/RunCMake/GeneratorExpression/BadStrGreaterEqual-stderr.txt

@@ -0,0 +1,40 @@
+CMake Error at BadStrGreaterEqual.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRGREATER_EQUAL>
+
+  \$<STRGREATER_EQUAL> expression requires 2 comma separated parameters, but
+  got 0 instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++CMake Error at BadStrGreaterEqual.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRGREATER_EQUAL:>
+
+  \$<STRGREATER_EQUAL> expression requires 2 comma separated parameters, but
+  got 1 instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Error at BadStrGreaterEqual.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRGREATER_EQUAL:,,>
+
+  \$<STRGREATER_EQUAL> expression requires 2 comma separated parameters, but
+  got 3 instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Error at BadStrGreaterEqual.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRGREATER_EQUAL:something,,>
+
+  \$<STRGREATER_EQUAL> expression requires 2 comma separated parameters, but
+  got 3 instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Generate step failed\.  Build files cannot be regenerated correctly\.$

+ 6 - 0
Tests/RunCMake/GeneratorExpression/BadStrGreaterEqual.cmake

@@ -0,0 +1,6 @@
+add_custom_target(check ALL COMMAND check
+  $<STRGREATER_EQUAL>
+  $<STRGREATER_EQUAL:>
+  $<STRGREATER_EQUAL:,,>
+  $<STRGREATER_EQUAL:something,,>
+  VERBATIM)

+ 1 - 0
Tests/RunCMake/GeneratorExpression/BadStrLess-result.txt

@@ -0,0 +1 @@
+1

+ 40 - 0
Tests/RunCMake/GeneratorExpression/BadStrLess-stderr.txt

@@ -0,0 +1,40 @@
+CMake Error at BadStrLess.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRLESS>
+
+  \$<STRLESS> expression requires 2 comma separated parameters, but got 0
+  instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++CMake Error at BadStrLess.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRLESS:>
+
+  \$<STRLESS> expression requires 2 comma separated parameters, but got 1
+  instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Error at BadStrLess.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRLESS:,,>
+
+  \$<STRLESS> expression requires 2 comma separated parameters, but got 3
+  instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Error at BadStrLess.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRLESS:something,,>
+
+  \$<STRLESS> expression requires 2 comma separated parameters, but got 3
+  instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Generate step failed\.  Build files cannot be regenerated correctly\.$

+ 6 - 0
Tests/RunCMake/GeneratorExpression/BadStrLess.cmake

@@ -0,0 +1,6 @@
+add_custom_target(check ALL COMMAND check
+  $<STRLESS>
+  $<STRLESS:>
+  $<STRLESS:,,>
+  $<STRLESS:something,,>
+  VERBATIM)

+ 1 - 0
Tests/RunCMake/GeneratorExpression/BadStrLessEqual-result.txt

@@ -0,0 +1 @@
+1

+ 40 - 0
Tests/RunCMake/GeneratorExpression/BadStrLessEqual-stderr.txt

@@ -0,0 +1,40 @@
+CMake Error at BadStrLessEqual.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRLESS_EQUAL>
+
+  \$<STRLESS_EQUAL> expression requires 2 comma separated parameters, but got
+  0 instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++CMake Error at BadStrLessEqual.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRLESS_EQUAL:>
+
+  \$<STRLESS_EQUAL> expression requires 2 comma separated parameters, but got
+  1 instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Error at BadStrLessEqual.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRLESS_EQUAL:,,>
+
+  \$<STRLESS_EQUAL> expression requires 2 comma separated parameters, but got
+  3 instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Error at BadStrLessEqual.cmake:1 \(add_custom_target\):
+  Error evaluating generator expression:
+
+    \$<STRLESS_EQUAL:something,,>
+
+  \$<STRLESS_EQUAL> expression requires 2 comma separated parameters, but got
+  3 instead.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Generate step failed\.  Build files cannot be regenerated correctly\.$

+ 6 - 0
Tests/RunCMake/GeneratorExpression/BadStrLessEqual.cmake

@@ -0,0 +1,6 @@
+add_custom_target(check ALL COMMAND check
+  $<STRLESS_EQUAL>
+  $<STRLESS_EQUAL:>
+  $<STRLESS_EQUAL:,,>
+  $<STRLESS_EQUAL:something,,>
+  VERBATIM)

+ 9 - 0
Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake

@@ -7,6 +7,15 @@ run_cmake(BadOR)
 run_cmake(BadAND)
 run_cmake(BadNOT)
 run_cmake(BadStrEqual)
+run_cmake(BadStrLess)
+run_cmake(BadStrLessEqual)
+run_cmake(BadStrGreater)
+run_cmake(BadStrGreaterEqual)
+run_cmake(STREQUAL)
+run_cmake(STRLESS)
+run_cmake(STRLESS_EQUAL)
+run_cmake(STRGREATER)
+run_cmake(STRGREATER_EQUAL)
 run_cmake(BadZero)
 run_cmake(BadTargetName)
 run_cmake(BadTargetTypeInterface)

+ 6 - 0
Tests/RunCMake/GeneratorExpression/STREQUAL-check.cmake

@@ -0,0 +1,6 @@
+file(READ "${RunCMake_TEST_BINARY_DIR}/STREQUAL-generated.txt" content)
+
+set(expected "1:0")
+if(NOT content STREQUAL expected)
+  set(RunCMake_TEST_FAILED "$<STREQUAL>: actual content:\n [[${content}]]\nbut expected:\n [[${expected}]]")
+endif()

+ 3 - 0
Tests/RunCMake/GeneratorExpression/STREQUAL.cmake

@@ -0,0 +1,3 @@
+cmake_policy(VERSION 4.2)
+
+file(GENERATE OUTPUT "STREQUAL-generated.txt" CONTENT "$<STREQUAL:AA,AA>:$<STREQUAL:AA,BB>")

Some files were not shown because too many files changed in this diff