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