cmGeneratorExpression: Re-write for multi-stage evaluation

The expressions may be parsed and then cached and evaluated multiple
times.  They are evaluated lazily so that literals such as ',' can be
treated as universal parameter separators, and can be processed from
results without appearing literally, and without interfering with the
parsing/evaluation of the entire expression.
This commit is contained in:
Stephen Kelly 2012-09-11 19:53:38 +02:00 committed by Brad King
parent 1d3db6b34d
commit f1eacf0e07
18 changed files with 1325 additions and 205 deletions

View File

@ -183,6 +183,12 @@ set(SRCS
cmFileTimeComparison.cxx
cmFileTimeComparison.h
cmGeneratedFileStream.cxx
cmGeneratorExpressionEvaluator.cxx
cmGeneratorExpressionEvaluator.h
cmGeneratorExpressionLexer.cxx
cmGeneratorExpressionLexer.h
cmGeneratorExpressionParser.cxx
cmGeneratorExpressionParser.h
cmGeneratorExpression.cxx
cmGeneratorExpression.h
cmGeneratorTarget.cxx

View File

@ -16,18 +16,17 @@
#include <cmsys/String.h>
#include "cmGeneratorExpressionEvaluator.h"
#include "cmGeneratorExpressionLexer.h"
#include "cmGeneratorExpressionParser.h"
//----------------------------------------------------------------------------
cmGeneratorExpression::cmGeneratorExpression(
cmMakefile* mf, const char* config,
cmListFileBacktrace const& backtrace, bool quiet):
Makefile(mf), Config(config), Backtrace(backtrace), Quiet(quiet)
Makefile(mf), Config(config), Backtrace(backtrace), Quiet(quiet),
NeedsParsing(true)
{
this->TargetInfo.compile("^\\$<TARGET"
"(|_SONAME|_LINKER)" // File with what purpose?
"_FILE(|_NAME|_DIR):" // Filename component.
"([A-Za-z0-9_.-]+)" // Target name.
">$");
this->TestConfig.compile("^\\$<CONFIG:([A-Za-z0-9_]*)>$");
}
//----------------------------------------------------------------------------
@ -39,210 +38,77 @@ const char* cmGeneratorExpression::Process(std::string const& input)
//----------------------------------------------------------------------------
const char* cmGeneratorExpression::Process(const char* input)
{
this->Data.clear();
// We construct and evaluate expressions directly in the output
// buffer. Each expression is replaced by its own output value
// after evaluation. A stack of barriers records the starting
// indices of open (pending) expressions.
for(const char* c = input; *c; ++c)
{
if(c[0] == '$' && c[1] == '<')
{
this->Barriers.push(this->Data.size());
this->Data.push_back('$');
this->Data.push_back('<');
c += 1;
}
else if(c[0] == '>' && !this->Barriers.empty())
{
this->Data.push_back('>');
if(!this->Evaluate()) { break; }
this->Barriers.pop();
}
else
{
this->Data.push_back(c[0]);
}
}
// Return a null-terminated output value.
this->Data.push_back('\0');
return &*this->Data.begin();
this->Parse(input);
return this->Evaluate(this->Makefile, this->Config, this->Quiet);
}
//----------------------------------------------------------------------------
bool cmGeneratorExpression::Evaluate()
void cmGeneratorExpression::Parse(const char* input)
{
// The top-most barrier points at the beginning of the expression.
size_t barrier = this->Barriers.top();
this->Evaluators.clear();
// Construct a null-terminated representation of the expression.
this->Data.push_back('\0');
const char* expr = &*(this->Data.begin()+barrier);
this->Input = input;
cmGeneratorExpressionLexer l;
std::vector<cmGeneratorExpressionToken> tokens = l.Tokenize(this->Input);
this->NeedsParsing = l.GetSawGeneratorExpression();
// Evaluate the expression.
std::string result;
if(this->Evaluate(expr, result))
if (!this->NeedsParsing)
{
// Success. Replace the expression with its evaluation result.
this->Data.erase(this->Data.begin()+barrier, this->Data.end());
this->Data.insert(this->Data.end(), result.begin(), result.end());
return true;
return;
}
else if(!this->Quiet)
{
// Failure. Report the error message.
cmOStringStream e;
e << "Error evaluating generator expression:\n"
<< " " << expr << "\n"
<< result;
this->Makefile->GetCMakeInstance()
->IssueMessage(cmake::FATAL_ERROR, e.str().c_str(),
this->Backtrace);
return false;
}
return true;
cmGeneratorExpressionParser p(tokens);
p.Parse(this->Evaluators);
}
//----------------------------------------------------------------------------
static bool cmGeneratorExpressionBool(const char* c, std::string& result,
const char* name,
const char* a, const char* b)
const char *cmGeneratorExpression::Evaluate(
cmMakefile* mf, const char* config, bool quiet)
{
result = a;
while((c[0] == '0' || c[0] == '1') && (c[1] == ',' || c[1] == '>'))
if (!this->NeedsParsing)
{
if(c[0] == b[0]) { result = b; }
c += 2;
return this->Input;
}
if(c[0])
this->Output = "";
std::vector<cmGeneratorExpressionEvaluator*>::const_iterator it
= this->Evaluators.begin();
const std::vector<cmGeneratorExpressionEvaluator*>::const_iterator end
= this->Evaluators.end();
cmGeneratorExpressionContext context;
context.Makefile = mf;
context.Config = config;
context.Quiet = quiet;
context.HadError = false;
context.Backtrace = this->Backtrace;
for ( ; it != end; ++it)
{
result = name;
result += " requires one or more comma-separated '0' or '1' values.";
return false;
this->Output += (*it)->Evaluate(&context);
if (context.HadError)
{
this->Output = "";
break;
}
}
return true;
this->Targets = context.Targets;
// TODO: Return a std::string from here instead?
return this->Output.c_str();
}
//----------------------------------------------------------------------------
bool cmGeneratorExpression::Evaluate(const char* expr, std::string& result)
cmGeneratorExpression::~cmGeneratorExpression()
{
if(this->TargetInfo.find(expr))
std::vector<cmGeneratorExpressionEvaluator*>::const_iterator it
= this->Evaluators.begin();
const std::vector<cmGeneratorExpressionEvaluator*>::const_iterator end
= this->Evaluators.end();
for ( ; it != end; ++it)
{
if(!this->EvaluateTargetInfo(result))
{
return false;
}
delete *it;
}
else if(strcmp(expr, "$<CONFIGURATION>") == 0)
{
result = this->Config? this->Config : "";
}
else if(strncmp(expr, "$<0:",4) == 0)
{
result = "";
}
else if(strncmp(expr, "$<1:",4) == 0)
{
result = std::string(expr+4, strlen(expr)-5);
}
else if(strncmp(expr, "$<NOT:",6) == 0)
{
const char* c = expr+6;
if((c[0] != '0' && c[0] != '1') || c[1] != '>' || c[2])
{
result = "NOT requires exactly one '0' or '1' value.";
return false;
}
result = c[0] == '1'? "0" : "1";
}
else if(strncmp(expr, "$<AND:",6) == 0)
{
return cmGeneratorExpressionBool(expr+6, result, "AND", "1", "0");
}
else if(strncmp(expr, "$<OR:",5) == 0)
{
return cmGeneratorExpressionBool(expr+5, result, "OR", "0", "1");
}
else if(this->TestConfig.find(expr))
{
result = cmsysString_strcasecmp(this->TestConfig.match(1).c_str(),
this->Config? this->Config:"") == 0
? "1":"0";
}
else
{
result = "Expression syntax not recognized.";
return false;
}
return true;
}
//----------------------------------------------------------------------------
bool cmGeneratorExpression::EvaluateTargetInfo(std::string& result)
{
// Lookup the referenced target.
std::string name = this->TargetInfo.match(3);
cmTarget* target = this->Makefile->FindTargetToUse(name.c_str());
if(!target)
{
result = "No target \"" + name + "\"";
return false;
}
if(target->GetType() >= cmTarget::UTILITY &&
target->GetType() != cmTarget::UNKNOWN_LIBRARY)
{
result = "Target \"" + name + "\" is not an executable or library.";
return false;
}
this->Targets.insert(target);
// Lookup the target file with the given purpose.
std::string purpose = this->TargetInfo.match(1);
if(purpose == "")
{
// The target implementation file (.so.1.2, .dll, .exe, .a).
result = target->GetFullPath(this->Config, false, true);
}
else if(purpose == "_LINKER")
{
// The file used to link to the target (.so, .lib, .a).
if(!target->IsLinkable())
{
result = ("TARGET_LINKER_FILE is allowed only for libraries and "
"executables with ENABLE_EXPORTS.");
return false;
}
result = target->GetFullPath(this->Config, target->HasImportLibrary());
}
else if(purpose == "_SONAME")
{
// The target soname file (.so.1).
if(target->IsDLLPlatform())
{
result = "TARGET_SONAME_FILE is not allowed for DLL target platforms.";
return false;
}
if(target->GetType() != cmTarget::SHARED_LIBRARY)
{
result = "TARGET_SONAME_FILE is allowed only for SHARED libraries.";
return false;
}
result = target->GetDirectory(this->Config);
result += "/";
result += target->GetSOName(this->Config);
}
// Extract the requested portion of the full path.
std::string part = this->TargetInfo.match(2);
if(part == "_NAME")
{
result = cmSystemTools::GetFilenameName(result);
}
else if(part == "_DIR")
{
result = cmSystemTools::GetFilenamePath(result);
}
return true;
}

View File

@ -19,6 +19,8 @@ class cmTarget;
class cmMakefile;
class cmListFileBacktrace;
struct cmGeneratorExpressionEvaluator;
/** \class cmGeneratorExpression
* \brief Evaluate generate-time query expression syntax.
*
@ -36,24 +38,29 @@ public:
cmListFileBacktrace const& backtrace,
bool quiet = false);
~cmGeneratorExpression();
/** Evaluate generator expressions in a string. */
const char* Process(std::string const& input);
const char* Process(const char* input);
void Parse(const char* input);
const char* Evaluate(cmMakefile* mf, const char* config,
bool quiet = false);
/** Get set of targets found during evaluations. */
std::set<cmTarget*> const& GetTargets() const
{ return this->Targets; }
private:
std::vector<cmGeneratorExpressionEvaluator*> Evaluators;
cmMakefile* Makefile;
const char* Config;
cmListFileBacktrace const& Backtrace;
bool Quiet;
std::vector<char> Data;
std::stack<size_t> Barriers;
cmsys::RegularExpression TargetInfo;
cmsys::RegularExpression TestConfig;
std::set<cmTarget*> Targets;
bool Evaluate();
bool Evaluate(const char* expr, std::string& result);
bool EvaluateTargetInfo(std::string& result);
const char* Input;
bool NeedsParsing;
std::string Output;
};

