summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Source/cmConditionEvaluator.cxx964
-rw-r--r--Source/cmConditionEvaluator.h30
-rw-r--r--Tests/RunCMake/if/IncompleteMatches-stdout.txt6
-rw-r--r--Tests/RunCMake/if/IncompleteMatches.cmake36
-rw-r--r--Tests/RunCMake/if/IncompleteMatchesFail-result.txt1
-rw-r--r--Tests/RunCMake/if/IncompleteMatchesFail-stderr.txt6
-rw-r--r--Tests/RunCMake/if/IncompleteMatchesFail.cmake2
-rw-r--r--Tests/RunCMake/if/RunCMakeTest.cmake2
8 files changed, 573 insertions, 474 deletions
diff --git a/Source/cmConditionEvaluator.cxx b/Source/cmConditionEvaluator.cxx
index f99592c..415f667 100644
--- a/Source/cmConditionEvaluator.cxx
+++ b/Source/cmConditionEvaluator.cxx
@@ -2,16 +2,21 @@
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 "cmExpandedCommandArgument.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmProperty.h"
@@ -20,40 +25,184 @@
#include "cmSystemTools.h"
#include "cmake.h"
-class cmTest;
-
-static std::string const keyAND = "AND";
-static std::string const keyCOMMAND = "COMMAND";
-static std::string const keyDEFINED = "DEFINED";
-static std::string const keyEQUAL = "EQUAL";
-static std::string const keyEXISTS = "EXISTS";
-static std::string const keyGREATER = "GREATER";
-static std::string const keyGREATER_EQUAL = "GREATER_EQUAL";
-static std::string const keyIN_LIST = "IN_LIST";
-static std::string const keyIS_ABSOLUTE = "IS_ABSOLUTE";
-static std::string const keyIS_DIRECTORY = "IS_DIRECTORY";
-static std::string const keyIS_NEWER_THAN = "IS_NEWER_THAN";
-static std::string const keyIS_SYMLINK = "IS_SYMLINK";
-static std::string const keyLESS = "LESS";
-static std::string const keyLESS_EQUAL = "LESS_EQUAL";
-static std::string const keyMATCHES = "MATCHES";
-static std::string const keyNOT = "NOT";
-static std::string const keyOR = "OR";
-static std::string const keyParenL = "(";
-static std::string const keyParenR = ")";
-static std::string const keyPOLICY = "POLICY";
-static std::string const keySTREQUAL = "STREQUAL";
-static std::string const keySTRGREATER = "STRGREATER";
-static std::string const keySTRGREATER_EQUAL = "STRGREATER_EQUAL";
-static std::string const keySTRLESS = "STRLESS";
-static std::string const keySTRLESS_EQUAL = "STRLESS_EQUAL";
-static std::string const keyTARGET = "TARGET";
-static std::string const keyTEST = "TEST";
-static std::string const keyVERSION_EQUAL = "VERSION_EQUAL";
-static std::string const keyVERSION_GREATER = "VERSION_GREATER";
-static std::string const keyVERSION_GREATER_EQUAL = "VERSION_GREATER_EQUAL";
-static std::string const keyVERSION_LESS = "VERSION_LESS";
-static std::string const keyVERSION_LESS_EQUAL = "VERSION_LESS_EQUAL";
+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;
+
+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(std::size_t(1), static_cast<char>('0' + 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, difference_type(this->current != args.end()));
+ return *this;
+ }
+
+ private:
+ CurrentAndNextIter(base_t& args)
+ : current(args.begin())
+ , next(std::next(this->current,
+ 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, difference_type(this->current != args.end()));
+ this->nextnext =
+ std::next(this->next, difference_type(this->next != args.end()));
+ return *this;
+ }
+
+ private:
+ CurrentAndTwoMoreIter(base_t& args)
+ : current(args.begin())
+ , next(std::next(this->current,
+ difference_type(this->current != args.end())))
+ , nextnext(
+ std::next(this->next, 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)
@@ -99,25 +248,23 @@ bool cmConditionEvaluator::IsTrue(
// 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
- if (!this->HandleLevel0(newArgs, errorString, status)) {
- return false;
- }
- // predicates
- if (!this->HandleLevel1(newArgs, errorString, status)) {
- return false;
- }
- // binary ops
- if (!this->HandleLevel2(newArgs, errorString, status)) {
- return false;
- }
-
- // NOT
- if (!this->HandleLevel3(newArgs, errorString, status)) {
- return false;
- }
- // AND OR
- if (!this->HandleLevel4(newArgs, errorString, status)) {
- return false;
+ 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)
+ for (auto beginSize = newArgs.size();
+ (this->*fn)(newArgs, errorString, status) &&
+ newArgs.size() < beginSize;
+ beginSize = newArgs.size()) {
+ }
}
// now at the end there should only be one argument left
@@ -147,11 +294,13 @@ cmProp cmConditionEvaluator::GetDefinitionIfUnquoted(
this->Policy54Status == cmPolicies::WARN) {
if (!this->Makefile.HasCMP0054AlreadyBeenReported(this->Backtrace.Top())) {
std::ostringstream e;
- e << (cmPolicies::GetPolicyWarning(cmPolicies::CMP0054)) << "\n";
- e << "Quoted variables like \"" << argument.GetValue()
- << "\" will no longer be dereferenced "
- "when the policy is set to NEW. "
+ // 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);
@@ -175,8 +324,9 @@ cmProp cmConditionEvaluator::GetVariableOrString(
}
//=========================================================================
-bool cmConditionEvaluator::IsKeyword(std::string const& keyword,
- cmExpandedCommandArgument& argument) const
+bool cmConditionEvaluator::IsKeyword(
+ cm::static_string_view keyword,
+ const cmExpandedCommandArgument& argument) const
{
if ((this->Policy54Status != cmPolicies::WARN &&
this->Policy54Status != cmPolicies::OLD) &&
@@ -184,17 +334,20 @@ bool cmConditionEvaluator::IsKeyword(std::string const& keyword,
return false;
}
- bool isKeyword = argument.GetValue() == keyword;
+ const auto isKeyword = argument.GetValue() == keyword;
if (isKeyword && argument.WasQuoted() &&
this->Policy54Status == cmPolicies::WARN) {
if (!this->Makefile.HasCMP0054AlreadyBeenReported(this->Backtrace.Top())) {
std::ostringstream e;
- e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0054) << "\n";
- e << "Quoted keywords like \"" << argument.GetValue()
- << "\" will no longer be interpreted as keywords "
+ // 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);
@@ -208,15 +361,7 @@ bool cmConditionEvaluator::IsKeyword(std::string const& keyword,
bool cmConditionEvaluator::GetBooleanValue(
cmExpandedCommandArgument& arg) const
{
- // Check basic constants.
- if (arg == "0") {
- return false;
- }
- if (arg == "1") {
- return true;
- }
-
- // Check named constants.
+ // Check basic and named constants.
if (cmIsOn(arg.GetValue())) {
return true;
}
@@ -227,7 +372,7 @@ bool cmConditionEvaluator::GetBooleanValue(
// Check for numbers.
if (!arg.empty()) {
char* end;
- double d = strtod(arg.GetValue().c_str(), &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);
@@ -242,7 +387,7 @@ bool cmConditionEvaluator::GetBooleanValue(
//=========================================================================
// Boolean value behavior from CMake 2.6.4 and below.
bool cmConditionEvaluator::GetBooleanValueOld(
- cmExpandedCommandArgument const& arg, bool one) const
+ cmExpandedCommandArgument const& arg, bool const one) const
{
if (one) {
// Old IsTrue behavior for single argument.
@@ -257,7 +402,7 @@ bool cmConditionEvaluator::GetBooleanValueOld(
}
// Old GetVariableOrNumber behavior.
cmProp def = this->GetDefinitionIfUnquoted(arg);
- if (!def && atoi(arg.GetValue().c_str())) {
+ if (!def && std::atoi(arg.GetValue().c_str())) {
def = &arg.GetValue();
}
return !cmIsOff(def);
@@ -267,7 +412,7 @@ bool cmConditionEvaluator::GetBooleanValueOld(
// returns the resulting boolean value
bool cmConditionEvaluator::GetBooleanValueWithAutoDereference(
cmExpandedCommandArgument& newArg, std::string& errorString,
- MessageType& status, bool oneArg) const
+ MessageType& status, bool const oneArg) const
{
// Use the policy if it is set.
if (this->Policy12Status == cmPolicies::NEW) {
@@ -278,8 +423,8 @@ bool cmConditionEvaluator::GetBooleanValueWithAutoDereference(
}
// Check policy only if old and new results differ.
- bool newResult = this->GetBooleanValue(newArg);
- bool oldResult = this->GetBooleanValueOld(newArg, oneArg);
+ const auto newResult = this->GetBooleanValue(newArg);
+ const auto oldResult = this->GetBooleanValueOld(newArg, oneArg);
if (newResult != oldResult) {
switch (this->Policy12Status) {
case cmPolicies::WARN:
@@ -304,56 +449,31 @@ bool cmConditionEvaluator::GetBooleanValueWithAutoDereference(
return newResult;
}
-//=========================================================================
-void cmConditionEvaluator::IncrementArguments(
- cmArgumentList& newArgs, cmArgumentList::iterator& argP1,
- cmArgumentList::iterator& argP2) const
+template <int N>
+inline int cmConditionEvaluator::matchKeysImpl(
+ const cmExpandedCommandArgument&)
{
- if (argP1 != newArgs.end()) {
- argP1++;
- argP2 = argP1;
- if (argP1 != newArgs.end()) {
- argP2++;
- }
- }
+ // Zero means "not found"
+ return 0;
}
-//=========================================================================
-// helper function to reduce code duplication
-void cmConditionEvaluator::HandlePredicate(
- bool value, int& reducible, cmArgumentList::iterator& arg,
- cmArgumentList& newArgs, cmArgumentList::iterator& argP1,
- cmArgumentList::iterator& argP2) const
+template <int N, typename T, typename... Keys>
+inline int cmConditionEvaluator::matchKeysImpl(
+ const cmExpandedCommandArgument& arg, T current, Keys... key)
{
- if (value) {
- *arg = cmExpandedCommandArgument("1", true);
- } else {
- *arg = cmExpandedCommandArgument("0", true);
+ if (this->IsKeyword(current, arg)) {
+ // Stop searching as soon as smth has found
+ return N;
}
- newArgs.erase(argP1);
- argP1 = arg;
- this->IncrementArguments(newArgs, argP1, argP2);
- reducible = 1;
+ return matchKeysImpl<N + 1>(arg, key...);
}
-//=========================================================================
-// helper function to reduce code duplication
-void cmConditionEvaluator::HandleBinaryOp(bool value, int& reducible,
- cmArgumentList::iterator& arg,
- cmArgumentList& newArgs,
- cmArgumentList::iterator& argP1,
- cmArgumentList::iterator& argP2)
+template <typename... Keys>
+inline int cmConditionEvaluator::matchKeys(
+ const cmExpandedCommandArgument& arg, Keys... key)
{
- if (value) {
- *arg = cmExpandedCommandArgument("1", true);
- } else {
- *arg = cmExpandedCommandArgument("0", true);
- }
- newArgs.erase(argP2);
- newArgs.erase(argP1);
- argP1 = arg;
- this->IncrementArguments(newArgs, argP1, argP2);
- reducible = 1;
+ // Get index of the matched key (1-based)
+ return matchKeysImpl<1>(arg, key...);
}
//=========================================================================
@@ -362,55 +482,35 @@ bool cmConditionEvaluator::HandleLevel0(cmArgumentList& newArgs,
std::string& errorString,
MessageType& status)
{
- int reducible;
- do {
- reducible = 0;
- auto arg = newArgs.begin();
- while (arg != newArgs.end()) {
- if (this->IsKeyword(keyParenL, *arg)) {
- // search for the closing paren for this opening one
- cmArgumentList::iterator argClose;
- argClose = arg;
- argClose++;
- unsigned int depth = 1;
- while (argClose != newArgs.end() && depth) {
- if (this->IsKeyword(keyParenL, *argClose)) {
- depth++;
- }
- if (this->IsKeyword(keyParenR, *argClose)) {
- depth--;
- }
- argClose++;
- }
- if (depth) {
- errorString = "mismatched parenthesis in condition";
- status = MessageType::FATAL_ERROR;
- return false;
- }
- // store the reduced args in this vector
- std::vector<cmExpandedCommandArgument> newArgs2;
-
- // copy to the list structure
- auto argP1 = arg;
- argP1++;
- cm::append(newArgs2, argP1, argClose);
- newArgs2.pop_back();
- // now recursively invoke IsTrue to handle the values inside the
- // parenthetical expression
- bool value = this->IsTrue(newArgs2, errorString, status);
- if (value) {
- *arg = cmExpandedCommandArgument("1", true);
- } else {
- *arg = cmExpandedCommandArgument("0", true);
- }
- argP1 = arg;
- argP1++;
- // remove the now evaluated parenthetical expression
- newArgs.erase(argP1, argClose);
+ 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;
}
- ++arg;
+
+ // 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);
}
- } while (reducible);
+ }
return true;
}
@@ -419,96 +519,104 @@ bool cmConditionEvaluator::HandleLevel0(cmArgumentList& newArgs,
bool cmConditionEvaluator::HandleLevel1(cmArgumentList& newArgs, std::string&,
MessageType&)
{
- int reducible;
- do {
- reducible = 0;
- auto arg = newArgs.begin();
- cmArgumentList::iterator argP1;
- cmArgumentList::iterator argP2;
- while (arg != newArgs.end()) {
- argP1 = arg;
- this->IncrementArguments(newArgs, argP1, argP2);
- // does a file exist
- if (this->IsKeyword(keyEXISTS, *arg) && argP1 != newArgs.end()) {
- this->HandlePredicate(cmSystemTools::FileExists(argP1->GetValue()),
- reducible, arg, newArgs, argP1, argP2);
- }
- // does a directory with this name exist
- if (this->IsKeyword(keyIS_DIRECTORY, *arg) && argP1 != newArgs.end()) {
- this->HandlePredicate(
- cmSystemTools::FileIsDirectory(argP1->GetValue()), reducible, arg,
- newArgs, argP1, argP2);
- }
- // does a symlink with this name exist
- if (this->IsKeyword(keyIS_SYMLINK, *arg) && argP1 != newArgs.end()) {
- this->HandlePredicate(cmSystemTools::FileIsSymlink(argP1->GetValue()),
- reducible, arg, newArgs, argP1, argP2);
- }
- // is the given path an absolute path ?
- if (this->IsKeyword(keyIS_ABSOLUTE, *arg) && argP1 != newArgs.end()) {
- this->HandlePredicate(cmSystemTools::FileIsFullPath(argP1->GetValue()),
- reducible, arg, newArgs, argP1, argP2);
- }
- // does a command exist
- if (this->IsKeyword(keyCOMMAND, *arg) && argP1 != newArgs.end()) {
- cmState::Command command =
- this->Makefile.GetState()->GetCommand(argP1->GetValue());
- this->HandlePredicate(command != nullptr, reducible, arg, newArgs,
- argP1, argP2);
- }
- // does a policy exist
- if (this->IsKeyword(keyPOLICY, *arg) && argP1 != newArgs.end()) {
- cmPolicies::PolicyID pid;
- this->HandlePredicate(
- cmPolicies::GetPolicyID(argP1->GetValue().c_str(), pid), reducible,
- arg, newArgs, argP1, argP2);
- }
- // does a target exist
- if (this->IsKeyword(keyTARGET, *arg) && argP1 != newArgs.end()) {
- this->HandlePredicate(
- this->Makefile.FindTargetToUse(argP1->GetValue()) != nullptr,
- reducible, arg, newArgs, argP1, argP2);
- }
- // does a test exist
- if (this->Policy64Status != cmPolicies::OLD &&
- this->Policy64Status != cmPolicies::WARN) {
- if (this->IsKeyword(keyTEST, *arg) && argP1 != newArgs.end()) {
- const cmTest* haveTest = this->Makefile.GetTest(argP1->GetValue());
- this->HandlePredicate(haveTest != nullptr, reducible, arg, newArgs,
- argP1, argP2);
- }
- } else if (this->Policy64Status == cmPolicies::WARN &&
- this->IsKeyword(keyTEST, *arg)) {
+ const auto policy64IsOld = this->Policy64Status == cmPolicies::OLD ||
+ this->Policy64Status == cmPolicies::WARN;
+
+ 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(cmPolicies::CMP0064) << "\n";
- e << "TEST will be interpreted as an operator "
+ 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());
}
- // is a variable defined
- if (this->IsKeyword(keyDEFINED, *arg) && argP1 != newArgs.end()) {
- size_t argP1len = argP1->GetValue().size();
- bool bdef = false;
- if (argP1len > 4 && cmHasLiteralPrefix(argP1->GetValue(), "ENV{") &&
- argP1->GetValue().operator[](argP1len - 1) == '}') {
- std::string env = argP1->GetValue().substr(4, argP1len - 5);
- bdef = cmSystemTools::HasEnv(env);
- } else if (argP1len > 6 &&
- cmHasLiteralPrefix(argP1->GetValue(), "CACHE{") &&
- argP1->GetValue().operator[](argP1len - 1) == '}') {
- std::string cache = argP1->GetValue().substr(6, argP1len - 7);
- bdef =
- this->Makefile.GetState()->GetCacheEntryValue(cache) != nullptr;
- } else {
- bdef = this->Makefile.IsDefinitionSet(argP1->GetValue());
- }
- this->HandlePredicate(bdef, reducible, arg, newArgs, argP1, argP2);
+ };
+
+ // 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(
+ 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(
+ 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 = bool(this->Makefile.GetState()->GetCacheEntryValue(cache));
+ }
+
+ else {
+ result = this->Makefile.IsDefinitionSet(args.next->GetValue());
}
- ++arg;
+ newArgs.ReduceOneArg(result, args);
}
- } while (reducible);
+ // does a test exist
+ else if (this->IsKeyword(keyTEST, *args.current)) {
+ if (policy64IsOld) {
+ continue;
+ }
+ newArgs.ReduceOneArg(bool(this->Makefile.GetTest(args.next->GetValue())),
+ args);
+ }
+ }
return true;
}
@@ -518,178 +626,143 @@ bool cmConditionEvaluator::HandleLevel2(cmArgumentList& newArgs,
std::string& errorString,
MessageType& status)
{
- int reducible;
- std::string def_buf;
- cmProp def;
- cmProp def2;
- do {
- reducible = 0;
- auto arg = newArgs.begin();
- cmArgumentList::iterator argP1;
- cmArgumentList::iterator argP2;
- while (arg != newArgs.end()) {
- argP1 = arg;
- this->IncrementArguments(newArgs, argP1, argP2);
- if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
- this->IsKeyword(keyMATCHES, *argP1)) {
- def = this->GetDefinitionIfUnquoted(*arg);
- if (!def) {
- def = &arg->GetValue();
- } else if (cmHasLiteralPrefix(arg->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 = &def_buf;
- }
- const std::string& rex = argP2->GetValue();
- this->Makefile.ClearMatches();
- 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;
- }
- if (regEntry.find(*def)) {
- this->Makefile.StoreMatches(regEntry);
- *arg = cmExpandedCommandArgument("1", true);
- } else {
- *arg = cmExpandedCommandArgument("0", true);
- }
- newArgs.erase(argP2);
- newArgs.erase(argP1);
- argP1 = arg;
- this->IncrementArguments(newArgs, argP1, argP2);
- reducible = 1;
- }
+ for (auto args = newArgs.make3ArgsIterator(); args.current != newArgs.end();
+ args.advance(newArgs)) {
- if (argP1 != newArgs.end() && this->IsKeyword(keyMATCHES, *arg)) {
- *arg = cmExpandedCommandArgument("0", true);
- newArgs.erase(argP1);
- argP1 = arg;
- this->IncrementArguments(newArgs, argP1, argP2);
- reducible = 1;
- }
+ int matchNo;
- if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
- (this->IsKeyword(keyLESS, *argP1) ||
- this->IsKeyword(keyLESS_EQUAL, *argP1) ||
- this->IsKeyword(keyGREATER, *argP1) ||
- this->IsKeyword(keyGREATER_EQUAL, *argP1) ||
- this->IsKeyword(keyEQUAL, *argP1))) {
- def = this->GetVariableOrString(*arg);
- def2 = this->GetVariableOrString(*argP2);
- double lhs;
- double rhs;
- bool result;
- if (sscanf(def->c_str(), "%lg", &lhs) != 1 ||
- sscanf(def2->c_str(), "%lg", &rhs) != 1) {
- result = false;
- } else if (*(argP1) == keyLESS) {
- result = (lhs < rhs);
- } else if (*(argP1) == keyLESS_EQUAL) {
- result = (lhs <= rhs);
- } else if (*(argP1) == keyGREATER) {
- result = (lhs > rhs);
- } else if (*(argP1) == keyGREATER_EQUAL) {
- result = (lhs >= rhs);
- } else {
- result = (lhs == rhs);
- }
- this->HandleBinaryOp(result, reducible, arg, newArgs, argP1, argP2);
- }
+ // 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);
+ }
- if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
- (this->IsKeyword(keySTRLESS, *argP1) ||
- this->IsKeyword(keySTRLESS_EQUAL, *argP1) ||
- this->IsKeyword(keySTRGREATER, *argP1) ||
- this->IsKeyword(keySTRGREATER_EQUAL, *argP1) ||
- this->IsKeyword(keySTREQUAL, *argP1))) {
- def = this->GetVariableOrString(*arg);
- def2 = this->GetVariableOrString(*argP2);
- int val = (*def).compare(*def2);
- bool result;
- if (*(argP1) == keySTRLESS) {
- result = (val < 0);
- } else if (*(argP1) == keySTRLESS_EQUAL) {
- result = (val <= 0);
- } else if (*(argP1) == keySTRGREATER) {
- result = (val > 0);
- } else if (*(argP1) == keySTRGREATER_EQUAL) {
- result = (val >= 0);
- } else // strequal
- {
- result = (val == 0);
- }
- this->HandleBinaryOp(result, reducible, arg, newArgs, argP1, argP2);
+ // 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)) {
+ cmProp def = this->GetDefinitionIfUnquoted(*args.current);
+
+ std::string def_buf;
+ if (!def) {
+ def = &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 = &def_buf;
}
- if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
- (this->IsKeyword(keyVERSION_LESS, *argP1) ||
- this->IsKeyword(keyVERSION_LESS_EQUAL, *argP1) ||
- this->IsKeyword(keyVERSION_GREATER, *argP1) ||
- this->IsKeyword(keyVERSION_GREATER_EQUAL, *argP1) ||
- this->IsKeyword(keyVERSION_EQUAL, *argP1))) {
- def = this->GetVariableOrString(*arg);
- def2 = this->GetVariableOrString(*argP2);
- cmSystemTools::CompareOp op;
- if (*argP1 == keyVERSION_LESS) {
- op = cmSystemTools::OP_LESS;
- } else if (*argP1 == keyVERSION_LESS_EQUAL) {
- op = cmSystemTools::OP_LESS_EQUAL;
- } else if (*argP1 == keyVERSION_GREATER) {
- op = cmSystemTools::OP_GREATER;
- } else if (*argP1 == keyVERSION_GREATER_EQUAL) {
- op = cmSystemTools::OP_GREATER_EQUAL;
- } else { // version_equal
- op = cmSystemTools::OP_EQUAL;
- }
- bool result =
- cmSystemTools::VersionCompare(op, def->c_str(), def2->c_str());
- this->HandleBinaryOp(result, reducible, arg, newArgs, argP1, argP2);
+ 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;
}
- // is file A newer than file B
- if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
- this->IsKeyword(keyIS_NEWER_THAN, *argP1)) {
- int fileIsNewer = 0;
- cmsys::Status ftcStatus = cmSystemTools::FileTimeCompare(
- arg->GetValue(), (argP2)->GetValue(), &fileIsNewer);
- this->HandleBinaryOp(
- (!ftcStatus || fileIsNewer == 1 || fileIsNewer == 0), reducible, arg,
- newArgs, argP1, argP2);
+ 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))) {
+
+ cmProp ldef = this->GetVariableOrString(*args.current);
+ cmProp 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 cmProp lhs = this->GetVariableOrString(*args.current);
+ const cmProp 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 cmProp lhs = this->GetVariableOrString(*args.current);
+ const cmProp rhs = this->GetVariableOrString(*args.nextnext);
+ const auto result =
+ cmSystemTools::VersionCompare(op, lhs->c_str(), rhs->c_str());
+ 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) {
- if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
- this->IsKeyword(keyIN_LIST, *argP1)) {
- if (this->Policy57Status != cmPolicies::OLD &&
- this->Policy57Status != cmPolicies::WARN) {
- bool result = false;
-
- def = this->GetVariableOrString(*arg);
- def2 = this->Makefile.GetDefinition(argP2->GetValue());
-
- if (def2) {
- std::vector<std::string> list = cmExpandedList(*def2, true);
- result = cm::contains(list, *def);
- }
-
- this->HandleBinaryOp(result, reducible, arg, newArgs, argP1, argP2);
- } else if (this->Policy57Status == cmPolicies::WARN) {
- std::ostringstream e;
- e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0057) << "\n";
- e << "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());
- }
+ cmProp lhs = this->GetVariableOrString(*args.current);
+ cmProp rhs = this->Makefile.GetDefinition(args.nextnext->GetValue());
+
+ newArgs.ReduceTwoArgs(
+ rhs && cm::contains(cmExpandedList(*rhs, true), *lhs), args);
}
- ++arg;
+ 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());
+ }
}
- } while (reducible);
+ }
return true;
}
@@ -699,23 +772,14 @@ bool cmConditionEvaluator::HandleLevel3(cmArgumentList& newArgs,
std::string& errorString,
MessageType& status)
{
- int reducible;
- do {
- reducible = 0;
- auto arg = newArgs.begin();
- cmArgumentList::iterator argP1;
- cmArgumentList::iterator argP2;
- while (arg != newArgs.end()) {
- argP1 = arg;
- this->IncrementArguments(newArgs, argP1, argP2);
- if (argP1 != newArgs.end() && this->IsKeyword(keyNOT, *arg)) {
- bool rhs = this->GetBooleanValueWithAutoDereference(
- *argP1, errorString, status);
- this->HandlePredicate(!rhs, reducible, arg, newArgs, argP1, argP2);
- }
- ++arg;
+ 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);
}
- } while (reducible);
+ }
return true;
}
@@ -725,38 +789,24 @@ bool cmConditionEvaluator::HandleLevel4(cmArgumentList& newArgs,
std::string& errorString,
MessageType& status)
{
- int reducible;
- bool lhs;
- bool rhs;
- do {
- reducible = 0;
- auto arg = newArgs.begin();
- cmArgumentList::iterator argP1;
- cmArgumentList::iterator argP2;
- while (arg != newArgs.end()) {
- argP1 = arg;
- this->IncrementArguments(newArgs, argP1, argP2);
- if (argP1 != newArgs.end() && this->IsKeyword(keyAND, *argP1) &&
- argP2 != newArgs.end()) {
- lhs =
- this->GetBooleanValueWithAutoDereference(*arg, errorString, status);
- rhs = this->GetBooleanValueWithAutoDereference(*argP2, errorString,
- status);
- this->HandleBinaryOp((lhs && rhs), reducible, arg, newArgs, argP1,
- argP2);
- }
-
- if (argP1 != newArgs.end() && this->IsKeyword(keyOR, *argP1) &&
- argP2 != newArgs.end()) {
- lhs =
- this->GetBooleanValueWithAutoDereference(*arg, errorString, status);
- rhs = this->GetBooleanValueWithAutoDereference(*argP2, errorString,
- status);
- this->HandleBinaryOp((lhs || rhs), reducible, arg, newArgs, argP1,
- argP2);
- }
- ++arg;
+ 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);
}
- } while (reducible);
+ }
return true;
}
diff --git a/Source/cmConditionEvaluator.h b/Source/cmConditionEvaluator.h
index cf00ede..00d896b 100644
--- a/Source/cmConditionEvaluator.h
+++ b/Source/cmConditionEvaluator.h
@@ -4,23 +4,22 @@
#include "cmConfigure.h" // IWYU pragma: keep
-#include <list>
#include <string>
#include <vector>
-#include "cmExpandedCommandArgument.h"
+#include <cmext/string_view>
+
#include "cmListFileCache.h"
#include "cmMessageType.h"
#include "cmPolicies.h"
#include "cmProperty.h"
+class cmExpandedCommandArgument;
class cmMakefile;
class cmConditionEvaluator
{
public:
- using cmArgumentList = std::list<cmExpandedCommandArgument>;
-
cmConditionEvaluator(cmMakefile& makefile, cmListFileBacktrace bt);
// this is a shared function for both If and Else to determine if the
@@ -30,14 +29,16 @@ public:
std::string& errorString, MessageType& status);
private:
+ class cmArgumentList;
+
// Filter the given variable definition based on policy CMP0054.
cmProp GetDefinitionIfUnquoted(
const cmExpandedCommandArgument& argument) const;
cmProp GetVariableOrString(const cmExpandedCommandArgument& argument) const;
- bool IsKeyword(std::string const& keyword,
- cmExpandedCommandArgument& argument) const;
+ bool IsKeyword(cm::static_string_view keyword,
+ const cmExpandedCommandArgument& argument) const;
bool GetBooleanValue(cmExpandedCommandArgument& arg) const;
@@ -49,19 +50,14 @@ private:
MessageType& status,
bool oneArg = false) const;
- void IncrementArguments(cmArgumentList& newArgs,
- cmArgumentList::iterator& argP1,
- cmArgumentList::iterator& argP2) const;
+ template <int N>
+ int matchKeysImpl(const cmExpandedCommandArgument&);
- void HandlePredicate(bool value, int& reducible,
- cmArgumentList::iterator& arg, cmArgumentList& newArgs,
- cmArgumentList::iterator& argP1,
- cmArgumentList::iterator& argP2) const;
+ template <int N, typename T, typename... Keys>
+ int matchKeysImpl(const cmExpandedCommandArgument&, T, Keys...);
- void HandleBinaryOp(bool value, int& reducible,
- cmArgumentList::iterator& arg, cmArgumentList& newArgs,
- cmArgumentList::iterator& argP1,
- cmArgumentList::iterator& argP2);
+ template <typename... Keys>
+ int matchKeys(const cmExpandedCommandArgument&, Keys...);
bool HandleLevel0(cmArgumentList& newArgs, std::string& errorString,
MessageType& status);
diff --git a/Tests/RunCMake/if/IncompleteMatches-stdout.txt b/Tests/RunCMake/if/IncompleteMatches-stdout.txt
new file mode 100644
index 0000000..634d988
--- /dev/null
+++ b/Tests/RunCMake/if/IncompleteMatches-stdout.txt
@@ -0,0 +1,6 @@
+-- Test #1 passed
+-- Test #2 passed
+-- Test #3 passed
+-- Test #4 passed
+-- Test #5 passed
+-- Test #6 passed
diff --git a/Tests/RunCMake/if/IncompleteMatches.cmake b/Tests/RunCMake/if/IncompleteMatches.cmake
new file mode 100644
index 0000000..7142cfc
--- /dev/null
+++ b/Tests/RunCMake/if/IncompleteMatches.cmake
@@ -0,0 +1,36 @@
+if(MATCHES)
+ message(SEND_ERROR "Test #1 failed")
+else()
+ message(STATUS "Test #1 passed")
+endif()
+
+if("" MATCHES "")
+ message(STATUS "Test #2 passed")
+else()
+ message(SEND_ERROR "Test #2 failed")
+endif()
+
+if(MATCHES RHS)
+ message(SEND_ERROR "Test #3 failed")
+else()
+ message(STATUS "Test #3 passed")
+endif()
+
+set(RHS "")
+if(MATCHES RHS)
+ message(SEND_ERROR "Test #4 failed")
+else()
+ message(STATUS "Test #4 passed")
+endif()
+
+if(MATCHES "^$")
+ message(SEND_ERROR "Test #5 failed")
+else()
+ message(STATUS "Test #5 passed")
+endif()
+
+if("" MATCHES "^$")
+ message(STATUS "Test #6 passed")
+else()
+ message(SEND_ERROR "Test #6 failed")
+endif()
diff --git a/Tests/RunCMake/if/IncompleteMatchesFail-result.txt b/Tests/RunCMake/if/IncompleteMatchesFail-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/if/IncompleteMatchesFail-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/if/IncompleteMatchesFail-stderr.txt b/Tests/RunCMake/if/IncompleteMatchesFail-stderr.txt
new file mode 100644
index 0000000..6e8ac80
--- /dev/null
+++ b/Tests/RunCMake/if/IncompleteMatchesFail-stderr.txt
@@ -0,0 +1,6 @@
+CMake Error at IncompleteMatchesFail\.cmake:1 \(if\):
+ if given arguments:
+
+ "LHS" "MATCHES"
+
+ Unknown arguments specified
diff --git a/Tests/RunCMake/if/IncompleteMatchesFail.cmake b/Tests/RunCMake/if/IncompleteMatchesFail.cmake
new file mode 100644
index 0000000..a3ef2b8
--- /dev/null
+++ b/Tests/RunCMake/if/IncompleteMatchesFail.cmake
@@ -0,0 +1,2 @@
+if(LHS MATCHES)
+endif()
diff --git a/Tests/RunCMake/if/RunCMakeTest.cmake b/Tests/RunCMake/if/RunCMakeTest.cmake
index f54edf7..239c167 100644
--- a/Tests/RunCMake/if/RunCMakeTest.cmake
+++ b/Tests/RunCMake/if/RunCMakeTest.cmake
@@ -10,6 +10,8 @@ run_cmake(elseif-message)
run_cmake(misplaced-elseif)
run_cmake(MatchesSelf)
+run_cmake(IncompleteMatches)
+run_cmake(IncompleteMatchesFail)
run_cmake(TestNameThatExists)
run_cmake(TestNameThatDoesNotExist)