| /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying | 
 |    file Copyright.txt or https://cmake.org/licensing for details.  */ | 
 | #include "cmConditionEvaluator.h" | 
 |  | 
 | #include <array> | 
 | #include <cstdio> | 
 | #include <cstdlib> | 
 | #include <functional> | 
 | #include <iterator> | 
 | #include <list> | 
 | #include <sstream> | 
 | #include <utility> | 
 |  | 
 | #include <cm/string_view> | 
 | #include <cmext/algorithm> | 
 |  | 
 | #include "cmsys/RegularExpression.hxx" | 
 |  | 
 | #include "cmCMakePath.h" | 
 | #include "cmExpandedCommandArgument.h" | 
 | #include "cmMakefile.h" | 
 | #include "cmMessageType.h" | 
 | #include "cmState.h" | 
 | #include "cmStringAlgorithms.h" | 
 | #include "cmSystemTools.h" | 
 | #include "cmValue.h" | 
 | #include "cmake.h" | 
 |  | 
 | namespace { | 
 | auto const keyAND = "AND"_s; | 
 | auto const keyCOMMAND = "COMMAND"_s; | 
 | auto const keyDEFINED = "DEFINED"_s; | 
 | auto const keyEQUAL = "EQUAL"_s; | 
 | auto const keyEXISTS = "EXISTS"_s; | 
 | auto const keyGREATER = "GREATER"_s; | 
 | auto const keyGREATER_EQUAL = "GREATER_EQUAL"_s; | 
 | auto const keyIN_LIST = "IN_LIST"_s; | 
 | auto const keyIS_ABSOLUTE = "IS_ABSOLUTE"_s; | 
 | auto const keyIS_DIRECTORY = "IS_DIRECTORY"_s; | 
 | auto const keyIS_NEWER_THAN = "IS_NEWER_THAN"_s; | 
 | auto const keyIS_SYMLINK = "IS_SYMLINK"_s; | 
 | auto const keyLESS = "LESS"_s; | 
 | auto const keyLESS_EQUAL = "LESS_EQUAL"_s; | 
 | auto const keyMATCHES = "MATCHES"_s; | 
 | auto const keyNOT = "NOT"_s; | 
 | auto const keyOR = "OR"_s; | 
 | auto const keyParenL = "("_s; | 
 | auto const keyParenR = ")"_s; | 
 | auto const keyPOLICY = "POLICY"_s; | 
 | auto const keySTREQUAL = "STREQUAL"_s; | 
 | auto const keySTRGREATER = "STRGREATER"_s; | 
 | auto const keySTRGREATER_EQUAL = "STRGREATER_EQUAL"_s; | 
 | auto const keySTRLESS = "STRLESS"_s; | 
 | auto const keySTRLESS_EQUAL = "STRLESS_EQUAL"_s; | 
 | auto const keyTARGET = "TARGET"_s; | 
 | auto const keyTEST = "TEST"_s; | 
 | auto const keyVERSION_EQUAL = "VERSION_EQUAL"_s; | 
 | auto const keyVERSION_GREATER = "VERSION_GREATER"_s; | 
 | auto const keyVERSION_GREATER_EQUAL = "VERSION_GREATER_EQUAL"_s; | 
 | auto const keyVERSION_LESS = "VERSION_LESS"_s; | 
 | auto const keyVERSION_LESS_EQUAL = "VERSION_LESS_EQUAL"_s; | 
 | auto const keyPATH_EQUAL = "PATH_EQUAL"_s; | 
 |  | 
 | cmSystemTools::CompareOp const MATCH2CMPOP[5] = { | 
 |   cmSystemTools::OP_LESS, cmSystemTools::OP_LESS_EQUAL, | 
 |   cmSystemTools::OP_GREATER, cmSystemTools::OP_GREATER_EQUAL, | 
 |   cmSystemTools::OP_EQUAL | 
 | }; | 
 |  | 
 | // Run-Time to Compile-Time template selector | 
 | template <template <typename> class Comp, template <typename> class... Ops> | 
 | struct cmRt2CtSelector | 
 | { | 
 |   template <typename T> | 
 |   static bool eval(int r, T lhs, T rhs) | 
 |   { | 
 |     switch (r) { | 
 |       case 0: | 
 |         return false; | 
 |       case 1: | 
 |         return Comp<T>()(lhs, rhs); | 
 |       default: | 
 |         return cmRt2CtSelector<Ops...>::eval(r - 1, lhs, rhs); | 
 |     } | 
 |   } | 
 | }; | 
 |  | 
 | template <template <typename> class Comp> | 
 | struct cmRt2CtSelector<Comp> | 
 | { | 
 |   template <typename T> | 
 |   static bool eval(int r, T lhs, T rhs) | 
 |   { | 
 |     return r == 1 && Comp<T>()(lhs, rhs); | 
 |   } | 
 | }; | 
 |  | 
 | std::string bool2string(bool const value) | 
 | { | 
 |   return std::string(static_cast<std::size_t>(1), | 
 |                      static_cast<char>('0' + static_cast<int>(value))); | 
 | } | 
 |  | 
 | bool looksLikeSpecialVariable(const std::string& var, | 
 |                               cm::static_string_view prefix, | 
 |                               const std::size_t varNameLen) | 
 | { | 
 |   // NOTE Expecting a variable name at least 1 char length: | 
 |   // <prefix> + `{` + <varname> + `}` | 
 |   return ((prefix.size() + 3) <= varNameLen) && | 
 |     cmHasPrefix(var, cmStrCat(prefix, '{')) && var[varNameLen - 1] == '}'; | 
 | } | 
 | } // anonymous namespace | 
 |  | 
 | #if defined(__SUNPRO_CC) | 
 | #  define CM_INHERIT_CTOR(Class, Base, Tpl)                                   \ | 
 |     template <typename... Args>                                               \ | 
 |     Class(Args&&... args)                                                     \ | 
 |       : Base Tpl(std::forward<Args>(args)...)                                 \ | 
 |     {                                                                         \ | 
 |     } | 
 | #else | 
 | #  define CM_INHERIT_CTOR(Class, Base, Tpl) using Base Tpl ::Base | 
 | #endif | 
 |  | 
 | // BEGIN cmConditionEvaluator::cmArgumentList | 
 | class cmConditionEvaluator::cmArgumentList | 
 |   : public std::list<cmExpandedCommandArgument> | 
 | { | 
 |   using base_t = std::list<cmExpandedCommandArgument>; | 
 |  | 
 | public: | 
 |   CM_INHERIT_CTOR(cmArgumentList, list, <cmExpandedCommandArgument>); | 
 |  | 
 |   class CurrentAndNextIter | 
 |   { | 
 |     friend class cmConditionEvaluator::cmArgumentList; | 
 |  | 
 |   public: | 
 |     base_t::iterator current; | 
 |     base_t::iterator next; | 
 |  | 
 |     CurrentAndNextIter advance(base_t& args) | 
 |     { | 
 |       this->current = std::next(this->current); | 
 |       this->next = | 
 |         std::next(this->current, | 
 |                   static_cast<difference_type>(this->current != args.end())); | 
 |       return *this; | 
 |     } | 
 |  | 
 |   private: | 
 |     CurrentAndNextIter(base_t& args) | 
 |       : current(args.begin()) | 
 |       , next( | 
 |           std::next(this->current, | 
 |                     static_cast<difference_type>(this->current != args.end()))) | 
 |     { | 
 |     } | 
 |   }; | 
 |  | 
 |   class CurrentAndTwoMoreIter | 
 |   { | 
 |     friend class cmConditionEvaluator::cmArgumentList; | 
 |  | 
 |   public: | 
 |     base_t::iterator current; | 
 |     base_t::iterator next; | 
 |     base_t::iterator nextnext; | 
 |  | 
 |     CurrentAndTwoMoreIter advance(base_t& args) | 
 |     { | 
 |       this->current = std::next(this->current); | 
 |       this->next = | 
 |         std::next(this->current, | 
 |                   static_cast<difference_type>(this->current != args.end())); | 
 |       this->nextnext = std::next( | 
 |         this->next, static_cast<difference_type>(this->next != args.end())); | 
 |       return *this; | 
 |     } | 
 |  | 
 |   private: | 
 |     CurrentAndTwoMoreIter(base_t& args) | 
 |       : current(args.begin()) | 
 |       , next( | 
 |           std::next(this->current, | 
 |                     static_cast<difference_type>(this->current != args.end()))) | 
 |       , nextnext(std::next( | 
 |           this->next, static_cast<difference_type>(this->next != args.end()))) | 
 |     { | 
 |     } | 
 |   }; | 
 |  | 
 |   CurrentAndNextIter make2ArgsIterator() { return *this; } | 
 |   CurrentAndTwoMoreIter make3ArgsIterator() { return *this; } | 
 |  | 
 |   template <typename Iter> | 
 |   void ReduceOneArg(const bool value, Iter args) | 
 |   { | 
 |     *args.current = cmExpandedCommandArgument(bool2string(value), true); | 
 |     this->erase(args.next); | 
 |   } | 
 |  | 
 |   void ReduceTwoArgs(const bool value, CurrentAndTwoMoreIter args) | 
 |   { | 
 |     *args.current = cmExpandedCommandArgument(bool2string(value), true); | 
 |     this->erase(args.nextnext); | 
 |     this->erase(args.next); | 
 |   } | 
 | }; | 
 |  | 
 | // END cmConditionEvaluator::cmArgumentList | 
 |  | 
 | cmConditionEvaluator::cmConditionEvaluator(cmMakefile& makefile, | 
 |                                            cmListFileBacktrace bt) | 
 |   : Makefile(makefile) | 
 |   , Backtrace(std::move(bt)) | 
 |   , Policy12Status(makefile.GetPolicyStatus(cmPolicies::CMP0012)) | 
 |   , Policy54Status(makefile.GetPolicyStatus(cmPolicies::CMP0054)) | 
 |   , Policy57Status(makefile.GetPolicyStatus(cmPolicies::CMP0057)) | 
 |   , Policy64Status(makefile.GetPolicyStatus(cmPolicies::CMP0064)) | 
 |   , Policy139Status(makefile.GetPolicyStatus(cmPolicies::CMP0139)) | 
 | { | 
 | } | 
 |  | 
 | //========================================================================= | 
 | // order of operations, | 
 | // 1.   ( )   -- parenthetical groups | 
 | // 2.  IS_DIRECTORY EXISTS COMMAND DEFINED etc predicates | 
 | // 3. MATCHES LESS GREATER EQUAL STRLESS STRGREATER STREQUAL etc binary ops | 
 | // 4. NOT | 
 | // 5. AND OR | 
 | // | 
 | // There is an issue on whether the arguments should be values of references, | 
 | // for example IF (FOO AND BAR) should that compare the strings FOO and BAR | 
 | // or should it really do IF (${FOO} AND ${BAR}) Currently IS_DIRECTORY | 
 | // EXISTS COMMAND and DEFINED all take values. EQUAL, LESS and GREATER can | 
 | // take numeric values or variable names. STRLESS and STRGREATER take | 
 | // variable names but if the variable name is not found it will use the name | 
 | // directly. AND OR take variables or the values 0 or 1. | 
 |  | 
 | bool cmConditionEvaluator::IsTrue( | 
 |   const std::vector<cmExpandedCommandArgument>& args, std::string& errorString, | 
 |   MessageType& status) | 
 | { | 
 |   errorString.clear(); | 
 |  | 
 |   // handle empty invocation | 
 |   if (args.empty()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // store the reduced args in this vector | 
 |   cmArgumentList newArgs(args.begin(), args.end()); | 
 |  | 
 |   // now loop through the arguments and see if we can reduce any of them | 
 |   // we do this multiple times. Once for each level of precedence | 
 |   // parens | 
 |   using handlerFn_t = bool (cmConditionEvaluator::*)( | 
 |     cmArgumentList&, std::string&, MessageType&); | 
 |   const std::array<handlerFn_t, 5> handlers = { { | 
 |     &cmConditionEvaluator::HandleLevel0, // parenthesis | 
 |     &cmConditionEvaluator::HandleLevel1, // predicates | 
 |     &cmConditionEvaluator::HandleLevel2, // binary ops | 
 |     &cmConditionEvaluator::HandleLevel3, // NOT | 
 |     &cmConditionEvaluator::HandleLevel4  // AND OR | 
 |   } }; | 
 |   for (auto fn : handlers) { | 
 |     // Call the reducer 'till there is anything to reduce... | 
 |     // (i.e., if after an iteration the size becomes smaller) | 
 |     auto levelResult = true; | 
 |     for (auto beginSize = newArgs.size(); | 
 |          (levelResult = (this->*fn)(newArgs, errorString, status)) && | 
 |          newArgs.size() < beginSize; | 
 |          beginSize = newArgs.size()) { | 
 |     } | 
 |  | 
 |     if (!levelResult) { | 
 |       // NOTE `errorString` supposed to be set already | 
 |       return false; | 
 |     } | 
 |   } | 
 |  | 
 |   // now at the end there should only be one argument left | 
 |   if (newArgs.size() != 1) { | 
 |     errorString = "Unknown arguments specified"; | 
 |     status = MessageType::FATAL_ERROR; | 
 |     return false; | 
 |   } | 
 |  | 
 |   return this->GetBooleanValueWithAutoDereference(newArgs.front(), errorString, | 
 |                                                   status, true); | 
 | } | 
 |  | 
 | //========================================================================= | 
 | cmValue cmConditionEvaluator::GetDefinitionIfUnquoted( | 
 |   cmExpandedCommandArgument const& argument) const | 
 | { | 
 |   if ((this->Policy54Status != cmPolicies::WARN && | 
 |        this->Policy54Status != cmPolicies::OLD) && | 
 |       argument.WasQuoted()) { | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   cmValue def = this->Makefile.GetDefinition(argument.GetValue()); | 
 |  | 
 |   if (def && argument.WasQuoted() && | 
 |       this->Policy54Status == cmPolicies::WARN) { | 
 |     if (!this->Makefile.HasCMP0054AlreadyBeenReported(this->Backtrace.Top())) { | 
 |       std::ostringstream e; | 
 |       // clang-format off | 
 |       e << (cmPolicies::GetPolicyWarning(cmPolicies::CMP0054)) | 
 |         << "\n" | 
 |            "Quoted variables like \"" << argument.GetValue() << "\" " | 
 |            "will no longer be dereferenced when the policy is set to NEW.  " | 
 |            "Since the policy is not set the OLD behavior will be used."; | 
 |       // clang-format on | 
 |  | 
 |       this->Makefile.GetCMakeInstance()->IssueMessage( | 
 |         MessageType::AUTHOR_WARNING, e.str(), this->Backtrace); | 
 |     } | 
 |   } | 
 |  | 
 |   return def; | 
 | } | 
 |  | 
 | //========================================================================= | 
 | cmValue cmConditionEvaluator::GetVariableOrString( | 
 |   const cmExpandedCommandArgument& argument) const | 
 | { | 
 |   cmValue def = this->GetDefinitionIfUnquoted(argument); | 
 |  | 
 |   if (!def) { | 
 |     def = cmValue(argument.GetValue()); | 
 |   } | 
 |  | 
 |   return def; | 
 | } | 
 |  | 
 | //========================================================================= | 
 | bool cmConditionEvaluator::IsKeyword( | 
 |   cm::static_string_view keyword, | 
 |   const cmExpandedCommandArgument& argument) const | 
 | { | 
 |   if ((this->Policy54Status != cmPolicies::WARN && | 
 |        this->Policy54Status != cmPolicies::OLD) && | 
 |       argument.WasQuoted()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   const auto isKeyword = argument.GetValue() == keyword; | 
 |  | 
 |   if (isKeyword && argument.WasQuoted() && | 
 |       this->Policy54Status == cmPolicies::WARN) { | 
 |     if (!this->Makefile.HasCMP0054AlreadyBeenReported(this->Backtrace.Top())) { | 
 |       std::ostringstream e; | 
 |       // clang-format off | 
 |       e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0054) | 
 |         << "\n" | 
 |            "Quoted keywords like \"" << argument.GetValue() << "\" " | 
 |            "will no longer be interpreted as keywords " | 
 |            "when the policy is set to NEW.  " | 
 |            "Since the policy is not set the OLD behavior will be used."; | 
 |       // clang-format on | 
 |  | 
 |       this->Makefile.GetCMakeInstance()->IssueMessage( | 
 |         MessageType::AUTHOR_WARNING, e.str(), this->Backtrace); | 
 |     } | 
 |   } | 
 |  | 
 |   return isKeyword; | 
 | } | 
 |  | 
 | //========================================================================= | 
 | bool cmConditionEvaluator::GetBooleanValue( | 
 |   cmExpandedCommandArgument& arg) const | 
 | { | 
 |   // Check basic and named constants. | 
 |   if (cmIsOn(arg.GetValue())) { | 
 |     return true; | 
 |   } | 
 |   if (cmIsOff(arg.GetValue())) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // Check for numbers. | 
 |   if (!arg.empty()) { | 
 |     char* end; | 
 |     const double d = std::strtod(arg.GetValue().c_str(), &end); | 
 |     if (*end == '\0') { | 
 |       // The whole string is a number.  Use C conversion to bool. | 
 |       return static_cast<bool>(d); | 
 |     } | 
 |   } | 
 |  | 
 |   // Check definition. | 
 |   cmValue def = this->GetDefinitionIfUnquoted(arg); | 
 |   return !cmIsOff(def); | 
 | } | 
 |  | 
 | //========================================================================= | 
 | // Boolean value behavior from CMake 2.6.4 and below. | 
 | bool cmConditionEvaluator::GetBooleanValueOld( | 
 |   cmExpandedCommandArgument const& arg, bool const one) const | 
 | { | 
 |   if (one) { | 
 |     // Old IsTrue behavior for single argument. | 
 |     if (arg == "0") { | 
 |       return false; | 
 |     } | 
 |     if (arg == "1") { | 
 |       return true; | 
 |     } | 
 |     cmValue def = this->GetDefinitionIfUnquoted(arg); | 
 |     return !cmIsOff(def); | 
 |   } | 
 |   // Old GetVariableOrNumber behavior. | 
 |   cmValue def = this->GetDefinitionIfUnquoted(arg); | 
 |   if (!def && std::atoi(arg.GetValue().c_str())) { | 
 |     def = cmValue(arg.GetValue()); | 
 |   } | 
 |   return !cmIsOff(def); | 
 | } | 
 |  | 
 | //========================================================================= | 
 | // returns the resulting boolean value | 
 | bool cmConditionEvaluator::GetBooleanValueWithAutoDereference( | 
 |   cmExpandedCommandArgument& newArg, std::string& errorString, | 
 |   MessageType& status, bool const oneArg) const | 
 | { | 
 |   // Use the policy if it is set. | 
 |   if (this->Policy12Status == cmPolicies::NEW) { | 
 |     return this->GetBooleanValue(newArg); | 
 |   } | 
 |   if (this->Policy12Status == cmPolicies::OLD) { | 
 |     return this->GetBooleanValueOld(newArg, oneArg); | 
 |   } | 
 |  | 
 |   // Check policy only if old and new results differ. | 
 |   const auto newResult = this->GetBooleanValue(newArg); | 
 |   const auto oldResult = this->GetBooleanValueOld(newArg, oneArg); | 
 |   if (newResult != oldResult) { | 
 |     switch (this->Policy12Status) { | 
 |       case cmPolicies::WARN: | 
 |         errorString = "An argument named \"" + newArg.GetValue() + | 
 |           "\" appears in a conditional statement.  " + | 
 |           cmPolicies::GetPolicyWarning(cmPolicies::CMP0012); | 
 |         status = MessageType::AUTHOR_WARNING; | 
 |         CM_FALLTHROUGH; | 
 |       case cmPolicies::OLD: | 
 |         return oldResult; | 
 |       case cmPolicies::REQUIRED_IF_USED: | 
 |       case cmPolicies::REQUIRED_ALWAYS: { | 
 |         errorString = "An argument named \"" + newArg.GetValue() + | 
 |           "\" appears in a conditional statement.  " + | 
 |           cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0012); | 
 |         status = MessageType::FATAL_ERROR; | 
 |         break; | 
 |       } | 
 |       case cmPolicies::NEW: | 
 |         break; | 
 |     } | 
 |   } | 
 |   return newResult; | 
 | } | 
 |  | 
 | template <int N> | 
 | inline int cmConditionEvaluator::matchKeysImpl( | 
 |   const cmExpandedCommandArgument&) | 
 | { | 
 |   // Zero means "not found" | 
 |   return 0; | 
 | } | 
 |  | 
 | template <int N, typename T, typename... Keys> | 
 | inline int cmConditionEvaluator::matchKeysImpl( | 
 |   const cmExpandedCommandArgument& arg, T current, Keys... key) | 
 | { | 
 |   if (this->IsKeyword(current, arg)) { | 
 |     // Stop searching as soon as smth has found | 
 |     return N; | 
 |   } | 
 |   return matchKeysImpl<N + 1>(arg, key...); | 
 | } | 
 |  | 
 | template <typename... Keys> | 
 | inline int cmConditionEvaluator::matchKeys( | 
 |   const cmExpandedCommandArgument& arg, Keys... key) | 
 | { | 
 |   // Get index of the matched key (1-based) | 
 |   return matchKeysImpl<1>(arg, key...); | 
 | } | 
 |  | 
 | //========================================================================= | 
 | // level 0 processes parenthetical expressions | 
 | bool cmConditionEvaluator::HandleLevel0(cmArgumentList& newArgs, | 
 |                                         std::string& errorString, | 
 |                                         MessageType& status) | 
 | { | 
 |   for (auto arg = newArgs.begin(); arg != newArgs.end(); ++arg) { | 
 |     if (this->IsKeyword(keyParenL, *arg)) { | 
 |       // search for the closing paren for this opening one | 
 |       auto depth = 1; | 
 |       auto argClose = std::next(arg); | 
 |       for (; argClose != newArgs.end() && depth; ++argClose) { | 
 |         depth += int(this->IsKeyword(keyParenL, *argClose)) - | 
 |           int(this->IsKeyword(keyParenR, *argClose)); | 
 |       } | 
 |       if (depth) { | 
 |         errorString = "mismatched parenthesis in condition"; | 
 |         status = MessageType::FATAL_ERROR; | 
 |         return false; | 
 |       } | 
 |  | 
 |       // store the reduced args in this vector | 
 |       auto argOpen = std::next(arg); | 
 |       const std::vector<cmExpandedCommandArgument> subExpr( | 
 |         argOpen, std::prev(argClose)); | 
 |  | 
 |       // now recursively invoke IsTrue to handle the values inside the | 
 |       // parenthetical expression | 
 |       const auto value = this->IsTrue(subExpr, errorString, status); | 
 |       *arg = cmExpandedCommandArgument(bool2string(value), true); | 
 |       argOpen = std::next(arg); | 
 |       // remove the now evaluated parenthetical expression | 
 |       newArgs.erase(argOpen, argClose); | 
 |     } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | //========================================================================= | 
 | // level one handles most predicates except for NOT | 
 | bool cmConditionEvaluator::HandleLevel1(cmArgumentList& newArgs, std::string&, | 
 |                                         MessageType&) | 
 | { | 
 |   for (auto args = newArgs.make2ArgsIterator(); args.current != newArgs.end(); | 
 |        args.advance(newArgs)) { | 
 |  | 
 |     auto policyCheck = [&, this](const cmPolicies::PolicyID id, | 
 |                                  const cmPolicies::PolicyStatus status, | 
 |                                  const cm::static_string_view kw) { | 
 |       if (status == cmPolicies::WARN && this->IsKeyword(kw, *args.current)) { | 
 |         std::ostringstream e; | 
 |         e << cmPolicies::GetPolicyWarning(id) << "\n" | 
 |           << kw | 
 |           << " will be interpreted as an operator " | 
 |              "when the policy is set to NEW.  " | 
 |              "Since the policy is not set the OLD behavior will be used."; | 
 |  | 
 |         this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str()); | 
 |       } | 
 |     }; | 
 |  | 
 |     // NOTE Checking policies for warnings are not require an access to the | 
 |     // next arg. Check them first! | 
 |     policyCheck(cmPolicies::CMP0064, this->Policy64Status, keyTEST); | 
 |  | 
 |     // NOTE Fail fast: All the predicates below require the next arg to be | 
 |     // valid | 
 |     if (args.next == newArgs.end()) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     // does a file exist | 
 |     if (this->IsKeyword(keyEXISTS, *args.current)) { | 
 |       newArgs.ReduceOneArg(cmSystemTools::FileExists(args.next->GetValue()), | 
 |                            args); | 
 |     } | 
 |     // does a directory with this name exist | 
 |     else if (this->IsKeyword(keyIS_DIRECTORY, *args.current)) { | 
 |       newArgs.ReduceOneArg( | 
 |         cmSystemTools::FileIsDirectory(args.next->GetValue()), args); | 
 |     } | 
 |     // does a symlink with this name exist | 
 |     else if (this->IsKeyword(keyIS_SYMLINK, *args.current)) { | 
 |       newArgs.ReduceOneArg(cmSystemTools::FileIsSymlink(args.next->GetValue()), | 
 |                            args); | 
 |     } | 
 |     // is the given path an absolute path ? | 
 |     else if (this->IsKeyword(keyIS_ABSOLUTE, *args.current)) { | 
 |       newArgs.ReduceOneArg( | 
 |         cmSystemTools::FileIsFullPath(args.next->GetValue()), args); | 
 |     } | 
 |     // does a command exist | 
 |     else if (this->IsKeyword(keyCOMMAND, *args.current)) { | 
 |       newArgs.ReduceOneArg( | 
 |         static_cast<bool>( | 
 |           this->Makefile.GetState()->GetCommand(args.next->GetValue())), | 
 |         args); | 
 |     } | 
 |     // does a policy exist | 
 |     else if (this->IsKeyword(keyPOLICY, *args.current)) { | 
 |       cmPolicies::PolicyID pid; | 
 |       newArgs.ReduceOneArg( | 
 |         cmPolicies::GetPolicyID(args.next->GetValue().c_str(), pid), args); | 
 |     } | 
 |     // does a target exist | 
 |     else if (this->IsKeyword(keyTARGET, *args.current)) { | 
 |       newArgs.ReduceOneArg(static_cast<bool>(this->Makefile.FindTargetToUse( | 
 |                              args.next->GetValue())), | 
 |                            args); | 
 |     } | 
 |     // is a variable defined | 
 |     else if (this->IsKeyword(keyDEFINED, *args.current)) { | 
 |       const auto& var = args.next->GetValue(); | 
 |       const auto varNameLen = var.size(); | 
 |  | 
 |       auto result = false; | 
 |       if (looksLikeSpecialVariable(var, "ENV"_s, varNameLen)) { | 
 |         const auto env = args.next->GetValue().substr(4, varNameLen - 5); | 
 |         result = cmSystemTools::HasEnv(env); | 
 |       } | 
 |  | 
 |       else if (looksLikeSpecialVariable(var, "CACHE"_s, varNameLen)) { | 
 |         const auto cache = args.next->GetValue().substr(6, varNameLen - 7); | 
 |         result = static_cast<bool>( | 
 |           this->Makefile.GetState()->GetCacheEntryValue(cache)); | 
 |       } | 
 |  | 
 |       else { | 
 |         result = this->Makefile.IsDefinitionSet(args.next->GetValue()); | 
 |       } | 
 |       newArgs.ReduceOneArg(result, args); | 
 |     } | 
 |     // does a test exist | 
 |     else if (this->IsKeyword(keyTEST, *args.current)) { | 
 |       if (this->Policy64Status == cmPolicies::OLD || | 
 |           this->Policy64Status == cmPolicies::WARN) { | 
 |         continue; | 
 |       } | 
 |       newArgs.ReduceOneArg( | 
 |         static_cast<bool>(this->Makefile.GetTest(args.next->GetValue())), | 
 |         args); | 
 |     } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | //========================================================================= | 
 | // level two handles most binary operations except for AND  OR | 
 | bool cmConditionEvaluator::HandleLevel2(cmArgumentList& newArgs, | 
 |                                         std::string& errorString, | 
 |                                         MessageType& status) | 
 | { | 
 |   for (auto args = newArgs.make3ArgsIterator(); args.current != newArgs.end(); | 
 |        args.advance(newArgs)) { | 
 |  | 
 |     int matchNo; | 
 |  | 
 |     // NOTE Handle special case `if(... BLAH_BLAH MATCHES)` | 
 |     // (i.e., w/o regex to match which is possibly result of | 
 |     // variable expansion to an empty string) | 
 |     if (args.next != newArgs.end() && | 
 |         this->IsKeyword(keyMATCHES, *args.current)) { | 
 |       newArgs.ReduceOneArg(false, args); | 
 |     } | 
 |  | 
 |     // NOTE Fail fast: All the binary ops below require 2 arguments. | 
 |     else if (args.next == newArgs.end() || args.nextnext == newArgs.end()) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     else if (this->IsKeyword(keyMATCHES, *args.next)) { | 
 |       cmValue def = this->GetDefinitionIfUnquoted(*args.current); | 
 |  | 
 |       std::string def_buf; | 
 |       if (!def) { | 
 |         def = cmValue(args.current->GetValue()); | 
 |       } else if (cmHasLiteralPrefix(args.current->GetValue(), | 
 |                                     "CMAKE_MATCH_")) { | 
 |         // The string to match is owned by our match result variables. | 
 |         // Move it to our own buffer before clearing them. | 
 |         def_buf = *def; | 
 |         def = cmValue(def_buf); | 
 |       } | 
 |  | 
 |       this->Makefile.ClearMatches(); | 
 |  | 
 |       const auto& rex = args.nextnext->GetValue(); | 
 |       cmsys::RegularExpression regEntry; | 
 |       if (!regEntry.compile(rex)) { | 
 |         std::ostringstream error; | 
 |         error << "Regular expression \"" << rex << "\" cannot compile"; | 
 |         errorString = error.str(); | 
 |         status = MessageType::FATAL_ERROR; | 
 |         return false; | 
 |       } | 
 |  | 
 |       const auto match = regEntry.find(*def); | 
 |       if (match) { | 
 |         this->Makefile.StoreMatches(regEntry); | 
 |       } | 
 |       newArgs.ReduceTwoArgs(match, args); | 
 |     } | 
 |  | 
 |     else if ((matchNo = | 
 |                 this->matchKeys(*args.next, keyLESS, keyLESS_EQUAL, keyGREATER, | 
 |                                 keyGREATER_EQUAL, keyEQUAL))) { | 
 |  | 
 |       cmValue ldef = this->GetVariableOrString(*args.current); | 
 |       cmValue rdef = this->GetVariableOrString(*args.nextnext); | 
 |  | 
 |       double lhs; | 
 |       double rhs; | 
 |       auto parseDoubles = [&]() { | 
 |         return std::sscanf(ldef->c_str(), "%lg", &lhs) == 1 && | 
 |           std::sscanf(rdef->c_str(), "%lg", &rhs) == 1; | 
 |       }; | 
 |       // clang-format off | 
 |       const auto result = parseDoubles() && | 
 |         cmRt2CtSelector< | 
 |             std::less, std::less_equal, | 
 |             std::greater, std::greater_equal, | 
 |             std::equal_to | 
 |           >::eval(matchNo, lhs, rhs); | 
 |       // clang-format on | 
 |       newArgs.ReduceTwoArgs(result, args); | 
 |     } | 
 |  | 
 |     else if ((matchNo = this->matchKeys(*args.next, keySTRLESS, | 
 |                                         keySTRLESS_EQUAL, keySTRGREATER, | 
 |                                         keySTRGREATER_EQUAL, keySTREQUAL))) { | 
 |  | 
 |       const cmValue lhs = this->GetVariableOrString(*args.current); | 
 |       const cmValue rhs = this->GetVariableOrString(*args.nextnext); | 
 |       const auto val = (*lhs).compare(*rhs); | 
 |       // clang-format off | 
 |       const auto result = cmRt2CtSelector< | 
 |             std::less, std::less_equal, | 
 |             std::greater, std::greater_equal, | 
 |             std::equal_to | 
 |           >::eval(matchNo, val, 0); | 
 |       // clang-format on | 
 |       newArgs.ReduceTwoArgs(result, args); | 
 |     } | 
 |  | 
 |     else if ((matchNo = | 
 |                 this->matchKeys(*args.next, keyVERSION_LESS, | 
 |                                 keyVERSION_LESS_EQUAL, keyVERSION_GREATER, | 
 |                                 keyVERSION_GREATER_EQUAL, keyVERSION_EQUAL))) { | 
 |       const auto op = MATCH2CMPOP[matchNo - 1]; | 
 |       const std::string& lhs = this->GetVariableOrString(*args.current); | 
 |       const std::string& rhs = this->GetVariableOrString(*args.nextnext); | 
 |       const auto result = cmSystemTools::VersionCompare(op, lhs, rhs); | 
 |       newArgs.ReduceTwoArgs(result, args); | 
 |     } | 
 |  | 
 |     // is file A newer than file B | 
 |     else if (this->IsKeyword(keyIS_NEWER_THAN, *args.next)) { | 
 |       auto fileIsNewer = 0; | 
 |       cmsys::Status ftcStatus = cmSystemTools::FileTimeCompare( | 
 |         args.current->GetValue(), args.nextnext->GetValue(), &fileIsNewer); | 
 |       newArgs.ReduceTwoArgs( | 
 |         (!ftcStatus || fileIsNewer == 1 || fileIsNewer == 0), args); | 
 |     } | 
 |  | 
 |     else if (this->IsKeyword(keyIN_LIST, *args.next)) { | 
 |  | 
 |       if (this->Policy57Status != cmPolicies::OLD && | 
 |           this->Policy57Status != cmPolicies::WARN) { | 
 |  | 
 |         cmValue lhs = this->GetVariableOrString(*args.current); | 
 |         cmValue rhs = this->Makefile.GetDefinition(args.nextnext->GetValue()); | 
 |  | 
 |         newArgs.ReduceTwoArgs( | 
 |           rhs && cm::contains(cmExpandedList(*rhs, true), *lhs), args); | 
 |       } | 
 |  | 
 |       else if (this->Policy57Status == cmPolicies::WARN) { | 
 |         std::ostringstream e; | 
 |         e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0057) | 
 |           << "\n" | 
 |              "IN_LIST will be interpreted as an operator " | 
 |              "when the policy is set to NEW.  " | 
 |              "Since the policy is not set the OLD behavior will be used."; | 
 |  | 
 |         this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str()); | 
 |       } | 
 |     } | 
 |  | 
 |     else if (this->IsKeyword(keyPATH_EQUAL, *args.next)) { | 
 |  | 
 |       if (this->Policy139Status != cmPolicies::OLD && | 
 |           this->Policy139Status != cmPolicies::WARN) { | 
 |  | 
 |         cmValue lhs = this->GetVariableOrString(*args.current); | 
 |         cmValue rhs = this->GetVariableOrString(*args.nextnext); | 
 |         const auto result = cmCMakePath{ *lhs } == cmCMakePath{ *rhs }; | 
 |         newArgs.ReduceTwoArgs(result, args); | 
 |       } | 
 |  | 
 |       else if (this->Policy139Status == cmPolicies::WARN) { | 
 |         std::ostringstream e; | 
 |         e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0139) | 
 |           << "\n" | 
 |              "PATH_EQUAL will be interpreted as an operator " | 
 |              "when the policy is set to NEW.  " | 
 |              "Since the policy is not set the OLD behavior will be used."; | 
 |  | 
 |         this->Makefile.IssueMessage(MessageType::AUTHOR_WARNING, e.str()); | 
 |       } | 
 |     } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | //========================================================================= | 
 | // level 3 handles NOT | 
 | bool cmConditionEvaluator::HandleLevel3(cmArgumentList& newArgs, | 
 |                                         std::string& errorString, | 
 |                                         MessageType& status) | 
 | { | 
 |   for (auto args = newArgs.make2ArgsIterator(); args.next != newArgs.end(); | 
 |        args.advance(newArgs)) { | 
 |     if (this->IsKeyword(keyNOT, *args.current)) { | 
 |       const auto rhs = this->GetBooleanValueWithAutoDereference( | 
 |         *args.next, errorString, status); | 
 |       newArgs.ReduceOneArg(!rhs, args); | 
 |     } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | //========================================================================= | 
 | // level 4 handles AND OR | 
 | bool cmConditionEvaluator::HandleLevel4(cmArgumentList& newArgs, | 
 |                                         std::string& errorString, | 
 |                                         MessageType& status) | 
 | { | 
 |   for (auto args = newArgs.make3ArgsIterator(); args.nextnext != newArgs.end(); | 
 |        args.advance(newArgs)) { | 
 |  | 
 |     int matchNo; | 
 |  | 
 |     if ((matchNo = this->matchKeys(*args.next, keyAND, keyOR))) { | 
 |       const auto lhs = this->GetBooleanValueWithAutoDereference( | 
 |         *args.current, errorString, status); | 
 |       const auto rhs = this->GetBooleanValueWithAutoDereference( | 
 |         *args.nextnext, errorString, status); | 
 |       // clang-format off | 
 |       const auto result = | 
 |         cmRt2CtSelector< | 
 |             std::logical_and, std::logical_or | 
 |           >::eval(matchNo, lhs, rhs); | 
 |       // clang-format on | 
 |       newArgs.ReduceTwoArgs(result, args); | 
 |     } | 
 |   } | 
 |   return true; | 
 | } |