View File

@ -0,0 +1,568 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2012 Stephen Kelly <steveire@gmail.com>
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmMakefile.h"
#include "cmGeneratorExpressionEvaluator.h"
#include "cmGeneratorExpressionParser.h"
//----------------------------------------------------------------------------
static void reportError(cmGeneratorExpressionContext *context,
const std::string &expr, const std::string &result)
{
context->HadError = true;
if (context->Quiet)
{
return;
}
cmOStringStream e;
e << "Error evaluating generator expression:\n"
<< " " << expr << "\n"
<< result;
context->Makefile->GetCMakeInstance()
->IssueMessage(cmake::FATAL_ERROR, e.str().c_str(),
context->Backtrace);
}
//----------------------------------------------------------------------------
struct cmGeneratorExpressionNode
{
virtual ~cmGeneratorExpressionNode() {}
virtual bool GeneratesContent() const { return true; }
virtual bool AcceptsSingleArbitraryContentParameter() const
{ return false; }
virtual int NumExpectedParameters() const { return 1; }
virtual std::string Evaluate(const std::vector<std::string> &parameters,
cmGeneratorExpressionContext *context,
const GeneratorExpressionContent *content
) const = 0;
};
//----------------------------------------------------------------------------
static const struct ZeroNode : public cmGeneratorExpressionNode
{
ZeroNode() {}
virtual bool GeneratesContent() const { return false; }
std::string Evaluate(const std::vector<std::string> &,
cmGeneratorExpressionContext *,
const GeneratorExpressionContent *) const
{
// Unreachable
return std::string();
}
} zeroNode;
//----------------------------------------------------------------------------
static const struct OneNode : public cmGeneratorExpressionNode
{
OneNode() {}
virtual bool AcceptsSingleArbitraryContentParameter() const { return true; }
std::string Evaluate(const std::vector<std::string> &,
cmGeneratorExpressionContext *,
const GeneratorExpressionContent *) const
{
// Unreachable
return std::string();
}
} oneNode;
//----------------------------------------------------------------------------
#define BOOLEAN_OP_NODE(OPNAME, OP, SUCCESS_VALUE, FAILURE_VALUE) \
static const struct OP ## Node : public cmGeneratorExpressionNode \
{ \
OP ## Node () {} \
/* We let -1 carry the meaning 'at least one' */ \
virtual int NumExpectedParameters() const { return -1; } \
\
std::string Evaluate(const std::vector<std::string> &parameters, \
cmGeneratorExpressionContext *context, \
const GeneratorExpressionContent *content) const \
{ \
std::vector<std::string>::const_iterator it = parameters.begin(); \
const std::vector<std::string>::const_iterator end = parameters.end(); \
for ( ; it != end; ++it) \
{ \
if (*it == #FAILURE_VALUE) \
{ \
return #FAILURE_VALUE; \
} \
else if (*it != #SUCCESS_VALUE) \
{ \
reportError(context, content->GetOriginalExpression(), \
"Parameters to $<" #OP "> must resolve to either '0' or '1'."); \
return std::string(); \
} \
} \
return #SUCCESS_VALUE; \
} \
} OPNAME;
BOOLEAN_OP_NODE(andNode, AND, 1, 0)
BOOLEAN_OP_NODE(orNode, OR, 0, 1)
#undef BOOLEAN_OP_NODE
//----------------------------------------------------------------------------
static const struct NotNode : public cmGeneratorExpressionNode
{
NotNode() {}
std::string Evaluate(const std::vector<std::string> &parameters,
cmGeneratorExpressionContext *context,
const GeneratorExpressionContent *content) const
{
if (*parameters.begin() != "0" && *parameters.begin() != "1")
{
reportError(context, content->GetOriginalExpression(),
"$<NOT> parameter must resolve to exactly one '0' or '1' value.");
return std::string();
}
return *parameters.begin() == "0" ? "1" : "0";
}
} notNode;
//----------------------------------------------------------------------------
static const struct ConfigurationNode : public cmGeneratorExpressionNode
{
ConfigurationNode() {}
virtual int NumExpectedParameters() const { return 0; }
std::string Evaluate(const std::vector<std::string> &,
cmGeneratorExpressionContext *context,
const GeneratorExpressionContent *) const
{
return context->Config ? context->Config : "";
}
} configurationNode;
//----------------------------------------------------------------------------
static const struct ConfigurationTestNode : public cmGeneratorExpressionNode
{
ConfigurationTestNode() {}
virtual int NumExpectedParameters() const { return 1; }
std::string Evaluate(const std::vector<std::string> &parameters,
cmGeneratorExpressionContext *context,
const GeneratorExpressionContent *content) const
{
if (!context->Config)
{
return std::string();
}
cmsys::RegularExpression configValidator;
configValidator.compile("^[A-Za-z0-9_]*$");
if (!configValidator.find(parameters.begin()->c_str()))
{
reportError(context, content->GetOriginalExpression(),
"Expression syntax not recognized.");
return std::string();
}
return *parameters.begin() == context->Config ? "1" : "0";
}
} configurationTestNode;
//----------------------------------------------------------------------------
template<bool linker, bool soname>
struct TargetFilesystemArtifactResultCreator
{
static std::string Create(cmTarget* target,
cmGeneratorExpressionContext *context,
const GeneratorExpressionContent *content);
};
//----------------------------------------------------------------------------
template<>
struct TargetFilesystemArtifactResultCreator<false, true>
{
static std::string Create(cmTarget* target,
cmGeneratorExpressionContext *context,
const GeneratorExpressionContent *content)
{
// The target soname file (.so.1).
if(target->IsDLLPlatform())
{
::reportError(context, content->GetOriginalExpression(),
"TARGET_SONAME_FILE is not allowed "
"for DLL target platforms.");
return std::string();
}
if(target->GetType() != cmTarget::SHARED_LIBRARY)
{
::reportError(context, content->GetOriginalExpression(),
"TARGET_SONAME_FILE is allowed only for "
"SHARED libraries.");
return std::string();
}
std::string result = target->GetDirectory(context->Config);
result += "/";
result += target->GetSOName(context->Config);
return result;
}
};
//----------------------------------------------------------------------------
template<>
struct TargetFilesystemArtifactResultCreator<true, false>
{
static std::string Create(cmTarget* target,
cmGeneratorExpressionContext *context,
const GeneratorExpressionContent *content)
{
// The file used to link to the target (.so, .lib, .a).
if(!target->IsLinkable())
{
::reportError(context, content->GetOriginalExpression(),
"TARGET_LINKER_FILE is allowed only for libraries and "
"executables with ENABLE_EXPORTS.");
return std::string();
}
return target->GetFullPath(context->Config,
target->HasImportLibrary());
}
};
//----------------------------------------------------------------------------
template<>
struct TargetFilesystemArtifactResultCreator<false, false>
{
static std::string Create(cmTarget* target,
cmGeneratorExpressionContext *context,
const GeneratorExpressionContent *)
{
return target->GetFullPath(context->Config, false, true);
}
};
//----------------------------------------------------------------------------
template<bool dirQual, bool nameQual>
struct TargetFilesystemArtifactResultGetter
{
static std::string Get(const std::string &result);
};
//----------------------------------------------------------------------------
template<>
struct TargetFilesystemArtifactResultGetter<false, true>
{
static std::string Get(const std::string &result)
{ return cmSystemTools::GetFilenameName(result); }
};
//----------------------------------------------------------------------------
template<>
struct TargetFilesystemArtifactResultGetter<true, false>
{
static std::string Get(const std::string &result)
{ return cmSystemTools::GetFilenamePath(result); }
};
//----------------------------------------------------------------------------
template<>
struct TargetFilesystemArtifactResultGetter<false, false>
{
static std::string Get(const std::string &result)
{ return result; }
};
//----------------------------------------------------------------------------
template<bool linker, bool soname, bool dirQual, bool nameQual>
struct TargetFilesystemArtifact : public cmGeneratorExpressionNode
{
TargetFilesystemArtifact() {}
virtual int NumExpectedParameters() const { return 1; }
std::string Evaluate(const std::vector<std::string> &parameters,
cmGeneratorExpressionContext *context,
const GeneratorExpressionContent *content) const
{
// Lookup the referenced target.
std::string name = *parameters.begin();
cmsys::RegularExpression targetValidator;
targetValidator.compile("^[A-Za-z0-9_]+$");
if (!targetValidator.find(name.c_str()))
{
::reportError(context, content->GetOriginalExpression(),
"Expression syntax not recognized.");
return std::string();
}
cmTarget* target = context->Makefile->FindTargetToUse(name.c_str());
if(!target)
{
::reportError(context, content->GetOriginalExpression(),
"No target \"" + name + "\"");
return std::string();
}
if(target->GetType() >= cmTarget::UTILITY &&
target->GetType() != cmTarget::UNKNOWN_LIBRARY)
{
::reportError(context, content->GetOriginalExpression(),
"Target \"" + name + "\" is not an executable or library.");
return std::string();
}
context->Targets.insert(target);
std::string result =
TargetFilesystemArtifactResultCreator<linker, soname>::Create(
target,
context,
content);
if (context->HadError)
{
return std::string();
}
return
TargetFilesystemArtifactResultGetter<dirQual, nameQual>::Get(result);
}
};
//----------------------------------------------------------------------------
static const
TargetFilesystemArtifact<false, false, false, false> targetFileNode;
static const
TargetFilesystemArtifact<true, false, false, false> targetLinkerFileNode;
static const
TargetFilesystemArtifact<false, true, false, false> targetSoNameFileNode;
static const
TargetFilesystemArtifact<false, false, false, true> targetFileNameNode;
static const
TargetFilesystemArtifact<true, false, false, true> targetLinkerFileNameNode;
static const
TargetFilesystemArtifact<false, true, false, true> targetSoNameFileNameNode;
static const
TargetFilesystemArtifact<false, false, true, false> targetFileDirNode;
static const
TargetFilesystemArtifact<true, false, true, false> targetLinkerFileDirNode;
static const
TargetFilesystemArtifact<false, true, true, false> targetSoNameFileDirNode;
//----------------------------------------------------------------------------
static const
cmGeneratorExpressionNode* GetNode(const std::string &identifier)
{
if (identifier == "0")
return &zeroNode;
if (identifier == "1")
return &oneNode;
if (identifier == "AND")
return &andNode;
if (identifier == "OR")
return &orNode;
if (identifier == "NOT")
return &notNode;
else if (identifier == "CONFIGURATION")
return &configurationNode;
else if (identifier == "CONFIG")
return &configurationTestNode;
else if (identifier == "TARGET_FILE")
return &targetFileNode;
else if (identifier == "TARGET_LINKER_FILE")
return &targetLinkerFileNode;
else if (identifier == "TARGET_SONAME_FILE")
return &targetSoNameFileNode;
else if (identifier == "TARGET_FILE_NAME")
return &targetFileNameNode;
else if (identifier == "TARGET_LINKER_FILE_NAME")
return &targetLinkerFileNameNode;
else if (identifier == "TARGET_SONAME_FILE_NAME")
return &targetSoNameFileNameNode;
else if (identifier == "TARGET_FILE_DIR")
return &targetFileDirNode;
else if (identifier == "TARGET_LINKER_FILE_DIR")
return &targetLinkerFileDirNode;
else if (identifier == "TARGET_SONAME_FILE_DIR")
return &targetSoNameFileDirNode;
return 0;
}
//----------------------------------------------------------------------------
GeneratorExpressionContent::GeneratorExpressionContent(
const char *startContent,
unsigned int length)
: StartContent(startContent), ContentLength(length)
{
}
//----------------------------------------------------------------------------
std::string GeneratorExpressionContent::GetOriginalExpression() const
{
return std::string(this->StartContent, this->ContentLength);
}
//----------------------------------------------------------------------------
std::string GeneratorExpressionContent::Evaluate(
cmGeneratorExpressionContext *context) const
{
std::string identifier;
{
std::vector<cmGeneratorExpressionEvaluator*>::const_iterator it
= this->IdentifierChildren.begin();
const std::vector<cmGeneratorExpressionEvaluator*>::const_iterator end
= this->IdentifierChildren.end();
for ( ; it != end; ++it)
{
identifier += (*it)->Evaluate(context);
if (context->HadError)
{
return std::string();
}
}
}
const cmGeneratorExpressionNode *node = GetNode(identifier);
if (!node)
{
reportError(context, this->GetOriginalExpression(),
"Expression did not evaluate to a known generator expression");
return std::string();
}
if (!node->GeneratesContent())
{
return std::string();
}
if (node->AcceptsSingleArbitraryContentParameter())
{
std::string result;
std::vector<std::vector<cmGeneratorExpressionEvaluator*> >::const_iterator
pit = this->ParamChildren.begin();
const
std::vector<std::vector<cmGeneratorExpressionEvaluator*> >::const_iterator
pend = this->ParamChildren.end();
for ( ; pit != pend; ++pit)
{
if (!result.empty())
{
result += ",";
}
std::vector<cmGeneratorExpressionEvaluator*>::const_iterator it
= pit->begin();
const std::vector<cmGeneratorExpressionEvaluator*>::const_iterator end
= pit->end();
for ( ; it != end; ++it)
{
result += (*it)->Evaluate(context);
if (context->HadError)
{
return std::string();
}
}
}
return result;
}
std::vector<std::string> parameters;
{
std::vector<std::vector<cmGeneratorExpressionEvaluator*> >::const_iterator
pit = this->ParamChildren.begin();
const
std::vector<std::vector<cmGeneratorExpressionEvaluator*> >::const_iterator
pend = this->ParamChildren.end();
for ( ; pit != pend; ++pit)
{
std::string parameter;
std::vector<cmGeneratorExpressionEvaluator*>::const_iterator it =
pit->begin();
const std::vector<cmGeneratorExpressionEvaluator*>::const_iterator end =
pit->end();
for ( ; it != end; ++it)
{
parameter += (*it)->Evaluate(context);
if (context->HadError)
{
return std::string();
}
}
parameters.push_back(parameter);
}
}
int numExpected = node->NumExpectedParameters();
if ((numExpected != -1 && (unsigned int)numExpected != parameters.size()))
{
if (numExpected == 0)
{
reportError(context, this->GetOriginalExpression(),
"$<" + identifier + "> expression requires no parameters.");
}
else if (numExpected == 1)
{
reportError(context, this->GetOriginalExpression(),
"$<" + identifier + "> expression requires "
"exactly one parameter.");
}
else
{
cmOStringStream e;
e << "$<" + identifier + "> expression requires "
<< numExpected
<< " comma separated parameters, but got "
<< parameters.size() << " instead.";
reportError(context, this->GetOriginalExpression(), e.str());
}
return std::string();
}
if (numExpected == -1 && parameters.empty())
{
reportError(context, this->GetOriginalExpression(), "$<" + identifier
+ "> expression requires at least one parameter.");
return std::string();
}
return node->Evaluate(parameters, context, this);
}
//----------------------------------------------------------------------------
static void deleteAll(const std::vector<cmGeneratorExpressionEvaluator*> &c)
{
std::vector<cmGeneratorExpressionEvaluator*>::const_iterator it
= c.begin();
const std::vector<cmGeneratorExpressionEvaluator*>::const_iterator end
= c.end();
for ( ; it != end; ++it)
{
delete *it;
}
}
//----------------------------------------------------------------------------
GeneratorExpressionContent::~GeneratorExpressionContent()
{
deleteAll(this->IdentifierChildren);
typedef std::vector<cmGeneratorExpressionEvaluator*> EvaluatorVector;
typedef std::vector<cmGeneratorExpressionToken> TokenVector;
std::vector<EvaluatorVector>::const_iterator pit =
this->ParamChildren.begin();
const std::vector<EvaluatorVector>::const_iterator pend =
this->ParamChildren.end();
for ( ; pit != pend; ++pit)
{
deleteAll(*pit);
}
}

View File

@ -0,0 +1,118 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2012 Stephen Kelly <steveire@gmail.com>
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#ifndef cmGeneratorExpressionEvaluator_h
#define cmGeneratorExpressionEvaluator_h
#include <vector>
#include <string>
//----------------------------------------------------------------------------
struct cmGeneratorExpressionContext
{
cmListFileBacktrace Backtrace;
std::set<cmTarget*> Targets;
cmMakefile *Makefile;
const char *Config;
cmTarget *Target;
bool Quiet;
bool HadError;
};
//----------------------------------------------------------------------------
struct cmGeneratorExpressionEvaluator
{
cmGeneratorExpressionEvaluator() {}
virtual ~cmGeneratorExpressionEvaluator() {}
enum Type
{
Text,
Generator
};
virtual Type GetType() const = 0;
virtual std::string Evaluate(cmGeneratorExpressionContext *context
) const = 0;
private:
cmGeneratorExpressionEvaluator(const cmGeneratorExpressionEvaluator &);
void operator=(const cmGeneratorExpressionEvaluator &);
};
struct TextContent : public cmGeneratorExpressionEvaluator
{
TextContent(const char *start, unsigned int length)
: Content(start), Length(length)
{
}
std::string Evaluate(cmGeneratorExpressionContext *) const
{
return std::string(this->Content, this->Length);
}
Type GetType() const
{
return cmGeneratorExpressionEvaluator::Text;
}
void Extend(unsigned int length)
{
this->Length += length;
}
unsigned int GetLength()
{
return this->Length;
}
private:
const char *Content;
unsigned int Length;
};
//----------------------------------------------------------------------------
struct GeneratorExpressionContent : public cmGeneratorExpressionEvaluator
{
GeneratorExpressionContent(const char *startContent, unsigned int length);
void SetIdentifier(std::vector<cmGeneratorExpressionEvaluator*> identifier)
{
this->IdentifierChildren = identifier;
}
void SetParameters(
std::vector<std::vector<cmGeneratorExpressionEvaluator*> > parameters)
{
this->ParamChildren = parameters;
}
Type GetType() const
{
return cmGeneratorExpressionEvaluator::Generator;
}
std::string Evaluate(cmGeneratorExpressionContext *context) const;
std::string GetOriginalExpression() const;
~GeneratorExpressionContent();
private:
std::vector<cmGeneratorExpressionEvaluator*> IdentifierChildren;
std::vector<std::vector<cmGeneratorExpressionEvaluator*> > ParamChildren;
const char *StartContent;
unsigned int ContentLength;
};
#endif

View File

@ -0,0 +1,85 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2012 Stephen Kelly <steveire@gmail.com>
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmGeneratorExpressionLexer.h"
//----------------------------------------------------------------------------
cmGeneratorExpressionLexer::cmGeneratorExpressionLexer()
: SawBeginExpression(false), SawGeneratorExpression(false)
{
}
//----------------------------------------------------------------------------
static void InsertText(const char *upto, const char *c,
std::vector<cmGeneratorExpressionToken> &result)
{
if (upto != c)
{
result.push_back(cmGeneratorExpressionToken(
cmGeneratorExpressionToken::Text, upto, c - upto));
}
}
//----------------------------------------------------------------------------
std::vector<cmGeneratorExpressionToken>
cmGeneratorExpressionLexer::Tokenize(const char *input)
{
std::vector<cmGeneratorExpressionToken> result;
if (!input)
return result;
const char *c = input;
const char *upto = c;
for ( ; *c; ++c)
{
if(c[0] == '$' && c[1] == '<')
{
InsertText(upto, c, result);
upto = c;
result.push_back(cmGeneratorExpressionToken(
cmGeneratorExpressionToken::BeginExpression, upto, 2));
upto = c + 2;
++c;
SawBeginExpression = true;
}
else if(c[0] == '>')
{
InsertText(upto, c, result);
upto = c;
result.push_back(cmGeneratorExpressionToken(
cmGeneratorExpressionToken::EndExpression, upto, 1));
upto = c + 1;
SawGeneratorExpression = SawBeginExpression;
}
else if(c[0] == ':')
{
InsertText(upto, c, result);
upto = c;
result.push_back(cmGeneratorExpressionToken(
cmGeneratorExpressionToken::ColonSeparator, upto, 1));
upto = c + 1;
}
else if(c[0] == ',')
{
InsertText(upto, c, result);
upto = c;
result.push_back(cmGeneratorExpressionToken(
cmGeneratorExpressionToken::CommaSeparator, upto, 1));
upto = c + 1;
}
}
InsertText(upto, c, result);
return result;
}

View File

@ -0,0 +1,58 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2012 Stephen Kelly <steveire@gmail.com>
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#ifndef cmGeneratorExpressionLexer_h
#define cmGeneratorExpressionLexer_h
#include "cmStandardIncludes.h"
#include <vector>
//----------------------------------------------------------------------------
struct cmGeneratorExpressionToken
{
cmGeneratorExpressionToken(unsigned type, const char *c, unsigned l)
: TokenType(type), Content(c), Length(l)
{
}
enum {
Text,
BeginExpression,
EndExpression,
ColonSeparator,
CommaSeparator
};
unsigned TokenType;
const char *Content;
unsigned Length;
};
/** \class cmGeneratorExpressionLexer
*
*/
class cmGeneratorExpressionLexer
{
public:
cmGeneratorExpressionLexer();
std::vector<cmGeneratorExpressionToken> Tokenize(const char *input);
bool GetSawGeneratorExpression() const
{
return this->SawGeneratorExpression;
}
private:
bool SawBeginExpression;
bool SawGeneratorExpression;
};
#endif

View File

@ -0,0 +1,235 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2012 Stephen Kelly <steveire@gmail.com>
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmGeneratorExpressionParser.h"
#include "cmGeneratorExpressionEvaluator.h"
//----------------------------------------------------------------------------
cmGeneratorExpressionParser::cmGeneratorExpressionParser(
const std::vector<cmGeneratorExpressionToken> &tokens)
: Tokens(tokens), NestingLevel(0)
{
}
//----------------------------------------------------------------------------
void cmGeneratorExpressionParser::Parse(
std::vector<cmGeneratorExpressionEvaluator*> &result)
{
it = this->Tokens.begin();
while (this->it != this->Tokens.end())
{
this->ParseContent(result);
}
}
//----------------------------------------------------------------------------
static void extendText(std::vector<cmGeneratorExpressionEvaluator*> &result,
std::vector<cmGeneratorExpressionToken>::const_iterator it)
{
if (result.size() > 0
&& (*(result.end() - 1))->GetType()
== cmGeneratorExpressionEvaluator::Text)
{
TextContent *textContent = static_cast<TextContent*>(*(result.end() - 1));
textContent->Extend(it->Length);
}
else
{
TextContent *textContent = new TextContent(it->Content, it->Length);
result.push_back(textContent);
}
}
//----------------------------------------------------------------------------
static void extendResult(std::vector<cmGeneratorExpressionEvaluator*> &result,
const std::vector<cmGeneratorExpressionEvaluator*> &contents)
{
if (result.size() > 0
&& (*(result.end() - 1))->GetType()
== cmGeneratorExpressionEvaluator::Text
&& (*contents.begin())->GetType()
== cmGeneratorExpressionEvaluator::Text)
{
TextContent *textContent = static_cast<TextContent*>(*(result.end() - 1));
textContent->Extend(
static_cast<TextContent*>(*contents.begin())->GetLength());
delete *contents.begin();
result.insert(result.end(), contents.begin() + 1, contents.end());
} else {
result.insert(result.end(), contents.begin(), contents.end());
}
}
//----------------------------------------------------------------------------
void cmGeneratorExpressionParser::ParseGeneratorExpression(
std::vector<cmGeneratorExpressionEvaluator*> &result)
{
unsigned int nestedLevel = this->NestingLevel;
++this->NestingLevel;
std::vector<cmGeneratorExpressionToken>::const_iterator startToken
= this->it - 1;
std::vector<cmGeneratorExpressionEvaluator*> identifier;
while(this->it->TokenType != cmGeneratorExpressionToken::EndExpression
&& this->it->TokenType != cmGeneratorExpressionToken::ColonSeparator)
{
this->ParseContent(identifier);
if (this->it == this->Tokens.end())
{
break;
}
}
if (identifier.empty())
{
// ERROR
}
if (this->it->TokenType == cmGeneratorExpressionToken::EndExpression)
{
GeneratorExpressionContent *content = new GeneratorExpressionContent(
startToken->Content, this->it->Content
- startToken->Content
+ this->it->Length);
++this->it;
--this->NestingLevel;
content->SetIdentifier(identifier);
result.push_back(content);
return;
}
std::vector<std::vector<cmGeneratorExpressionEvaluator*> > parameters;
std::vector<std::vector<cmGeneratorExpressionToken>::const_iterator>
commaTokens;
std::vector<cmGeneratorExpressionToken>::const_iterator colonToken;
if (this->it->TokenType == cmGeneratorExpressionToken::ColonSeparator)
{
colonToken = this->it;
parameters.resize(parameters.size() + 1);
++this->it;
while(this->it->TokenType != cmGeneratorExpressionToken::EndExpression)
{
this->ParseContent(*(parameters.end() - 1));
if (this->it->TokenType == cmGeneratorExpressionToken::CommaSeparator)
{
commaTokens.push_back(this->it);
parameters.resize(parameters.size() + 1);
++this->it;
}
if (this->it == this->Tokens.end())
{
break;
}
}
if(this->it->TokenType == cmGeneratorExpressionToken::EndExpression)
{
--this->NestingLevel;
++this->it;
}
if (parameters.empty())
{
// ERROR
}
}
if (nestedLevel != this->NestingLevel)
{
// There was a '$<' in the text, but no corresponding '>'. Rebuild to
// treat the '$<' as having been plain text, along with the
// corresponding : and , tokens that might have been found.
extendText(result, startToken);
extendResult(result, identifier);
if (!parameters.empty())
{
extendText(result, colonToken);
typedef std::vector<cmGeneratorExpressionEvaluator*> EvaluatorVector;
typedef std::vector<cmGeneratorExpressionToken> TokenVector;
std::vector<EvaluatorVector>::const_iterator pit = parameters.begin();
const std::vector<EvaluatorVector>::const_iterator pend =
parameters.end();
std::vector<TokenVector::const_iterator>::const_iterator commaIt =
commaTokens.begin();
for ( ; pit != pend; ++pit, ++commaIt)
{
extendResult(result, *pit);
if (commaIt != commaTokens.end())
{
extendText(result, *commaIt);
}
}
}
return;
}
int contentLength = ((this->it - 1)->Content
- startToken->Content)
+ (this->it - 1)->Length;
GeneratorExpressionContent *content = new GeneratorExpressionContent(
startToken->Content, contentLength);
content->SetIdentifier(identifier);
content->SetParameters(parameters);
result.push_back(content);
}
//----------------------------------------------------------------------------
void cmGeneratorExpressionParser::ParseContent(
std::vector<cmGeneratorExpressionEvaluator*> &result)
{
switch(this->it->TokenType)
{
case cmGeneratorExpressionToken::Text:
{
if (this->NestingLevel == 0)
{
if (result.size() > 0
&& (*(result.end() - 1))->GetType()
== cmGeneratorExpressionEvaluator::Text)
{
// A comma in 'plain text' could have split text that should
// otherwise be continuous. Extend the last text content instead of
// creating a new one.
TextContent *textContent =
static_cast<TextContent*>(*(result.end() - 1));
textContent->Extend(this->it->Length);
++this->it;
return;
}
}
cmGeneratorExpressionEvaluator* n = new TextContent(this->it->Content,
this->it->Length);
result.push_back(n);
++this->it;
return ;
}
case cmGeneratorExpressionToken::BeginExpression:
++this->it;
this->ParseGeneratorExpression(result);
return;
case cmGeneratorExpressionToken::EndExpression:
case cmGeneratorExpressionToken::ColonSeparator:
case cmGeneratorExpressionToken::CommaSeparator:
if (this->NestingLevel == 0)
{
extendText(result, this->it);
}
else
{
// TODO: Unreachable. Assert?
}
++this->it;
return;
}
// Unreachable. Assert?
}

View File

@ -0,0 +1,45 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2012 Stephen Kelly <steveire@gmail.com>
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#ifndef cmGeneratorExpressionParser_h
#define cmGeneratorExpressionParser_h
#include "cmGeneratorExpressionLexer.h"
#include <set>
#include <vector>
#include "cmListFileCache.h"
class cmMakefile;
class cmTarget;
struct cmGeneratorExpressionEvaluator;
//----------------------------------------------------------------------------
struct cmGeneratorExpressionParser
{
cmGeneratorExpressionParser(
const std::vector<cmGeneratorExpressionToken> &tokens);
void Parse(std::vector<cmGeneratorExpressionEvaluator*> &result);
private:
void ParseContent(std::vector<cmGeneratorExpressionEvaluator*> &);
void ParseGeneratorExpression(
std::vector<cmGeneratorExpressionEvaluator*> &);
private:
std::vector<cmGeneratorExpressionToken>::const_iterator it;
const std::vector<cmGeneratorExpressionToken> Tokens;
unsigned int NestingLevel;
};
#endif

View File

@ -1,9 +1,18 @@
CMake Error at BadAND.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<AND>
\$<AND> expression requires at least one parameter.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at BadAND.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<AND:>
AND requires one or more comma-separated '0' or '1' values.
Parameters to \$<AND> must resolve to either '0' or '1'.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
@ -12,6 +21,24 @@ CMake Error at BadAND.cmake:1 \(add_custom_target\):
\$<AND:,>
AND requires one or more comma-separated '0' or '1' values.
Parameters to \$<AND> must resolve to either '0' or '1'.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at BadAND.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<AND:01>
Parameters to \$<AND> must resolve to either '0' or '1'.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at BadAND.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<AND:nothing>
Parameters to \$<AND> must resolve to either '0' or '1'.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)$

View File

@ -1,4 +1,7 @@
add_custom_target(check ALL COMMAND check
$<AND>
$<AND:>
$<AND:,>
$<AND:01>
$<AND:nothing>
VERBATIM)

View File

@ -1,8 +1,44 @@
CMake Error at BadCONFIG.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<CONFIG>
\$<CONFIG> expression requires exactly one parameter.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at BadCONFIG.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<CONFIG:.>
Expression syntax not recognized.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at BadCONFIG.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<CONFIG:Foo,Bar>
\$<CONFIG> expression requires exactly one parameter.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at BadCONFIG.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<CONFIG:Foo-Bar>
Expression syntax not recognized.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at BadCONFIG.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<CONFIG:Foo-Nested>
Expression syntax not recognized.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)$

View File

@ -1,3 +1,7 @@
add_custom_target(check ALL COMMAND check
$<CONFIG>
$<CONFIG:.>
$<CONFIG:Foo,Bar>
$<CONFIG:Foo-Bar>
$<$<CONFIG:Foo-Nested>:foo>
VERBATIM)

View File

@ -1,9 +1,17 @@
CMake Error at BadNOT.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<NOT>
\$<NOT> expression requires exactly one parameter.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+CMake Error at BadNOT.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<NOT:>
NOT requires exactly one '0' or '1' value.
\$<NOT> parameter must resolve to exactly one '0' or '1' value.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
@ -12,7 +20,7 @@ CMake Error at BadNOT.cmake:1 \(add_custom_target\):
\$<NOT:,>
NOT requires exactly one '0' or '1' value.
\$<NOT> parameter must resolve to exactly one '0' or '1' value.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
@ -21,6 +29,24 @@ CMake Error at BadNOT.cmake:1 \(add_custom_target\):
\$<NOT:0,1>
NOT requires exactly one '0' or '1' value.
\$<NOT> expression requires exactly one parameter.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at BadNOT.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<NOT:01>
\$<NOT> parameter must resolve to exactly one '0' or '1' value.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at BadNOT.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<NOT:nothing>
\$<NOT> parameter must resolve to exactly one '0' or '1' value.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)$

