mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
GenEx: generalize bound operands to a frame and add $<_1>
Allow a binding operation to bind more than one operand at once, exposed as $<_0>, $<_1>, .... A single value remains the common case, but the upcoming SORT COMPARATOR must bind the two elements being compared, so the binding becomes an indexed frame and $<_1> is added. Referencing an index the active binding does not provide is reported as an error. Issue: #27892
This commit is contained in:
+20
-8
@@ -2,8 +2,10 @@
|
||||
file LICENSE.rst or https://cmake.org/licensing for details. */
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cm/optional>
|
||||
|
||||
@@ -26,26 +28,36 @@ struct Context final
|
||||
void SetCMP0189(cmPolicies::PolicyStatus cmp0189);
|
||||
cmPolicies::PolicyStatus GetCMP0189() const;
|
||||
|
||||
void SetBoundOperands(std::vector<std::string> operands);
|
||||
void SetBoundOperand(std::string value);
|
||||
bool HasBoundOperand() const;
|
||||
std::string const& GetBoundOperand() const;
|
||||
std::size_t BoundOperandCount() const;
|
||||
bool HasBoundOperand(std::size_t index = 0) const;
|
||||
std::string const& GetBoundOperand(std::size_t index = 0) const;
|
||||
|
||||
private:
|
||||
cm::optional<cmPolicies::PolicyStatus> CMP0189;
|
||||
cm::optional<std::string> BoundOperand;
|
||||
std::vector<std::string> BoundOperands;
|
||||
};
|
||||
|
||||
inline void Context::SetBoundOperands(std::vector<std::string> operands)
|
||||
{
|
||||
this->BoundOperands = std::move(operands);
|
||||
}
|
||||
inline void Context::SetBoundOperand(std::string value)
|
||||
{
|
||||
this->BoundOperand = std::move(value);
|
||||
this->SetBoundOperands({ std::move(value) });
|
||||
}
|
||||
inline bool Context::HasBoundOperand() const
|
||||
inline std::size_t Context::BoundOperandCount() const
|
||||
{
|
||||
return this->BoundOperand.has_value();
|
||||
return this->BoundOperands.size();
|
||||
}
|
||||
inline std::string const& Context::GetBoundOperand() const
|
||||
inline bool Context::HasBoundOperand(std::size_t index) const
|
||||
{
|
||||
return *this->BoundOperand;
|
||||
return index < this->BoundOperandCount();
|
||||
}
|
||||
inline std::string const& Context::GetBoundOperand(std::size_t index) const
|
||||
{
|
||||
return this->BoundOperands[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,17 +107,17 @@ std::string cmGeneratorExpressionNode::EvaluateDependentExpression(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Re-evaluate the unevaluated <body> subtree of a binding operation with
|
||||
// `$<_0>` bound to the given operand. A fresh Evaluation is built from a
|
||||
// copied, mutated Context so that nested binding operations can shadow `$<_0>`
|
||||
// and restore it on exit.
|
||||
static std::string EvaluateBodyWithBoundOperand(
|
||||
// Re-evaluate the unevaluated <body> subtree of a binding operation with the
|
||||
// given operands bound (accessible as $<_0>, $<_1>, ...). A fresh Evaluation
|
||||
// is built from a copied, mutated Context so that nested binding operations
|
||||
// can shadow the operands and restore them on exit.
|
||||
static std::string EvaluateBodyWithBoundOperands(
|
||||
cmGeneratorExpressionEvaluatorVector const& bodyExpr,
|
||||
std::string const& operand, cm::GenEx::Evaluation* eval,
|
||||
std::vector<std::string> operands, cm::GenEx::Evaluation* eval,
|
||||
cmGeneratorExpressionDAGChecker* dagChecker)
|
||||
{
|
||||
cm::GenEx::Context elemContext = eval->Context; // copy
|
||||
elemContext.SetBoundOperand(operand);
|
||||
elemContext.SetBoundOperands(std::move(operands));
|
||||
cm::GenEx::Evaluation elemEval(
|
||||
elemContext, eval->Quiet, eval->HeadTarget, eval->CurrentTarget,
|
||||
eval->EvaluateForBuildsystem, eval->Backtrace);
|
||||
@@ -149,6 +149,15 @@ static std::string EvaluateBodyWithBoundOperand(
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string EvaluateBodyWithBoundOperand(
|
||||
cmGeneratorExpressionEvaluatorVector const& bodyExpr,
|
||||
std::string const& operand, cm::GenEx::Evaluation* eval,
|
||||
cmGeneratorExpressionDAGChecker* dagChecker)
|
||||
{
|
||||
return EvaluateBodyWithBoundOperands(bodyExpr, { operand }, eval,
|
||||
dagChecker);
|
||||
}
|
||||
|
||||
// Evaluate `predicateBody` once per element of `list`, binding `$<_0>` to the
|
||||
// element (reusing EvaluateBodyWithBoundOperand). Each result must be exactly
|
||||
// "0" or "1". Returns the per-element boolean mask, or cm::nullopt after
|
||||
@@ -220,9 +229,12 @@ static const struct OneNode : public cmGeneratorExpressionNode
|
||||
}
|
||||
} oneNode;
|
||||
|
||||
static const struct BoundOperandNode : public cmGeneratorExpressionNode
|
||||
struct BoundOperandNode : public cmGeneratorExpressionNode
|
||||
{
|
||||
BoundOperandNode() {} // NOLINT(modernize-use-equals-default)
|
||||
explicit BoundOperandNode(std::size_t index)
|
||||
: Index(index)
|
||||
{
|
||||
}
|
||||
|
||||
int NumExpectedParameters() const override { return 0; }
|
||||
|
||||
@@ -231,15 +243,31 @@ static const struct BoundOperandNode : public cmGeneratorExpressionNode
|
||||
cm::GenEx::Evaluation* eval, GeneratorExpressionContent const* content,
|
||||
cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override
|
||||
{
|
||||
if (!eval->Context.HasBoundOperand()) {
|
||||
reportError(eval, content->GetOriginalExpression(),
|
||||
"$<_0> may only be used inside the body of a binding "
|
||||
"operation.");
|
||||
if (!eval->Context.HasBoundOperand(this->Index)) {
|
||||
std::size_t const count = eval->Context.BoundOperandCount();
|
||||
if (count == 0) {
|
||||
reportError(eval, content->GetOriginalExpression(),
|
||||
cmStrCat("$<_", this->Index,
|
||||
"> may only be used inside the body of a binding "
|
||||
"operation."));
|
||||
} else {
|
||||
reportError(
|
||||
eval, content->GetOriginalExpression(),
|
||||
cmStrCat(
|
||||
"$<_", this->Index,
|
||||
"> is out of range for the current binding operation, which "
|
||||
"binds only ",
|
||||
count, " operand(s) (maximum $<_", count - 1, ">)."));
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
return eval->Context.GetBoundOperand();
|
||||
return eval->Context.GetBoundOperand(this->Index);
|
||||
}
|
||||
} boundOperandNode;
|
||||
|
||||
std::size_t Index;
|
||||
};
|
||||
static BoundOperandNode const boundOperandNode0{ 0 };
|
||||
static BoundOperandNode const boundOperandNode1{ 1 };
|
||||
|
||||
static const struct OneNode buildInterfaceNode;
|
||||
|
||||
@@ -6279,7 +6307,8 @@ cmGeneratorExpressionNode const* cmGeneratorExpressionNode::GetNode(
|
||||
{ "PATH_EQUAL", &pathEqualNode },
|
||||
{ "MAKE_C_IDENTIFIER", &makeCIdentifierNode },
|
||||
{ "BOOL", &boolNode },
|
||||
{ "_0", &boundOperandNode },
|
||||
{ "_0", &boundOperandNode0 },
|
||||
{ "_1", &boundOperandNode1 },
|
||||
{ "IF", &ifNode },
|
||||
{ "ANGLE-R", &angle_rNode },
|
||||
{ "COMMA", &commaNode },
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
file LICENSE.rst or https://cmake.org/licensing for details. */
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cmGenExContext.h"
|
||||
|
||||
@@ -9,22 +10,57 @@ static bool testContextBinding()
|
||||
{
|
||||
cm::GenEx::Context ctx(nullptr, "Debug");
|
||||
bool ok = true;
|
||||
if (ctx.HasBoundOperand()) {
|
||||
if (ctx.HasBoundOperand() || ctx.BoundOperandCount() != 0) {
|
||||
std::cerr << "binding should start unset\n";
|
||||
ok = false;
|
||||
}
|
||||
ctx.SetBoundOperand("net");
|
||||
if (!ctx.HasBoundOperand() || ctx.GetBoundOperand() != "net") {
|
||||
if (!ctx.HasBoundOperand() || ctx.BoundOperandCount() != 1 ||
|
||||
ctx.GetBoundOperand() != "net") {
|
||||
std::cerr << "binding did not round-trip\n";
|
||||
ok = false;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool testContextMultipleOperands()
|
||||
{
|
||||
cm::GenEx::Context ctx(nullptr, "Debug");
|
||||
bool ok = true;
|
||||
ctx.SetBoundOperands({ "a", "b" });
|
||||
if (ctx.BoundOperandCount() != 2 || !ctx.HasBoundOperand(0) ||
|
||||
!ctx.HasBoundOperand(1) || ctx.GetBoundOperand(0) != "a" ||
|
||||
ctx.GetBoundOperand(1) != "b") {
|
||||
std::cerr << "two-operand binding did not round-trip\n";
|
||||
ok = false;
|
||||
}
|
||||
if (ctx.HasBoundOperand(2)) {
|
||||
std::cerr << "index past the frame should be out of range\n";
|
||||
ok = false;
|
||||
}
|
||||
// Re-binding replaces the whole frame, which the shadow/restore of nested
|
||||
// bindings relies on.
|
||||
ctx.SetBoundOperand("x");
|
||||
if (ctx.BoundOperandCount() != 1 || ctx.HasBoundOperand(1) ||
|
||||
ctx.GetBoundOperand(0) != "x") {
|
||||
std::cerr << "re-binding did not replace the frame\n";
|
||||
ok = false;
|
||||
}
|
||||
ctx.SetBoundOperands({});
|
||||
if (ctx.BoundOperandCount() != 0 || ctx.HasBoundOperand(0)) {
|
||||
std::cerr << "empty frame should clear the binding\n";
|
||||
ok = false;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
int testGenExBoundOperand(int /*argc*/, char* /*argv*/[])
|
||||
{
|
||||
if (!testContextBinding()) {
|
||||
return 1;
|
||||
}
|
||||
if (!testContextMultipleOperands()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1 @@
|
||||
is out of range for the current binding operation
|
||||
@@ -0,0 +1,4 @@
|
||||
# $<_1> requires a binary binding (e.g. SORT COMPARATOR); using it in a unary
|
||||
# APPLY body, which binds only $<_0>, is an error.
|
||||
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/x.txt"
|
||||
CONTENT "$<LIST:TRANSFORM,a;b,APPLY,X$<_1>Y>")
|
||||
@@ -72,6 +72,7 @@ run_cmake(ListTransformPredicateLinkLibraries)
|
||||
run_cmake(ListFilterPredicateMissingBody)
|
||||
run_cmake(ListFilterPredicateNonBool)
|
||||
run_cmake(BoundOperandOutsideBinding)
|
||||
run_cmake(BoundOperand1OutsideBinding)
|
||||
|
||||
function(run_cmake_build test)
|
||||
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build)
|
||||
|
||||
Reference in New Issue
Block a user