CMake Nightly Date Stamp
[kiteware-cmake.git] / Source / cmConditionEvaluator.cxx
blob5a2b33a47eb716c5b1909a0c870f864fbbf32b18
1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmConditionEvaluator.h"
5 #include <array>
6 #include <cstdio>
7 #include <cstdlib>
8 #include <functional>
9 #include <iterator>
10 #include <list>
11 #include <sstream>
12 #include <utility>
14 #include <cm/string_view>
15 #include <cmext/algorithm>
17 #include "cmsys/RegularExpression.hxx"
19 #include "cmCMakePath.h"
20 #include "cmExpandedCommandArgument.h"
21 #include "cmList.h"
22 #include "cmMakefile.h"
23 #include "cmMessageType.h"
24 #include "cmState.h"
25 #include "cmStringAlgorithms.h"
26 #include "cmSystemTools.h"
27 #include "cmValue.h"
28 #include "cmake.h"
30 namespace {
31 auto const keyAND = "AND"_s;
32 auto const keyCOMMAND = "COMMAND"_s;
33 auto const keyDEFINED = "DEFINED"_s;
34 auto const keyEQUAL = "EQUAL"_s;
35 auto const keyEXISTS = "EXISTS"_s;
36 auto const keyIS_READABLE = "IS_READABLE"_s;
37 auto const keyIS_WRITABLE = "IS_WRITABLE"_s;
38 auto const keyIS_EXECUTABLE = "IS_EXECUTABLE"_s;
39 auto const keyGREATER = "GREATER"_s;
40 auto const keyGREATER_EQUAL = "GREATER_EQUAL"_s;
41 auto const keyIN_LIST = "IN_LIST"_s;
42 auto const keyIS_ABSOLUTE = "IS_ABSOLUTE"_s;
43 auto const keyIS_DIRECTORY = "IS_DIRECTORY"_s;
44 auto const keyIS_NEWER_THAN = "IS_NEWER_THAN"_s;
45 auto const keyIS_SYMLINK = "IS_SYMLINK"_s;
46 auto const keyLESS = "LESS"_s;
47 auto const keyLESS_EQUAL = "LESS_EQUAL"_s;
48 auto const keyMATCHES = "MATCHES"_s;
49 auto const keyNOT = "NOT"_s;
50 auto const keyOR = "OR"_s;
51 auto const keyParenL = "("_s;
52 auto const keyParenR = ")"_s;
53 auto const keyPOLICY = "POLICY"_s;
54 auto const keySTREQUAL = "STREQUAL"_s;
55 auto const keySTRGREATER = "STRGREATER"_s;
56 auto const keySTRGREATER_EQUAL = "STRGREATER_EQUAL"_s;
57 auto const keySTRLESS = "STRLESS"_s;
58 auto const keySTRLESS_EQUAL = "STRLESS_EQUAL"_s;
59 auto const keyTARGET = "TARGET"_s;
60 auto const keyTEST = "TEST"_s;
61 auto const keyVERSION_EQUAL = "VERSION_EQUAL"_s;
62 auto const keyVERSION_GREATER = "VERSION_GREATER"_s;
63 auto const keyVERSION_GREATER_EQUAL = "VERSION_GREATER_EQUAL"_s;
64 auto const keyVERSION_LESS = "VERSION_LESS"_s;
65 auto const keyVERSION_LESS_EQUAL = "VERSION_LESS_EQUAL"_s;
66 auto const keyPATH_EQUAL = "PATH_EQUAL"_s;
68 cmSystemTools::CompareOp const MATCH2CMPOP[5] = {
69 cmSystemTools::OP_LESS, cmSystemTools::OP_LESS_EQUAL,
70 cmSystemTools::OP_GREATER, cmSystemTools::OP_GREATER_EQUAL,
71 cmSystemTools::OP_EQUAL
74 // Run-Time to Compile-Time template selector
75 template <template <typename> class Comp, template <typename> class... Ops>
76 struct cmRt2CtSelector
78 template <typename T>
79 static bool eval(int r, T lhs, T rhs)
81 switch (r) {
82 case 0:
83 return false;
84 case 1:
85 return Comp<T>()(lhs, rhs);
86 default:
87 return cmRt2CtSelector<Ops...>::eval(r - 1, lhs, rhs);
92 template <template <typename> class Comp>
93 struct cmRt2CtSelector<Comp>
95 template <typename T>
96 static bool eval(int r, T lhs, T rhs)
98 return r == 1 && Comp<T>()(lhs, rhs);
102 std::string bool2string(bool const value)
104 return std::string(static_cast<std::size_t>(1),
105 static_cast<char>('0' + static_cast<int>(value)));
108 bool looksLikeSpecialVariable(const std::string& var,
109 cm::static_string_view prefix,
110 const std::size_t varNameLen)
112 // NOTE Expecting a variable name at least 1 char length:
113 // <prefix> + `{` + <varname> + `}`
114 return ((prefix.size() + 3) <= varNameLen) &&
115 cmHasPrefix(var, cmStrCat(prefix, '{')) && var[varNameLen - 1] == '}';
117 } // anonymous namespace
119 #if defined(__SUNPRO_CC)
120 # define CM_INHERIT_CTOR(Class, Base, Tpl) \
121 template <typename... Args> \
122 Class(Args&&... args) \
123 : Base Tpl(std::forward<Args>(args)...) \
126 #else
127 # define CM_INHERIT_CTOR(Class, Base, Tpl) using Base Tpl ::Base
128 #endif
130 // BEGIN cmConditionEvaluator::cmArgumentList
131 class cmConditionEvaluator::cmArgumentList
132 : public std::list<cmExpandedCommandArgument>
134 using base_t = std::list<cmExpandedCommandArgument>;
136 public:
137 CM_INHERIT_CTOR(cmArgumentList, list, <cmExpandedCommandArgument>);
139 class CurrentAndNextIter
141 friend class cmConditionEvaluator::cmArgumentList;
143 public:
144 base_t::iterator current;
145 base_t::iterator next;
147 CurrentAndNextIter advance(base_t& args)
149 this->current = std::next(this->current);
150 this->next =
151 std::next(this->current,
152 static_cast<difference_type>(this->current != args.end()));
153 return *this;
156 private:
157 CurrentAndNextIter(base_t& args)
158 : current(args.begin())
159 , next(
160 std::next(this->current,
161 static_cast<difference_type>(this->current != args.end())))
166 class CurrentAndTwoMoreIter
168 friend class cmConditionEvaluator::cmArgumentList;
170 public:
171 base_t::iterator current;
172 base_t::iterator next;
173 base_t::iterator nextnext;
175 CurrentAndTwoMoreIter advance(base_t& args)
177 this->current = std::next(this->current);
178 this->next =
179 std::next(this->current,
180 static_cast<difference_type>(this->current != args.end()));
181 this->nextnext = std::next(
182 this->next, static_cast<difference_type>(this->next != args.end()));
183 return *this;
186 private:
187 CurrentAndTwoMoreIter(base_t& args)
188 : current(args.begin())
189 , next(
190 std::next(this->current,
191 static_cast<difference_type>(this->current != args.end())))
192 , nextnext(std::next(
193 this->next, static_cast<difference_type>(this->next != args.end())))
198 CurrentAndNextIter make2ArgsIterator() { return *this; }
199 CurrentAndTwoMoreIter make3ArgsIterator() { return *this; }
201 template <typename Iter>
202 void ReduceOneArg(const bool value, Iter args)
204 *args.current = cmExpandedCommandArgument(bool2string(value), true);
205 this->erase(args.next);
208 void ReduceTwoArgs(const bool value, CurrentAndTwoMoreIter args)
210 *args.current = cmExpandedCommandArgument(bool2string(value), true);
211 this->erase(args.nextnext);
212 this->erase(args.next);
216 // END cmConditionEvaluator::cmArgumentList
218 cmConditionEvaluator::cmConditionEvaluator(cmMakefile& makefile,
219 cmListFileBacktrace bt)
220 : Makefile(makefile)
221 , Backtrace(std::move(bt))
222 , Policy12Status(makefile.GetPolicyStatus(cmPolicies::CMP0012))
223 , Policy54Status(makefile.GetPolicyStatus(cmPolicies::CMP0054))
224 , Policy57Status(makefile.GetPolicyStatus(cmPolicies::CMP0057))
225 , Policy64Status(makefile.GetPolicyStatus(cmPolicies::CMP0064))
226 , Policy139Status(makefile.GetPolicyStatus(cmPolicies::CMP0139))
230 //=========================================================================
231 // order of operations,
232 // 1. ( ) -- parenthetical groups
233 // 2. IS_DIRECTORY EXISTS COMMAND DEFINED etc predicates
234 // 3. MATCHES LESS GREATER EQUAL STRLESS STRGREATER STREQUAL etc binary ops
235 // 4. NOT
236 // 5. AND OR
238 // There is an issue on whether the arguments should be values of references,
239 // for example IF (FOO AND BAR) should that compare the strings FOO and BAR
240 // or should it really do IF (${FOO} AND ${BAR}) Currently IS_DIRECTORY
241 // EXISTS COMMAND and DEFINED all take values. EQUAL, LESS and GREATER can
242 // take numeric values or variable names. STRLESS and STRGREATER take
243 // variable names but if the variable name is not found it will use the name
244 // directly. AND OR take variables or the values 0 or 1.
246 bool cmConditionEvaluator::IsTrue(
247 const std::vector<cmExpandedCommandArgument>& args, std::string& errorString,
248 MessageType& status)
250 errorString.clear();
252 // handle empty invocation
253 if (args.empty()) {
254 return false;
257 // store the reduced args in this vector
258 cmArgumentList newArgs(args.begin(), args.end());
260 // now loop through the arguments and see if we can reduce any of them
261 // we do this multiple times. Once for each level of precedence
262 // parens
263 using handlerFn_t = bool (cmConditionEvaluator::*)(
264 cmArgumentList&, std::string&, MessageType&);
265 const std::array<handlerFn_t, 5> handlers = { {
266 &cmConditionEvaluator::HandleLevel0, // parenthesis
267 &cmConditionEvaluator::HandleLevel1, // predicates
268 &cmConditionEvaluator::HandleLevel2, // binary ops
269 &cmConditionEvaluator::HandleLevel3, // NOT
270 &cmConditionEvaluator::HandleLevel4 // AND OR
271 } };
272 for (auto fn : handlers) {
273 // Call the reducer 'till there is anything to reduce...
274 // (i.e., if after an iteration the size becomes smaller)
275 auto levelResult = true;
276 for (auto beginSize = newArgs.size();
277 (levelResult = (this->*fn)(newArgs, errorString, status)) &&
278 newArgs.size() < beginSize;
279 beginSize = newArgs.size()) {
282 if (!levelResult) {
283 // NOTE `errorString` supposed to be set already
284 return false;
288 // now at the end there should only be one argument left
289 if (newArgs.size() != 1) {
290 errorString = "Unknown arguments specified";
291 status = MessageType::FATAL_ERROR;
292 return false;
295 return this->GetBooleanValueWithAutoDereference(newArgs.front(), errorString,
296 status, true);
299 //=========================================================================
300 cmValue cmConditionEvaluator::GetDefinitionIfUnquoted(
301 cmExpandedCommandArgument const& argument) const
303 if ((this->Policy54Status != cmPolicies::WARN &&
304 this->Policy54Status != cmPolicies::OLD) &&
305 argument.WasQuoted()) {
306 return nullptr;
309 cmValue def = this->Makefile.GetDefinition(argument.GetValue());
311 if (def && argument.WasQuoted() &&
312 this->Policy54Status == cmPolicies::WARN) {
313 if (!this->Makefile.HasCMP0054AlreadyBeenReported(this->Backtrace.Top())) {
314 std::ostringstream e;
315 // clang-format off
316 e << (cmPolicies::GetPolicyWarning(cmPolicies::CMP0054))
317 << "\n"
318 "Quoted variables like \"" << argument.GetValue() << "\" "
319 "will no longer be dereferenced when the policy is set to NEW. "
320 "Since the policy is not set the OLD behavior will be used.";
321 // clang-format on
323 this->Makefile.GetCMakeInstance()->IssueMessage(
324 MessageType::AUTHOR_WARNING, e.str(), this->Backtrace);
328 return def;
331 //=========================================================================
332 cmValue cmConditionEvaluator::GetVariableOrString(
333 const cmExpandedCommandArgument& argument) const
335 cmValue def = this->GetDefinitionIfUnquoted(argument);
337 if (!def) {
338 def = cmValue(argument.GetValue());
341 return def;
344 //=========================================================================
345 bool cmConditionEvaluator::IsKeyword(
346 cm::static_string_view keyword,
347 const cmExpandedCommandArgument& argument) const
349 if ((this->Policy54Status != cmPolicies::WARN &&
350 this->Policy54Status != cmPolicies::OLD) &&
351 argument.WasQuoted()) {
352 return false;
355 const auto isKeyword = argument.GetValue() == keyword;
357 if (isKeyword && argument.WasQuoted() &&
358 this->Policy54Status == cmPolicies::WARN) {
359 if (!this->Makefile.HasCMP0054AlreadyBeenReported(this->Backtrace.Top())) {
360 std::ostringstream e;
361 // clang-format off
362 e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0054)
363 << "\n"
364 "Quoted keywords like \"" << argument.GetValue() << "\" "
365 "will no longer be interpreted as keywords "
366 "when the policy is set to NEW. "
367 "Since the policy is not set the OLD behavior will be used.";
368 // clang-format on
370 this->Makefile.GetCMakeInstance()->IssueMessage(
371 MessageType::AUTHOR_WARNING, e.str(), this->Backtrace);
375 return isKeyword;
378 //=========================================================================
379 bool cmConditionEvaluator::GetBooleanValue(
380 cmExpandedCommandArgument& arg) const
382 // Check basic and named constants.
383 if (cmIsOn(arg.GetValue())) {
384 return true;
386 if (cmIsOff(arg.GetValue())) {
387 return false;
390 // Check for numbers.
391 if (!arg.empty()) {
392 char* end;
393 const double d = std::strtod(arg.GetValue().c_str(), &end);
394 if (*end == '\0') {
395 // The whole string is a number. Use C conversion to bool.
396 return static_cast<bool>(d);
400 // Check definition.
401 cmValue def = this->GetDefinitionIfUnquoted(arg);
402 return !def.IsOff();
405 //=========================================================================
406 // Boolean value behavior from CMake 2.6.4 and below.
407 bool cmConditionEvaluator::GetBooleanValueOld(
408 cmExpandedCommandArgument const& arg, bool const one) const
410 if (one) {
411 // Old IsTrue behavior for single argument.
412 if (arg == "0") {
413 return false;
415 if (arg == "1") {
416 return true;
418 cmValue def = this->GetDefinitionIfUnquoted(arg);
419 return !def.IsOff();
421 // Old GetVariableOrNumber behavior.
422 cmValue def = this->GetDefinitionIfUnquoted(arg);
423 if (!def && std::atoi(arg.GetValue().c_str())) {
424 def = cmValue(arg.GetValue());
426 return !def.IsOff();
429 //=========================================================================
430 // returns the resulting boolean value
431 bool cmConditionEvaluator::GetBooleanValueWithAutoDereference(
432 cmExpandedCommandArgument& newArg, std::string& errorString,
433 MessageType& status, bool const oneArg) const
435 // Use the policy if it is set.
436 if (this->Policy12Status == cmPolicies::NEW) {
437 return this->GetBooleanValue(newArg);
439 if (this->Policy12Status == cmPolicies::OLD) {
440 return this->GetBooleanValueOld(newArg, oneArg);
443 // Check policy only if old and new results differ.
444 const auto newResult = this->GetBooleanValue(newArg);
445 const auto oldResult = this->GetBooleanValueOld(newArg, oneArg);
446 if (newResult != oldResult) {
447 switch (this->Policy12Status) {
448 case cmPolicies::WARN:
449 errorString = "An argument named \"" + newArg.GetValue() +
450 "\" appears in a conditional statement. " +
451 cmPolicies::GetPolicyWarning(cmPolicies::CMP0012);
452 status = MessageType::AUTHOR_WARNING;
453 CM_FALLTHROUGH;
454 case cmPolicies::OLD:
455 return oldResult;
456 case cmPolicies::REQUIRED_IF_USED:
457 case cmPolicies::REQUIRED_ALWAYS: {
458 errorString = "An argument named \"" + newArg.GetValue() +
459 "\" appears in a conditional statement. " +
460 cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0012);
461 status = MessageType::FATAL_ERROR;
462 break;
464 case cmPolicies::NEW:
465 break;
468 return newResult;
471 template <int N>
472 inline int cmConditionEvaluator::matchKeysImpl(
473 const cmExpandedCommandArgument&)
475 // Zero means "not found"
476 return 0;
479 template <int N, typename T, typename... Keys>
480 inline int cmConditionEvaluator::matchKeysImpl(
481 const cmExpandedCommandArgument& arg, T current, Keys... key)
483 if (this->IsKeyword(current, arg)) {
484 // Stop searching as soon as smth has found
485 return N;
487 return matchKeysImpl<N + 1>(arg, key...);
490 template <typename... Keys>
491 inline int cmConditionEvaluator::matchKeys(
492 const cmExpandedCommandArgument& arg, Keys... key)
494 // Get index of the matched key (1-based)
495 return matchKeysImpl<1>(arg, key...);
498 //=========================================================================
499 // level 0 processes parenthetical expressions
500 bool cmConditionEvaluator::HandleLevel0(cmArgumentList& newArgs,
501 std::string& errorString,
502 MessageType& status)
504 for (auto arg = newArgs.begin(); arg != newArgs.end(); ++arg) {
505 if (this->IsKeyword(keyParenL, *arg)) {
506 // search for the closing paren for this opening one
507 auto depth = 1;
508 auto argClose = std::next(arg);
509 for (; argClose != newArgs.end() && depth; ++argClose) {
510 depth += int(this->IsKeyword(keyParenL, *argClose)) -
511 int(this->IsKeyword(keyParenR, *argClose));
513 if (depth) {
514 errorString = "mismatched parenthesis in condition";
515 status = MessageType::FATAL_ERROR;
516 return false;
519 // store the reduced args in this vector
520 auto argOpen = std::next(arg);
521 const std::vector<cmExpandedCommandArgument> subExpr(
522 argOpen, std::prev(argClose));
524 // now recursively invoke IsTrue to handle the values inside the
525 // parenthetical expression
526 const auto value = this->IsTrue(subExpr, errorString, status);
527 *arg = cmExpandedCommandArgument(bool2string(value), true);
528 argOpen = std::next(arg);
529 // remove the now evaluated parenthetical expression
530 newArgs.erase(argOpen, argClose);
533 return true;
536 //=========================================================================
537 // level one handles most predicates except for NOT
538 bool cmConditionEvaluator::HandleLevel1(cmArgumentList& newArgs, std::string&,
539 MessageType&)
541 for (auto args = newArgs.make2ArgsIterator(); args.current != newArgs.end();
542 args.advance(newArgs)) {
544 auto policyCheck = [&, this](const cmPolicies::PolicyID id,
545 const cmPolicies::PolicyStatus status,
546 const cm::static_string_view kw) {
547 if (status == cmPolicies::WARN && this->IsKeyword(kw, *args.current)) {
548 std::ostringstream e;
549 e << cmPolicies::GetPolicyWarning(id) << "\n"
550 << kw
551 << " will be interpreted as an operator "
552 "when the policy is set to NEW. "
553 "Since the policy is not set the OLD behavior will be used.";
555 this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str());
559 // NOTE Checking policies for warnings are not require an access to the
560 // next arg. Check them first!
561 policyCheck(cmPolicies::CMP0064, this->Policy64Status, keyTEST);
563 // NOTE Fail fast: All the predicates below require the next arg to be
564 // valid
565 if (args.next == newArgs.end()) {
566 continue;
569 // does a file exist
570 if (this->IsKeyword(keyEXISTS, *args.current)) {
571 newArgs.ReduceOneArg(cmSystemTools::FileExists(args.next->GetValue()),
572 args);
574 // check if a file is readable
575 else if (this->IsKeyword(keyIS_READABLE, *args.current)) {
576 newArgs.ReduceOneArg(cmSystemTools::TestFileAccess(
577 args.next->GetValue(), cmsys::TEST_FILE_READ),
578 args);
580 // check if a file is writable
581 else if (this->IsKeyword(keyIS_WRITABLE, *args.current)) {
582 newArgs.ReduceOneArg(cmSystemTools::TestFileAccess(
583 args.next->GetValue(), cmsys::TEST_FILE_WRITE),
584 args);
586 // check if a file is executable
587 else if (this->IsKeyword(keyIS_EXECUTABLE, *args.current)) {
588 newArgs.ReduceOneArg(cmSystemTools::TestFileAccess(
589 args.next->GetValue(), cmsys::TEST_FILE_EXECUTE),
590 args);
592 // does a directory with this name exist
593 else if (this->IsKeyword(keyIS_DIRECTORY, *args.current)) {
594 newArgs.ReduceOneArg(
595 cmSystemTools::FileIsDirectory(args.next->GetValue()), args);
597 // does a symlink with this name exist
598 else if (this->IsKeyword(keyIS_SYMLINK, *args.current)) {
599 newArgs.ReduceOneArg(cmSystemTools::FileIsSymlink(args.next->GetValue()),
600 args);
602 // is the given path an absolute path ?
603 else if (this->IsKeyword(keyIS_ABSOLUTE, *args.current)) {
604 newArgs.ReduceOneArg(
605 cmSystemTools::FileIsFullPath(args.next->GetValue()), args);
607 // does a command exist
608 else if (this->IsKeyword(keyCOMMAND, *args.current)) {
609 newArgs.ReduceOneArg(
610 static_cast<bool>(
611 this->Makefile.GetState()->GetCommand(args.next->GetValue())),
612 args);
614 // does a policy exist
615 else if (this->IsKeyword(keyPOLICY, *args.current)) {
616 cmPolicies::PolicyID pid;
617 newArgs.ReduceOneArg(
618 cmPolicies::GetPolicyID(args.next->GetValue().c_str(), pid), args);
620 // does a target exist
621 else if (this->IsKeyword(keyTARGET, *args.current)) {
622 newArgs.ReduceOneArg(static_cast<bool>(this->Makefile.FindTargetToUse(
623 args.next->GetValue())),
624 args);
626 // is a variable defined
627 else if (this->IsKeyword(keyDEFINED, *args.current)) {
628 const auto& var = args.next->GetValue();
629 const auto varNameLen = var.size();
631 auto result = false;
632 if (looksLikeSpecialVariable(var, "ENV"_s, varNameLen)) {
633 const auto env = args.next->GetValue().substr(4, varNameLen - 5);
634 result = cmSystemTools::HasEnv(env);
637 else if (looksLikeSpecialVariable(var, "CACHE"_s, varNameLen)) {
638 const auto cache = args.next->GetValue().substr(6, varNameLen - 7);
639 result = static_cast<bool>(
640 this->Makefile.GetState()->GetCacheEntryValue(cache));
643 else {
644 result = this->Makefile.IsDefinitionSet(args.next->GetValue());
646 newArgs.ReduceOneArg(result, args);
648 // does a test exist
649 else if (this->IsKeyword(keyTEST, *args.current)) {
650 if (this->Policy64Status == cmPolicies::OLD ||
651 this->Policy64Status == cmPolicies::WARN) {
652 continue;
654 newArgs.ReduceOneArg(
655 static_cast<bool>(this->Makefile.GetTest(args.next->GetValue())),
656 args);
659 return true;
662 //=========================================================================
663 // level two handles most binary operations except for AND OR
664 bool cmConditionEvaluator::HandleLevel2(cmArgumentList& newArgs,
665 std::string& errorString,
666 MessageType& status)
668 for (auto args = newArgs.make3ArgsIterator(); args.current != newArgs.end();
669 args.advance(newArgs)) {
671 int matchNo;
673 // NOTE Handle special case `if(... BLAH_BLAH MATCHES)`
674 // (i.e., w/o regex to match which is possibly result of
675 // variable expansion to an empty string)
676 if (args.next != newArgs.end() &&
677 this->IsKeyword(keyMATCHES, *args.current)) {
678 newArgs.ReduceOneArg(false, args);
681 // NOTE Fail fast: All the binary ops below require 2 arguments.
682 else if (args.next == newArgs.end() || args.nextnext == newArgs.end()) {
683 continue;
686 else if (this->IsKeyword(keyMATCHES, *args.next)) {
687 cmValue def = this->GetDefinitionIfUnquoted(*args.current);
689 std::string def_buf;
690 if (!def) {
691 def = cmValue(args.current->GetValue());
692 } else if (cmHasLiteralPrefix(args.current->GetValue(),
693 "CMAKE_MATCH_")) {
694 // The string to match is owned by our match result variables.
695 // Move it to our own buffer before clearing them.
696 def_buf = *def;
697 def = cmValue(def_buf);
700 this->Makefile.ClearMatches();
702 const auto& rex = args.nextnext->GetValue();
703 cmsys::RegularExpression regEntry;
704 if (!regEntry.compile(rex)) {
705 std::ostringstream error;
706 error << "Regular expression \"" << rex << "\" cannot compile";
707 errorString = error.str();
708 status = MessageType::FATAL_ERROR;
709 return false;
712 const auto match = regEntry.find(*def);
713 if (match) {
714 this->Makefile.StoreMatches(regEntry);
716 newArgs.ReduceTwoArgs(match, args);
719 else if ((matchNo =
720 this->matchKeys(*args.next, keyLESS, keyLESS_EQUAL, keyGREATER,
721 keyGREATER_EQUAL, keyEQUAL))) {
723 cmValue ldef = this->GetVariableOrString(*args.current);
724 cmValue rdef = this->GetVariableOrString(*args.nextnext);
726 double lhs;
727 double rhs;
728 auto parseDoubles = [&]() {
729 return std::sscanf(ldef->c_str(), "%lg", &lhs) == 1 &&
730 std::sscanf(rdef->c_str(), "%lg", &rhs) == 1;
732 // clang-format off
733 const auto result = parseDoubles() &&
734 cmRt2CtSelector<
735 std::less, std::less_equal,
736 std::greater, std::greater_equal,
737 std::equal_to
738 >::eval(matchNo, lhs, rhs);
739 // clang-format on
740 newArgs.ReduceTwoArgs(result, args);
743 else if ((matchNo = this->matchKeys(*args.next, keySTRLESS,
744 keySTRLESS_EQUAL, keySTRGREATER,
745 keySTRGREATER_EQUAL, keySTREQUAL))) {
747 const cmValue lhs = this->GetVariableOrString(*args.current);
748 const cmValue rhs = this->GetVariableOrString(*args.nextnext);
749 const auto val = (*lhs).compare(*rhs);
750 // clang-format off
751 const auto result = cmRt2CtSelector<
752 std::less, std::less_equal,
753 std::greater, std::greater_equal,
754 std::equal_to
755 >::eval(matchNo, val, 0);
756 // clang-format on
757 newArgs.ReduceTwoArgs(result, args);
760 else if ((matchNo =
761 this->matchKeys(*args.next, keyVERSION_LESS,
762 keyVERSION_LESS_EQUAL, keyVERSION_GREATER,
763 keyVERSION_GREATER_EQUAL, keyVERSION_EQUAL))) {
764 const auto op = MATCH2CMPOP[matchNo - 1];
765 const cmValue lhs = this->GetVariableOrString(*args.current);
766 const cmValue rhs = this->GetVariableOrString(*args.nextnext);
767 const auto result = cmSystemTools::VersionCompare(op, lhs, rhs);
768 newArgs.ReduceTwoArgs(result, args);
771 // is file A newer than file B
772 else if (this->IsKeyword(keyIS_NEWER_THAN, *args.next)) {
773 auto fileIsNewer = 0;
774 cmsys::Status ftcStatus = cmSystemTools::FileTimeCompare(
775 args.current->GetValue(), args.nextnext->GetValue(), &fileIsNewer);
776 newArgs.ReduceTwoArgs(
777 (!ftcStatus || fileIsNewer == 1 || fileIsNewer == 0), args);
780 else if (this->IsKeyword(keyIN_LIST, *args.next)) {
782 if (this->Policy57Status != cmPolicies::OLD &&
783 this->Policy57Status != cmPolicies::WARN) {
785 cmValue lhs = this->GetVariableOrString(*args.current);
786 cmValue rhs = this->Makefile.GetDefinition(args.nextnext->GetValue());
788 newArgs.ReduceTwoArgs(
789 rhs &&
790 cm::contains(cmList{ *rhs, cmList::EmptyElements::Yes }, *lhs),
791 args);
794 else if (this->Policy57Status == cmPolicies::WARN) {
795 std::ostringstream e;
796 e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0057)
797 << "\n"
798 "IN_LIST will be interpreted as an operator "
799 "when the policy is set to NEW. "
800 "Since the policy is not set the OLD behavior will be used.";
802 this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str());
806 else if (this->IsKeyword(keyPATH_EQUAL, *args.next)) {
808 if (this->Policy139Status != cmPolicies::OLD &&
809 this->Policy139Status != cmPolicies::WARN) {
811 cmValue lhs = this->GetVariableOrString(*args.current);
812 cmValue rhs = this->GetVariableOrString(*args.nextnext);
813 const auto result = cmCMakePath{ *lhs } == cmCMakePath{ *rhs };
814 newArgs.ReduceTwoArgs(result, args);
817 else if (this->Policy139Status == cmPolicies::WARN) {
818 std::ostringstream e;
819 e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0139)
820 << "\n"
821 "PATH_EQUAL will be interpreted as an operator "
822 "when the policy is set to NEW. "
823 "Since the policy is not set the OLD behavior will be used.";
825 this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str());
829 return true;
832 //=========================================================================
833 // level 3 handles NOT
834 bool cmConditionEvaluator::HandleLevel3(cmArgumentList& newArgs,
835 std::string& errorString,
836 MessageType& status)
838 for (auto args = newArgs.make2ArgsIterator(); args.next != newArgs.end();
839 args.advance(newArgs)) {
840 if (this->IsKeyword(keyNOT, *args.current)) {
841 const auto rhs = this->GetBooleanValueWithAutoDereference(
842 *args.next, errorString, status);
843 newArgs.ReduceOneArg(!rhs, args);
846 return true;
849 //=========================================================================
850 // level 4 handles AND OR
851 bool cmConditionEvaluator::HandleLevel4(cmArgumentList& newArgs,
852 std::string& errorString,
853 MessageType& status)
855 for (auto args = newArgs.make3ArgsIterator(); args.nextnext != newArgs.end();
856 args.advance(newArgs)) {
858 int matchNo;
860 if ((matchNo = this->matchKeys(*args.next, keyAND, keyOR))) {
861 const auto lhs = this->GetBooleanValueWithAutoDereference(
862 *args.current, errorString, status);
863 const auto rhs = this->GetBooleanValueWithAutoDereference(
864 *args.nextnext, errorString, status);
865 // clang-format off
866 const auto result =
867 cmRt2CtSelector<
868 std::logical_and, std::logical_or
869 >::eval(matchNo, lhs, rhs);
870 // clang-format on
871 newArgs.ReduceTwoArgs(result, args);
874 return true;