mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
GenEx: add $<LIST:FILTER> PREDICATE filter and REGEX keyword
Let $<LIST:FILTER> select elements by an arbitrary condition: PREDICATE keeps or drops each element according to a <body> evaluated with $<_0> bound, complementing the existing regular-expression form. Also accept an explicit REGEX keyword before the pattern so the regex and predicate forms read symmetrically; the bare pattern keeps working. Issue: #27892
This commit is contained in:
@@ -882,6 +882,25 @@ List Transformations
|
||||
(``EXCLUDE``) the regular expression ``regex``. The result is the same as
|
||||
:genex:`$<FILTER:list,INCLUDE|EXCLUDE,regex>`.
|
||||
|
||||
.. versionadded:: 4.5
|
||||
|
||||
The regular expression may be introduced explicitly with a ``REGEX``
|
||||
keyword, and a ``PREDICATE`` keyword selects items using a generator
|
||||
expression instead:
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
$<LIST:FILTER,list,INCLUDE|EXCLUDE,REGEX,regex>
|
||||
$<LIST:FILTER,list,INCLUDE|EXCLUDE,PREDICATE,body>
|
||||
|
||||
With ``PREDICATE``, ``body`` is evaluated once per item with the bound
|
||||
operand :genex:`$<_0>` expanding to the current item. The body must
|
||||
evaluate to exactly ``0`` or ``1``; ``INCLUDE`` keeps items whose body
|
||||
yields ``1`` and ``EXCLUDE`` removes them. Use ``$<BOOL:...>`` to
|
||||
coerce other values. Because ``REGEX`` and ``PREDICATE`` are now keywords,
|
||||
a bare regular expression equal to ``REGEX`` or ``PREDICATE`` must use the
|
||||
explicit ``REGEX`` form.
|
||||
|
||||
.. genex:: $<LIST:TRANSFORM,list,ACTION[,SELECTOR]>
|
||||
|
||||
.. versionadded:: 3.27
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
genex-list-filter-transform-predicate
|
||||
-------------------------------------
|
||||
|
||||
* The :genex:`LIST` generator expression's ``TRANSFORM`` operation gained a
|
||||
``PREDICATE`` selector that chooses the items to transform by evaluating an
|
||||
* The :genex:`LIST` generator expression's ``FILTER`` and ``TRANSFORM``
|
||||
operations gained a ``PREDICATE`` keyword that selects items by evaluating an
|
||||
arbitrary generator expression once per item, with ``$<_0>`` referring to the
|
||||
current item.
|
||||
current item. ``FILTER`` also gained an explicit ``REGEX`` keyword.
|
||||
|
||||
@@ -2228,6 +2228,12 @@ static const struct ListNode : public cmGeneratorExpressionNode
|
||||
bool ShouldEvaluateNextParameter(std::vector<std::string> const& parameters,
|
||||
std::string&) const override
|
||||
{
|
||||
// Leave the FILTER PREDICATE <body> (slot 4) unevaluated so $<_0> is never
|
||||
// evaluated unbound.
|
||||
if (parameters.size() == 4 && parameters[0] == "FILTER" &&
|
||||
parameters[3] == "PREDICATE") {
|
||||
return false;
|
||||
}
|
||||
// Leave a TRANSFORM PREDICATE selector's <body> unevaluated. PREDICATE is
|
||||
// the selector keyword only when it sits exactly at the selector position
|
||||
// (not when it is a literal action argument such as APPEND PREDICATE).
|
||||
@@ -2314,6 +2320,48 @@ static const struct ListNode : public cmGeneratorExpressionNode
|
||||
.to_string();
|
||||
}
|
||||
|
||||
// FILTER ... PREDICATE <body>: genex-native predicate filter.
|
||||
if (parameters.size() >= 4 && parameters[0] == "FILTER" &&
|
||||
parameters[3] == "PREDICATE") {
|
||||
if (parameters.size() != 5) {
|
||||
reportError(eval, content->GetOriginalExpression(),
|
||||
"sub-command FILTER, PREDICATE expects a single <body> "
|
||||
"argument.");
|
||||
return std::string();
|
||||
}
|
||||
std::string const& op = parameters[2];
|
||||
if (op != "INCLUDE" && op != "EXCLUDE") {
|
||||
reportError(
|
||||
eval, content->GetOriginalExpression(),
|
||||
cmStrCat("sub-command FILTER does not recognize operator \"", op,
|
||||
"\". It must be either INCLUDE or EXCLUDE."));
|
||||
return std::string();
|
||||
}
|
||||
cmList list = GetList(parameters[1]);
|
||||
if (list.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
cmGeneratorExpressionEvaluatorVector const& predicateBody =
|
||||
content->GetParamChildren()[4];
|
||||
auto mask = EvaluatePredicateMask(predicateBody, list, "FILTER"_s, eval,
|
||||
content, dagChecker);
|
||||
if (!mask) {
|
||||
return std::string();
|
||||
}
|
||||
bool const keepWhenTrue = (op == "INCLUDE");
|
||||
std::vector<std::string> out;
|
||||
std::size_t i = 0;
|
||||
for (auto const& element : list) {
|
||||
if ((*mask)[i] == keepWhenTrue) {
|
||||
out.push_back(element);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return cmList{ out.begin(), out.end(), cmList::ExpandElements::No,
|
||||
cmList::EmptyElements::Yes }
|
||||
.to_string();
|
||||
}
|
||||
|
||||
static std::unordered_map<
|
||||
cm::string_view,
|
||||
std::function<std::string(cm::GenEx::Evaluation*,
|
||||
@@ -2526,7 +2574,13 @@ static const struct ListNode : public cmGeneratorExpressionNode
|
||||
{ "FILTER"_s,
|
||||
[](cm::GenEx::Evaluation* ev, GeneratorExpressionContent const* cnt,
|
||||
Arguments& args) -> std::string {
|
||||
if (CheckListParameters(ev, cnt, "FILTER"_s, args, 3)) {
|
||||
// args = [list, INCLUDE|EXCLUDE, <regex> | REGEX <regex>].
|
||||
// (PREDICATE is handled up-front in Evaluate and never reaches
|
||||
// here.)
|
||||
bool const explicitRegex =
|
||||
args.size() >= 3 && args[2] == "REGEX"_s;
|
||||
int const required = explicitRegex ? 4 : 3;
|
||||
if (CheckListParameters(ev, cnt, "FILTER"_s, args, required)) {
|
||||
auto const& op = args[1];
|
||||
if (op != "INCLUDE"_s && op != "EXCLUDE"_s) {
|
||||
reportError(
|
||||
@@ -2535,9 +2589,10 @@ static const struct ListNode : public cmGeneratorExpressionNode
|
||||
op, "\". It must be either INCLUDE or EXCLUDE."));
|
||||
return std::string{};
|
||||
}
|
||||
auto const& regex = explicitRegex ? args[3] : args[2];
|
||||
try {
|
||||
return GetList(args.front())
|
||||
.filter(args[2],
|
||||
.filter(regex,
|
||||
op == "INCLUDE"_s ? cmList::FilterMode::INCLUDE
|
||||
: cmList::FilterMode::EXCLUDE)
|
||||
.to_string();
|
||||
@@ -2545,7 +2600,7 @@ static const struct ListNode : public cmGeneratorExpressionNode
|
||||
reportError(
|
||||
ev, cnt->GetOriginalExpression(),
|
||||
cmStrCat("sub-command FILTER, failed to compile regex \"",
|
||||
args[2], "\"."));
|
||||
regex, "\"."));
|
||||
return std::string{};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,43 @@ bool expectEq(char const* name, std::string const& got,
|
||||
}
|
||||
}
|
||||
|
||||
static bool testFilterPredicateInclude()
|
||||
{
|
||||
GenExFixture fx;
|
||||
// Keep only elements equal to "a".
|
||||
return expectEq(
|
||||
"testFilterPredicateInclude",
|
||||
fx.Eval("$<LIST:FILTER,a;b;a;c,INCLUDE,PREDICATE,$<STREQUAL:$<_0>,a>>"),
|
||||
"a;a");
|
||||
}
|
||||
|
||||
static bool testFilterPredicateExclude()
|
||||
{
|
||||
GenExFixture fx;
|
||||
// Drop elements equal to "a".
|
||||
return expectEq(
|
||||
"testFilterPredicateExclude",
|
||||
fx.Eval("$<LIST:FILTER,a;b;a;c,EXCLUDE,PREDICATE,$<STREQUAL:$<_0>,a>>"),
|
||||
"b;c");
|
||||
}
|
||||
|
||||
static bool testFilterRegexKeyword()
|
||||
{
|
||||
GenExFixture fx;
|
||||
// Explicit REGEX keyword behaves like the bare form.
|
||||
return expectEq("testFilterRegexKeyword",
|
||||
fx.Eval("$<LIST:FILTER,foo;bar;baz,INCLUDE,REGEX,^ba>"),
|
||||
"bar;baz");
|
||||
}
|
||||
|
||||
static bool testFilterBareRegexUnchanged()
|
||||
{
|
||||
GenExFixture fx;
|
||||
// The legacy bare form still works.
|
||||
return expectEq("testFilterBareRegexUnchanged",
|
||||
fx.Eval("$<LIST:FILTER,foo;bar;baz,EXCLUDE,^ba>"), "foo");
|
||||
}
|
||||
|
||||
static bool testCannedTransformStillWorks()
|
||||
{
|
||||
GenExFixture fx;
|
||||
@@ -99,6 +136,13 @@ static bool testTransformPredicateApplyShadowing()
|
||||
"aa;bb");
|
||||
}
|
||||
|
||||
static bool testFilterPredicateEmptyList()
|
||||
{
|
||||
GenExFixture fx;
|
||||
return expectEq("testFilterPredicateEmptyList",
|
||||
fx.Eval("$<LIST:FILTER,,INCLUDE,PREDICATE,1>"), "");
|
||||
}
|
||||
|
||||
static bool testTransformPredicateEmptyList()
|
||||
{
|
||||
GenExFixture fx;
|
||||
@@ -108,6 +152,18 @@ static bool testTransformPredicateEmptyList()
|
||||
|
||||
int testGenExListPredicate(int /*argc*/, char* /*argv*/[])
|
||||
{
|
||||
if (!testFilterPredicateInclude()) {
|
||||
return 1;
|
||||
}
|
||||
if (!testFilterPredicateExclude()) {
|
||||
return 1;
|
||||
}
|
||||
if (!testFilterRegexKeyword()) {
|
||||
return 1;
|
||||
}
|
||||
if (!testFilterBareRegexUnchanged()) {
|
||||
return 1;
|
||||
}
|
||||
if (!testCannedTransformStillWorks()) {
|
||||
return 1;
|
||||
}
|
||||
@@ -123,6 +179,9 @@ int testGenExListPredicate(int /*argc*/, char* /*argv*/[])
|
||||
if (!testTransformPredicateApplyShadowing()) {
|
||||
return 1;
|
||||
}
|
||||
if (!testFilterPredicateEmptyList()) {
|
||||
return 1;
|
||||
}
|
||||
if (!testTransformPredicateEmptyList()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
file(READ "${RunCMake_TEST_BINARY_DIR}/out.txt" actual)
|
||||
string(STRIP "${actual}" actual)
|
||||
if(NOT actual STREQUAL "beta")
|
||||
set(RunCMake_TEST_FAILED "unexpected FILTER PREDICATE output: [${actual}]")
|
||||
endif()
|
||||
@@ -0,0 +1,3 @@
|
||||
set(input "alpha;beta;gamma")
|
||||
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/out.txt"
|
||||
CONTENT "$<LIST:FILTER,${input},INCLUDE,PREDICATE,$<STREQUAL:$<_0>,beta>>\n")
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1 @@
|
||||
sub-command FILTER, PREDICATE expects a single <body> argument
|
||||
@@ -0,0 +1,2 @@
|
||||
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/x.txt"
|
||||
CONTENT "$<LIST:FILTER,a;b,INCLUDE,PREDICATE>")
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1 @@
|
||||
PREDICATE body must evaluate to "0" or "1"
|
||||
@@ -0,0 +1,2 @@
|
||||
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/x.txt"
|
||||
CONTENT "$<LIST:FILTER,a;b,INCLUDE,PREDICATE,maybe>")
|
||||
@@ -64,10 +64,13 @@ run_cmake(ListTransformApplyNested)
|
||||
run_cmake(ListTransformApplyBadSelector)
|
||||
run_cmake(ListTransformApplyBodyError)
|
||||
run_cmake(ListTransformApplyMissingBody)
|
||||
run_cmake(ListFilterPredicate)
|
||||
run_cmake(ListTransformPredicateNonBool)
|
||||
run_cmake(ListTransformPredicateCombined)
|
||||
run_cmake(ListTransformPredicateMissingBody)
|
||||
run_cmake(ListTransformPredicateLinkLibraries)
|
||||
run_cmake(ListFilterPredicateMissingBody)
|
||||
run_cmake(ListFilterPredicateNonBool)
|
||||
run_cmake(BoundOperandOutsideBinding)
|
||||
|
||||
function(run_cmake_build test)
|
||||
|
||||
Reference in New Issue
Block a user