View File

@ -1,5 +1,8 @@
add_custom_target(check ALL COMMAND check
$<NOT>
$<NOT:>
$<NOT:,>
$<NOT:0,1>
$<NOT:01>
$<NOT:nothing>
VERBATIM)

View File

@ -1,9 +1,18 @@
CMake Error at BadOR.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<OR>
\$<OR> expression requires at least one parameter.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at BadOR.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<OR:>
OR requires one or more comma-separated '0' or '1' values.
Parameters to \$<OR> must resolve to either '0' or '1'.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
@ -12,6 +21,24 @@ CMake Error at BadOR.cmake:1 \(add_custom_target\):
\$<OR:,>
OR requires one or more comma-separated '0' or '1' values.
Parameters to \$<OR> must resolve to either '0' or '1'.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at BadOR.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<OR:01>
Parameters to \$<OR> must resolve to either '0' or '1'.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at BadOR.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<OR:nothing>
Parameters to \$<OR> must resolve to either '0' or '1'.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)$

View File

@ -1,4 +1,7 @@
add_custom_target(check ALL COMMAND check
$<OR>
$<OR:>
$<OR:,>
$<OR:01>
$<OR:nothing>
VERBATIM)

View File

@ -202,6 +202,9 @@ CMAKE_CXX_SOURCES="\
cmInstallDirectoryGenerator \
cmGeneratedFileStream \
cmGeneratorTarget \
cmGeneratorExpressionEvaluator \
cmGeneratorExpressionLexer \
cmGeneratorExpressionParser \
cmGeneratorExpression \
cmGlobalGenerator \
cmLocalGenerator \