mirror of
https://github.com/Kitware/CMake.git
synced 2026-06-30 19:57:41 +00:00
GenEx: factor TRANSFORM selector parsing and selection
Give the AT/FOR/REGEX selector syntax shared by the $<LIST:TRANSFORM> actions a single definition, so its diagnostics and index/REGEX semantics cannot drift between actions. Also let a caller learn which elements a selector picks without running a transform, as groundwork for actions that drive their own per-element loop. Issue: #27892
This commit is contained in:
@@ -1858,6 +1858,122 @@ inline cmList GetList(std::string const& list)
|
||||
{
|
||||
return list.empty() ? cmList{} : cmList{ list, cmList::EmptyElements::Yes };
|
||||
}
|
||||
|
||||
// Parse the optional trailing selector of a $<LIST:TRANSFORM,...> action
|
||||
// (AT <i>... / FOR <start> <stop> [<step>] / REGEX <re>) into a
|
||||
// cmList::TransformSelector. Returns nullptr (after reporting via `eval`) on
|
||||
// a malformed selector; empty `tokens` yields a select-all selector.
|
||||
std::unique_ptr<cmList::TransformSelector> ParseTransformSelector(
|
||||
std::vector<std::string> const& tokens, cm::GenEx::Evaluation* eval,
|
||||
GeneratorExpressionContent const* content)
|
||||
{
|
||||
static std::string const REGEX{ "REGEX" };
|
||||
static std::string const AT{ "AT" };
|
||||
static std::string const FOR{ "FOR" };
|
||||
|
||||
std::unique_ptr<cmList::TransformSelector> selector;
|
||||
|
||||
std::size_t i = 0;
|
||||
while (i < tokens.size()) {
|
||||
std::string const& tok = tokens[i];
|
||||
|
||||
if ((tok == REGEX || tok == AT || tok == FOR) && selector) {
|
||||
reportError(
|
||||
eval, content->GetOriginalExpression(),
|
||||
cmStrCat("sub-command TRANSFORM, selector already specified (",
|
||||
selector->GetTag(), ")."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (tok == REGEX) {
|
||||
if (i + 1 >= tokens.size()) {
|
||||
reportError(eval, content->GetOriginalExpression(),
|
||||
"sub-command TRANSFORM, selector REGEX expects "
|
||||
"'regular expression' argument.");
|
||||
return nullptr;
|
||||
}
|
||||
selector =
|
||||
cmList::TransformSelector::New<cmList::TransformSelector::REGEX>(
|
||||
tokens[i + 1]);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok == AT) {
|
||||
++i;
|
||||
std::vector<cmList::index_type> indexes;
|
||||
for (; i < tokens.size(); ++i) {
|
||||
cmList indexList{ tokens[i] };
|
||||
for (auto const& index : indexList) {
|
||||
cmList::index_type value;
|
||||
if (!GetNumericArgument(index, value)) {
|
||||
reportError(eval, content->GetOriginalExpression(),
|
||||
cmStrCat("sub-command TRANSFORM, selector AT: '",
|
||||
index, "': unexpected argument."));
|
||||
return nullptr;
|
||||
}
|
||||
indexes.push_back(value);
|
||||
}
|
||||
}
|
||||
if (indexes.empty()) {
|
||||
reportError(eval, content->GetOriginalExpression(),
|
||||
"sub-command TRANSFORM, selector AT expects at least one "
|
||||
"numeric value.");
|
||||
return nullptr;
|
||||
}
|
||||
selector = cmList::TransformSelector::New<cmList::TransformSelector::AT>(
|
||||
std::move(indexes));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tok == FOR) {
|
||||
if (i + 2 >= tokens.size()) {
|
||||
reportError(eval, content->GetOriginalExpression(),
|
||||
"sub-command TRANSFORM, selector FOR expects, at least, "
|
||||
"two arguments.");
|
||||
return nullptr;
|
||||
}
|
||||
cmList::index_type start = 0;
|
||||
cmList::index_type stop = 0;
|
||||
cmList::index_type step = 1;
|
||||
if (!GetNumericArgument(tokens[i + 1], start) ||
|
||||
!GetNumericArgument(tokens[i + 2], stop)) {
|
||||
reportError(eval, content->GetOriginalExpression(),
|
||||
"sub-command TRANSFORM, selector FOR expects, at least, "
|
||||
"two numeric values.");
|
||||
return nullptr;
|
||||
}
|
||||
i += 3;
|
||||
if (i < tokens.size()) {
|
||||
if (!GetNumericArgument(tokens[i], step)) {
|
||||
step = -1;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
if (step <= 0) {
|
||||
reportError(eval, content->GetOriginalExpression(),
|
||||
"sub-command TRANSFORM, selector FOR expects positive "
|
||||
"numeric value for <step>.");
|
||||
return nullptr;
|
||||
}
|
||||
selector =
|
||||
cmList::TransformSelector::New<cmList::TransformSelector::FOR>(
|
||||
{ start, stop, step });
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<std::string> const rest(tokens.begin() + i, tokens.end());
|
||||
reportError(eval, content->GetOriginalExpression(),
|
||||
cmStrCat("sub-command TRANSFORM, '", cmJoin(rest, ", "),
|
||||
"': unexpected argument(s)."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!selector) {
|
||||
selector = cmList::TransformSelector::New();
|
||||
}
|
||||
return selector;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct ListNode : public cmGeneratorExpressionNode
|
||||
@@ -2177,137 +2293,14 @@ static const struct ListNode : public cmGeneratorExpressionNode
|
||||
args.advance(descriptor->Arity);
|
||||
}
|
||||
|
||||
std::string const REGEX{ "REGEX" };
|
||||
std::string const AT{ "AT" };
|
||||
std::string const FOR{ "FOR" };
|
||||
std::unique_ptr<cmList::TransformSelector> selector;
|
||||
|
||||
try {
|
||||
// handle optional arguments
|
||||
while (!args.empty()) {
|
||||
if ((args.front() == REGEX || args.front() == AT ||
|
||||
args.front() == FOR) &&
|
||||
selector) {
|
||||
reportError(ev, cnt->GetOriginalExpression(),
|
||||
cmStrCat("sub-command TRANSFORM, selector "
|
||||
"already specified (",
|
||||
selector->GetTag(), ")."));
|
||||
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
// REGEX selector
|
||||
if (args.front() == REGEX) {
|
||||
if (args.advance(1).empty()) {
|
||||
reportError(
|
||||
ev, cnt->GetOriginalExpression(),
|
||||
"sub-command TRANSFORM, selector REGEX expects "
|
||||
"'regular expression' argument.");
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
selector = cmList::TransformSelector::New<
|
||||
cmList::TransformSelector::REGEX>(args.front());
|
||||
|
||||
args.advance(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// AT selector
|
||||
if (args.front() == AT) {
|
||||
args.advance(1);
|
||||
// get all specified indexes
|
||||
std::vector<cmList::index_type> indexes;
|
||||
while (!args.empty()) {
|
||||
cmList indexList{ args.front() };
|
||||
for (auto const& index : indexList) {
|
||||
cmList::index_type value;
|
||||
|
||||
if (!GetNumericArgument(index, value)) {
|
||||
// this is not a number, stop processing
|
||||
reportError(
|
||||
ev, cnt->GetOriginalExpression(),
|
||||
cmStrCat("sub-command TRANSFORM, selector AT: '",
|
||||
index, "': unexpected argument."));
|
||||
return std::string{};
|
||||
}
|
||||
indexes.push_back(value);
|
||||
}
|
||||
args.advance(1);
|
||||
}
|
||||
|
||||
if (indexes.empty()) {
|
||||
reportError(ev, cnt->GetOriginalExpression(),
|
||||
"sub-command TRANSFORM, selector AT "
|
||||
"expects at least one "
|
||||
"numeric value.");
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
selector = cmList::TransformSelector::New<
|
||||
cmList::TransformSelector::AT>(std::move(indexes));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// FOR selector
|
||||
if (args.front() == FOR) {
|
||||
if (args.advance(1).size() < 2) {
|
||||
reportError(ev, cnt->GetOriginalExpression(),
|
||||
"sub-command TRANSFORM, selector FOR "
|
||||
"expects, at least,"
|
||||
" two arguments.");
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
cmList::index_type start = 0;
|
||||
cmList::index_type stop = 0;
|
||||
cmList::index_type step = 1;
|
||||
bool valid = false;
|
||||
|
||||
if (GetNumericArgument(args.front(), start) &&
|
||||
GetNumericArgument(args.advance(1).front(), stop)) {
|
||||
valid = true;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
reportError(
|
||||
ev, cnt->GetOriginalExpression(),
|
||||
"sub-command TRANSFORM, selector FOR expects, "
|
||||
"at least, two numeric values.");
|
||||
return std::string{};
|
||||
}
|
||||
// try to read a third numeric value for step
|
||||
if (!args.advance(1).empty()) {
|
||||
if (!GetNumericArgument(args.front(), step)) {
|
||||
// this is not a number
|
||||
step = -1;
|
||||
}
|
||||
args.advance(1);
|
||||
}
|
||||
|
||||
if (step <= 0) {
|
||||
reportError(
|
||||
ev, cnt->GetOriginalExpression(),
|
||||
"sub-command TRANSFORM, selector FOR expects "
|
||||
"positive numeric value for <step>.");
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
selector = cmList::TransformSelector::New<
|
||||
cmList::TransformSelector::FOR>({ start, stop, step });
|
||||
continue;
|
||||
}
|
||||
|
||||
reportError(ev, cnt->GetOriginalExpression(),
|
||||
cmStrCat("sub-command TRANSFORM, '",
|
||||
cmJoin(args, ", "),
|
||||
"': unexpected argument(s)."));
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
std::vector<std::string> const tokens(args.begin(),
|
||||
args.end());
|
||||
selector = ParseTransformSelector(tokens, ev, cnt);
|
||||
if (!selector) {
|
||||
selector = cmList::TransformSelector::New();
|
||||
return std::string{};
|
||||
}
|
||||
selector->Makefile = ev->Context.LG->GetMakefile();
|
||||
|
||||
|
||||
@@ -437,6 +437,17 @@ public:
|
||||
std::transform(list.begin(), list.end(), list.begin(), transform);
|
||||
}
|
||||
|
||||
// Return, for each element, whether the selector selects it via InSelection.
|
||||
virtual std::vector<bool> Selection(cmList::container_type const& list)
|
||||
{
|
||||
std::vector<bool> selected;
|
||||
selected.reserve(list.size());
|
||||
for (auto const& value : list) {
|
||||
selected.push_back(this->InSelection(value));
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
protected:
|
||||
TransformSelector(std::string&& tag)
|
||||
: Tag(std::move(tag))
|
||||
@@ -516,6 +527,19 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Select the computed Indexes; Validate throws transform_error on an
|
||||
// out-of-range index.
|
||||
std::vector<bool> Selection(cmList::container_type const& list) override
|
||||
{
|
||||
this->Validate(list.size());
|
||||
|
||||
std::vector<bool> selected(list.size(), false);
|
||||
for (auto index : this->Indexes) {
|
||||
selected[index] = true;
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
protected:
|
||||
TransformSelectorIndexes(std::string&& tag)
|
||||
: TransformSelector(std::move(tag))
|
||||
@@ -1148,6 +1172,12 @@ cmList& cmList::transform(TransformAction action, std::string const& arg,
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<bool> cmList::GetTransformSelection(
|
||||
cmList::TransformSelector& selector) const
|
||||
{
|
||||
return static_cast<::TransformSelector&>(selector).Selection(this->Values);
|
||||
}
|
||||
|
||||
std::string& cmList::append(std::string& list, std::string&& value)
|
||||
{
|
||||
if (list.empty()) {
|
||||
|
||||
@@ -975,6 +975,11 @@ public:
|
||||
cmMakefile& makefile,
|
||||
std::unique_ptr<TransformSelector> = {});
|
||||
|
||||
// Return, for each element of this list, whether `selector` selects it.
|
||||
// Throws transform_error on a malformed selector (e.g. an out-of-range
|
||||
// index), like transform().
|
||||
std::vector<bool> GetTransformSelection(TransformSelector& selector) const;
|
||||
|
||||
std::string join(cm::string_view glue) const
|
||||
{
|
||||
return cmList::Join(this->Values, glue);
|
||||
|
||||
Reference in New Issue
Block a user