ENH: support parenthesis as arguments and in conditionals feature request #6191

This commit is contained in:
Ken Martin 2008-06-26 13:01:35 -04:00
parent d8e05b43a1
commit 19e891532a
11 changed files with 346 additions and 228 deletions

View File

@ -21,8 +21,10 @@
#include <list>
#include <cmsys/RegularExpression.hxx>
//=========================================================================
bool cmIfFunctionBlocker::
IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile &mf,
IsFunctionBlocked(const cmListFileFunction& lff,
cmMakefile &mf,
cmExecutionStatus &inStatus)
{
// Prevent recusion and don't let this blocker block its own
@ -140,6 +142,7 @@ IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile &mf,
return true;
}
//=========================================================================
bool cmIfFunctionBlocker::ShouldRemove(const cmListFileFunction& lff,
cmMakefile&)
{
@ -157,8 +160,8 @@ bool cmIfFunctionBlocker::ShouldRemove(const cmListFileFunction& lff,
return false;
}
void cmIfFunctionBlocker::
ScopeEnded(cmMakefile &mf)
//=========================================================================
void cmIfFunctionBlocker::ScopeEnded(cmMakefile &mf)
{
std::string errmsg = "The end of a CMakeLists file was reached with an "
"IF statement that was not closed properly.\nWithin the directory: ";
@ -175,6 +178,7 @@ ScopeEnded(cmMakefile &mf)
cmSystemTools::Message(errmsg.c_str(), "Warning");
}
//=========================================================================
bool cmIfCommand
::InvokeInitialPass(const std::vector<cmListFileArgument>& args,
cmExecutionStatus &)
@ -221,6 +225,7 @@ bool cmIfCommand
namespace
{
//=========================================================================
void IncrementArguments(std::list<std::string> &newArgs,
std::list<std::string>::iterator &argP1,
std::list<std::string>::iterator &argP2)
@ -235,60 +240,140 @@ namespace
}
}
}
}
//=========================================================================
// helper function to reduce code duplication
void HandlePredicate(bool value, int &reducible,
std::list<std::string>::iterator &arg,
std::list<std::string> &newArgs,
std::list<std::string>::iterator &argP1,
std::list<std::string>::iterator &argP2)
{
if(value)
{
*arg = "1";
}
else
{
*arg = "0";
}
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
}
// order of operations,
// IS_DIRECTORY EXISTS COMMAND DEFINED
// MATCHES LESS GREATER EQUAL STRLESS STRGREATER STREQUAL
// 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.
//=========================================================================
// helper function to reduce code duplication
void HandleBinaryOp(bool value, int &reducible,
std::list<std::string>::iterator &arg,
std::list<std::string> &newArgs,
std::list<std::string>::iterator &argP1,
std::list<std::string>::iterator &argP2)
{
if(value)
{
*arg = "1";
}
else
{
*arg = "0";
}
newArgs.erase(argP2);
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
}
bool cmIfCommand::IsTrue(const std::vector<std::string> &args,
char **errorString, cmMakefile *makefile)
{
// check for the different signatures
const char *def;
const char *def2;
const char* msg = "Unknown arguments specified";
*errorString = new char[strlen(msg) + 1];
strcpy(*errorString, msg);
// handle empty invocation
if (args.size() < 1)
{
delete [] *errorString;
*errorString = 0;
return false;
}
// store the reduced args in this vector
std::list<std::string> newArgs;
//=========================================================================
// level 0 processes parenthetical expressions
bool HandleLevel0(std::list<std::string> &newArgs,
cmMakefile *makefile,
char **errorString)
{
int reducible;
unsigned int i;
// copy to the list structure
for(i = 0; i < args.size(); ++i)
{
newArgs.push_back(args[i]);
}
std::list<std::string>::iterator argP1;
std::list<std::string>::iterator argP2;
// 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
do
{
reducible = 0;
std::list<std::string>::iterator arg = newArgs.begin();
while (arg != newArgs.end())
{
if (*arg == "(")
{
// search for the closing paren for this opening one
std::list<std::string>::iterator argClose;
argClose = arg;
argClose++;
unsigned int depth = 1;
while (argClose != newArgs.end() && depth)
{
if (*argClose == "(")
{
depth++;
}
if (*argClose == ")")
{
depth--;
}
argClose++;
}
if (depth)
{
cmOStringStream error;
error << "mismatched parenthesis in condition";
delete [] *errorString;
*errorString = new char[error.str().size() + 1];
strcpy(*errorString, error.str().c_str());
return false;
}
// store the reduced args in this vector
std::vector<std::string> newArgs2;
// copy to the list structure
std::list<std::string>::iterator argP1 = arg;
argP1++;
for(; argP1 != argClose; argP1++)
{
newArgs2.push_back(*argP1);
}
newArgs2.pop_back();
// now recursively invoke IsTrue to handle the values inside the parenthetical expression
bool value =
cmIfCommand::IsTrue(newArgs2, errorString, makefile);
if(value)
{
*arg = "1";
}
else
{
*arg = "0";
}
argP1 = arg;
argP1++;
// remove the now evaluated parenthetical expression
newArgs.erase(argP1,argClose);
}
++arg;
}
}
while (reducible);
return true;
}
//=========================================================================
// level one handles most predicates except for NOT
bool HandleLevel1(std::list<std::string> &newArgs,
cmMakefile *makefile,
char **)
{
int reducible;
do
{
reducible = 0;
std::list<std::string>::iterator arg = newArgs.begin();
std::list<std::string>::iterator argP1;
std::list<std::string>::iterator argP2;
while (arg != newArgs.end())
{
argP1 = arg;
@ -296,83 +381,38 @@ bool cmIfCommand::IsTrue(const std::vector<std::string> &args,
// does a file exist
if (*arg == "EXISTS" && argP1 != newArgs.end())
{
if(cmSystemTools::FileExists((argP1)->c_str()))
{
*arg = "1";
}
else
{
*arg = "0";
}
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
HandlePredicate(
cmSystemTools::FileExists((argP1)->c_str()),
reducible, arg, newArgs, argP1, argP2);
}
// does a directory with this name exist
if (*arg == "IS_DIRECTORY" && argP1 != newArgs.end())
{
if(cmSystemTools::FileIsDirectory((argP1)->c_str()))
{
*arg = "1";
}
else
{
*arg = "0";
}
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
HandlePredicate(
cmSystemTools::FileIsDirectory((argP1)->c_str()),
reducible, arg, newArgs, argP1, argP2);
}
// is the given path an absolute path ?
if (*arg == "IS_ABSOLUTE" && argP1 != newArgs.end())
{
if(cmSystemTools::FileIsFullPath((argP1)->c_str()))
{
*arg = "1";
}
else
{
*arg = "0";
}
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
HandlePredicate(
cmSystemTools::FileIsFullPath((argP1)->c_str()),
reducible, arg, newArgs, argP1, argP2);
}
// does a command exist
if (*arg == "COMMAND" && argP1 != newArgs.end())
{
if(makefile->CommandExists((argP1)->c_str()))
{
*arg = "1";
}
else
{
*arg = "0";
}
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
HandlePredicate(
makefile->CommandExists((argP1)->c_str()),
reducible, arg, newArgs, argP1, argP2);
}
// does a policy exist
if (*arg == "POLICY" && argP1 != newArgs.end())
{
cmPolicies::PolicyID pid;
if(makefile->GetPolicies()->GetPolicyID((argP1)->c_str(), pid))
{
*arg = "1";
}
else
{
*arg = "0";
}
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
HandlePredicate(
makefile->GetPolicies()->GetPolicyID((argP1)->c_str(), pid),
reducible, arg, newArgs, argP1, argP2);
}
// is a variable defined
if (*arg == "DEFINED" && argP1 != newArgs.end())
@ -389,32 +429,30 @@ bool cmIfCommand::IsTrue(const std::vector<std::string> &args,
{
bdef = makefile->IsDefinitionSet((argP1)->c_str());
}
if(bdef)
{
*arg = "1";
}
else
{
*arg = "0";
}
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
HandlePredicate(bdef, reducible, arg, newArgs, argP1, argP2);
}
++arg;
}
}
while (reducible);
return true;
}
// 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
//=========================================================================
// level two handles most binary operations except for AND OR
bool HandleLevel2(std::list<std::string> &newArgs,
cmMakefile *makefile,
char **errorString)
{
int reducible;
const char *def;
const char *def2;
do
{
reducible = 0;
std::list<std::string>::iterator arg = newArgs.begin();
std::list<std::string>::iterator argP1;
std::list<std::string>::iterator argP2;
while (arg != newArgs.end())
{
argP1 = arg;
@ -468,49 +506,26 @@ bool cmIfCommand::IsTrue(const std::vector<std::string> &args,
def2 = cmIfCommand::GetVariableOrString((argP2)->c_str(), makefile);
double lhs;
double rhs;
bool result;
if(sscanf(def, "%lg", &lhs) != 1 ||
sscanf(def2, "%lg", &rhs) != 1)
{
*arg = "0";
result = false;
}
else if (*(argP1) == "LESS")
{
if(lhs < rhs)
{
*arg = "1";
}
else
{
*arg = "0";
}
result = (lhs < rhs);
}
else if (*(argP1) == "GREATER")
{
if(lhs > rhs)
{
*arg = "1";
}
else
{
*arg = "0";
}
result = (lhs > rhs);
}
else
{
if(lhs == rhs)
{
*arg = "1";
}
else
{
*arg = "0";
}
result = (lhs == rhs);
}
newArgs.erase(argP2);
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
HandleBinaryOp(result,
reducible, arg, newArgs, argP1, argP2);
}
if (argP1 != newArgs.end() && argP2 != newArgs.end() &&
@ -521,7 +536,7 @@ bool cmIfCommand::IsTrue(const std::vector<std::string> &args,
def = cmIfCommand::GetVariableOrString(arg->c_str(), makefile);
def2 = cmIfCommand::GetVariableOrString((argP2)->c_str(), makefile);
int val = strcmp(def,def2);
int result;
bool result;
if (*(argP1) == "STRLESS")
{
result = (val < 0);
@ -534,19 +549,8 @@ bool cmIfCommand::IsTrue(const std::vector<std::string> &args,
{
result = (val == 0);
}
if(result)
{
*arg = "1";
}
else
{
*arg = "0";
}
newArgs.erase(argP2);
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
HandleBinaryOp(result,
reducible, arg, newArgs, argP1, argP2);
}
// is file A newer than file B
@ -557,33 +561,32 @@ bool cmIfCommand::IsTrue(const std::vector<std::string> &args,
bool success=cmSystemTools::FileTimeCompare(arg->c_str(),
(argP2)->c_str(),
&fileIsNewer);
if(success==false || fileIsNewer==1 || fileIsNewer==0)
{
*arg = "1";
}
else
{
*arg = "0";
}
newArgs.erase(argP2);
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
HandleBinaryOp(
(success==false || fileIsNewer==1 || fileIsNewer==0),
reducible, arg, newArgs, argP1, argP2);
}
++arg;
}
}
while (reducible);
return true;
}
// 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
//=========================================================================
// level 3 handles NOT
bool HandleLevel3(std::list<std::string> &newArgs,
cmMakefile *makefile,
char **)
{
int reducible;
const char *def;
do
{
reducible = 0;
std::list<std::string>::iterator arg = newArgs.begin();
std::list<std::string>::iterator argP1;
std::list<std::string>::iterator argP2;
while (arg != newArgs.end())
{
argP1 = arg;
@ -591,30 +594,31 @@ bool cmIfCommand::IsTrue(const std::vector<std::string> &args,
if (argP1 != newArgs.end() && *arg == "NOT")
{
def = cmIfCommand::GetVariableOrNumber((argP1)->c_str(), makefile);
if(!cmSystemTools::IsOff(def))
{
*arg = "0";
}
else
{
*arg = "1";
}
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
HandlePredicate(cmSystemTools::IsOff(def),
reducible, arg, newArgs, argP1, argP2);
}
++arg;
}
}
while (reducible);
return true;
}
// 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
//=========================================================================
// level 4 handles AND OR
bool HandleLevel4(std::list<std::string> &newArgs,
cmMakefile *makefile,
char **)
{
int reducible;
const char *def;
const char *def2;
do
{
reducible = 0;
std::list<std::string>::iterator arg = newArgs.begin();
std::list<std::string>::iterator argP1;
std::list<std::string>::iterator argP2;
while (arg != newArgs.end())
{
argP1 = arg;
@ -624,19 +628,9 @@ bool cmIfCommand::IsTrue(const std::vector<std::string> &args,
{
def = cmIfCommand::GetVariableOrNumber(arg->c_str(), makefile);
def2 = cmIfCommand::GetVariableOrNumber((argP2)->c_str(), makefile);
if(cmSystemTools::IsOff(def) || cmSystemTools::IsOff(def2))
{
*arg = "0";
}
else
{
*arg = "1";
}
newArgs.erase(argP2);
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
HandleBinaryOp(
!(cmSystemTools::IsOff(def) || cmSystemTools::IsOff(def2)),
reducible, arg, newArgs, argP1, argP2);
}
if (argP1 != newArgs.end() && *(argP1) == "OR" &&
@ -644,25 +638,84 @@ bool cmIfCommand::IsTrue(const std::vector<std::string> &args,
{
def = cmIfCommand::GetVariableOrNumber(arg->c_str(), makefile);
def2 = cmIfCommand::GetVariableOrNumber((argP2)->c_str(), makefile);
if(cmSystemTools::IsOff(def) && cmSystemTools::IsOff(def2))
{
*arg = "0";
}
else
{
*arg = "1";
}
newArgs.erase(argP2);
newArgs.erase(argP1);
argP1 = arg;
IncrementArguments(newArgs,argP1,argP2);
reducible = 1;
HandleBinaryOp(
!(cmSystemTools::IsOff(def) && cmSystemTools::IsOff(def2)),
reducible, arg, newArgs, argP1, argP2);
}
++arg;
}
}
while (reducible);
return true;
}
}
//=========================================================================
// 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 cmIfCommand::IsTrue(const std::vector<std::string> &args,
char **errorString, cmMakefile *makefile)
{
// check for the different signatures
const char *def;
const char* msg = "Unknown arguments specified";
*errorString = new char[strlen(msg) + 1];
strcpy(*errorString, msg);
// handle empty invocation
if (args.size() < 1)
{
delete [] *errorString;
*errorString = 0;
return false;
}
// store the reduced args in this vector
std::list<std::string> newArgs;
// copy to the list structure
for(unsigned int i = 0; i < args.size(); ++i)
{
newArgs.push_back(args[i]);
}
// 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
if (!HandleLevel0(newArgs, makefile, errorString)) // parens
{
return false;
}
if (!HandleLevel1(newArgs, makefile, errorString)) //predicates
{
return false;
}
if (!HandleLevel2(newArgs, makefile, errorString)) // binary ops
{
return false;
}
if (!HandleLevel3(newArgs, makefile, errorString)) // NOT
{
return false;
}
if (!HandleLevel4(newArgs, makefile, errorString)) // AND OR
{
return false;
}
// now at the end there should only be one argument left
if (newArgs.size() == 1)
@ -687,6 +740,7 @@ bool cmIfCommand::IsTrue(const std::vector<std::string> &args,
return true;
}
//=========================================================================
const char* cmIfCommand::GetVariableOrString(const char* str,
const cmMakefile* mf)
{
@ -698,6 +752,7 @@ const char* cmIfCommand::GetVariableOrString(const char* str,
return def;
}
//=========================================================================
const char* cmIfCommand::GetVariableOrNumber(const char* str,
const cmMakefile* mf)
{

View File

@ -242,11 +242,26 @@ bool cmListFileCacheParseFunction(cmListFileLexer* lexer,
// Arguments.
unsigned long lastLine = cmListFileLexer_GetCurrentLine(lexer);
unsigned long parenDepth = 0;
while((token = cmListFileLexer_Scan(lexer)))
{
if(token->type == cmListFileLexer_Token_ParenRight)
if(token->type == cmListFileLexer_Token_ParenLeft)
{
return true;
parenDepth++;
cmListFileArgument a("(",
false, filename, token->line);
function.Arguments.push_back(a);
}
else if(token->type == cmListFileLexer_Token_ParenRight)
{
if (parenDepth == 0)
{
return true;
}
parenDepth--;
cmListFileArgument a(")",
false, filename, token->line);
function.Arguments.push_back(a);
}
else if(token->type == cmListFileLexer_Token_Identifier ||
token->type == cmListFileLexer_Token_ArgumentUnquoted)

View File

@ -336,6 +336,12 @@ if (NOT ELSEIF_RESULT EQUAL 2)
set (ELSEIF_RESULT 0)
endif (NOT ELSEIF_RESULT EQUAL 2)
# test handling of parenthetical groups in conditionals
if (2 GREATER 1 AND (4 LESS 3 OR 5 LESS 6) AND NOT (7 GREATER 8))
set(CONDITIONAL_PARENTHESES 1)
endif()
#
# Configure file
# (plug vars to #define so that they can be tested)

View File

@ -369,6 +369,12 @@ int main()
cmFailed("ELSEIF did not work");
#endif
#ifdef CONDITIONAL_PARENTHESES
cmPassed("CONDITIONAL_PARENTHESES did work");
#else
cmFailed("CONDITIONAL_PARENTHESES did not work");
#endif
if(file2() != 1)
{
cmFailed("Call to file2 function from library failed.");

View File

@ -81,3 +81,7 @@
// test elseif
#cmakedefine ELSEIF_RESULT
// test parenthesis in conditionals
#cmakedefine CONDITIONAL_PARENTHESES

View File

@ -336,6 +336,12 @@ if (NOT ELSEIF_RESULT EQUAL 2)
set (ELSEIF_RESULT 0)
endif (NOT ELSEIF_RESULT EQUAL 2)
# test handling of parenthetical groups in conditionals
if (2 GREATER 1 AND (4 LESS 3 OR 5 LESS 6) AND NOT (7 GREATER 8))
set(CONDITIONAL_PARENTHESES 1)
endif()
#
# Configure file
# (plug vars to #define so that they can be tested)

View File

@ -369,6 +369,12 @@ int main()
cmFailed("ELSEIF did not work");
#endif
#ifdef CONDITIONAL_PARENTHESES
cmPassed("CONDITIONAL_PARENTHESES did work");
#else
cmFailed("CONDITIONAL_PARENTHESES did not work");
#endif
if(file2() != 1)
{
cmFailed("Call to file2 function from library failed.");

View File

@ -81,3 +81,7 @@
// test elseif
#cmakedefine ELSEIF_RESULT
// test parenthesis in conditionals
#cmakedefine CONDITIONAL_PARENTHESES

View File

@ -336,6 +336,12 @@ if (NOT ELSEIF_RESULT EQUAL 2)
set (ELSEIF_RESULT 0)
endif (NOT ELSEIF_RESULT EQUAL 2)
# test handling of parenthetical groups in conditionals
if (2 GREATER 1 AND (4 LESS 3 OR 5 LESS 6) AND NOT (7 GREATER 8))
set(CONDITIONAL_PARENTHESES 1)
endif()
#
# Configure file
# (plug vars to #define so that they can be tested)

View File

@ -369,6 +369,12 @@ int main()
cmFailed("ELSEIF did not work");
#endif
#ifdef CONDITIONAL_PARENTHESES
cmPassed("CONDITIONAL_PARENTHESES did work");
#else
cmFailed("CONDITIONAL_PARENTHESES did not work");
#endif
if(file2() != 1)
{
cmFailed("Call to file2 function from library failed.");

View File

@ -81,3 +81,7 @@
// test elseif
#cmakedefine ELSEIF_RESULT
// test parenthesis in conditionals
#cmakedefine CONDITIONAL_PARENTHESES